fix(quic-interop): drop per-event QlogWriter flush — it stalls the connection

aioquic still 0/7 after the close-race fix because there's a deeper
issue. QlogWriter.writeLineLocked() called writer.flush() on every
event. On macOS Docker Desktop the filesystem is virtualized and
per-event flush is multi-millisecond. The qlog hooks fire from
QuicConnectionWriter.drainOutbound() which runs INSIDE
conn.lock.withLock { ... } on the send loop. Every flush stalls
that lock; the receive loop blocks indefinitely trying to acquire
it for feedDatagram().

Symptom matched: client.sqlog showed our ClientHello packet_sent
at t=400ms, then NOTHING for the rest of the test — no
packet_received (server's response never decoded), no PTO probe
(send loop stuck behind disk IO).

Fix: remove per-event flush. close() flushes once at the end of
the run, which gives us the trace for everything except a hard
JVM kill mid-test (acceptable trade). BufferedWriter still buffers
in-memory; we don't lose events, just don't sync them to disk
synchronously.

Should restore aioquic to 5/7. picoquic was already 5/7 because
its server-response timing apparently let our send loop drain
between flushes; aioquic's faster turnaround tripped the lock more
reliably.

https://claude.ai/code/session_01HcvfQq1ttPV9PkRoJb4nyT
This commit is contained in:
Claude
2026-05-07 00:46:28 +00:00
parent bd9d717dff
commit 99a1a91de4
@@ -301,9 +301,16 @@ class QlogWriter(
try {
writer.write(line)
writer.write("\n")
// Flush every line so a hard-killed process still leaves a
// partial-but-parseable trace.
writer.flush()
// Deliberately NOT flushing per event: this method runs
// inside conn.lock.withLock { drainOutbound(...) } on the
// hot send path. On macOS Docker Desktop the filesystem
// is virtualized and per-event flush is multi-millisecond.
// Every flush stalls the connection lock, blocks the
// receive loop, and the connection silently dies
// mid-handshake. close() does the only flush we need
// for normal completion. A hard-killed JVM loses recent
// events but that's an acceptable trade — the alternative
// is the connection never completing in the first place.
} catch (_: java.io.IOException) {
// Stream closed under us. Latch closed so subsequent emits
// skip the lock and return immediately.