fix(quic): let the streams already stopped finish before ~ClientQUIC closes

~ClientQUIC shut the connection down first and drained its streams
after. msquic aborts every stream with the connection, and with them
whatever it had not sent yet, so a server that replied with
SendAsync(..., finish = true), let the stream go and deleted the
connection lost the reply whenever the shutdown, queued right behind
the send, reached the worker before the flush that would have sent it.
The interface promised the opposite: that the destructor waits for
every stream "to finish closing first".

Found with Crafter.Debug, whose session answers every request still
waiting when it ends and then deletes its connections: the follower
it told "shutting down" missed the reply in 4 runs of 30.

Streams now count as finishing from QUICStream::Stop to their
SHUTDOWN_COMPLETE, in the registry beside the open count, and the
destructor waits for that count to reach zero before the shutdown.
Only streams that can send are counted, since a stream that cannot has
nothing on its way. A stopped stream finishes once the peer has
acknowledged its FIN, about a round trip; the wait is bounded at 1 s
so a peer that stopped answering, or one that never reads, holds the
destructor no longer than that, and a peer closing meanwhile aborts the
streams, which ends the wait too. Streams their owner still holds are
not waited for: they are aborted as before.

Measured with ShouldFinishStoppedStreamsQUIC: before, 0 of 20 replies of
64 KiB arrived; after, 20 of 20, with the slowest delete at 1 ms.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Jorijn van der Graaf 2026-11-01 03:03:20 +01:00
commit 9ea07c54f1
Signed by: jorijnvdgraaf
GPG key ID: 2937E59CDCC1BCFB
3 changed files with 78 additions and 15 deletions

View file

@ -195,6 +195,7 @@ extern "C" Configuration CrafterBuildProject(std::span<const std::string_view> a
cfg.AddTest("ShouldBindLoopbackQUIC").Dependencies({ &cfg });
cfg.AddTest("ShouldEchoWebTransport").Dependencies({ &cfg });
cfg.AddTest("ShouldFallbackUnknownRoutes").Dependencies({ &cfg });
cfg.AddTest("ShouldFinishStoppedStreamsQUIC").Dependencies({ &cfg });
cfg.AddTest("ShouldHonourQUICSettings").Dependencies({ &cfg });
cfg.AddTest("ShouldNotDropEarlyStreams").Dependencies({ &cfg });
cfg.AddTest("ShouldNotifyClosedConnectionQUIC").Dependencies({ &cfg });