Repository navigation
H3wire p2 mq h3 - #43
Merged
Merged
Conversation
…n_h3_request errors
h3wire emits no StreamAborted once Finished went out, so h3_reset on a request whose receive side had finished left the request un-aborted with its DATA frame in flight; the next h3_send_body wrote to a stream h3wire had reaped (data_written: UnknownStream, a debug_assert). h3_reset now marks the request aborted itself, as it already did without h3wire state (adoption spec §4.3). Found by the C7 property test.
A peer RESET_STREAM after the request's FIN (xqc_h3 sends one on any cancel) makes h3wire reset our send side, but after Finished it emits no StreamAborted. H3Wire kept its DATA frame in flight and the next h3_send_body wrote to a reaped stream (data_written: UnknownStream, a debug_assert), or got Stale from send_data. It now ends the send side as SendStopped does: H3Writable, then the client discards and the server gets Err(Reset) (adoption spec §4.5). Same root cause as the h3_reset fix, reached from the peer.
random_ops_hold_invariants (adoption spec §6.1): 200 cases of up to 64 steps over the Rig, both roles. Steps: peer HEADERS / body / FIN / reset / raw reset / STOP_SENDING / GOAWAY, the gateway's h3_* ops (keeping its re-offer contract), transport StreamWritable / StreamClosed / close, send limits and pumps. After every step debug_bounds_hold() (new, behind test-support: carry <= max(BOOT_READ, largest buf), h3wire buffered <= bound); throughout, one H3Request and at most one H3Closed per request, nothing after it, and H3Closed once the transport closed the stream and the gateway has its receive end; at the end, after the close and a drain, exactly one H3Closed per known request. The Rig models what xquic can deliver: StreamClosed (after StreamCloseStats) only once both directions are over, every stream's close before ConnClosed on the close step, one ConnClosed, no traffic after it, open_stream Stale after it, and w's resets / STOP_SENDING relayed to the peer.
A response to a request the peer already reset (aborted before Finished, or send side stopped after it) reached h3wire's send_headers on a reaped stream: UnknownStream, mapped to Err(Stale) for a request H3Wire still holds. send_headers now returns Err(Reset) once the request is aborted or its send side stopped, as h3_send_body does (adoption spec §4.5). Found by the property test's new Stale check.
- PROPTEST_CASES overrides the case count (default 200) for deep runs. - peer_reset_after_finished_client_discards: client role of the peer RESET after Finished (spec §4.5): upload accepted and discarded, (0, true) still read, one H3Closed. - The drain runs in request order, so a shrunk case replays the same. - PeerRawReset mostly targets a stream past the peer's FIN, where h3wire may be Finished; a new check makes Err(Stale) before H3Closed a failure. Together they catch a revert of the peer-reset fix in the default 200 cases. - Model: a synthetic peer RESET / STOP_SENDING only on a stream the peer knows; the gateway sends body and finish only after its headers and nothing after its FIN.
Passthrough H3 events and conn-level readiness/creation events (both modes) were popped without a liveness check, so one that went stale between the inner drain and the pop reached the app, unlike the bare transport. H3 events now probe h3_req_info in passthrough, conn events probe conn_stats; close events still always pop (adoption spec §4.1).
`matrix!` runs a scenario once per (client, server) backend cell (xx/xw/wx/ww). Every compliant pair_gateway scenario and pair_h3's h3_40k_section_both_ways now run in all four cells (adoption spec §6.2). Request paths carry the cell tag so no cell can satisfy another's log marker; the 403 drain line (path "-") is keyed by a per-cell body length. Per-cell expectations from spec §5.3 (6): on a Wire server, content-length 10 with FIN on HEADERS is reset before the gateway sees the request, so the cell asserts a nonzero stream_err and an untouched origin. The direct-H3 CONNECT is now the valid authority form, which reaches the gateway's 400 in every cell.
The malformed producers move to RawH3Peer and run against an xqc_h3 and a Wire receiver (`receivers!`, cells x / w; adoption spec §6.2): - a response DATA frame cut by FIN: the fetch socket is reset in both cells; h3wire closes the conn with H3_FRAME_ERROR (spec §5.3 (1)); - the same cut on a request: xqc_h3 logs the gateway's reset; on Wire the conn closes with H3_FRAME_ERROR and the fan-out logs it once; - NUL in x-mq-auth / :path / x-mq-class and a CONNECT with :scheme / :path: xqc_h3 keeps today's 403 / 400 / `?` and 400; h3wire resets with H3_MESSAGE_ERROR and the gateway logs nothing (spec §5.3 (6)). fetch_short_cl_response_aborts_client joins the 4-cell matrix. EchoMode::CutResponseFrame, H3Script.truncate_after_partial, CUT_BODY and H3Recorded.accepted lose their last users and are deleted.
Every matrixed pair_gateway request that the server logs now waits for its mq.req line before shutdown and asserts the full record, field for field (adoption spec §6.2). `assert_req` takes one expected record per scenario: a timing field written `T` needs a value >= 0 (its stamps were set), and -1 stays exact. The §5.3 (6) differences are written per cell: `req_bytes` 50 on an xqc_h3 server and 0 on a Wire server for the short and excess uploads. Lines with `path="-"` are keyed by a per-cell `req_bytes`: the 403 drain as before, and the direct-H3 CONNECT, which now carries a `cell + 1` byte body. `cell_index(cells)` is the one source of that index. On a Wire server the client's `stream_err` is asserted as H3_MESSAGE_ERROR, and the cl-with-FIN cell also asserts that no `:status` and no FIN arrived.
Every call that takes `now` serviced every H3 conn, so a tunnel stream op or a UDP-lane datagram paid O(H3 conns x stream map). Only conns whose h3wire state an op touched, or that still wait on the transport (a pending FIN, a short core-bytes write), are swept now; event handlers keep servicing their own conn. The outer queue keeps its level events as (kind, slot) flags instead of scanning the queue.
A gateway that offered `fin` has ended its body: a re-offer after a Blocked or partial h3_send_body keeps the fin and adds no bytes. The model dropped it, so H3Wire finished the stream the gateway still believed open and a later contract-breaking h3_send_headers got Stale before H3Closed (a PROPTEST_CASES=30000 run hit it at d4ff0b6).
spawn_mitm_on takes the (client, server) backends; spawn_mitm keeps the xqc_h3 pair. Every pair_mitm scenario becomes a matrix! test. The cells of a test that waits for an opaque-route log line run one at a time, each waiting for a line logged after it started, since the line is the same in every cell. mq-h3: the MITM's empty-buffer terminal probe gets Rig tests: a bare FIN gives (0, true) as on xqc_h3; framing alone before the FIN stays Blocked (no byte fits an empty slice), which xqc_h3 answers (0, true).
No request exists for it on a client, so h3wire was never fed, no H3_STREAM_CREATION_ERROR was raised (RFC 9114 §6.1) and the stream never retired. Its NewStream now feeds h3wire an empty read, which closes the connection.
A peer RESET that arrived after the transport FIN was read into the carry, but before h3wire reached Finished, aborted a complete response; one h3_recv_body later the same reset was handled as after Finished (adoption spec §5.3 (7)). The code is now held and passed to h3wire once the feed has given it the FIN.
`reqs_of` collects a conn's requests for both fan-outs; `Req::stop_send` queues the H3Writable that every caller pushed after it; Req has one impl block. The latched-finish comment in `flush` now says that Closed and GoingAway drop it silently.
adoption spec §6.2 compares status and headers. Every matrixed pair_gateway scenario now asserts the full response header list, the fetch reply's and the direct H3 response's, against one list with `date` normalised; h3_40k_section_both_ways compares both sections exactly. All four cells agree: no difference to write in.
A raw peer sends a request HEADERS frame over 64 KiB encoded (h3wire closes the connection with H3_EXCESSIVE_LOAD; xqc_h3 resets the stream with H3_MESSAGE_ERROR), a request stream with a FIN and no HEADERS (h3wire resets with H3_REQUEST_INCOMPLETE; xqc_h3 drops it silently), and a response that ends without a final response, bare or after a 103 (h3wire aborts it and the fetch client answers 502 upstream-reset; xqc_h3 relays the 103 as the final response).
…port The origin connect succeeds, so origin_tls=connect_fail and origin_connect_ms=-1 are an existing gateway misreport outside this plan; a later fix to them is not a regression.
Tiny DATA frames made every feed memmove the rest of the carry. The carry is refilled only when empty, so a cursor needs no compaction; the bound is unchanged.
service() called h3wire's sendable(), a scan of every live stream, on each request event and body op. h3wire's core bytes come from bind_uni, send_headers and GOAWAY, so keep a per-conn set (our uni streams plus requests with undrained HEADERS) and flush just those.
H3Wire::poll_event used conn_stats (hash lookup, snprintf, malloc on xquic) to check conn liveness of every conn-level pop. conn_live is the slot-table check; Transport, ScriptedTransport and H3Wire implement it. No default body: no impl lives under mq-proxy/src, so the gateway is untouched.
… requests
assert_eq!(c.handle.lock().x, v, "{:?}", c.handle.lock()) held the guard
through the failure branch and relocked it: a wrong code hung. One guard now,
scoped to the assert.
The malformed-request checks searched the log for the submitted path, which a
gateway line with path="-" (before intake) does not carry. spawn_gateway_on
pairs now count the H3Request events the server app is handed; the h3wire
receiver must have none. xqc_h3 does hand over the stream before decoding, so
it keeps the path search.
Deploying mqproxy with
|
| Latest commit: |
fff9b3c
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://c9e40f5d.mqproxy.pages.dev |
| Branch Preview URL: | https://h3wire-p2-mq-h3.mqproxy.pages.dev |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.