Files
amethyst/quic/src
Claude 03cfb3188f feat(quic): rewrite SendBuffer for retain-until-ACK with markAcked/markLost
Foundation for STREAM and CRYPTO retransmit (commits C, D of the
deferred-follow-ups pass). Replaces the prior best-effort mode
where takeChunk released bytes immediately.

# Three logical regions

The buffer covers `[flushedFloor, nextOffset)`. Each byte is in one
of three states:

  - In-flight: sent but not yet ACK'd. Sorted-by-offset list.
  - Needs retransmit: declared lost; re-sent before any fresh bytes.
    FIFO queue.
  - Unsent: `[nextSendOffset, nextOffset)`.

# New API

  - markAcked(offset, length): removes the range from in-flight,
    advances flushedFloor through any contiguous low-end ACKs and
    shifts the underlying byte storage forward. Length=0 means
    FIN-only ACK; latches finAcked = true.
  - markLost(offset, length, fin): removes from in-flight, appends
    to retransmit queue. If fin, clears finSent so the FIN gets
    re-emitted. Idempotent: stale loss notifications below
    flushedFloor are absorbed.

takeChunk priority order:
  1. retransmit queue (preserves original offset; same byte data)
  2. fresh unsent bytes from [nextSendOffset, nextOffset)
  3. FIN-only zero-byte chunk if finPending and everything drained

# Range arithmetic

removeOverlap walks in-flight, computes the three-piece split for
each overlapping range (leftKept, covered, rightKept) and either
drops the covered portion (ACK) or pushes it onto retransmit
(loss). FIN belongs to the rightmost piece, so split at the end of
the original range correctly preserves it.

# Compaction

advanceFlushedFloorIfPossible bumps the floor whenever the lowest
in-flight / retransmit / unsent offset is above it, then
ByteArray.copyInto shifts the data window forward. Memory bounded
by `nextOffset - flushedFloor` rather than growing unboundedly.

# FIN handling

Treated as a virtual byte at offset = nextOffset. finPending arms
takeChunk to attach FIN to the final data chunk; finSent latches
true on emission; markLost(fin=true) clears it for re-emission;
finAcked latches true when the FIN-bearing range is ACK'd.

# Tests added (14, all pass)

  - takeChunk_releasesNothingUntilAcked
  - markAcked_full_releasesBytes_andDoesNotResend
  - markLost_movesBytesToRetransmitQueue_takeChunkReplaysSameOffset
  - markLost_partialRange_splitsInFlight
  - markAcked_partialRange_splitsInFlight
  - retransmitDrainsBeforeFreshBytes (priority order)
  - maxBytesSplits_acrossRetransmitAndFresh
  - fin_carriedOnFinalDataChunk_andRetransmittedOnLoss
  - fin_only_emittedAfterDataDrained
  - fin_only_lostAndRetransmits
  - markAcked_advancesFlushedFloor_releasesMemory
  - markAcked_outOfOrder_preventsFloorAdvance
  - markLost_belowFlushedFloor_isNoop (defensive)
  - finAcked_latchesTrueOnce
  - readableBytes_reflectsRetransmitPlusFresh
  - multipleSendsWithinSingleEnqueue_acksIndependently

Existing :quic tests (handshake, flow control, frame routing) +
nestsClient moq-lite + amethyst Android compile all pass.
SendBufferConcurrencyTest unchanged — the synchronized-on-this
discipline carries over from the prior implementation.

Wires nothing yet — commits C/D will route Stream/Crypto tokens
through markLost. The dispatcher in QuicConnection.onTokensLost
already calls markLost (added in commit A as a no-op stub); now
those calls actually do work.

https://claude.ai/code/session_01PYYez8a6sjiakyjAxsfCEQ
2026-05-04 23:20:39 +00:00
..