Skip to content

lib: format default abort reason stack eagerly - #66265

Open
kuldeeepy wants to merge 1 commit into
nodejs:mainfrom
kuldeeepy:abort-controller-default-reason-stack
Open

kuldeeepy wants to merge 1 commit into
nodejs:mainfrom
kuldeeepy:abort-controller-default-reason-stack

Conversation

@kuldeeepy

Copy link
Copy Markdown

When abort() is called without a reason, the default AbortError keeps its stack unformatted, and V8 then holds on to the caller's frames (receivers and closures). A retained signal can keep the cancelled job and its buffers alive.

This reads the stack once when the default reason is created, like checkError() in streams does. It covers both controller.abort() and AbortSignal.abort(). AbortSignal.timeout() doesn't have this problem because its reason is created in a timer callback. Reasons passed by the caller aren't touched, and a throwing Error.prepareStackTrace doesn't make abort() throw.

abort() without a reason gets slower (static abort benchmark: about 569k to 340k ops/s). Passing a reason isn't affected.

Fixes: #66192


Disclosure: prepared with assistance from Claude Code. I reproduced the leak, checked the fix against the source, and ran the new and existing abort tests on a local build.

The default AbortError created by AbortController.prototype.abort()
and AbortSignal.abort() keeps its stack unformatted. Until it is
formatted, V8 holds on to the call sites, which retain the receivers
and functions of the calling frames. A signal that outlives the work
it cancelled can then keep that work alive, including large buffers.

Read the stack once when the default reason is created, as streams
already do. Reasons passed by the caller are left untouched.

Signed-off-by: Kuldeep Yadav <kuldeeep.yadav1@gmail.com>
@nodejs-github-bot nodejs-github-bot added the needs-ci PRs that need a full CI run. label Sep 24, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Welcome to Node.js, and thank you for your first contribution!

Before review, please take a moment to read:

Please make sure every commit is signed off. For a first pull request, GitHub Actions require collaborator approval and Jenkins CI must be started by a collaborator or triager, so an initial wait is normal.

@codecov

codecov Bot commented Sep 25, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 90.28%. Comparing base (e7d8ab5) to head (b03f41d).
⚠️ Report is 20 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main   #66265      +/-   ##
==========================================
- Coverage   90.30%   90.28%   -0.02%     
==========================================
  Files         789      789              
  Lines      272880   272893      +13     
  Branches    52110    52111       +1     
==========================================
- Hits       246418   246390      -28     
- Misses      16912    16963      +51     
+ Partials     9550     9540      -10     
Files with missing lines Coverage Δ
lib/internal/abort_controller.js 95.49% <100.00%> (+0.08%) ⬆️

... and 26 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

needs-ci PRs that need a full CI run.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Eagerly format default AbortController abort reasons to avoid retaining canceled work

2 participants