Commit Graph

6 Commits

Author SHA1 Message Date
Claude 9d1d053407 diag(quic-interop): hunt the connBudget-exhausted hypothesis
The 2026-05-07 qlog post-streamsLock-fix shows the smoking gun:
  - 1406 packets with exactly 1 stream frame each
  - 1 packet (out of 1407) with >1 stream frames
  - first stream packets at 1665ms / 1709ms (one RTT apart)

Wire shape says: writer is NOT bursting 64 streams per drain.
Hypothesis: connBudget exhaustion. Trace:

  Iteration 1 of buildApplicationPacket:
    streamA.takeChunk(maxBytes = min(streamCredit, connBudget))
    → returns 50-byte chunk
    → connBudget -= 50

  Iteration N: connBudget == 0
    streamN.takeChunk(maxBytes = 0)
    → returns null (fresh-bytes path: cap==0 ⇒ null)
    → skip
    → next iteration also skips
    → drain returns 1-stream packet

  Wait for peer's MAX_DATA (one RTT)
  → connBudget bumps by maybe 50 bytes
  → emit one more stream
  → repeat

This matches the 40ms-per-stream cadence in the qlog exactly.

If the hypothesis is right, peer's initial_max_data is too small and
we're connection-flow-control bound by design (or by aioquic-qns
config). Three new sections in inspect-multiplexing.sh:

  1. peer transport_parameters — directly shows initial_max_data
  2. MAX_DATA arrivals — confirms the cadence + delta-per-bump
  3. per-packet stream_id — confirms each packet carries a different
     stream's first chunk

Also filtered the runner's "Generated random file" + "Requests:"
spam from run-matrix.sh output (separately requested).

Re-run inspect on the existing log dir to verify (no new matrix run
needed):
  ./quic/interop/inspect-multiplexing.sh

If initial_max_data is small, the fix is on us — we should pre-
advertise a larger initial_max_data on our side AND push for a
larger one from the peer (via setting our initial_max_data so peer
knows we can receive a lot, which may inform their MAX_DATA cadence).

https://claude.ai/code/session_01HcvfQq1ttPV9PkRoJb4nyT
2026-05-07 12:43:57 +00:00
Claude b7f63a6854 diag(quic-interop): targeted multiplex traces
The matrix run on 2026-05-07 showed the same 1-stream-per-packet wire
shape AFTER the streamsLock fix — meaning the lock fix was correct
but didn't address the root cause. Adding diagnostics so the next
investigation has data to work with, instead of more theorizing.

Two pieces, both quiet by default:

(1) inspect-multiplexing.sh: histograms over packet_sent events that
   answer "is the writer coalescing or not" without re-running the
   matrix. Re-run on the existing run dir and we get:
     - frames-per-packet histogram: should be skewed high (≥4) if
       coalescing is working; skewed to 1 if regressed
     - stream-frames-per-packet histogram: same shape but only
       counting STREAM frames (filters out ack-only packets)
     - first 30 packet_sent timestamps: did we burst 64 streams in
       <50ms or did we dribble them out one-RTT-per-stream?

(2) InteropClient: per-chunk wall-clock split (enqueue ms vs
   responses ms vs cumulative). Behind QUIC_INTEROP_DEBUG=1, off in
   matrix runs by default. Tells us whether the bottleneck is
   client-side (long enqueue ms — writer can't pack the batch) or
   server-side (long responses ms — server processes streams
   serially).

These together should localize bug to either:
  A) our writer regressing one-stream-per-packet under live driver
     load (despite MultiplexingCoalescingTest passing synchronously)
  B) aioquic-qns serving 32-byte files from disk on macOS Docker FS
     at 30-40ms each, so 32 chunks * 64 streams sequential = 60s

If (A), we have a writer bug to fix. If (B), the test runner is the
bottleneck and we should validate against a faster server (quic-go).

https://claude.ai/code/session_01HcvfQq1ttPV9PkRoJb4nyT
2026-05-07 12:26:58 +00:00
Claude 226fa14882 chore(quic-interop): inspect — point at the actual runner paths
Runner persists:
  <case>/output.txt              — runner stdout/stderr (+ client logs)
  <case>/client/qlog/*.sqlog     — qlog under client/qlog/, not client/
  <case>/server/stderr.log       — server stderr (CONNECTION_CLOSE reason)

Plus targeted grep for the events we actually need to localize a
multiplexing failure:
  - last 5 packet_received / packet_sent (where did the flow stall)
  - connection_closed (peer error code + reason)
  - packet_dropped (sign of decrypt fails / unknown CID / etc.)
2026-05-07 03:09:19 +00:00
Claude c0131b8125 chore(quic-interop): inspect — list every file in case dir first
Layout varies by runner version; print the tree so we can see what
the runner actually wrote (qlog/pcap/log filenames, sizes) before
guessing at paths.
2026-05-07 03:08:23 +00:00
Claude b924df1632 fix(quic-interop): inspect script — runner layout is <pair>/<testcase> 2026-05-07 03:07:56 +00:00
Claude 01a342f592 chore(quic-interop): inspect-multiplexing.sh post-mortem helper
Pulls the diagnostics needed to localize a multiplexing failure:
  - tail client/log.txt (per-stream errors, final outcome)
  - tail server/log.txt (peer's CONNECTION_CLOSE reason)
  - tail client qlog (frame-level event sequence)
  - count downloaded files (how many of N actually finished)
  - qlog event-type histogram (e.g. "lots of packet_dropped",
    "no stream_state_updated past t=30s", etc.)

Resolves the most recent run-* dir under ../quic-interop-runner/logs
automatically — no need to copy-paste paths after every run.

https://claude.ai/code/session_01HcvfQq1ttPV9PkRoJb4nyT
2026-05-07 03:06:48 +00:00