fix(mqtt): stale observed refs BadNoCommunication on connection loss so the identical replay recovers (05/STAB-18 shape a)
This commit is contained in:
@@ -96,6 +96,9 @@ public sealed class MqttSubscriptionManager
|
||||
/// </summary>
|
||||
private const uint StatusBadWaitingForInitialData = 0x80320000u;
|
||||
|
||||
/// <summary>The broker session this driver was observing is gone — see <see cref="OnConnectionLost"/>.</summary>
|
||||
private const uint StatusBadNoCommunication = 0x80310000u;
|
||||
|
||||
private readonly int _defaultQos;
|
||||
private readonly string _driverId;
|
||||
private readonly ILogger? _logger;
|
||||
@@ -237,6 +240,45 @@ public sealed class MqttSubscriptionManager
|
||||
_transport = connection;
|
||||
connection.MessageReceived += HandleMessage;
|
||||
connection.Reconnected += OnReconnectedAsync;
|
||||
connection.ConnectionLost += OnConnectionLost;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Marks every already-observed reference <c>BadNoCommunication</c>, <b>keeping its last
|
||||
/// value</b>, and publishes the change — the driver's answer to a lost broker session
|
||||
/// (archreview 05/STAB-18 shape a). Wired to <see cref="MqttConnection.ConnectionLost"/> by
|
||||
/// <see cref="AttachTo"/>; public so tests (and Sparkplug's ingestor) can drive the edge
|
||||
/// directly.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// <b>This is what makes the reconnect recover.</b> The steady-state suppression in
|
||||
/// <see cref="Deliver"/> compares value AND status against the driver-owned cache, which
|
||||
/// survives reconnects and redeploys — so the retained replay of an unchanged payload was
|
||||
/// suppressed and every node latched Bad by the host stayed Bad until the plant value
|
||||
/// happened to change. Staling the status first makes that identical replay a change.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// A never-observed reference is left alone (it is already
|
||||
/// <see cref="StatusBadWaitingForInitialData"/>, which says something more precise), and an
|
||||
/// already-Bad one is not re-stamped. Runs on MQTTnet's dispatcher thread: dictionary work
|
||||
/// and synchronous <see cref="OnDataChange"/> invokes only — exactly what
|
||||
/// <see cref="Deliver"/> already does there — and never awaits.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public void OnConnectionLost()
|
||||
{
|
||||
var now = DateTime.UtcNow;
|
||||
foreach (var rawPath in _authored.ByRawPath.Keys)
|
||||
{
|
||||
var prior = Values.Read(rawPath);
|
||||
if (prior.StatusCode == StatusBadWaitingForInitialData) continue; // never observed
|
||||
if ((prior.StatusCode >> 30) >= 2) continue; // already Bad
|
||||
|
||||
var staled = prior with { StatusCode = StatusBadNoCommunication, ServerTimestampUtc = now };
|
||||
Values.Update(rawPath, staled);
|
||||
Raise(rawPath, staled);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
Reference in New Issue
Block a user