fix(localdb): fault live sync session on cap-prune; keepalive + newest-wins sync guard (0.3.2, ScadaBridge arch-review P4-1 + P4-3)

This commit is contained in:
Joseph Doherty
2026-08-19 14:49:31 -04:00
parent dbcbc5bd41
commit f62b017cc7
13 changed files with 574 additions and 21 deletions
@@ -64,6 +64,34 @@ public sealed class ReplicationOptions
/// <summary>Upper bound on the exponential reconnect backoff.</summary>
public TimeSpan ReconnectBackoffMax { get; set; } = TimeSpan.FromSeconds(60);
/// <summary>
/// How long the initiator's HTTP/2 connection may sit without a keepalive PING before one is
/// sent. Default 60s.
/// </summary>
/// <remarks>
/// <para>
/// A sync stream is long-lived and, on a converged pair, almost entirely silent — which is
/// exactly the shape a half-open TCP connection hides behind. Without pings, a hard crash or a
/// partition of one node leaves the other holding a stream that will never produce another byte
/// and never fail either: the initiator sits in a session it believes is healthy, and the
/// passive node's handler sits inside its single-stream guard, refusing the reconnect that would
/// heal the pair. Pings turn that into a detected failure on both ends.
/// </para>
/// <para>
/// Sent with <c>KeepAlivePingPolicy.Always</c>, so they flow on an idle connection too — an
/// active-streams-only policy would not fire in precisely the silent-pair case above. Safe over
/// h2c: a PING is an HTTP/2 frame, nothing to do with TLS.
/// </para>
/// </remarks>
public TimeSpan KeepAlivePingDelay { get; set; } = TimeSpan.FromSeconds(60);
/// <summary>
/// How long the initiator waits for a keepalive PING ack before tearing the connection down.
/// Default 20s. Detection therefore lands within roughly
/// <see cref="KeepAlivePingDelay"/> + this.
/// </summary>
public TimeSpan KeepAlivePingTimeout { get; set; } = TimeSpan.FromSeconds(20);
/// <summary>How often the maintenance service enforces the oplog caps and prunes expired tombstones.</summary>
public TimeSpan MaintenanceInterval { get; set; } = TimeSpan.FromSeconds(60);