Skip to content

Commit cb3acbe

Browse files
Fix flaky TestProbe supervision test by preventing restart storm (#688)
* Fix flaky TestProbe supervision test by preventing restart storm The test was sending a new message on each AwaitAssert retry (every 100ms for 3 seconds), creating a restart storm that could exceed the supervisor's restart limit and cause the child actor to be stopped permanently. This resulted in the restart counter remaining at 0. Changes: - Send exactly 2 messages before AwaitAssert to trigger exactly 2 restarts - Move the message sending outside the assertion loop to avoid accumulating messages - Fix FailingActor.PostRestart to call base.PostRestart() to maintain proper actor lifecycle This eliminates the race condition and makes the test deterministic. * Use message-passing for deterministic restart verification Replaced counter-based polling with direct message verification using TestProbe. Now the actor sends a "restarted" message on each PostRestart, and the test uses ExpectMsg to block until both restart notifications arrive. This is more idiomatic Akka.NET testing and eliminates all timing assumptions: - No AwaitAssert polling - No AtomicCounter shared state - Direct synchronization via message passing - ExpectMsg blocks until message arrives or times out The test is now truly deterministic and clearly expresses its intent. * Convert test to use async/await pattern Changed ExpectMsg to ExpectMsgAsync for modern async test patterns. Test still passes reliably in ~43ms.
1 parent 33b7410 commit cb3acbe

File tree

1 file changed

+19
-15
lines changed

1 file changed

+19
-15
lines changed

src/Akka.Hosting.TestKit.Tests/TestActorRefTests/TestProbeSpec.cs

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -84,35 +84,39 @@ public void TestProbe_should_create_a_child_when_invoking_ChildActorOf()
8484
}
8585

8686
[Fact]
87-
public void TestProbe_restart_a_failing_child_if_the_given_supervisor_says_so()
87+
public async Task TestProbe_restart_a_failing_child_if_the_given_supervisor_says_so()
8888
{
89-
var restarts = new AtomicCounter(0);
9089
var probe = CreateTestProbe();
91-
var child = probe.ChildActorOf(Props.Create(() => new FailingActor(restarts)), SupervisorStrategy.DefaultStrategy);
92-
AwaitAssert(() =>
93-
{
94-
child.Tell("hello");
95-
restarts.Current.Should().BeGreaterThan(1);
96-
});
90+
var restartWatcher = CreateTestProbe();
91+
var child = probe.ChildActorOf(Props.Create(() => new FailingActor(restartWatcher)), SupervisorStrategy.DefaultStrategy);
92+
93+
// Send two messages that will cause failures and restarts
94+
child.Tell("hello");
95+
child.Tell("hello");
96+
97+
// Wait for exactly 2 restart notifications
98+
await restartWatcher.ExpectMsgAsync("restarted");
99+
await restartWatcher.ExpectMsgAsync("restarted");
97100
}
98-
101+
99102
class FailingActor : ActorBase
100103
{
101-
private AtomicCounter Restarts { get; }
102-
103-
public FailingActor(AtomicCounter restarts)
104+
private readonly IActorRef _restartWatcher;
105+
106+
public FailingActor(IActorRef restartWatcher)
104107
{
105-
Restarts = restarts;
108+
_restartWatcher = restartWatcher;
106109
}
107-
110+
108111
protected override bool Receive(object message)
109112
{
110113
throw new Exception("Simulated failure");
111114
}
112115

113116
protected override void PostRestart(Exception reason)
114117
{
115-
Restarts.IncrementAndGet();
118+
_restartWatcher.Tell("restarted");
119+
base.PostRestart(reason);
116120
}
117121
}
118122
}

0 commit comments

Comments
 (0)