Skip to content

fix(after): don't retain the caller's async context for tasks that never settle - #98568

Open
mircoservices wants to merge 1 commit into
vercel:canaryfrom
mircoservices:fix/after-waituntil-context-retention
Open

fix(after): don't retain the caller's async context for tasks that never settle#98568
mircoservices wants to merge 1 commit into
vercel:canaryfrom
mircoservices:fix/after-waituntil-context-retention

Conversation

@mircoservices

@mircoservices mircoservices commented Sep 11, 2026

Copy link
Copy Markdown

fixes #98561

AwaiterMulti.waitUntil registers a bookkeeping .then() in the caller’s async context. If the promise never settles, that context stays in memory. On self-hosted servers, the shared awaiter only drains when the server closes, so retained request data keeps growing.

The fix uses createSnapshot() to register the bookkeeping callback in an empty context. Tasks are still tracked and awaited.

All nine unit tests pass with the fix. Reverting the fix causes the two never-settling tests to fail.

…ver settle

`AwaiterMulti.waitUntil` registers its bookkeeping `.then()` inside the
caller's async context. A `.then()` holds the context that was current when
it was registered for as long as the promise stays unsettled, so a task that
never settles pins the context of the request that registered it.

On a self-hosted server that is unbounded: `createInternalWaitUntil` builds
one `AwaiterOnce` per server and only drains it on `onServerClose`, so
nothing releases the entry while the process runs. For a render the retained
context holds the RSC flight payload.

Register the reaction through `createSnapshot()` instead, which restores an
empty context. The promise is still tracked and still awaited, so `after()`
semantics are unchanged; only the retained graph shrinks.

Measured on 16.4.0-canary.26 with a route calling `after()` on a
never-settling promise, 300 requests each holding 1 MiB in request-scoped
context: 300.0 MiB retained before, 0.0 MiB after.

Claude-Session: https://claude.ai/code/session_014QS6jWEQXPeLzx5L9jELTf
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.

after(): a task that never settles retains the request's async context for the life of the server

1 participant