docs(quic): OnRecieve and Abort, and serving a stream without a thread
The README's QUIC section gains receiving by callback: a server answering one request per stream with a size cap enforced by Abort, the threads the callbacks run on, what they may call, and the std::stop_callback rule for destroying the wrapper; its follower example now drops a lagging peer with Abort rather than a graceful Stop. The consumer notes give the full contract, the weak_ptr idiom for reaching a stream from its own callbacks, and list the callbacks among what runs on msquic's workers; the agent notes count the new test, describe the per-stream delivery in the partition list, and add the callbacks to the worker-thread gotcha. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
bdca27b871
commit
f29c4a83d2
3 changed files with 36 additions and 6 deletions
32
README.md
32
README.md
|
|
@ -130,11 +130,40 @@ Keep the callback short: it runs on the msquic worker, so no `*Sync` call and no
|
|||
```cpp
|
||||
stream.SendAsync(line.data(), static_cast<std::uint32_t>(line.size()), /*finish=*/false,
|
||||
[](bool sent) { if (!sent) { /* the peer is gone */ } });
|
||||
if (stream.BytesInFlight() > 4 * 1024 * 1024) stream.Stop(); // a follower that cannot keep up
|
||||
if (stream.BytesInFlight() > 4 * 1024 * 1024) stream.Abort(); // a follower that cannot keep up
|
||||
```
|
||||
|
||||
The callback runs on msquic's worker, so it must not block (no `*Sync` call) or throw. A send that fails before msquic takes it, on a closed stream for one, reports from a pool task instead, never from inside `SendAsync`. Sends made before the wrapper is destroyed still go out and report. `SendSync` is the blocking form, and throws only when its own send was canceled.
|
||||
|
||||
#### Receiving by callback
|
||||
|
||||
`QUICStream::OnRecieve` hands a stream's bytes to one callback as they arrive and tells another when the peer is done, holding no thread in between: no reader parked in `RecieveSync`, no pool task blocked in `RecieveUntilCloseAsync`. A server answering one request per stream, with a size cap:
|
||||
|
||||
```cpp
|
||||
peer->OnStream([](Crafter::QUICStream incoming) {
|
||||
auto stream = std::make_shared<Crafter::QUICStream>(std::move(incoming));
|
||||
auto request = std::make_shared<std::string>();
|
||||
std::weak_ptr<Crafter::QUICStream> weak = stream;
|
||||
stream->OnRecieve(
|
||||
[request, weak](std::span<const char> bytes) {
|
||||
request->append(bytes.begin(), bytes.end());
|
||||
if (request->size() > 64 * 1024) {
|
||||
if (auto s = weak.lock()) s->Abort(1); // too big: reset both directions
|
||||
}
|
||||
},
|
||||
[request, weak](bool clean) {
|
||||
if (!clean) return; // reset, or the connection went
|
||||
if (auto s = weak.lock()) {
|
||||
const std::string answer = Answer(*request); // or later, from any thread
|
||||
s->SendAsync(answer.data(), static_cast<std::uint32_t>(answer.size()), /*finish=*/true, nullptr);
|
||||
}
|
||||
});
|
||||
Keep(stream); // destroying the wrapper unregisters its callbacks
|
||||
});
|
||||
```
|
||||
|
||||
Bytes that arrived before `OnRecieve` are delivered by it, on the calling thread, before it returns; after that the callbacks run on msquic's worker thread. No two callbacks of one stream, `SendAsync`'s included, ever run at once. `onEnd` fires once, after the last `onData`: `true` when the peer finished its side, `false` for a reset by either side or a lost connection. Inside the callbacks, `SendAsync`, `Abort` and destroying or moving the wrapper are fine; nothing that blocks (`*Sync` calls wait for the very worker they run on) and nothing that throws. Destroying the wrapper unregisters both, as `std::stop_callback` does: once the destructor returns neither runs again, and it waits for one running on another thread, so don't destroy it while holding a lock the callbacks take. `Abort(code)` resets both directions at once, from any thread or from inside `onData`; no `onData` follows it and `onEnd(false)` does. Don't mix `OnRecieve` with the `Recieve*` calls on one stream.
|
||||
|
||||
### HTTP/3 Components
|
||||
|
||||
HTTP/3 runs over QUIC, which always requires TLS. Pass server credentials when constructing the listener (or set `selfSigned = true` for a development-only ephemeral cert) and matching client credentials when constructing the client (`insecureNoServerValidation = true` for self-signed servers).
|
||||
|
|
@ -373,6 +402,7 @@ The library includes tests covering:
|
|||
- Closed-connection notices (`ShouldNotifyClosedConnectionQUIC`) — a server deletes each of 100 accepted connections from its `OnClosed` and the process then exits normally; a peer that exits or freezes, a server-side `Stop()`, a callback set after the close and an owner that deletes first each behave as documented
|
||||
- The peer hears a close (`ShouldTellPeerOfCloseQUIC`) — the server side of each of 20 connections learns at once that the client destroyed its `ClientQUIC`, rather than at the idle timeout
|
||||
- `SendSync` under a fast peer (`ShouldReturnFromSendSyncQUIC`) — 2000 sends to a peer that answers and finishes the stream at once all return
|
||||
- Receiving by callback (`ShouldRecieveByCallbackQUIC`) — bytes waiting before `OnRecieve` are delivered by it; registered mid-flow no two callbacks overlap and every byte arrives in order; a stream is echoed both ways with every pool worker parked; resets and lost connections end it once, not clean; `Abort` inside `onData` enforces a byte cap; and a wrapper destroyed or moved under its callbacks behaves as documented
|
||||
- Sending without a thread (`ShouldSendWithoutBlockingQUIC`) — a 16 MiB burst of `SendAsync` returns at once and completes in order; a chain of sends from inside callbacks completes with every pool worker parked; a stopped connection, a destroyed wrapper, concurrent senders and a closed stream each report as documented
|
||||
- WebTransport echo (`ShouldEchoWebTransport`) — extended-CONNECT acceptance, draft-02 SETTINGS, bidi data stream framing (`WT_STREAM 0x41` + session-id varint), and byte-for-byte echo
|
||||
- HTTP/1.1 wire format (`ShouldParseHTTP1`) — serialisation, incremental parsing fed one byte at a time, chunked bodies with trailers, pipelining, interim 1xx, HTTP/1.0 and HEAD framing, and the malformed inputs the parser is required to reject
|
||||
|
|
|
|||
Loading…
Reference in a new issue