Skip to content

Commit 6b7973a

Browse files
Minipadaclaude
andcommitted
fix(dc_bridge): stop supervisor_test flaking on loaded CI runners
Supervisor.RestartsProcessThatExitsOnItsOwn and RespectsRestartBackoff asserted the supervised child had exited after a fixed sleep_for(200-300ms) — a margin that assumes fork+exec+process-teardown always gets scheduled that fast, which a shared/loaded CI runner can blow (observed failing once on GitHub Actions right after a full workspace build, while passing locally and on the prior jazzy CI run with identical code). Replace the fixed-sleep-then-assert with the poll-until-condition-or-deadline pattern this same test file already uses correctly in SupervisedProcessDiesWithItsSpawner: wait_until() polls every 10ms up to a 5s deadline instead of guessing a wall-clock margin, so it only waits as long as actually needed and still fails loudly on a real hang. Verified: 30/30 clean runs of supervisor_test in the same Podman workspace image CI uses, after an incremental `colcon build --packages-select dc_bridge`. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01StNXjcK7b1kY2KYRd8qN6N Signed-off-by: David Bensoussan <d.bensoussan@proton.me>
1 parent 48c816c commit 6b7973a

1 file changed

Lines changed: 24 additions & 6 deletions

File tree

dc_bridge/test/supervisor_test.cpp

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,27 @@ SupervisorConfig sh(const std::string& script, std::chrono::milliseconds backoff
2222
cfg.restart_backoff = backoff;
2323
return cfg;
2424
}
25+
26+
// Polls `pred` until it's true or `timeout` elapses. A fixed sleep_for() followed by a
27+
// single assertion assumes the supervised child (fork+exec, then whatever it runs) is
28+
// always scheduled within that margin — a loaded/shared CI runner can stall it well
29+
// past a couple hundred milliseconds, which made RestartsProcessThatExitsOnItsOwn and
30+
// RespectsRestartBackoff flaky. Polling only waits as long as actually needed and
31+
// still fails loudly if the condition is never met by the deadline.
32+
template <typename Pred>
33+
bool wait_until(Pred pred, std::chrono::milliseconds timeout = std::chrono::seconds(5))
34+
{
35+
const auto deadline = std::chrono::steady_clock::now() + timeout;
36+
while (std::chrono::steady_clock::now() < deadline)
37+
{
38+
if (pred())
39+
{
40+
return true;
41+
}
42+
std::this_thread::sleep_for(std::chrono::milliseconds(10));
43+
}
44+
return pred();
45+
}
2546
} // namespace
2647

2748
TEST(Supervisor, RestartsProcessThatExitsOnItsOwn)
@@ -30,8 +51,7 @@ TEST(Supervisor, RestartsProcessThatExitsOnItsOwn)
3051
s.start();
3152
EXPECT_TRUE(s.is_running());
3253

33-
std::this_thread::sleep_for(std::chrono::milliseconds(300));
34-
EXPECT_FALSE(s.is_running());
54+
EXPECT_TRUE(wait_until([&] { return !s.is_running(); })) << "child never exited";
3555

3656
EXPECT_TRUE(s.poll_restart());
3757
EXPECT_TRUE(s.is_running());
@@ -41,12 +61,10 @@ TEST(Supervisor, RespectsRestartBackoff)
4161
{
4262
Supervisor s(sh("exit 1", std::chrono::seconds(60)));
4363
s.start();
44-
std::this_thread::sleep_for(std::chrono::milliseconds(200));
45-
EXPECT_FALSE(s.is_running());
64+
EXPECT_TRUE(wait_until([&] { return !s.is_running(); })) << "child never exited";
4665

4766
EXPECT_TRUE(s.poll_restart()); // first restart is immediate (no prior exit recorded)
48-
std::this_thread::sleep_for(std::chrono::milliseconds(200));
49-
EXPECT_FALSE(s.is_running());
67+
EXPECT_TRUE(wait_until([&] { return !s.is_running(); })) << "child never exited";
5068

5169
EXPECT_FALSE(s.poll_restart()); // second exit within the 60s backoff window
5270
}

0 commit comments

Comments
 (0)