Merge branch 'fix/gwc-25-replaygap-trio'
# Conflicts: # archreview/2026-07-12/remediation/00-tracking.md # archreview/2026-07-12/remediation/10-gateway-core.md
This commit is contained in:
@@ -390,10 +390,15 @@ public sealed class SessionEventDistributor : IAsyncDisposable
|
||||
/// <see cref="TryGetReplayFrom"/> gap semantics.
|
||||
/// </param>
|
||||
/// <param name="oldestAvailableSequence">
|
||||
/// The oldest worker sequence still retained and replayable. <c>0</c> when nothing is
|
||||
/// retained. Meaningful to the caller only when <paramref name="gap"/> is
|
||||
/// <see langword="true"/> (it populates the ReplayGap sentinel's
|
||||
/// <c>oldest_available_sequence</c>).
|
||||
/// The resume anchor reported to a gapped client: the oldest worker sequence still
|
||||
/// retained and replayable, or — when age/capacity eviction has emptied the ring
|
||||
/// entirely — the next sequence that can possibly be delivered (highest observed + 1).
|
||||
/// Either way the client's documented
|
||||
/// <c>after_worker_sequence = oldest_available_sequence - 1</c> formula yields a cursor
|
||||
/// that resumes without dropping live events. <c>0</c> when <paramref name="gap"/> is
|
||||
/// <see langword="false"/>, where the value is meaningless and never emitted. Meaningful
|
||||
/// to the caller only when <paramref name="gap"/> is <see langword="true"/> (it populates
|
||||
/// the ReplayGap sentinel's <c>oldest_available_sequence</c>).
|
||||
/// </param>
|
||||
/// <param name="liveResumeSequence">
|
||||
/// The worker sequence the live channel must resume strictly after: the highest
|
||||
@@ -463,7 +468,20 @@ public sealed class SessionEventDistributor : IAsyncDisposable
|
||||
if (_replayCount == 0)
|
||||
{
|
||||
gap = _anyEventSeen && afterSequence < _highestSequenceSeen;
|
||||
oldestAvailableSequence = 0; // meaningful only when gap == true; 0 here since nothing is retained
|
||||
|
||||
// GWC-25: nothing is retained, but a gapped client still needs a usable resume
|
||||
// anchor. The documented client formula is
|
||||
// after_worker_sequence = oldest_available_sequence - 1, so reporting 0 here made
|
||||
// an unsigned client compute ulong.MaxValue: the follow-up resume then replayed
|
||||
// nothing and reported no gap (MaxValue is below no real sequence, in this branch
|
||||
// and in the retained branch's wrap guard alike), and the caller's live filter
|
||||
// (sequence > liveResumeSequence) dropped every subsequent event — a silently
|
||||
// dead stream. Reporting the next sequence that can possibly
|
||||
// be delivered (highest observed + 1) makes oldest - 1 land exactly on the
|
||||
// highest observed sequence, so the resume delivers everything newer. Nothing is
|
||||
// recoverable either way; the sentinel's job is to say "re-snapshot".
|
||||
// Still 0 when gap == false, where the field is documented as meaningless.
|
||||
oldestAvailableSequence = gap ? _highestSequenceSeen + 1 : 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user