fix(vtags): prune _planByVtag on child termination + crash-then-change test (H1b review follow-up)

This commit is contained in:
Joseph Doherty
2026-06-15 10:12:11 -04:00
parent ada01e1af8
commit ebf2f1dd7a
2 changed files with 42 additions and 4 deletions
@@ -243,6 +243,40 @@ public sealed class VirtualTagHostActorTests : RuntimeActorTestBase
mux.ExpectNoMsg(TimeSpan.FromMilliseconds(400));
}
/// <summary>
/// H1b crash-then-change: after a child terminates unexpectedly AND the next apply carries a
/// CHANGED plan for the same vtagId, the replacement child must adopt the NEW plan (refs "B"),
/// not the stale one (refs "A"). This pins that OnChildTerminated prunes _planByVtag so the
/// change-detect guard can't be confused by a dead child's old plan.
/// </summary>
[Fact]
public void Respawn_after_termination_with_a_changed_plan_adopts_the_new_refs()
{
var publish = CreateTestProbe();
var mux = CreateTestProbe();
var host = Sys.ActorOf(VirtualTagHostActor.Props(publish.Ref, mux.Ref, new StubEvaluator()));
// First apply — child registers interest in "A".
host.Tell(new VirtualTagHostActor.ApplyVirtualTags(
new[] { PlanWithRefs("vt-1", "eq-1", "speed-rpm", "ctx.GetTag(\"A\")", "A") }));
mux.ExpectMsg<DependencyMuxActor.RegisterInterest>().TagRefs.ShouldContain("A");
var firstChild = mux.LastSender;
// Kill the child deterministically and drain its PostStop UnregisterInterest.
Watch(firstChild);
Sys.Stop(firstChild);
ExpectTerminated(firstChild);
mux.ExpectMsg<DependencyMuxActor.UnregisterInterest>(TimeSpan.FromSeconds(5));
// Re-apply with a CHANGED plan (refs "B"). The replacement must register the NEW refs.
host.Tell(new VirtualTagHostActor.ApplyVirtualTags(
new[] { PlanWithRefs("vt-1", "eq-1", "speed-rpm", "ctx.GetTag(\"B\")", "B") }));
var reg2 = mux.ExpectMsg<DependencyMuxActor.RegisterInterest>(TimeSpan.FromSeconds(5));
reg2.TagRefs.ShouldContain("B");
reg2.TagRefs.ShouldNotContain("A");
mux.LastSender.ShouldNotBe(firstChild);
}
/// <summary>Deterministic no-op evaluator: keeps spawned children inert so tests drive the host's
/// OnResult path directly via synthetic EvaluationResults.</summary>
private sealed class StubEvaluator : IVirtualTagEvaluator