diff --git a/src/Server/ZB.MOM.WW.OtOpcUa.Runtime/Drivers/DriverInstanceActor.cs b/src/Server/ZB.MOM.WW.OtOpcUa.Runtime/Drivers/DriverInstanceActor.cs index bedb5ca1..1255b4f8 100644 --- a/src/Server/ZB.MOM.WW.OtOpcUa.Runtime/Drivers/DriverInstanceActor.cs +++ b/src/Server/ZB.MOM.WW.OtOpcUa.Runtime/Drivers/DriverInstanceActor.cs @@ -88,9 +88,10 @@ public sealed class DriverInstanceActor : ReceiveActor, IWithTimers /// private readonly Queue _faultTimestamps = new(); - /// 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). + /// Active subscription handle (null when not subscribed). Tracks the current live + /// subscription; the actor auto-(re)subscribes on (re)connect and on each + /// message via / , so callers + /// do not need to re-send subscription requests after a reconnect. private ISubscriptionHandle? _subscriptionHandle; private EventHandler? _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. diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverInstanceActorTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverInstanceActorTests.cs index 3dd9ae32..bc5230fc 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverInstanceActorTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverInstanceActorTests.cs @@ -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); } /// Verifies that subscribing to a non-ISubscribable driver replies with failure.