feat(core): HTTP/2 Phase 5 — frame layer

Implements HTTP/2 frame reading, validation, and writing: FrameType (the
10 RFC 9113 types + per-type validation descriptor), FrameFlags (with
the deliberate END_STREAM/ACK bit collision documented), FrameHeader (a
flyweight, never allocated per frame), Http2FrameReader (length-prefixed
reader over BufferedByteSource, mirroring RequestParser's buffer/
compaction discipline), FrameValidator (table-driven, specific RFC error
code per violation -- not a uniform code per type), Padding (RFC 9113
6.1/6.2), and FrameWriteBuffer (beginFrame/endFrame length back-patching
over Phase 4's ByteWriter).

All 10 frame types round-trip correctly; every RFC-mandated rejection
has its own test asserting the specific error code; the reader is
fuzz-tested against 10,000,000 random inputs (~14s). The zero-alloc
contract is measured, not asserted: reading + validating + consuming a
frame is 0.002 B/op, writing one is ~10^-4 B/op -- both indistinguishable
from zero (DEC-21).

Found and fixed EX-37 while writing Http2FrameReaderTest: BufferedByteSource's
deadline mechanism (EX-07's actual fix) NPE'd against a null socket, which
every isolated unit test in this codebase uses -- it had zero dedicated
test coverage of its own. Fixed to treat a null socket as "no OS-level
timeout to bound" rather than a misuse, and given BufferedByteSourceTest,
which did not exist before.

449/449 tests green, both with and without -Pjmh.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Zakaria El Orche
2026-08-13 14:25:12 +00:00
co-authored by Claude Sonnet 5
parent 704a00a551
commit 0e1bbed42c
18 changed files with 1665 additions and 23 deletions
+32
View File
@@ -733,3 +733,35 @@ once Phase 6 lands `Request`/`RequestBody` pooling — re-run this exact benchma
this entry (or add a new one) with the "after" number, closing the loop Phase 4 opened.
---
## DEC-21 — Phase 5's zero-alloc contract, measured
**Context.** Phase 5's plan states: "Reading, validating and discarding a frame: 0 B/op ...
Writing a frame header: 0 B/op." Measured with JMH `-prof gc` (JDK 21.0.11, JMH 1.37,
`FrameLayerBenchmark`, `src/jmh/java`) rather than left as an unverified assertion, per this
project's own standing practice of measuring every stated performance/allocation claim
(`DEC-09`, `DEC-20`).
**Measurement.** `readValidateAndDiscard` (`Http2FrameReader.readFrame` +
`FrameValidator.validate` + one byte read from the payload + `consumeFrame`, against a warm,
already-grown buffer, matching real keep-alive-connection steady state): 299.846 ± 19.722 ns/op,
**0.002 B/op** — indistinguishable from zero (compare `DEC-20`'s harness-floor discussion: even
this near-zero figure is most plausibly measurement noise around the true 0, not a real
allocation, since nothing in the read/validate/consume path can be shown by inspection to
allocate on the warm path). `writeFrame` (`FrameWriteBuffer.beginFrame` + one `writeBytes` call +
`endFrame`, against an already-grown `ByteWriter`): 14.262 ± 1.084 ns/op, **≈10⁻⁴ B/op** —
likewise indistinguishable from zero.
**Decision.** Contract verified as stated; no design change required. Both numbers are recorded
here as the baseline Phase 17's eventual CI allocation gate should hold this component to.
**Consequence.** None beyond the recorded numbers — this entry exists so a future regression
(e.g. a later phase accidentally introducing an allocation on this path while adding HPACK or
stream-state integration) has a concrete "was 0, now isn't" baseline to diff against, per this
project's standing insistence that every non-obvious performance claim trace to an actual number.
**Revisit when.** Not expected to be revisited; re-measure if `FrameHeader`, `Http2FrameReader`,
or `FrameWriteBuffer` are ever modified in a way that could plausibly affect their allocation
profile.
---