Skip to content

async_hooks: add using scopes to AsyncLocalStorage#61674

Open
Qard wants to merge 1 commit intonodejs:mainfrom
Qard:als-run-scope
Open

async_hooks: add using scopes to AsyncLocalStorage#61674
Qard wants to merge 1 commit intonodejs:mainfrom
Qard:als-run-scope

Conversation

@Qard
Copy link
Member

@Qard Qard commented Feb 4, 2026

Adds support for using scope = storage.withScope(data) to do the equivalent of a storage.run(data, fn) with using syntax. This enables avoiding unnecessary closures.

cc @nodejs/diagnostics

@Qard Qard self-assigned this Feb 4, 2026
@Qard Qard added async_hooks Issues and PRs related to the async hooks subsystem. async_local_storage AsyncLocalStorage labels Feb 4, 2026
@nodejs-github-bot nodejs-github-bot added the needs-ci PRs that need a full CI run. label Feb 4, 2026
@Qard Qard force-pushed the als-run-scope branch 2 times, most recently from af2ad3e to d8e83aa Compare February 4, 2026 11:50
@Flarna
Copy link
Member

Flarna commented Feb 4, 2026

I guess this replaces #58104 right?

@Qard
Copy link
Member Author

Qard commented Feb 4, 2026

Ah, yes. Forgot that one existed. 😅

Adds support for using scope = storage.withScope(data) to do
the equivalent of a storage.run(data, fn) with using syntax.
This enables avoiding unnecessary closures.
@Qard Qard added the request-ci Add this label to start a Jenkins CI on a PR. label Feb 4, 2026
@github-actions github-actions bot added request-ci-failed An error occurred while starting CI via request-ci label, and manual interventon is needed. and removed request-ci Add this label to start a Jenkins CI on a PR. labels Feb 4, 2026
@github-actions
Copy link
Contributor

github-actions bot commented Feb 4, 2026

Failed to start CI
   ⚠  No approving reviews found
   ✘  Refusing to run CI on potentially unsafe PR
https://github.com/nodejs/node/actions/runs/21678728231

@codecov
Copy link

codecov bot commented Feb 4, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 89.75%. Comparing base (5e818c9) to head (09cb6ad).
⚠️ Report is 17 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main   #61674      +/-   ##
==========================================
+ Coverage   89.74%   89.75%   +0.01%     
==========================================
  Files         674      675       +1     
  Lines      204389   204428      +39     
  Branches    39280    39284       +4     
==========================================
+ Hits       183424   183480      +56     
- Misses      13264    13265       +1     
+ Partials     7701     7683      -18     
Files with missing lines Coverage Δ
...nternal/async_local_storage/async_context_frame.js 100.00% <100.00%> (ø)
lib/internal/async_local_storage/async_hooks.js 98.03% <100.00%> (+0.08%) ⬆️
lib/internal/async_local_storage/run_scope.js 100.00% <100.00%> (ø)

... and 28 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.

@addaleax addaleax added semver-minor PRs that contain new features and should be released in the next minor version. request-ci Add this label to start a Jenkins CI on a PR. author ready PRs that have at least one approval, no pending requests for changes, and a CI started. and removed request-ci-failed An error occurred while starting CI via request-ci label, and manual interventon is needed. labels Feb 6, 2026
@github-actions github-actions bot removed the request-ci Add this label to start a Jenkins CI on a PR. label Feb 6, 2026
@nodejs-github-bot

This comment was marked as outdated.

@nodejs-github-bot
Copy link
Collaborator

scope[Symbol.dispose]();
assert.strictEqual(storage.getStore(), undefined);

// Double dispose should be idempotent
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe adapt to following to better verify that second call does nothing:

  storage.enterWith('test2');
  assert.strictEqual(storage.getStore(), 'test2');

  // Double dispose should be idempotent
  scope[Symbol.dispose]();
  assert.strictEqual(storage.getStore(), 'test2');

const asyncLocalStorage = new AsyncLocalStorage();

{
using scope = asyncLocalStorage.withScope('my-store');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
using scope = asyncLocalStorage.withScope('my-store');
using _ = asyncLocalStorage.withScope('my-store');

I think this is the usual guideline if unused. Feel free to ignore.

const asyncLocalStorage = new AsyncLocalStorage();

{
using scope = asyncLocalStorage.withScope('my-store');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
using scope = asyncLocalStorage.withScope('my-store');
using _ = asyncLocalStorage.withScope('my-store');

const asyncLocalStorage = new AsyncLocalStorage();

try {
using scope = asyncLocalStorage.withScope('my-store');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
using scope = asyncLocalStorage.withScope('my-store');
using _ = asyncLocalStorage.withScope('my-store');

const asyncLocalStorage = new AsyncLocalStorage();

try {
using scope = asyncLocalStorage.withScope('my-store');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
using scope = asyncLocalStorage.withScope('my-store');
using _ = asyncLocalStorage.withScope('my-store');

@Flarna Flarna added dont-land-on-v20.x PRs that should not land on the v20.x-staging branch and should not be released in v20.x. dont-land-on-v22.x PRs that should not land on the v22.x-staging branch and should not be released in v22.x. labels Feb 6, 2026

Creates a disposable scope that enters the given store and automatically
restores the previous store value when the scope is disposed. This method is
designed to work with JavaScript's explicit resource management (`using` syntax).
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should document the caveat that this cause side effects outside of an async function scope if this is called before the first await:

async function foo() {
  using value = als.withScope(newValue);
  await 0;
}

foo() // no await
al's.getStore() // value changed.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep.

This is also exactly why I had previously suggested that AsyncContext create context boundaries around the initial segment of async functions too in order to make the set/get model work.

In my opinion the entire async function execution model is wrong until the first segment either has its own context or is made async the way the rest of the function is. It was a strange decision making execution of that initial segment synchronous, especially given that thrown errors in that segment are not synchronous. That whole part of the spec is full of landmines. 😬

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

Labels

async_hooks Issues and PRs related to the async hooks subsystem. async_local_storage AsyncLocalStorage author ready PRs that have at least one approval, no pending requests for changes, and a CI started. dont-land-on-v20.x PRs that should not land on the v20.x-staging branch and should not be released in v20.x. dont-land-on-v22.x PRs that should not land on the v22.x-staging branch and should not be released in v22.x. needs-ci PRs that need a full CI run. semver-minor PRs that contain new features and should be released in the next minor version.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants