You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
A streamed result has no backpressure. The response stream is built with no pull and no queuing strategy, and every codec node is enqueued the moment it is parsed, so the producer runs as fast as it can resolve regardless of whether anyone is reading.
That is one consumer reading six chunks over ~300 ms. On a large or infinite stream — a live source, a paginated read, anything backed by a cursor — a single slow client buffers the entire result in server memory. Application code cannot see it happening, and nothing bounds it.
Cancellation is fine: dropping the consumer does stop the producer. It is only the reading-too-slowly case that runs away.
Steps to reproduce
# Run against a build of the `next` BRANCH. The published `next` dist-tag is# 2.0.0-rc.4, which predates the `<endpoint>/data/<id>` address (#3094) and# answers 404 to every request below.
node repro.mjs
A producer that nobody is reading waits, the way a ReadableStream normally makes it wait.
Options
Gate the source pull on consumer demand — fix(web): pull a streamed result behind a demand gate #3124. I wrote the sentence below expecting this to be a question for the seroval seam rather than a small edit, and that was wrong: the runtime already installs an iterator wrapper described as "the only seam where a dropped consumer can stop the producer", and the same seam lets a slow one slow it. A read drives pull, pull releases one source pull, teardown releases a parked one. Measured over 200 idle event-loop turns, the producer advances by 1 instead of tracking the turn count.
Give the stream a queuing strategy and respect desiredSize. Smaller: keep the push shape but stop pulling the source while the queue is over its mark. Bounds the memory without inverting the pump.
Cap the queue and fail loudly past it. Turns an invisible memory leak into an error — worse for the well-behaved slow client, better than an OOM.
Document it: a streamed server-function result is buffered at the producer's pace, so do not stream something unbounded to a client you do not control. Honest, and it leaves the hazard in place.
(1) turned out to be both, so (2)-(4) are moot unless #3124 is the wrong shape.
Related
A failing test for this is in #3112 (counted in event-loop turns, so it means the same thing on any machine).
Describe the bug
A streamed result has no backpressure. The response stream is built with no
pulland no queuing strategy, and every codec node is enqueued the moment it is parsed, so the producer runs as fast as it can resolve regardless of whether anyone is reading.That is one consumer reading six chunks over ~300 ms. On a large or infinite stream — a live source, a paginated read, anything backed by a cursor — a single slow client buffers the entire result in server memory. Application code cannot see it happening, and nothing bounds it.
Cancellation is fine: dropping the consumer does stop the producer. It is only the reading-too-slowly case that runs away.
Steps to reproduce
repro.mjs:Output on
next(e2b21041) — the numbers above.Expected behavior
A producer that nobody is reading waits, the way a
ReadableStreamnormally makes it wait.Options
pull,pullreleases one source pull, teardown releases a parked one. Measured over 200 idle event-loop turns, the producer advances by 1 instead of tracking the turn count.desiredSize. Smaller: keep the push shape but stop pulling the source while the queue is over its mark. Bounds the memory without inverting the pump.(1) turned out to be both, so (2)-(4) are moot unless #3124 is the wrong shape.
Related
A failing test for this is in #3112 (counted in event-loop turns, so it means the same thing on any machine).