Skip to content

Add support for sending ReadableStream and WritableStream over RPC, with automatic flow control.#132

Open
kentonv wants to merge 8 commits intomainfrom
kenton/streams
Open

Add support for sending ReadableStream and WritableStream over RPC, with automatic flow control.#132
kentonv wants to merge 8 commits intomainfrom
kenton/streams

Conversation

@kentonv
Copy link
Member

@kentonv kentonv commented Feb 6, 2026

When you send a WritableStream over RPC, the remote side gets a WritableStream. They can write to it. If they write faster than the connection can handle, or faster than your app actually consumes the data, they'll experience backpressure.

When you send a ReadableStream over RPC, the RPC system immediately begins reading from the stream and sending the chunks over the wire so that they are already ready for the remote end to read when it starts reading. This again applies backpressure appropriately. Under the hood, we ask the other end to create a "pipe" -- exposing a WritableStream back to us -- and then we pump chunks into that stream. Meanwhile, the call receiver receives the read end of the pipe.

The flow control is at present based on a fixed window size of 256kb per WritableStream. I intend to fix that in a subsequent change but this is enough to unblock remote bindings that use streams.

@kentonv kentonv requested a review from dmmulroy February 6, 2026 05:40
@changeset-bot
Copy link

changeset-bot bot commented Feb 6, 2026

🦋 Changeset detected

Latest commit: ef3f6e6

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
capnweb Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@kentonv kentonv requested a review from penalosa February 6, 2026 05:41
@pkg-pr-new
Copy link

pkg-pr-new bot commented Feb 6, 2026

Open in StackBlitz

npm i https://pkg.pr.new/cloudflare/capnweb@132

commit: ef3f6e6

Instead of storing an array of RpcStub, we now store an array of the underlying StubHooks.

This will make it easier to add support for new types like streams, which aren't RpcStubs, but they will wrap / be wrapped in StubHooks.
(Flow control is left for a future commit.)

Written using Claude+Opencode: https://share.opencode.cloudflare.dev/share/gJU0pT8p

(I cleaned some stuff up manually.)
As described in protocol.md, the basic idea here is that whenever we want to send a ReadableStream, we first send a message to the other side creating a "pipe". We pump our ReadableStream to the pipe's WritableSteam end, and we deliver the pipe's ReadableStream end to the remote peer. This way, we can begin pushing bytes immediately upon sending a ReadableStream, without waiting for the remote end to call back asking for the bytes (which would be an unnecessary round trip).

Written using Claude+Opencode: https://share.opencode.cloudflare.dev/share/ctbbBnOu
If more than 256kb of writes are in flight, we pause writes until past writes complete so that the number drops back below 256kb.

Future commits will expand the window size.

Written using Claude+Opencode: https://share.opencode.cloudflare.dev/share/D1bqkx2K

This was not the best Claude session. I could probably have done it faster by hand.
We add a new "stream" message to the protocol which skips these. See protocol.md for explanation.

(Since this is only used for streams, which were just introduced in this PR, this is not a breaking change.)

Written using Claude+Opencode: https://share.opencode.cloudflare.dev/share/OfY838e7
With this change, we'll automatically update a stream's window size based on the observed bandwidth-delay product, in order to fully saturate the stream with minimal additional buffer bloat.

The algorithm works by observing when each stream chunk is sent and acknowledged (via return from the RPC), allowing us to calculate:
1. Minimum round trip time.
2. Running average bandwidth over the last RTT.

From that we calculate bandwidth-delay product and adjust the window to match. We actually set the window a bit bigger than the calculated BDP during startup (2x) and steady-state (1.25x) so that we can observe if the actual bandwidth is greater than expected, and thus update the window accordingly.

I worked with Claude+Opencode to design the algorithm and implement, although I significantly refactored almost everything it wrote as the code was pretty meh: https://share.opencode.cloudflare.dev/share/rGV0SKLW
@kentonv
Copy link
Member Author

kentonv commented Feb 7, 2026

Update: I went ahead and added adaptive window size adjustment!

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