Skip to content

Conversation

@jasnell
Copy link
Collaborator

@jasnell jasnell commented Jan 14, 2026

In the WritableStreamDefaultWriter, the ready and closed promises end up being always created even if the accessors are never called and nothing is ever paying attention. In cases where there are lots of writes to a stream, this means that many ready promises end up being created but never paid attention to and never resolved, just adding overhead.

The jsg::LazyPromise<T> lazily produces jsg::Promise<T> instances, by replacing the ready and closed promises with jsg::LazyPromise<T> we can significantly reduce the number of promises created. The interface still allows the ready/closed to be rejected/resolved while deferring the creation of the actual JS promise and wrapping jsg::Promise until it is actually requested.

For example, in the sample introduced in this PR, for the paths that use the TransformStream, there end up being 20 writes to the TransformStream writable side. Because it's a pipe-through, we're not using the typical backpressure signal and we end up with 20 extraneously created ready promises that are wasted allocations. These are visible in the graph below as the orphaned row of promises across the top.

image

While these 20 promises do not individually add significant overhead on their own, in aggregate that's a lot of extraneous allocation (20x wasted promise allocations per request is non-trivial in aggregate)

The jsg::LazyPromise<T> is not another promise API... it just will lazily create the jsg::Promise<T> and underlying JS promise only when absolutely necessary to do so while preserving the flow of being able to resolve/reject them when necessary.

I have this PR as draft because I'm evaluating the effectiveness of the change. For the pipethrough case above it's not immediately effective because TransformStream's have their own backpressure signal that ends up materializing the promise always anyway, so if the stream is operating fully synchronously we end up hitting backpressure toggles frequently. Async transforms tend to avoid this churn. So it's not yet immediately clear if this is going to have enough of a benefit. Until I've proven one way or the other, I'll leave this as a draft.

@jasnell jasnell requested review from a team as code owners January 14, 2026 06:13
@jasnell jasnell force-pushed the jasnell/lazy-jsg-promise branch from 1cdf4ec to 539bf61 Compare January 14, 2026 14:58
@codspeed-hq

This comment was marked as outdated.

@jasnell jasnell marked this pull request as draft January 14, 2026 17:12
@jasnell
Copy link
Collaborator Author

jasnell commented Jan 14, 2026

Moved to draft. I had meant to open this as draft initially while I'm evaluating the effectiveness/completeness of it.

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