Skip to content

Commit a5b0d3c

Browse files
committed
TASK-190: Correct H2 corpus executor ordering and cancellation proof
Encode completed-frame dispatch with suspended opening handlers for five later-error transcripts, preserving all 52 wire cases, route expectations and RFC error verdicts. Add paired deferred/started cancellation, HPACK suffix, resource release and fully completed response controls. Verified normal and ASan/nonrecovering UBSan TLS-off/on focused suites (35/36 programs, zero skips); 4352 original corpus and 2202 paired schedule route checks per lane. Both complete pinned H2 gates pass two clients and 145 h2spec cases with exactly two unchanged exclusions. C++ lint, gate-runner and littletest contracts pass. Task remains In Progress for caller validation/finalization.
1 parent 92f0918 commit a5b0d3c

6 files changed

Lines changed: 303 additions & 21 deletions

File tree

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
# TASK-190 implementation evidence
2+
3+
Worktree `/home/etr/progs/libhttpserver/.worktrees/TASK-190`, branch `task/TASK-190`, base `v3` at `92f09185e28a8e19edeb7ed2435c21d8d318cc55`. Manual Groundwork implementation; caller owns validation, finalization, merge and cleanup. Task card/index remain In Progress. No configured executor memory store was found; reusable setup observations are retained in the invocation journal.
4+
5+
## Cause and repair boundary
6+
7+
Journal `.debug/h2-route-effect-ordering-s9j48boo/journal.md`; evidence root `build/task190`. The immutable four TASK-185 normal/ASan TLS-off/on logs and pinned H2 receipt were read and hashed in `retained-identities.json`. They contain five route-effect failures plus the aggregate 47!=52 assertion: six checks, one oracle cause. The fresh unchanged task-local baseline exactly reproduces those six checks (`baseline-red.log`, four tests, 944 checks, zero skips). Bootstrap/configure/library/baseline command logs are retained.
8+
9+
PRD-V3N-REQ-023 makes decisions possible at complete request headers; it does not require a lazy handler to execute before the parser's next frame. REQ-025 requires cancellation. Architecture §§3.3–3.4, DR-V3-001/003/006, `docs/task-140-http2-evidence.md` and `docs/task-142-http2-evidence.md` retain connection-owned HPACK/flow control and serialized, executor-affine guarded route resumes. The existing `http2_reset/peer_reset_invalidates_queued_and_parked_handlers_and_returns_reservations` explicitly requires zero calls if reset precedes executor start and one call if it already started.
10+
11+
`http2_request_state.hpp::admit` posts the route task. `reset/discard` disconnects it; subsequent owner reaping invalidates its queued frame witness. The original five `feed` entries parse both their valid opening and later error before running any pending handler, then incorrectly expect route 1. Running stale work or invoking handlers inline would violate the documented lifecycle. No production defect is demonstrated.
12+
13+
The minimal repair gives those same five entries the explicit `dispatch` stage. Executor turns occur only at completed protocol frames. The opening handler records its actual head and suspends on an application signal, leaving the request active until its later stream error cancels it. Output retirement remains deferred until the transcript is parsed. Every original route payload, RFC verdict and wire file is unchanged; the corpus still contains exactly 52 entries. Unknown stage values now fail explicitly.
14+
15+
A completed response is a distinct state: `prepare_response` marks the send side ended at the semantic response, and the next owner reap removes an END_STREAM request even before output retirement. The initial dispatch-only probe documents that `closed-data` and `half-closed-head` then correctly produce connection STREAM_CLOSED (`schedule-green-initial.log`). The final dispatch stage suspends those handlers rather than pretending a completed stream is still half-closed. Separate completed-response regressions retain and verify that connection verdict for both inputs across every fragmentation split. No wire-error oracle is weakened or made nondeterministic.
16+
17+
## Causal RED and discriminating GREEN
18+
19+
Before correcting the harness/metadata, new paired regressions passed all eight deferred controls but failed all eight started controls at `route effects`, yielding 14 failures together with the six original checks (`schedule-red.log`). This is the expected missing executor boundary, not a timeout or build error. Final GREEN logs retain the source identity and exact protocol observations.
20+
21+
A bounded task-local discriminator also compiles a copy of the final harness
22+
with only dispatch-stage executor turns removed. It reintroduces all five
23+
original route failures and all eight started-control failures, plus the two
24+
aggregate completion-count failures (exit 15). `discriminators/receipt.json`
25+
records the exact compile command and source SHA256
26+
`9875ffd708a273c06f97c7de401e3b1553e594979aecdcf4ce18fd966a58e2fc`;
27+
`discriminators/red.log` retains the assertions. The real harness and production
28+
tree are untouched by this probe. Exact baseline source/metadata copies are
29+
retained alongside it. This independently confirms the causal scheduling seam
30+
after adding final diagnostic/count assertions.
31+
32+
For each named later error, paired schedules append an independently authored dynamic-table insertion on reset stream 1, then a sibling on stream 3 references index 62. They require its actual `x-route: after` field, `GET /sibling`, exact route order and reset-before-sibling-response ordering. All nonterminal suffix bytes are consumed. A skipped rejected block would instead lose dynamic state and fail the sibling decode/head assertion. Invalid-opening controls remain in the complete original corpus and still never route.
33+
34+
| Transcript | Required stream 1 error | Deferred route order | Started route order | Full suffix bytes |
35+
|---|---|---|---|---:|
36+
| content-length-data | PROTOCOL_ERROR (1) | 3 | 1, 3 | 144 |
37+
| malformed-trailer | PROTOCOL_ERROR (1) | 3 | 1, 3 | 126 |
38+
| closed-data | STREAM_CLOSED (5) | 3 | 1, 3 | 126 |
39+
| stream-window-overflow | FLOW_CONTROL_ERROR (3) | 3 | 1, 3 | 129 |
40+
| half-closed-head | STREAM_CLOSED (5) | 3 | 1, 3 | 153 |
41+
42+
Existing scalar diagnostics and test-local lifetime guards provide actual event order, without production instrumentation. Deferred: `admitted:1,cancelled:1,admitted:3,started:3`; admitted-but-cancelled is distinguished from never admitted. Started application wait: `admitted:1,started:1,cancelled:1,token-completed:1,destroyed:1,admitted:3,started:3`. Each started handler has exactly one destruction and one typed token completion (`cancelled`); its exchange disconnect reason is `connection_closed`. A late application signal executes no cancelled code. Three incomplete-body controls separately allow exactly one failed body-read completion (`connection_closed`), then one destruction and token cancellation. This is failure delivery before the next owner reap, not stale signal reentry; the body does not respond after failure.
43+
44+
Every variant requires exact RST_STREAM scope/code/count, no GOAWAY, complete suffix consumption and zero usage of all 13 resource kinds after engine/executor/route teardown. Connection-error controls require one GOAWAY, final output ordering, sticky failure and no duplicate output. Compression-error precedence remains in the unchanged `peer-reset-invalid-hpack`/`closed-invalid-hpack` entries. No production source, limits, client predicates, deadlines, exclusions or suppression inventory change.
45+
46+
The complete corpus executes **52 entries / 4352 variants / 4352 route checks** in each lane. The paired regressions execute **5 transcripts / 16 schedules / 2202 variants / 2202 route checks**; every schedule includes coalesced input, bytewise input, every split and three-byte output retirement. The two fully completed response controls add 193 replay variants. Within each fixed schedule, output bytes and consumed bytes must match exactly. Per-case and aggregate counts and actual event traces are printed in the test log; the littletest count does not include every throwing corpus predicate.
47+
48+
## Required local lanes
49+
50+
Fresh C++20 normal and ASan/nonrecovering UBSan lanes, TLS-off/on, use `/usr/bin/clang-18` and `/usr/bin/clang++-18` (18.1.3), `--disable-examples`, `-std=c++20 -pthread -O1 -g -fno-omit-frame-pointer`. TLS-on uses the existing `/home/etr/progs/libhttpserver/build/tooling/provider` include/lib and `-lssl -lcrypto`; it is not rebuilt. Sanitizers add `-fsanitize=address,undefined -fno-sanitize-recover=undefined -fsanitize-address-use-after-scope`; all runs retain `ASAN_OPTIONS=detect_leaks=1:detect_stack_use_after_return=1:halt_on_error=1` and `UBSAN_OPTIONS=halt_on_error=1:print_stacktrace=1`, outside ptrace. No diagnostics are disabled. No production concurrency changes, so a new TSan lane is unnecessary; TASK-200 remains separate.
51+
52+
`build/task190/run_lanes.py` records configure/library/scoped-test/gate commands, environments, exits and durations in lane `receipt.json` files. The library builds first; TESTS/check_PROGRAMS/noinst_PROGRAMS are scoped together. All 18 existing H2 engine programs, HPACK corpus/connection, affected semantic owners, relevant public header consumers and v3_header_hygiene run in all four lanes (35 programs off, 36 on). TLS-on additionally builds the original `http2_tls_fixture` and runs `http2_tls_boundary`.
53+
54+
Normal and sanitizer TLS-on lanes run unchanged `scripts/run-v3-http2-gates.py` with `/tmp/task185-clients/bin/python`, Node `/tmp/task185-tools/node-v24.15.0-linux-x64/bin/node`, and `/tmp/task185-tools/h2spec`. Actual tool probes verify hyper-h2 4.4.1/hpack 4.2.0/hyperframe 6.1.0, Node 24.15.0 and h2spec 2.6.0 commit `70ac2294010887f48b18e2d64f5cccd48421fad1`. Each complete gate must retain both clients' multiplexing/reset/upload_flow/response_flow/websocket predicates and every nonexcluded h2spec inventory entry. The exact two RFC 9113 exclusions remain byte-identical (SHA256 `9256a4d025cb35a6056f142599ce34a29df8e70c4c857a9ab2cd472e11eeb4c3`). Gate-runner unit contracts, C++ lint, littletest result/skip contract and diff checks also run.
55+
56+
These gates exercise the existing **private negotiated-h2 engine TLS fixture**. They do not establish new public-listener H2 capability. No affected public runtime path changes, so no new installed public transport witness is claimed. TLS-off runs the complete engine/semantic selection without TLS negotiation. BSD/macOS/Windows and nonlocal backends remain unexecuted and CI/v3-PR owned under AGENTS.md.
57+
58+
`final-identities.json` hashes final test/corpus metadata, provider/tool identities and exclusions, and verifies all tracked production cpp/hpp and all 52 wire files against unchanged `v3`. The caller must archive `build/task190` and the unique `.debug` journal before cleanup. TASK-185's incomplete status and immutable artifacts, TASK-200 ownership, unrelated FEATURE worktrees and the clean primary checkout are preserved. No validation-loop, merge, cleanup, push, tag or publication is performed by this executor.
59+
60+
## Final local receipts
61+
62+
All four focused lanes pass with zero skips and no ASan/LeakSanitizer/UBSan diagnostics. Both unchanged pinned TLS-on gates pass **two independent clients, all five scenarios each, 145 of 147 h2spec inventory entries, and exactly two unchanged RFC 9113 exclusions**. Each independent client retains upload stall 65535 bytes, upload 163840 bytes, response stall 32 bytes, response 131072 bytes, cancellation count 1 and clean WebSocket close. Node reports nghttp2 1.68.0. Provider identity is OpenSSL 3.5.9 (29 Sep 2026).
63+
64+
| Lane | Programs | Littletest cases | Checks | Skips | Corpus entries / variants / route checks |
65+
|---|---:|---:|---:|---:|---|
66+
| normal-off | 35 | 333 | 31981 | 0 | 52 /4352 /4352 |
67+
| normal-on | 36 | 338 | 32199 | 0 | 52 /4352 /4352 |
68+
| asan-off | 35 | 333 | 31981 | 0 | 52 /4352 /4352 |
69+
| asan-on | 36 | 338 | 32199 | 0 | 52 /4352 /4352 |
70+
71+
Each off lane contains 29 littletest programs plus six standalone header consumers; each on lane adds the five-case TLS boundary suite. The six-case engine harness reports 943 littletest checks in every lane, in addition to its explicit 4352 corpus and 2202 paired-schedule route predicates. Every lane executes the 16 paired schedules and 193 completed-response replay controls. Final source/object freshness is verified in all lanes; a nonfatal normal-on clock-skew warning is retained, and all four conformance objects are newer than the unchanged final source. Existing unrelated unused-helper compiler warnings are retained.
72+
73+
C++ cpplint passes the changed test. All six gate-runner contract tests pass; task-local Python tools compile; littletest skip/result contracts pass (77 all-skip, 0 mixed pass+skip, failure count for fail+skip); git diff --check passes. `final-summary.json` records per-program cases/checks/skips, per-lane source/object freshness, exact full gate receipts, final runtime log/executable/library hashes and unchanged frozen receipt verification. `final-identities.json` also verifies byte identity of the original H2 fixture, independent clients and gate runner/predicates against v3. The normal-off baseline configure/library logs precede the scripted matrix and remain separately identified.
74+
75+
No required local implementation check remains incomplete. Groundwork validation, final acceptance status, merge and cleanup remain caller-owned. TASK-190 action items are checked while card/index remain In Progress.
76+
77+
Final changed C++ source SHA256: `b2261aca6e26ef4fa1985d35d4e1f1ee34bd1e72c8362acdeda9e3fca762088e`.

‎specs/tasks/M14-v3-release-repairs/TASK-190.md‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,10 @@
99
**Workflow:** Follow `../v3-release-repair-workflow.md`; helper-resolve TASK-190, require `base_branch=v3`, systematic cause analysis and RED/GREEN, then Groundwork validation/finalization.
1010

1111
**Action Items:**
12-
- [ ] Reproduce `http2_engine_conformance/rfc9113_engine_transcripts` failures for `content-length-data`, `malformed-trailer`, `closed-data`, `stream-window-overflow`, `half-closed-head` (47/52 transcripts currently complete). Trace protocol events versus scheduled header-time handler execution, resets and cancellation across executor batching and all fragmentation splits.
13-
- [ ] Establish whether corpus route expectations or engine behavior violate the documented header-time decision contract. Add focused failing regression for each distinct cause, asserting route observations, terminal scope/code/count, compression state and resource release. A handler may validly observe headers before a later malformed body; an expectation change requires specification-backed evidence and a regression, never blanket acceptance of fewer route checks.
14-
- [ ] Repair minimal engine scheduling/lifetime/error handling or demonstrably invalid oracle; preserve strict RFC 9113 error scope and suffix/HPACK handling after stream resets.
15-
- [ ] Run all corpus fragmentation variants, stream/body/response/WebSocket/drain suites and existing pinned h2spec/independent-client lane. Keep the exact h2spec exclusion inventory and named corpus requirements unchanged.
12+
- [x] Reproduce `http2_engine_conformance/rfc9113_engine_transcripts` failures for `content-length-data`, `malformed-trailer`, `closed-data`, `stream-window-overflow`, `half-closed-head` (47/52 transcripts currently complete). Trace protocol events versus scheduled header-time handler execution, resets and cancellation across executor batching and all fragmentation splits.
13+
- [x] Establish whether corpus route expectations or engine behavior violate the documented header-time decision contract. Add focused failing regression for each distinct cause, asserting route observations, terminal scope/code/count, compression state and resource release. A handler may validly observe headers before a later malformed body; an expectation change requires specification-backed evidence and a regression, never blanket acceptance of fewer route checks.
14+
- [x] Repair minimal engine scheduling/lifetime/error handling or demonstrably invalid oracle; preserve strict RFC 9113 error scope and suffix/HPACK handling after stream resets.
15+
- [x] Run all corpus fragmentation variants, stream/body/response/WebSocket/drain suites and existing pinned h2spec/independent-client lane. Keep the exact h2spec exclusion inventory and named corpus requirements unchanged.
1616

