From b6b2028a80aa55cf9f02eb31e02f13df258c5ea9 Mon Sep 17 00:00:00 2001 From: Julian Oes Date: Sat, 4 Jul 2026 08:09:03 +1200 Subject: [PATCH] fix(lockstep_scheduler): wait for threads before advancing time in test (#27798) test_multiple_semaphores_waiting() spawns a worker thread per TestCase, then advances virtual time in a loop and completes each case in check(). Since check() early-returns while !_thread_ready, a slow-to-start worker thread can miss the whole loop: the loop advances past the case's timeout before the thread registers, so the case is never completed. ~TestCase() then fails EXPECT_TRUE(_is_done) and destroys a still-joinable std::thread, which calls std::terminate() and aborts the test. This was flaky (~50% locally, worse under CI load). Wait for every worker thread to be ready before advancing virtual time. Also re-comment the per-iteration std::cout that was left enabled. Signed-off-by: Julian Oes --- .../test/src/lockstep_scheduler_test.cpp | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/platforms/posix/src/px4/common/lockstep_scheduler/test/src/lockstep_scheduler_test.cpp b/platforms/posix/src/px4/common/lockstep_scheduler/test/src/lockstep_scheduler_test.cpp index e41307cd7c0..7b7066484fb 100644 --- a/platforms/posix/src/px4/common/lockstep_scheduler/test/src/lockstep_scheduler_test.cpp +++ b/platforms/posix/src/px4/common/lockstep_scheduler/test/src/lockstep_scheduler_test.cpp @@ -159,6 +159,18 @@ public: }); } + void wait_until_ready() + { + // Block until the worker thread has locked its mutex and is about to wait + // on the condition. Otherwise advancing virtual time could pass this case's + // timeout before the thread even starts, so check() (which early-returns on + // !_thread_ready) would never complete it and its thread would be left + // joinable at destruction. + while (!_thread_ready) { + std::this_thread::yield(); + } + } + void check() { if (_is_done || !_thread_ready) { @@ -258,6 +270,12 @@ void test_multiple_semaphores_waiting() test_case->run(); } + // Make sure every thread has started and is waiting before we advance virtual + // time, so the loop below can't skip past a case's timeout before it registers. + for (auto &test_case : test_cases) { + test_case->wait_until_ready(); + } + const int min_step_size = 1; const int max_step_size = 100; @@ -321,7 +339,7 @@ void test_usleep() TEST(LockstepScheduler, All) { for (unsigned iteration = 1; iteration <= 100; ++iteration) { - std::cout << "Test iteration: " << iteration << "\n"; + //std::cout << "Test iteration: " << iteration << "\n"; test_absolute_time(); test_condition_timing_out(); test_locked_semaphore_getting_unlocked();