Skip to content

perf(h1): deliver fixed-length response bodies without copying into wasm#5523

Open
mcollina wants to merge 2 commits into
mainfrom
perf/h1-body-fastpath
Open

perf(h1): deliver fixed-length response bodies without copying into wasm#5523
mcollina wants to merge 2 commits into
mainfrom
perf/h1-body-fastpath

Conversation

@mcollina

@mcollina mcollina commented Jul 7, 2026

Copy link
Copy Markdown
Member

This relates to...

N/A

Rationale

Reduce CPU and memory-copy overhead when receiving HTTP/1.1 responses with a known fixed content length by delivering body bytes directly to the request instead of copying them through the wasm llhttp parser.

Changes

  • Add a fixed-length HTTP/1 response body fast path that pauses llhttp after headers and streams the known body bytes directly.
  • Preserve existing parsing behavior for transfer-encoding, HEAD, CONNECT/upgrade, informational/bodyless responses, and user-paused responses.
  • Detect truncated fixed-length bodies explicitly and guard readMore() against re-entrant reads while completing a response.
  • Add regression coverage for pipelining, keep-alive reuse, truncation, backpressure, max response size, chunked responses, bodyless responses, large headers, and informational responses.
  • Make benchmarks/server.js configurable for response body size and synchronous responses.

Features

N/A

Bug Fixes

N/A

Breaking Changes and Deprecations

N/A

Validation

  • git diff --check origin/main..HEAD
  • npx eslint lib/dispatcher/client-h1.js test/client-content-length-fastpath.js benchmarks/server.js
  • node --test test/client-content-length-fastpath.js

Status

mcollina added 2 commits July 7, 2026 17:12
Pause llhttp right after the headers of a response with a known
content-length and hand the body bytes straight to the request,
skipping the copy into wasm memory and llhttp's byte scan. While
waiting for headers, feed llhttp bounded 4KB slices so the body bytes
sharing a chunk with the headers are not copied either. At the message
boundary llhttp is re-synchronized with llhttp_reset; trailing bytes
are unshifted onto the socket and parsed normally.

The fast path only activates for plain content-length framing:
transfer-encoding, HEAD, CONNECT/upgrade, 1xx/204/304 and user-paused
responses keep the existing llhttp path. Truncation is detected
explicitly since llhttp_finish cannot see bytes it never parsed.

readMore() is now guarded against re-entrancy so a resume triggered
inside onMessageComplete cannot consume socket data before the
completing frame has settled pipeline state.

Benchmarks (local, Client x4, pipelining 10, GET, CPU per request via
process.cpuUsage):

  body   | req/s           | CPU/request
  64B    | unchanged       | unchanged
  64KB   | +5-9%           | -3-7%
  1MB    | +17%            | -10% (-16% user CPU)

Signed-off-by: Matteo Collina <hello@matteocollina.com>
BODY_SIZE selects the response payload (default unchanged at 64KB) and
TIMEOUT=0 now responds synchronously instead of falling back to 1ms,
so both small-response and bulk-transfer workloads can be measured
with the same server.

Signed-off-by: Matteo Collina <hello@matteocollina.com>
@codecov-commenter

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 99.60317% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 93.47%. Comparing base (0dee09d) to head (db3f26a).

Files with missing lines Patch % Lines
lib/dispatcher/client-h1.js 99.60% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #5523      +/-   ##
==========================================
+ Coverage   93.44%   93.47%   +0.02%     
==========================================
  Files         110      110              
  Lines       37329    37526     +197     
==========================================
+ Hits        34883    35077     +194     
- Misses       2446     2449       +3     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants