fix(runtime): in-place ApplyDelta releases and re-subscribes data + alarms (#663)
A Connected ApplyDelta called ReinitializeAsync and kept the actor's data and native-alarm handles, though every driver's reinit drops the driver-side subscriptions behind them (#630). The reconcile only re-subscribes a missing handle, so the driver went silent until a reconnect. Release both at the driver before the reinit and re-subscribe both in a finally. Unreachable from deploys since #516; pinned by a two-case test, each case failing against a different wrong fix. Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
This commit is contained in:
@@ -881,9 +881,21 @@ public sealed class DriverInstanceActor : ReceiveActor, IWithTimers
|
||||
Sender.Tell(new ApplyResult(true, "config adopted; reinitializing", msg.Correlation));
|
||||
}
|
||||
|
||||
/// <summary>Adopt a new config while Connected, in place, via <see cref="IDriver.ReinitializeAsync"/>.
|
||||
/// <para>#663: every driver's reinit releases its device/session state (#630), so the driver-side data and
|
||||
/// native-alarm subscriptions do not survive it — but this handler used to keep both handles, and the
|
||||
/// health-poll reconcile only re-subscribes a MISSING handle, so the driver went silent until a
|
||||
/// reconnect or respawn. Both subscriptions are now released explicitly BEFORE the reinit (explicitly,
|
||||
/// not just by nulling the handles, so a driver whose reinit keeps its subscriptions is not left
|
||||
/// holding two) and re-established after it — in a <c>finally</c>, because a failed reinit has already
|
||||
/// lost them too. Reinit stays the adoption path rather than shutdown + initialise: Calculation and
|
||||
/// Galaxy take their config at construction and ignore <see cref="IDriver.InitializeAsync"/>'s JSON.</para>
|
||||
/// <para>Not reachable from a deploy since #516 (a DriverConfig change respawns the driver).</para></summary>
|
||||
private async Task HandleApplyDeltaAsync(ApplyDelta msg)
|
||||
{
|
||||
var replyTo = Sender;
|
||||
await UnsubscribeAsync();
|
||||
await ReleaseAlarmSubscriptionAsync();
|
||||
try
|
||||
{
|
||||
await _driver.ReinitializeAsync(msg.DriverConfigJson, CancellationToken.None);
|
||||
@@ -900,6 +912,37 @@ public sealed class DriverInstanceActor : ReceiveActor, IWithTimers
|
||||
{
|
||||
replyTo.Tell(new ApplyResult(false, ex.Message, msg.Correlation));
|
||||
}
|
||||
finally
|
||||
{
|
||||
ResubscribeDesired();
|
||||
AttachAlarmSource();
|
||||
SubscribeDesiredAlarms();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Release the native-alarm subscription at the driver (best-effort, bounded) and drop the
|
||||
/// actor's handler + handle. The in-place reinit counterpart of the data <see cref="UnsubscribeAsync"/>
|
||||
/// (#663).</summary>
|
||||
private async Task ReleaseAlarmSubscriptionAsync()
|
||||
{
|
||||
if (_driver is IAlarmSource src && _alarmSubscriptionHandle is { } handle)
|
||||
{
|
||||
try
|
||||
{
|
||||
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));
|
||||
await _invoker.ExecuteAsync(
|
||||
DriverCapability.AlarmSubscribe,
|
||||
_driverInstanceId,
|
||||
async ct => await src.UnsubscribeAlarmsAsync(handle, ct),
|
||||
cts.Token);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_log.Warning(ex, "DriverInstance {Id}: releasing the native-alarm subscription ({Diag}) threw (continuing)",
|
||||
_driverInstanceId, handle.DiagnosticId);
|
||||
}
|
||||
}
|
||||
DetachAlarmSource();
|
||||
}
|
||||
|
||||
private async Task HandleWriteAsync(WriteAttribute msg)
|
||||
|
||||
Reference in New Issue
Block a user