Skip to content

Start a native source pull or sink write before marking it in flight - #7646

Merged
jasnell merged 3 commits into
mainfrom
jasnell/ts-streams-bridge-inflight-flags
Oct 9, 2026
Merged

jasnell merged 3 commits into
mainfrom
jasnell/ts-streams-bridge-inflight-flags

Conversation

@jasnell

@jasnell jasnell commented Oct 6, 2026

Copy link
Copy Markdown
Collaborator

Multiple TS streams bridge updates. See individual commits for details.

@jasnell
jasnell requested review from guybedford and npaun October 6, 2026 22:07
@jasnell
jasnell added this pull request to stack #7531 October 6, 2026 22:07
@jasnell
jasnell requested review from a team as code owners October 6, 2026 22:07
@ask-bonk

ask-bonk Bot commented Oct 6, 2026 •

Copy link
Copy Markdown
Contributor

Since last review: 0 resolved, 0 still open, 0 new.
LGTM!


Reviewed commit: 6edb0715 · github run

Comment thread src/workerd/api/js-writable-stream-test.c++

@guybedford guybedford left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ran js-readable-stream-test@{,all-autogates}, js-writable-stream-test@{,all-autogates}, the readable/writable TS suites and //src/wpt:streams-ts@ locally; all pass. Also reverted the two source files to the base branch and confirmed both new KJ tests fail with the stale already in flight error, and pass again with the change.

The reorder is sound: the IoOwn derefs are the only thing that can throw cross-request, nothing between the deref and the flag runs JS, tryGetLength → tryRead order is preserved, and moving onChunkEnqueued keeps it balanced with the onChunkDequeued in the awaitIo continuation. It also covers a synchronously throwing tryRead/write, which is a nice side effect.

The frozen null-proto cpp_exports table turns a missing name into a hard KJ_REQUIRE_NONNULL failure in getCppExport rather than an Object.prototype hit; the test covers prototype, planted-name lookup and frozenness.

@jasnell
jasnell force-pushed the jasnell/ts-streams-bridge-inflight-flags branch from 5566b3f to c8981c7 Compare October 7, 2026 22:18
@codecov-commenter

codecov-commenter commented Oct 7, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 86.20690% with 16 lines in your changes missing coverage. Please review.
✅ Project coverage is 38.62%. Comparing base (d08010b) to head (6edb071).
⚠️ Report is 5 commits behind head on main.

Files with missing lines Patch % Lines
src/workerd/api/js-writable-stream-test.c++ 80.76% 0 Missing and 10 partials ⚠️
src/workerd/api/js-readable-stream-test.c++ 93.33% 0 Missing and 3 partials ⚠️
src/workerd/api/js-writable-stream.c++ 66.66% 2 Missing and 1 partial ⚠️
Additional details and impacted files
@@           Coverage Diff            @@
##             main    #7646    +/-   ##
========================================
  Coverage   38.61%   38.62%            
========================================
  Files         868      871     +3     
  Lines      268517   268972   +455     
  Branches    25432    25466    +34     
========================================
+ Hits       103676   103878   +202     
- Misses     150619   150851   +232     
- Partials    14222    14243    +21     

☔ 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.

@jasnell
jasnell force-pushed the jasnell/ts-streams-bridge-inflight-flags branch 6 times, most recently from 6a6090a to e842024 Compare October 8, 2026 23:23
Base automatically changed from jasnell/ts-streams-compression-drain-cancel to main October 9, 2026 13:38
ReadableStreamNativeSource and WritableStreamNativeSink set pullInFlight
or writeInFlight before dereferencing their IoOwns, which throws when
called from a request other than the one that created them. The pull or
write then failed with "Cannot perform I/O on behalf of a different
request", but the flag stayed set, so every later pull threw "pull() is
already in flight." and every later write threw "write() is already in
flight.". A cancel would defer the source's release to a read that never
started, a tee would wait on it, and an abort or pipeFrom would treat
the sink as busy. The sink's observer also counted a chunk that never
passed through.

Today a throwing hook errors the TS stream, so the JS layer does not
call the source or sink again; the stale flag is visible only from C++.
With the sync fast-path autogate on, the fast path's own catch releases
the source or sink first and masks it.

Both now dereference the IoOwns and start the read or write first, and
only then set the flag (and, for the sink, count the chunk).

Pins: js-readable-stream-test.c++ "ReadableStreamNativeSource pull from
another request leaves no pull in flight" (default and BYOB pulls) and
js-writable-stream-test.c++ "WritableStreamNativeSink write from
another request leaves no write in flight": two attempts from a second
request; the first reports the cross-request error, and neither reports
an operation in flight. Without the source change, both fail in their
default variants (all-autogates passes through the fast path).

Compatibility: the change is behind the experimental
typescript_implemented_streams flag (the native source and sink back
only TS streams).
The C++ bridge fetches every TypeScript streams entry point with
getCppExport(), an ordinary property get on the webstreams/cpp_exports
module's exports. That object was a plain object literal spreading the
two frozen halves (readable.ts and writable.ts cppExports): unfrozen,
and inheriting from Object.prototype. Every name the bridge asks for is
an own property today, so nothing resolves through the prototype now,
and user code cannot reach the table itself (it lives in the bootstrap's
C++-side module cache). But a name that went missing, through a rename
or a typo, would resolve to whatever user code had put on
Object.prototype, and the bridge would call it with internal objects.

The table is now frozen and has a null prototype, so such a lookup fails
the bridge's KJ_REQUIRE_NONNULL instead.

Pin: js-writable-stream-test.c++ "The cpp_exports table has a null
prototype and is frozen" checks the prototype, that a function planted
on Object.prototype under a missing name is not returned by
getCppExport(), and that a new property does not stick. Without the
source change, all three assertions fail. The webstreams AGENTS.md key
rules record both properties.

Compatibility: no user-visible change; the table is internal to the
experimental typescript_implemented_streams implementation.
@jasnell
jasnell force-pushed the jasnell/ts-streams-bridge-inflight-flags branch from e842024 to 662d443 Compare October 9, 2026 13:41
@jasnell
jasnell force-pushed the jasnell/ts-streams-bridge-inflight-flags branch from 4cfff4b to 6edb071 Compare October 9, 2026 16:16
@jasnell
jasnell merged commit b08b755 into main Oct 9, 2026
24 checks passed
@jasnell
jasnell deleted the jasnell/ts-streams-bridge-inflight-flags branch October 9, 2026 17:07
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.

3 participants