From 99a1a91de4634a335fd1c7309fcb57ecf23e30c5 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 7 May 2026 00:46:28 +0000 Subject: [PATCH] =?UTF-8?q?fix(quic-interop):=20drop=20per-event=20QlogWri?= =?UTF-8?q?ter=20flush=20=E2=80=94=20it=20stalls=20the=20connection?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- .../vitorpamplona/quic/interop/runner/QlogWriter.kt | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/quic/interop/src/main/kotlin/com/vitorpamplona/quic/interop/runner/QlogWriter.kt b/quic/interop/src/main/kotlin/com/vitorpamplona/quic/interop/runner/QlogWriter.kt index e334fa089..59f4f5afc 100644 --- a/quic/interop/src/main/kotlin/com/vitorpamplona/quic/interop/runner/QlogWriter.kt +++ b/quic/interop/src/main/kotlin/com/vitorpamplona/quic/interop/runner/QlogWriter.kt @@ -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.