Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add backpressure information to tee, clone #16804

Merged
merged 4 commits into from
Jun 16, 2022

Conversation

yonran
Copy link
Contributor

@yonran yonran commented May 31, 2022

Add information on what causes backpressure to ReadableStream.tee, Request.clone, Response.clone.

Summary

Add warning that ReadableStream.tee, Request.clone, Response.clone will backpressure to the speed of the faster consumer, and unread data will be buffered at the slower ReadableStream without limit and without backpressure.

Motivation

I researched this issue when I studied the discrepancy between nodejs implementations such as node-fetch and minipass-fetch based on Readable.pipe (which backpressures to the slower consumed stream to limit buffering) and browsers (which backpressure to the faster consumed stream and have unlimited buffering). I wrote my findings in node-fetch/node-fetch#1568

Supporting details

The original sin was that the implementation of tee in the streams spec backpressured to the faster rather than the slower stream whatwg/streams#311, which contradicted the bounded-buffering suggestion in Stream requirements:. You must be able to pipe a stream to more than one writable stream. which said, “The tee stream can use a number of strategies to govern how the speed of its outputs affect the backpressure signals it gives off, but the simplest strategy is to pass aggregate backpressure signals directly up the chain, thus letting the speed of the slowest output determine the speed of the tee.”

The informative text in the streams spec is ambiguous; it suggests that tee is useful for “independent” consumers (Streams Specification: 2.1. Readable Streams, Streams Specification 4.2.4. ReadableStream example with tee). But tee() is dangerous to use for independent consumers, since the slower consumer buffers without backpressure or limit and will use up all your memory and crash. But if we can’t fix tee(), at least we should document its shortcomings.
.

Related issues

Metadata

  • Adds a new document
  • Rewrites (or significantly expands) a document
  • Fixes a typo, bug, or other error

Add information on what causes backpressure to ReadableStream.tee, Request.clone, Response.clone.
@yonran yonran requested a review from a team as a code owner May 31, 2022 02:09
@yonran yonran requested review from wbamberg and removed request for a team May 31, 2022 02:09
@github-actions github-actions bot added the Content:WebAPI Web API docs label May 31, 2022
a response from the server and stream it to the browser, but also stream it to the
ServiceWorker cache. Since a response body cannot be consumed more than once, you'd need
two copies to do this.

A teed stream will backpressure to the speed of the *faster* consumed `ReadableStream`,
Copy link
Contributor

Choose a reason for hiding this comment

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

Drive-by comment here.

Even if I know well how connections and messages do work (I was able to manually decode TCP messages back then), as a non-native reader, I find the term backpressure difficult to understand. I think we should find a better term, or at least succintly define it the first time we use it in a page.

Copy link
Contributor Author

@yonran yonran May 31, 2022

Choose a reason for hiding this comment

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

Fair point. I realize that I was using backpressure as a verb like in the akka-streams documentation e.g. Source.alsoTo. But in nodejs stream and streams spec, backpressure is only used as a noun. Also the growable internal queue and support for both pull sources and push sources when implementing an underlying source make backpressure more complicated.

For a ReadableStream in the streams spec, backpressure is specifically the condition that controller.desiredSize <= 0, which a push underlying source is expected to check periodically and pause calling controller.enqueue(chunk), and the controller will stop repeatedly calling pull(controller) to the underlying source which might be a pull source.

Copy link
Contributor Author

@yonran yonran Jun 1, 2022

Choose a reason for hiding this comment

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

@teoli2003 I have added a technical explanation of backpressure in ReadableStream.tee, and also I opened a PR to the streams spec so hopefully the spec will match my description whatwg/streams#1234.

I also added an informal description of backpressure to Response.clone.

Copy link
Collaborator

Choose a reason for hiding this comment

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

This is interesting. I hadn't got to my reading around Tees yet!

FYI Just to be a little bit pedantic because my head is in this at the moment for byte streams work ...

A push underlying source does use backpressure to change how it enqueues new data - it will always enqueue whatever data it gets. If it gets "backpressure" it is supposed to signal the thing that is providing the data (e.g. a socket) that it should pause or throttle supply. The difference is that not every supply has a mechanism for throttling or pausing.

As you say, for a pull source the controller manages the pull requests based on need (spec appears to say that it keeps requesting until bufffers are filled (desired size = 0) then stops calling it until empty and more data is requested. Though it looks like there are different strategies you can use there.

Copy link
Collaborator

@hamishwillee hamishwillee Jun 13, 2022

Choose a reason for hiding this comment

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

PS Backpressure is actually not a bad term IMO, but as you say @teoli2003 it needs to be properly defined. I'd do that by linking to the section in the using streams guide (and make sure that is OK).

Backpressure is a bit filling a somewhat empty a pipe - at a certain rate the water starts pushing back up the pipe, and eventually you can't fill it any more.

Copy link
Contributor

@teoli2003 teoli2003 left a comment

Choose a reason for hiding this comment

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

Two minor changes and I think we can merge this.

files/en-us/web/api/request/clone/index.md Outdated Show resolved Hide resolved
files/en-us/web/api/readablestream/tee/index.md Outdated Show resolved Hide resolved
@github-actions
Copy link
Contributor

github-actions bot commented Jun 12, 2022

Preview URLs

Flaws

None! 🎉

External URLs

URL: /en-US/docs/Web/API/Response/clone
Title: Response.clone()
on GitHub

No new external URLs


URL: /en-US/docs/Web/API/ReadableStream/tee
Title: ReadableStream.tee()
on GitHub


URL: /en-US/docs/Web/API/Request/clone
Title: Request.clone()
on GitHub

No new external URLs

(this comment was updated 2022-06-16 16:38:31.204691)

Copy link
Contributor

@teoli2003 teoli2003 left a comment

Choose a reason for hiding this comment

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

👍

@yonran
Copy link
Contributor Author

yonran commented Jun 16, 2022

Do you want me to rebase and squash and add the PR number (#16804) to the commit message?

@teoli2003 teoli2003 merged commit 014c78f into mdn:main Jun 16, 2022
@teoli2003
Copy link
Contributor

It's fine like this!

Thanks a lot!

Congratulations upon your first merged commit here! Welcome aboard! 🎉

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Content:WebAPI Web API docs
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants