Skip to content

Commit 9fd15eb

Browse files
authored
chore: Enable TSAN without ignoring errors (#2828)
1 parent cf77a10 commit 9fd15eb

File tree

8 files changed

+27
-87
lines changed

8 files changed

+27
-87
lines changed

.github/scripts/execute-tests-under-sanitizer.sh

Lines changed: 0 additions & 46 deletions
This file was deleted.

.github/workflows/reusable-test.yml

Lines changed: 9 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,6 @@ jobs:
4545

4646
if: ${{ inputs.run_unit_tests }}
4747

48-
env:
49-
# TODO: remove completely when we have fixed all currently existing issues with sanitizers
50-
SANITIZER_IGNORE_ERRORS: ${{ endsWith(inputs.conan_profile, '.tsan') }}
51-
5248
steps:
5349
- name: Cleanup workspace
5450
if: ${{ runner.os == 'macOS' }}
@@ -65,45 +61,27 @@ jobs:
6561
- name: Make clio_tests executable
6662
run: chmod +x ./clio_tests
6763

68-
- name: Run clio_tests (regular)
69-
if: ${{ env.SANITIZER_IGNORE_ERRORS == 'false' }}
64+
- name: Run clio_tests
65+
continue-on-error: true
66+
id: run_clio_tests
7067
run: ./clio_tests
7168

72-
- name: Run clio_tests (sanitizer errors ignored)
73-
if: ${{ env.SANITIZER_IGNORE_ERRORS == 'true' }}
74-
run: ./.github/scripts/execute-tests-under-sanitizer.sh ./clio_tests
75-
76-
- name: Check for sanitizer report
77-
if: ${{ env.SANITIZER_IGNORE_ERRORS == 'true' }}
78-
id: check_report
79-
run: |
80-
if ls .sanitizer-report/* 1> /dev/null 2>&1; then
81-
echo "found_report=true" >> $GITHUB_OUTPUT
82-
else
83-
echo "found_report=false" >> $GITHUB_OUTPUT
84-
fi
85-
86-
- name: Upload sanitizer report
87-
if: ${{ env.SANITIZER_IGNORE_ERRORS == 'true' && steps.check_report.outputs.found_report == 'true' }}
88-
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
89-
with:
90-
name: sanitizer_report_${{ runner.os }}_${{ inputs.build_type }}_${{ inputs.conan_profile }}
91-
path: .sanitizer-report/*
92-
include-hidden-files: true
93-
9469
- name: Create an issue
95-
if: ${{ false && env.SANITIZER_IGNORE_ERRORS == 'true' && steps.check_report.outputs.found_report == 'true' }}
70+
if: ${{ steps.run_clio_tests.outcome == 'failure' && endsWith(inputs.conan_profile, 'san') }}
9671
uses: ./.github/actions/create-issue
9772
env:
9873
GH_TOKEN: ${{ github.token }}
9974
with:
10075
labels: "bug"
10176
title: "[${{ inputs.conan_profile }}] reported issues"
10277
body: >
103-
Clio tests failed one or more sanitizer checks when built with ${{ inputs.conan_profile }}`.
78+
Clio tests failed one or more sanitizer checks when built with `${{ inputs.conan_profile }}`.
10479
10580
Workflow: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}/
106-
Reports are available as artifacts.
81+
82+
- name: Fail the job if clio_tests failed
83+
if: ${{ steps.run_clio_tests.outcome == 'failure' }}
84+
run: exit 1
10785

10886
integration_tests:
10987
name: Integration testing

.github/workflows/sanitizers.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ on:
1515
- ".github/actions/**"
1616
- "!.github/actions/build-docker-image/**"
1717
- "!.github/actions/create-issue/**"
18-
- .github/scripts/execute-tests-under-sanitizer.sh
1918

2019
- CMakeLists.txt
2120
- conanfile.py

src/cluster/impl/RepeatedTask.hpp

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@
3030
#include <boost/asio/spawn.hpp>
3131
#include <boost/asio/steady_timer.hpp>
3232
#include <boost/asio/strand.hpp>
33-
#include <boost/asio/use_future.hpp>
3433

3534
#include <atomic>
3635
#include <chrono>
@@ -96,7 +95,12 @@ class RepeatedTask {
9695
if (auto expected = State::Running; not state_.compare_exchange_strong(expected, State::Stopped))
9796
return; // Already stopped or not started
9897

99-
boost::asio::spawn(strand_, [this](auto&&) { timer_.cancel(); }, boost::asio::use_future).wait();
98+
std::binary_semaphore cancelSemaphore{0};
99+
boost::asio::post(strand_, [this, &cancelSemaphore]() {
100+
timer_.cancel();
101+
cancelSemaphore.release();
102+
});
103+
cancelSemaphore.acquire();
100104
semaphore_.acquire();
101105
}
102106
};

src/feed/impl/SingleFeedBase.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ SingleFeedBase::unsub(SubscriberSharedPtr const& subscriber)
6464
void
6565
SingleFeedBase::pub(std::string msg)
6666
{
67-
[[maybe_unused]] auto task = strand_.execute([this, msg = std::move(msg)]() {
67+
strand_.submit([this, msg = std::move(msg)] {
6868
auto const msgPtr = std::make_shared<std::string>(msg);
6969
signal_.emit(msgPtr);
7070
});

src/feed/impl/TrackableSignal.hpp

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -73,10 +73,15 @@ class TrackableSignal {
7373

7474
// This class can't hold the trackable's shared_ptr, because disconnect should be able to be called in the
7575
// the trackable's destructor. However, the trackable can not be destroyed when the slot is being called
76-
// either. track_foreign will hold a weak_ptr to the connection, which makes sure the connection is valid when
77-
// the slot is called.
76+
// either. `track_foreign` is racey when one shared_ptr is tracked by multiple signals. Therefore we are storing
77+
// a weak_ptr of the trackable and using weak_ptr::lock() to atomically check existence and acquire a shared_ptr
78+
// during slot invocation. This guarantees to keep the trackable alive for the duration of the slot call and
79+
// avoids potential race conditions.
7880
connections->emplace(
79-
trackable.get(), signal_.connect(typename SignalType::slot_type(slot).track_foreign(trackable))
81+
trackable.get(), signal_.connect([slot, weakTrackable = std::weak_ptr(trackable)](Args&&... args) {
82+
if (auto lifeExtender = weakTrackable.lock(); lifeExtender)
83+
std::invoke(slot, std::forward<Args...>(args)...);
84+
})
8085
);
8186
return true;
8287
}

tests/unit/feed/SubscriptionManagerTests.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,11 +72,11 @@ class SubscriptionManagerBaseTest : public util::prometheus::WithPrometheus, pub
7272
}
7373

7474
StrictMockAmendmentCenterSharedPtr mockAmendmentCenterPtr_;
75-
std::shared_ptr<SubscriptionManager> subscriptionManagerPtr_ =
76-
std::make_shared<SubscriptionManager>(Execution(2), backend_, mockAmendmentCenterPtr_);
7775
web::SubscriptionContextPtr session_ = std::make_shared<MockSession>();
7876
MockSession* sessionPtr_ = dynamic_cast<MockSession*>(session_.get());
7977
uint32_t const networkID_ = 123;
78+
std::shared_ptr<SubscriptionManager> subscriptionManagerPtr_ =
79+
std::make_shared<SubscriptionManager>(Execution(2), backend_, mockAmendmentCenterPtr_);
8080
};
8181

8282
using SubscriptionManagerTest = SubscriptionManagerBaseTest<util::async::SyncExecutionContext>;

tests/unit/util/async/AsyncExecutionContextTests.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,7 @@ TYPED_TEST(ExecutionContextTests, repeatingOperation)
209209
{
210210
auto const repeatDelay = std::chrono::milliseconds{1};
211211
auto const timeout = std::chrono::milliseconds{15};
212-
auto callCount = 0uz;
212+
std::atomic_size_t callCount = 0uz;
213213

214214
auto res = this->ctx.executeRepeatedly(repeatDelay, [&] { ++callCount; });
215215
auto timeSpent = util::timed([timeout] { std::this_thread::sleep_for(timeout); }); // calculate actual time spent

0 commit comments

Comments
 (0)