refactor(runtime): capture-first in HandleWriteAsync; assert no handler leak on resubscribe; fix stale comment

This commit is contained in:
Joseph Doherty
2026-06-07 10:31:20 -04:00
parent 98259ab026
commit 6b36eff2d3
2 changed files with 8 additions and 5 deletions
@@ -88,9 +88,10 @@ public sealed class DriverInstanceActor : ReceiveActor, IWithTimers
/// </summary>
private readonly Queue<DateTime> _faultTimestamps = new();
/// <summary>Active subscription handle (null when not subscribed). Lifetime is one-per-actor —
/// re-subscribe across reconnects is the consumer's responsibility today (subscribe-once
/// semantics keep the actor simple; mux-driven re-subscribe is tracked as F8b/#113).</summary>
/// <summary>Active subscription handle (null when not subscribed). Tracks the current live
/// subscription; the actor auto-(re)subscribes on (re)connect and on each <see cref="Subscribe"/>
/// message via <see cref="ResubscribeDesired"/> / <see cref="HandleSubscribeAsync"/>, so callers
/// do not need to re-send subscription requests after a reconnect.</summary>
private ISubscriptionHandle? _subscriptionHandle;
private EventHandler<DataChangeEventArgs>? _dataChangeHandler;
@@ -314,13 +315,13 @@ public sealed class DriverInstanceActor : ReceiveActor, IWithTimers
private async Task HandleWriteAsync(WriteAttribute msg)
{
var replyTo = Sender;
if (_driver is not IWritable writable)
{
Sender.Tell(new WriteAttributeResult(false, "Driver does not implement IWritable"));
replyTo.Tell(new WriteAttributeResult(false, "Driver does not implement IWritable"));
return;
}
var replyTo = Sender;
var request = new[] { new WriteRequest(msg.TagId, msg.Value) };
// Bound the write so a hung backend can't pin this actor forever — decision #44/#45 keeps
// retry off by default, but a stalled call still needs an answer.
@@ -215,6 +215,8 @@ public sealed class DriverInstanceActorTests : RuntimeActorTestBase
reply.ReferenceCount.ShouldBe(2);
driver.SubscribeCount.ShouldBe(2);
// Old handler must have been detached before the new one was attached — no leak.
driver.OnDataChangeSubscriberCount.ShouldBe(1);
}
/// <summary>Verifies that subscribing to a non-ISubscribable driver replies with failure.</summary>