1717
**Acceptance Criteria:**
1818
- All 52 engine transcripts and existing engine tests pass with every route-effect check executed, under normal and ASan/nonrecovering UBSan TLS-off/on.
@@ -24,4 +24,4 @@
2424
- Blocks: TASK-191, TASK-185
2525
**Related Requirements:** PRD-V3N-REQ-005, PRD-V3N-REQ-006, PRD-V3N-REQ-009, PRD-V3N-REQ-023, PRD-V3N-REQ-025, PRD-V3N-REQ-037.
2626
**Related Decisions:** DR-V3-001, DR-V3-003, DR-V3-006.
27-
**Status:** Not Started
27+
**Status:** In Progress

‎specs/tasks/_index.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -337,7 +337,7 @@ Nominally: **13 sequential tasks**, each S–XL. Most other tasks parallelize of
337337
| TASK-187 | [Repair response-source and multipart coroutine lifetimes](M14-v3-release-repairs/TASK-187.md) | M14 | Complete | TASK-186 |
338338
| TASK-188 | [Eliminate peer-policy mutation starvation](M14-v3-release-repairs/TASK-188.md) | M14 | Complete | TASK-187 |
339339
| TASK-189 | [Repair native HTTP/1 WebSocket handshake and flood progress](M14-v3-release-repairs/TASK-189.md) | M14 | Complete | TASK-188 |
340-
| TASK-190 | [Repair HTTP/2 conformance route-effect ordering](M14-v3-release-repairs/TASK-190.md) | M14 | Not Started | TASK-189 |
340+
| TASK-190 | [Repair HTTP/2 conformance route-effect ordering](M14-v3-release-repairs/TASK-190.md) | M14 | In Progress | TASK-189 |
341341
| TASK-191 | [Repair independent HTTP/3 semantic drain completion](M14-v3-release-repairs/TASK-191.md) | M14 | Not Started | TASK-190 |
342342
| TASK-192 | [Supply current-v3 cookie parity witnesses](M14-v3-release-repairs/TASK-192.md) | M14 | Not Started | TASK-191 |
343343
| TASK-193 | [Install public credentials and serve native TCP TLS](M14-v3-release-repairs/TASK-193.md) | M14 | Not Started | TASK-192 |

