feat(ext/web): transferable {Readable,Writable,Transform}Stream#31126
feat(ext/web): transferable {Readable,Writable,Transform}Stream#31126bartlomieju merged 4 commits intomainfrom
Conversation
04fdba5 to
5e24cd8
Compare
5e24cd8 to
6b59191
Compare
fd3bafd to
a648831
Compare
a648831 to
ca149e3
Compare
| "cancel should be propagated to the original", | ||
| "cancel should abort a pending read()", |
There was a problem hiding this comment.
I'm a bit worried that these tests are currently failing - have you investigated why they fail?
There was a problem hiding this comment.
They fails because of DOMException not being serialized properly.
| ], | ||
| "worker.html": true, | ||
| "writable-stream.html": [ | ||
| "writing a unclonable object should error the stream" |
WalkthroughThe pull request implements cross-realm stream transfer support. Changes span the streams module (06_streams.js) to add MessagePort-based transferable support for ReadableStream, WritableStream, and TransformStream with appropriate host object branding and transfer lifecycle management. The message port layer (13_message_port.js and message_port.rs) extends serialization/deserialization to handle multiple resources simultaneously. Supporting changes include Pyenv entries in .gitignore and test infrastructure updates for dynamic shim generation and expanded test expectations. Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
ext/web/message_port.rs (1)
261-267: Missing check for MultiResource in self-transfer validation.The
op_message_port_post_messagefunction checks if aResourceis being transferred to itself, but it doesn't handleMultiResource. If aMultiResourcecontains the message port's RID, the self-transfer check will be bypassed.Consider extending the check to handle
MultiResource:for js_transferable in &data.transferables { if let JsTransferable::Resource(_name, id) = js_transferable && *id == rid { return Err(MessagePortError::TransferSelf); } + if let JsTransferable::MultiResource(_name, ids) = js_transferable { + for id in ids { + if *id == rid { + return Err(MessagePortError::TransferSelf); + } + } + } }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (6)
.gitignore(1 hunks)ext/web/06_streams.js(6 hunks)ext/web/13_message_port.js(2 hunks)ext/web/message_port.rs(4 hunks)tests/wpt/runner/expectation.json(1 hunks)tests/wpt/runner/runner.ts(2 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.rs
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.rs: For debugging Rust code, set breakpoints in IDE debuggers (VS Code with rust-analyzer, IntelliJ IDEA) or uselldbdirectly
Useeprintln!()ordbg!()macros for debug prints in Rust code
Files:
ext/web/message_port.rs
⚙️ CodeRabbit configuration file
Don't worry about coverage of Rust docstrings. Don't be nitpicky about it. Leave it to the author's judgement if such a documentation is necessary.
Files:
ext/web/message_port.rs
**/*.{ts,tsx,js}
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.{ts,tsx,js}: For JavaScript runtime debugging, enable V8 inspector with--inspect-brkflag and connect Chrome DevTools tochrome://inspect
Useconsole.log()for debug prints in JavaScript runtime code
Files:
tests/wpt/runner/runner.tsext/web/13_message_port.jsext/web/06_streams.js
🧠 Learnings (1)
📚 Learning: 2025-11-24T16:19:37.808Z
Learnt from: CR
Repo: denoland/deno PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T16:19:37.808Z
Learning: Applies to tests/specs/**/{__test__.jsonc,*.out} : Output assertions in spec tests should use `__test__.jsonc` inline fields or `.out` files with special matching syntax: `[WILDCARD]`, `[WILDLINE]`, `[WILDCHAR]`, `[WILDCHARS(n)]`, `[UNORDERED_START]`/`[UNORDERED_END]`, and `[# comment]`
Applied to files:
tests/wpt/runner/expectation.json
🧬 Code graph analysis (1)
ext/web/message_port.rs (1)
ext/web/13_message_port.js (2)
rid(469-469)transferables(371-371)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (8)
- GitHub Check: test debug windows-x86_64
- GitHub Check: test debug linux-x86_64
- GitHub Check: test debug macos-x86_64
- GitHub Check: test debug macos-aarch64
- GitHub Check: lint debug linux-x86_64
- GitHub Check: lint debug windows-x86_64
- GitHub Check: build libs
- GitHub Check: lint debug macos-x86_64
🔇 Additional comments (20)
.gitignore (1)
48-50: LGTM!Standard pyenv configuration for ignoring local Python version files.
ext/web/13_message_port.js (2)
388-394: LGTM!The multiResource deserialization correctly mirrors the resource case and passes multiple rids to the receive method.
470-480: LGTM!The conditional logic correctly handles both single and multi-resource transfers by checking the type of the returned rid. The implementation branches appropriately and maintains consistent data structures.
tests/wpt/runner/runner.ts (1)
192-219: Clarify whether per-script MessageChannel mock isolation is intentional.The shim code is re-executed before each script (lines 242–265), creating a new MessageChannel and overriding
globalThis.postMessagefor each script execution. Whileport1andport2are block-scoped and discarded after each script runs, thepostMessageoverride is global and persists, meaning only the last script's override remains active. If test scripts depend on shared mock channel state or expectpostMessageto route to a consistent channel instance, this per-script recreation should be refactored to run the shim setup only once per test.tests/wpt/runner/expectation.json (1)
5374-5394: Test expectations align with PR scope and known issues.The mixed success/failure states make sense—tests are fully passing where stream transfer works, and partially/failing where they involve serialization of DOMException or non-serializable objects (the known issue deferred to a follow-up). The specific failing test cases all relate directly to error handling or non-serializable chunks, which confirms the expectations reflect the current implementation state accurately.
To ensure the test case names and expectations are accurate, please verify that:
- The test names match the actual test definitions in the WPT files (e.g., "transferring a non-serializable chunk should error both sides" exists in readable-stream.html)
- No additional tests should be passing given that DOMException serialization is not yet fixed
ext/web/message_port.rs (4)
47-51: LGTM - MultiResource variant addition.The new
MultiResourcevariant follows the same pattern as the existingResourcevariant, appropriately extending the enum to support multiple transferred resources.
179-185: LGTM - JsTransferable MultiResource variant.The serialization format mirrors the existing
Resourcevariant structure with aVec<ResourceId>for multiple resources.
202-213: LGTM - Deserialization logic for MultiResource.The deserialization correctly iterates over resource IDs, takes each resource from the table, transfers them, and aggregates into a
Vec. Error handling is consistent with the single-resource path.
234-240: LGTM - Serialization logic for MultiResource.The serialization correctly maps over transferred resources, receives each one, and adds them to the resource table. The iterator chain is clean and idiomatic.
ext/web/06_streams.js (11)
5161-5162: LGTM - Host object branding for ReadableStream.Adding
[core.hostObjectBrand]enables the core transfer mechanism to identify and handle ReadableStream instances.
6170-6171: LGTM - Host object branding for TransformStream.Consistent with ReadableStream branding pattern.
6182-6185: LGTM - Brand placeholder path for TransformStream.This enables internal construction of TransformStream instances during transfer receiving steps, matching the pattern used by ReadableStream and WritableStream.
6386-6387: LGTM - Host object branding for WritableStream.Consistent with the branding pattern used for other stream types.
6754-6769: LGTM - Message packing utilities.Clean helper functions for cross-realm message protocol. The error handling wrapper properly re-throws after notifying the other side.
6775-6820: LGTM - Cross-realm readable stream setup.The implementation correctly:
- Sets up message handlers for chunk/close/error messages
- Handles messageerror events with proper cleanup
- Uses appropriate pull/cancel algorithms that communicate via the port
- Calls
port.start()to begin receiving messages
6901-6912: LGTM - ReadableStream transfer steps.Properly checks for locked state, sets up cross-realm writable, and pipes the stream. The promise is correctly marked as handled.
6928-6939: LGTM - WritableStream transfer steps.Mirrors the readable pattern - checks lock, sets up cross-realm readable, and pipes.
6956-6971: LGTM - TransformStream transfer steps.Correctly checks both readable and writable sides for locks before transferring. Delegates to the individual stream transfer steps.
6987-7029: LGTM - Transferable resource registrations.All three stream types are properly registered with send/receive handlers that:
- Create MessageChannel for communication
- Call appropriate transfer step functions
- Use the MessagePort transferable resource for the underlying port transfer
- TransformStream correctly uses two channels (one per direction)
6826-6895: The backpressure gating logic is intentional and working as designed.Lines 6855-6858 are unreachable on the first write invocation, but this is by design: the initial
backpressurePromiseacts as a gate that blocks the first write until the remote signals readiness via a "pull" message (line 6833). On subsequent writes, ifbackpressurePromisehas been set toundefined, these lines execute to allow writes to proceed immediately. The code correctly implements a cross-realm backpressure mechanism and does not contain the issue suggested.
|
It seems that the deno/cli/tsc/dts/lib.deno_web.d.ts Lines 1217 to 1257 in ed55de8 |
|
@ericf can you give an example program that fails to type-check properly? |
|
@bartlomieju the example used in the description of this PR: // example
const INDEX_HTML = Deno.readTextFileSync("./index.html");
const worker = new Worker("./the_algorithm.js", { type: "module" });
Deno.serve(async (req) => {
if (req.method === "POST" && req.path === "/the-algorithm") {
const { port1, port2 } = new MessageChannel();
worker.postMessage({ stream: req.body, port: port1 }, { transfer: [req.body, port1] });
const res = await new Promise((resolve) => {
port1.onmessage = (e) => resolve(e.data);
});
return new Response(res);
}
if (req.path === "/") {
return new Response(INDEX_HTML, { "content-type": "text/html" });
}
return new Response(null, { status: 404 });
});And a very simple example: |
## Summary - Enables `structuredClone()` and `postMessage()` for `DOMException` objects using the cloneable resource registry from #32672 - Serializes `message`, `name`, and `stack`; `code` is derived from `name` on deserialization - Supersedes #31156 Ref #31126 (comment) Closes #31983 --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
https://streams.spec.whatwg.org/#rs-transfer
https://streams.spec.whatwg.org/#ws-transfer
https://streams.spec.whatwg.org/#ts-transfer
remaining test failures are due to our
DOMExceptionnot correctly being serializable and can be solved in a followup.