fix(quic): let ~ClientQUIC's CONNECTION_CLOSE reach the peer
~ClientQUIC asked msquic to shut the connection down, waited for its streams to drain, and closed the handle. With no stream open the drain returns at once, so ConnectionClose followed ConnectionShutdown within microseconds. msquic sends the CONNECTION_CLOSE frame from a FLUSH_SEND operation it queues while it processes the shutdown; the close the destructor had queued meanwhile reached the worker first, forced the shutdown to complete silently (QuicConnTryClose: "silent close forced after we already started the close process"), and the flush, which is ignored once ShutdownComplete is set, sent nothing. The peer learned of the close only at its idle timeout, two minutes by default: a server whose clients each live for one request, such as a debug server driven by a CLI that connects once per command, kept every one of them that long. The destructor now also waits, after the drain, for the connection's SHUTDOWN_COMPLETE before closing the handle. That comes about one round trip later once the peer answers the close, or after msquic's closing period (three PTOs) when it does not; the wait is bounded at 1 s, since the frame went out at the start either way and a peer that is gone must not hold the destructor for the whole closing period. The handler sets a flag for it before it looks at the handle, which the destructor holds, so the close stays the destructor's. Measured with ShouldTellPeerOfCloseQUIC: before, the server was not told of the first of 20 client closes within 2 s (and, counted another way, 8 of 100 peer closes reached the server within 10 s in one run, 0 of 100 in the next); after, all 20 within a millisecond. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
76e927595d
commit
795f06178d
2 changed files with 32 additions and 4 deletions
|
|
@ -196,7 +196,10 @@ namespace Crafter {
|
|||
//
|
||||
// Lifetime: ~ClientQUIC shuts the connection down and closes it, waiting
|
||||
// (bounded — 5s) for every stream on it to finish closing first, since
|
||||
// QUICStream::Stop only initiates a shutdown that completes asynchronously.
|
||||
// QUICStream::Stop only initiates a shutdown that completes asynchronously,
|
||||
// and then (bounded — 1s, about a round trip when the peer answers) for
|
||||
// the shutdown to complete, so the peer is sent the close rather than
|
||||
// left to time the connection out.
|
||||
// Streams outliving the connection are therefore safe to hold, though they
|
||||
// are dead once it goes; prefer destroying or moving them out first.
|
||||
//
|
||||
|
|
|
|||
Loading…
Reference in a new issue