‎test/conformance/http2-engine/README.md‎

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,42 @@ stream IDs; `/` identifies stream 1 and `/sibling` identifies stream 3.
66
The stage `eof` requires EOF during a field block. `unadvertised` withholds
77
server SETTINGS exposure to exercise RFC 8441 capability gating.
88

9+
Executor schedules are semantic boundaries, independent of input fragmentation:
10+
11+
- `feed` defers executor work until the entire transcript has been parsed.
12+
- `pump` runs pending work and retires output after each completed frame.
13+
- `dispatch` runs pending work after each completed frame, suspends the stream 1
14+
handler on an application signal, and defers output retirement until input is
15+
parsed. The later stream error cancels this suspended handler. This schedule
16+
applies to `content-length-data`, `malformed-trailer`, `closed-data`,
17+
`stream-window-overflow` and `half-closed-head`.
18+
919
Binary HPACK literals are authored independently of the library encoder. The
1020
harness checks resets, GOAWAY, response order, sticky failure, consumption,
1121
routes and complete budget release. Coalesced, bytewise, every split and partial
12-
output retirement must give identical wire output. Body-error fixtures include
13-
routing of their initially valid opening request before the later error.
22+
output retirement must give identical wire output within a fixed schedule.
23+
The five later-error fixtures retain their original route 1 observation and
24+
exact stream error verdicts. Paired deferred-start regressions require zero
25+
stream 1 calls: a reset invalidates queued lazy work before execution. Started
26+
application waits must be destroyed once without running after a late signal;
27+
started body reads may complete once with typed `connection_closed`. A separate
28+
token observer must complete once with typed `cancelled`. All reservations must
29+
return after teardown. These rules follow PRD-V3N-REQ-023/025, DR-V3-003/006 and
30+
the queued-handler cancellation contract in `docs/task-142-http2-evidence.md`.
31+
32+
Dispatching an immediately completed response is a different schedule: an
33+
END_STREAM opening becomes fully closed before later input. The harness checks
34+
`closed-data` and `half-closed-head` separately in that schedule for connection
35+
STREAM_CLOSED, rather than treating its result as interchangeable with the
36+
original half-closed stream verdict. Changing only input chunk boundaries never
37+
selects a different schedule.
38+
39+
The paired tests append an independently authored dynamic-table insertion in a
40+
discarded HEADERS block on reset stream 1, then stream 3 references index 62.
41+
They assert the sibling's actual `x-route: after` field, exact route order,
42+
reset-before-response ordering, full suffix consumption, no duplicate reset or
43+
GOAWAY, admission/cancellation counts and all fragmentation variants. Per-case
44+
and aggregate corpus/schedule execution counts are printed in the test log.
1445

1546
RFC 9113 sections 5.3.2, 6.2, 6.3 and Appendix B deprecate priority dependency
1647
semantics. Self-dependency therefore has no error verdict. PRIORITY length,

0 commit comments

Comments
 (0)