Skip to content

stream: drop per-chunk Promise alloc in pipeTo#63572

Open
mcollina wants to merge 1 commit into
nodejs:mainfrom
mcollina:stream/pipeto-mark-as-handled
Open

stream: drop per-chunk Promise alloc in pipeTo#63572
mcollina wants to merge 1 commit into
nodejs:mainfrom
mcollina:stream/pipeto-mark-as-handled

Conversation

@mcollina
Copy link
Copy Markdown
Member

Replace setPromiseHandled with markPromiseAsHandled at the two pipeTo per-chunk call sites in readablestream.js. The .then(undefined, () => {}) chain that setPromiseHandled allocates per chunk has no observer in pipeTo: errors already propagate through writer.closed (which watchErrored monitors) and the last state.currentWrite is awaited in waitForCurrentWrite during shutdown (markAsHandled does not affect that subsequent .then). The per-chunk chain Promise + noop closure exist solely to silence the unhandled-rejection event — markPromiseAsHandled sets V8's MarkAsHandled + MarkAsSilent flags directly with no allocation.

benchmark/webstreams/pipe-to.js at HWM=1024: ~5% throughput improvement (728K vs 694K ops/s mean, 6 alternating samples), with substantially tighter run-to-run variance.

WPT streams parity preserved: 1403 subtests passing, 0 unexpected failures.

Replace setPromiseHandled with markPromiseAsHandled at the two pipeTo
per-chunk call sites in readablestream.js (the fast-path drain loop and
PipeToReadableStreamReadRequest's chunk handler).

setPromiseHandled chains a `.then(undefined, () => {})` to the writer's
per-chunk write Promise, purely to suppress its unhandled-rejection
event. That chain Promise and the noop closure have no other observer
in this code path: errors already propagate to pipeTo through the
writer.closed promise that watchErrored monitors, and the LAST
state.currentWrite is awaited by waitForCurrentWrite during shutdown
(which still throws because markAsHandled only suppresses the event,
not the rejection state — subsequent .then chains still observe it).

markPromiseAsHandled sets V8's MarkAsHandled + MarkAsSilent flags
directly via the util binding, with no per-chunk JS allocation.

`benchmark/webstreams/pipe-to.js` at HWM=1024 improves by ~5% on my
workstation (728K vs 694K ops/s mean over 6 alternating samples), and
notably exhibits much tighter run-to-run variance because the per-chunk
allocations no longer perturb GC timing.

WPT streams parity preserved: 1403 subtests passing, 0 unexpected
failures.
@nodejs-github-bot nodejs-github-bot added needs-ci PRs that need a full CI run. web streams labels May 25, 2026
@codecov
Copy link
Copy Markdown

codecov Bot commented May 25, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 90.34%. Comparing base (74ccf38) to head (52772fe).
⚠️ Report is 30 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main   #63572      +/-   ##
==========================================
+ Coverage   90.32%   90.34%   +0.01%     
==========================================
  Files         730      730              
  Lines      234209   234478     +269     
  Branches    43934    43911      -23     
==========================================
+ Hits       211558   211842     +284     
+ Misses      14372    14364       -8     
+ Partials     8279     8272       -7     
Files with missing lines Coverage Δ
lib/internal/webstreams/readablestream.js 98.55% <100.00%> (+<0.01%) ⬆️
lib/internal/webstreams/util.js 99.57% <100.00%> (+<0.01%) ⬆️

... and 52 files with indirect coverage changes

🚀 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

needs-ci PRs that need a full CI run. web streams

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants