feat(mtconnect): CommsLossConfirmMs AdminUI field, refused-connection fast path reaches native conditions, dedicated live-gate Agent (#852)
- AdminUI MTConnect driver form: "Comms-loss confirm (ms)" in the Reconnect section, always written (0 = first refusal, negative = off is never dropped), case-insensitive load, unknown keys preserved, advisory at >= 90000 ms (the watchdog fires first). Form output byte-pinned and parsed through MTConnectDriver.ParseOptions on the driver side. - Driver: a confirmed refused-connection outage now annotates every told CONDITION quality-only BadNoCommunication (state/ack kept, once per outage, UNAVAILABLE-Bad skipped); the first delivery afterwards re-diffs all subscribed conditions so unchanged ones get a quality-only Good. - docker-dev/docker-compose.mtconnect-gate.yml: throwaway Agent in its adapter's network namespace so a stop is a genuine ConnectionRefused (a plain stopped container is a DNS failure = ambiguous). - Docs: MTConnect.md comms-loss section + live-gate recipe, docker-dev README, drivers.md rule. Refs #852
This commit is contained in:
+38
-3
@@ -107,6 +107,16 @@
|
||||
<InputNumber @bind-Value="_form.ReconnectBackoffMultiplier" @bind-Value:after="EmitAsync" class="form-control form-control-sm" />
|
||||
<div class="form-text">Default 2.0 (doubles each retry).</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label class="form-label">Comms-loss confirm (ms)</label>
|
||||
<InputNumber @bind-Value="_form.CommsLossConfirmMs" @bind-Value:after="EmitAsync" class="form-control form-control-sm" />
|
||||
<div class="form-text">
|
||||
Default 2000. How long the Agent must keep <strong>refusing</strong> connections before every
|
||||
subscribed value and native condition goes <span class="mono">BadNoCommunication</span>.
|
||||
<span class="mono">0</span> = on the first refusal; negative = fast path off (only the ~90 s
|
||||
comms-loss watchdog). Timeouts and a paused Agent always wait for the watchdog.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -299,6 +309,12 @@
|
||||
/// <summary>Multiplier applied to the reconnect backoff each retry.</summary>
|
||||
public double ReconnectBackoffMultiplier { get; set; } = 2.0;
|
||||
/// <summary>
|
||||
/// #846/#852 — how long refused connections must persist before values and native conditions go
|
||||
/// <c>BadNoCommunication</c>. Every integer is meaningful to the driver: <c>0</c> = the first refusal,
|
||||
/// negative = the fast path is off (watchdog only). So, unlike the timing knobs, it is always written.
|
||||
/// </summary>
|
||||
public int CommsLossConfirmMs { get; set; } = DefaultCommsLossConfirmMs;
|
||||
/// <summary>
|
||||
/// Whether the driver's half of the write-back gate is armed. Default <c>false</c> — the
|
||||
/// driver shipped read-only, and the Agent's own <c>AllowPut</c> is an independent second
|
||||
/// gate that this cannot arm.
|
||||
@@ -329,6 +345,7 @@
|
||||
ReconnectMinBackoffMs = dto.Reconnect?.MinBackoffMs ?? defaults.ReconnectMinBackoffMs,
|
||||
ReconnectMaxBackoffMs = dto.Reconnect?.MaxBackoffMs ?? defaults.ReconnectMaxBackoffMs,
|
||||
ReconnectBackoffMultiplier = dto.Reconnect?.BackoffMultiplier ?? defaults.ReconnectBackoffMultiplier,
|
||||
CommsLossConfirmMs = dto.CommsLossConfirmMs ?? defaults.CommsLossConfirmMs,
|
||||
WriteEnabled = dto.Write?.Enabled ?? defaults.WriteEnabled,
|
||||
};
|
||||
}
|
||||
@@ -344,6 +361,9 @@
|
||||
SampleIntervalMs = Positive(SampleIntervalMs),
|
||||
SampleCount = Positive(SampleCount),
|
||||
HeartbeatMs = Positive(HeartbeatMs),
|
||||
// Written as-is, negative included: a negative is "off", not an unusable value to drop — dropping it
|
||||
// would silently re-enable the fast path at the driver's 2000 ms default.
|
||||
CommsLossConfirmMs = CommsLossConfirmMs,
|
||||
Probe = new MTConnectProbeConfigDto
|
||||
{
|
||||
Enabled = ProbeEnabled,
|
||||
@@ -383,11 +403,24 @@
|
||||
if (ProbeEnabled && ProbeTimeoutMs <= 0) { bad.Add("Probe timeout"); }
|
||||
if (ReconnectMaxBackoffMs <= 0) { bad.Add("Max backoff"); }
|
||||
|
||||
return bad.Count == 0
|
||||
? null
|
||||
: $"Must be greater than zero: {string.Join(", ", bad)}. A non-positive value is dropped on save and the driver's default applies.";
|
||||
if (bad.Count > 0)
|
||||
{
|
||||
return $"Must be greater than zero: {string.Join(", ", bad)}. A non-positive value is dropped on save and the driver's default applies.";
|
||||
}
|
||||
|
||||
// Advisory only (the value is still saved): the runtime's comms-loss watchdog re-initializes a driver
|
||||
// whose reads have been frozen for 90 s, so a window that long can never fire first.
|
||||
return CommsLossConfirmMs >= CommsLossWatchdogMs
|
||||
? $"Comms-loss confirm of {CommsLossConfirmMs} ms is at or beyond the ~{CommsLossWatchdogMs / 1000} s comms-loss watchdog, so the fast path never fires before it. Use a negative value to turn the fast path off explicitly."
|
||||
: null;
|
||||
}
|
||||
|
||||
/// <summary>The driver's own default for <see cref="CommsLossConfirmMs"/> (<c>MTConnectDriverOptions</c>).</summary>
|
||||
public const int DefaultCommsLossConfirmMs = 2_000;
|
||||
|
||||
/// <summary>The runtime's hardcoded comms-loss watchdog threshold (<c>DefaultCommsLossTimeout</c>, 90 s).</summary>
|
||||
public const int CommsLossWatchdogMs = 90_000;
|
||||
|
||||
private static int? Positive(int value) => value > 0 ? value : null;
|
||||
|
||||
private static string? Trimmed(string? value)
|
||||
@@ -418,6 +451,8 @@
|
||||
public int? SampleCount { get; init; }
|
||||
/// <summary>The <c>/sample</c> <c>heartbeat</c> query parameter in ms.</summary>
|
||||
public int? HeartbeatMs { get; init; }
|
||||
/// <summary>Refused-connection confirmation window in ms (#846; 0 = first refusal, negative = off).</summary>
|
||||
public int? CommsLossConfirmMs { get; init; }
|
||||
/// <summary>Background connectivity-probe knobs.</summary>
|
||||
public MTConnectProbeConfigDto? Probe { get; init; }
|
||||
/// <summary>Reconnect backoff knobs.</summary>
|
||||
|
||||
Reference in New Issue
Block a user