Skip to content

Conversation

@kentonv
Copy link
Member

@kentonv kentonv commented Dec 22, 2025

The design of StreamSink makes it fairly complicated and somewhat inefficient to implement. For example:

  • It requires the use of setPipeline() so that the caller can start to enable promise pipelining on the StreamSink before it actually returns results.
  • Every call must create a resultsStreamSink object in advance, before it knows if there will be any streams in the results.
  • Generally there's just a lot of contortions involved in supporting it.

It occurred to me that a different design is possible: one where we have an object that is created per-IoContext (instead of per-call) which can be used to "push" values into that IoContext, so that they can then be referenced as externals by subsequent JsValues.

This PR implements this design, guarded behind an autogate. The autogate should not be enabled anywhere in production until all of production has received the code update -- this ensures that all of prod can understand the new protocol before anyone tries to use it.

Once the autogate is fully rolled out, we can delete StreamSink, which should be a pleasing reduction in complexity -- more so than the complexity that this PR adds.

#5744 shows what we'll be able to delete once this is landed. While the total quantity of code is neutral, I feel that StreamSink was a lot more complicated in the implementation details.

@kentonv kentonv requested a review from jasnell December 22, 2025 05:32
@kentonv kentonv requested review from a team as code owners December 22, 2025 05:32
@kentonv kentonv force-pushed the kenton/rpc-external-pusher branch from 15f0180 to 27b2baf Compare December 22, 2025 05:32
@kentonv
Copy link
Member Author

kentonv commented Dec 22, 2025

This PR sets up for some other changes I need to make, including:

  • Supporting channel tokens in process sandboxes.
  • Supporting embedding remote promises into RPC payloads, and having them automatically replaced with their resolutions before delivery -- matching Cap'n Web's functionality.

@codspeed-hq
Copy link

codspeed-hq bot commented Dec 22, 2025

CodSpeed Performance Report

Merging #5738 will not alter performance

Comparing kenton/rpc-external-pusher (e7fe4d0) with main (91275d4)

Summary

✅ 57 untouched
⏩ 34 skipped1

Footnotes

  1. 34 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

@kentonv kentonv force-pushed the kenton/rpc-external-pusher branch from 27b2baf to e9605b0 Compare December 22, 2025 13:54
@kentonv
Copy link
Member Author

kentonv commented Dec 22, 2025

Bolted awake at 6AM realizing: I broke e-order. Ugh.

@kentonv kentonv marked this pull request as draft December 22, 2025 14:41
@kentonv kentonv removed the request for review from jasnell December 22, 2025 16:35
kentonv added a commit to capnproto/capnproto that referenced this pull request Dec 22, 2025
This is a bit of a hack, but works in certain cases where we know for sure that promises are already resolved, including a case I'm working on in: cloudflare/workerd#5738
@kentonv kentonv force-pushed the kenton/rpc-external-pusher branch 3 times, most recently from cbc6d8a to c2af264 Compare December 22, 2025 18:46
The design of `StreamSink` makes it fairly complicated and somewhat inefficient to implement. For example:

* It requires the use of `setPipeline()` so that the caller can start to enable promise pipelining on the `StreamSink` before it actually returns results.
* Every call must create a `resultsStreamSink` object in advance, before it knows if there will be any streams in the results.
* Generally there's just a lot of contortions involved in supporting it.

It occurred to me that a different design is possible: one where we have an object that is created *per-IoContext* (instead of per-call) which can be used to "push" values into that IoContext, so that they can then be referenced as externals by subsequent JsValues.

This commit introduces that design, including specifying how it would work to implement `ReadableStream`. Subsequent commits will actually implement this design.

Eventually, after everyone in production is updated to understand and then use the new design, we can deprecate and remove StreamSink, thus cleaning up all the mess it created.
@kentonv kentonv force-pushed the kenton/rpc-external-pusher branch from c2af264 to 88b293e Compare December 22, 2025 18:49
@github-actions
Copy link

github-actions bot commented Dec 22, 2025

The generated output of @cloudflare/workers-types matches the snapshot in types/generated-snapshot 🎉

capnp PR: capnproto/capnproto#2475

The comment in this file says not to hand-edit it, but I don't understand how to use update-deps.py to update to a not-yet-merged PR...
@kentonv kentonv force-pushed the kenton/rpc-external-pusher branch from 88b293e to fd2e29a Compare December 22, 2025 18:51
@kentonv
Copy link
Member Author

kentonv commented Dec 22, 2025

OK I fixed e-order and actually simplified this quite a bit in the process.

It did require a hack in capnp: capnproto/capnproto#2475

But it works OK here.

This doesn't yet support any actual pushed types, this is just the infrastructure needed to make it available.

This introduces an autogate which opts into using ExternalPusher for calls. The caller decides (based on the autogate) whether to use ExternalPusher for the whole call. We can turn on the autogate once all call receivers in production understand the new protocol.
Currently, it appears that we do not run wd-tests with all-autogates. We probably should but that'll have to be a separate change. For now, this arranges just to run js-rpc-test and abortsignal-test a second time with the ExternalPusher autogate on.
@kentonv kentonv force-pushed the kenton/rpc-external-pusher branch from fd2e29a to e7fe4d0 Compare December 22, 2025 18:57
@kentonv kentonv marked this pull request as ready for review December 22, 2025 18:58
kentonv added a commit that referenced this pull request Dec 22, 2025
DO NOT MERGE until the autogate introduced in #5738 is fully rolled out and working.

Until then I'm putting this up just to show what we'll be able to remove.
kentonv added a commit that referenced this pull request Dec 22, 2025
DO NOT MERGE until the autogate introduced in #5738 is fully rolled out and working.

Until then I'm putting this up just to show what we'll be able to remove.
@kentonv kentonv mentioned this pull request Dec 22, 2025
@kentonv kentonv requested a review from jasnell December 22, 2025 19:48
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.

1 participant