docs(quic): what ~ClientQUIC lets finish before it closes
The README and the consumer notes say that a reply sent on a stream let go just before deleting the connection still arrives, and the agent notes add the gotcha with the test that guards it, and count 39 tests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
4876ff14a8
commit
79a2f12629
3 changed files with 5 additions and 4 deletions
|
|
@ -28,7 +28,7 @@ Partitions and what they own:
|
|||
- `:URL`: `URL`, an absolute URL as RFC 3986 reads it (`Parse`), the resolution of a reference against it (`Resolve`, RFC 3986 §5.2, dot segments removed), the recomposition, and the pieces a request is built from. Inline, native only; what `ClientHTTP1::Fetch` follows a Location with.
|
||||
- `:HTTP1`: the RFC 9112 codec, inline: `SerializeRequest`, `SerializeResponse`, `SerializeContinue`, `MessageParser`, `MessageLimits`, `HTTP1ProtocolError`, and the framing it refuses to guess at.
|
||||
- `:ClientHTTP1`, `:ListenerHTTP1`: HTTP/1.1 over a `ByteStream&`. The listener is a `ListenerTCP` with a thread per accepted connection; the client keeps one pooled connection with a replay-once rule, and `ClientHTTP1::Fetch` is the URL fetch that follows redirects by RFC 9110 §15.4 with a client per hop (`FetchOptions`, `FetchResult`, `FetchStop`).
|
||||
- `:ClientQUIC`: `ClientQUIC` and `QUICStream` for both ends of a connection, `QUICSettings`, both QUIC credential structs, `QUICException`, `QUICClosedException`. The native unit holds the process-wide `MsQuicRuntime` (API table and registration, closed by a static destructor at exit), the stream registry with its refcounted handles, a `SendRequest` per send carrying its own outcome, the per-stream delivery of `OnRecieve`'s and `SendAsync`'s callbacks (whichever thread finds nobody delivering runs what is ready; the rest queue and return), the `CloseNotice` behind `OnClosed`, and the drain in `~ClientQUIC` (streams, then the connection's own `SHUTDOWN_COMPLETE`).
|
||||
- `:ClientQUIC`: `ClientQUIC` and `QUICStream` for both ends of a connection, `QUICSettings`, both QUIC credential structs, `QUICException`, `QUICClosedException`. The native unit holds the process-wide `MsQuicRuntime` (API table and registration, closed by a static destructor at exit), the stream registry with its refcounted handles, a `SendRequest` per send carrying its own outcome, the per-stream delivery of `OnRecieve`'s and `SendAsync`'s callbacks (whichever thread finds nobody delivering runs what is ready; the rest queue and return), the `CloseNotice` behind `OnClosed`, and the drain in `~ClientQUIC` (the streams already stopped, then every stream, then the connection's own `SHUTDOWN_COMPLETE`).
|
||||
- `:ListenerQUIC`: the msquic listener, its four `Listen*` variants, the self-signed certificate's on-disk cache (per user under `$XDG_CACHE_HOME/crafter-network/quic-cert/`, behind a lock; the OpenSSL work is module-internal functions of `:TLS`), `ComputeCertificateHashSHA256`, `GetSelfSignedCertificatePath`.
|
||||
- `:HTTP3`: QUIC varints, HTTP/3 frames, the QPACK static table, Huffman decoding, the control-stream preludes and the WebTransport stream prefixes. Native only: it throws for protocol errors.
|
||||
- `:ClientHTTP`, `:ListenerHTTP`: HTTP/3 on `:ClientQUIC` and `:ListenerQUIC`. The listener demuxes every bidirectional stream into an HTTP/3 request or a WebTransport data stream and accepts extended CONNECT when `wtRoutes` is non-empty.
|
||||
|
|
@ -51,7 +51,7 @@ crafter-build # native: TCP, TLS, HTTP/1.1, QUI
|
|||
crafter-build --no-quic # native without QUIC, HTTP/3, WebTransport and msquic
|
||||
crafter-build --local # Crafter.Thread from ../Crafter.Thread
|
||||
crafter-build --target=wasm32-wasip1 # the browser library
|
||||
crafter-build test # the suite, native: 38 tests
|
||||
crafter-build test # the suite, native: 39 tests
|
||||
crafter-build test 'Should*HTTP1' # a name glob
|
||||
crafter-build test --no-quic # the TCP, URL, TLS and HTTP/1.1 tests plus ShouldBuildWithoutQUIC: 19
|
||||
crafter-build test --list
|
||||
|
|
@ -123,6 +123,7 @@ Decided deliberately; change them only with the maintainer's say-so.
|
|||
- **`:TLS` declares module-internal functions** (`MakeWebTransportCertificatePem`, `CertificatePairExpiry`, `EncodePkcs12`, `CertificateSha256`) without `export`: `:ListenerQUIC` imports `:TLS` for them so libssl stays behind one partition, and a consumer never sees them.
|
||||
- **msquic's registration is closed by a static destructor that waits for every handle.** A test that returns from `main` with an accepted peer alive waits two minutes, the idle timeout; the tests `_Exit`. The exception, `ShouldSurviveConnectionChurn`, exists because a leaked stream handle once hung that close forever (`fix(quic)` in `git log`, with the `SHUTDOWN_ON_FAIL` story).
|
||||
- **Callbacks the interfaces say run "on the msquic worker" run as pool tasks**: `ClientQUIC::OnStream` and `OnDatagram` both enqueue. What arrives before a handler is set is queued: streams are delivered on `OnStream`; datagrams go to `RecieveDatagramSync`'s queue and never to a later `OnDatagram`. `OnClosed`, the native `QUICStream::SendAsync` callback and `OnRecieve`'s callbacks are the exceptions: they do run on the worker, `OnClosed` inside `SHUTDOWN_COMPLETE` after the handle is closed (a pool task only when set after the close), `onSent` inside the send's `SEND_COMPLETE` (a pool task only for a send msquic never took), `onData`/`onEnd` inside `RECEIVE` and the shutdown events (on the caller's thread for what `OnRecieve` finds waiting), so that none depends on a pool the host shares. A blocking call inside any of them stalls every connection on that worker.
|
||||
- **`~ClientQUIC` lets the streams already stopped finish, up to 1 s, before `ConnectionShutdown`,** which aborts every stream and whatever msquic had not sent of it: a server that replied with `SendAsync(..., finish = true)`, let the stream go and deleted the connection lost the reply every time in `ShouldFinishStoppedStreamsQUIC` before. Streams count as finishing from `QUICStream::Stop` (a stream that can send) to `SHUTDOWN_COMPLETE`, in the registry's `finishing`.
|
||||
- **`~ClientQUIC` waits for the connection's `SHUTDOWN_COMPLETE`, up to 1 s, before `ConnectionClose`.** msquic sends `CONNECTION_CLOSE` from a `FLUSH_SEND` it queues while processing `ConnectionShutdown` and ignores once `ShutdownComplete` is set; a close queued right behind the shutdown completed it silently, and the peer only timed the connection out (`fix(quic)` in `git log`, `ShouldTellPeerOfCloseQUIC`). So a destructor on an msquic worker thread (inside a stream callback) blocks that second and then some; only `OnClosed`'s, which has nothing left to close, is free.
|
||||
- **`ListenerQUIC::ListenAsync*` run the accept loop on a `std::thread`**, not the pool as their comments say, and `ListenerHTTP::Listen` is `ListenSyncAsync` underneath, so a `ListenerHTTP`'s per-connection setup is a pool task. A saturated pool delays a new connection's SETTINGS; the early streams are buffered meanwhile (`ShouldNotDropEarlyStreams`).
|
||||
- **A `ClientQUIC` against an HTTP/3 server must drain the server's unidirectional streams** (control, QPACK encoder and decoder) with an `OnStream` reader, or they back up and msquic may abort the connection. `ClientHTTP` does it; a hand-rolled WebTransport client (`ShouldEchoWebTransport`) does it itself.
|
||||
|
|
|
|||
Loading…
Reference in a new issue