Repository navigation
Conversation
The background fetch registers an abort listener on the caller's signal, and that listener keeps the fetch reachable until the signal aborts. A signal that is never aborted after a successful fetch keeps the stale value and the fetch's internals alive, so recommend aborting it once the fetch settles. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
FetchOptions.signalhas no documentation. When asignalis passed for an expired entry, the background fetch adds an abort listener to it with{ signal: ac.signal }. Node keeps that listener alive until one of the two signals aborts. After a successful fetch neither does, so the internal AbortController, the fetch's closures and the replaced stale value stay in memory.We hit this in production. A new AbortController per call, with a 2 s timeout as a stale fallback for a slow DB, ran the heap out after several hours. Repro on
main(50 keys, 20 ms TTL, 10,000 refreshes per round): heap grows 32 → 173 MB withsignal, and stays at about 5 MB with.finally(() => ac.abort())or withoutsignal.This PR documents the behavior and the workaround. A code fix would release the listener on the caller's signal once the background fetch settles; I can open an issue or a PR for that if you'd prefer.
🤖 Generated with Claude Code