Repository: nodejs/node
Pull request: lib: make ALS default to AsyncContextFrame

I've marked AsyncContextFrame as stable and made it the default. With this we should see the substantial performance improvements to AsyncLocalStorage as the default case for users on future versions of Node.js. Getting more users on this will also help to identify if there are any perf outliers to tweak further.

I hope to land this in v24. I _assume_ we would generally consider this change as major, but I'm happy to downgrade it to minor if we think it's safe to do so.

(Pushing from the train somewhere between Valencia and Barcelona! 🚅💨)

cc @nodejs/diagnostics APMs have had some time to try this out and there have been no further issues after the initial iteration. I'm taking this to mean it seems to be stable. If you disagree, please speak up.

cc @nodejs/electron-installer Note that this makes use of the ContinuationPreservedEmbedderData API which Chromium _also_ makes use of. This likely means Electron will need to change to default to disabled due to this conflict. I _believe_ it's possible for Electron to do some work on their end to enable layering embedder data providers. If anyone is interested in picking this up to support both please reach out, I'm happy to help however I can! 🙂 

Changed files:
filename: doc/api/cli.md
patch: @@ -930,21 +930,6 @@ and `"` are usable.
 It is possible to run code containing inline types by passing
 [`--experimental-strip-types`][].
 
-### `--experimental-async-context-frame`
-
-<!-- YAML
-added: v22.7.0
--->
-
-> Stability: 1 - Experimental
-
-Enables the use of [`AsyncLocalStorage`][] backed by `AsyncContextFrame` rather
-than the default implementation which relies on async\_hooks. This new model is
-implemented very differently and so could have differences in how context data
-flows within the application. As such, it is presently recommended to be sure
-your application behaviour is unaffected by this change before using it in
-production.
-
 ### `--experimental-default-type=type`
 
 <!-- YAML
@@ -1663,6 +1648,19 @@ Disable the `node-addons` exports condition as well as disable loading
 native addons. When `--no-addons` is specified, calling `process.dlopen` or
 requiring a native C++ addon will fail and throw an exception.
 
+### `--no-async-context-frame`
+
+<!-- YAML
+added: REPLACEME
+-->
+
+> Stability: 2 - Stable
+
+Disables the use of [`AsyncLocalStorage`][] backed by `AsyncContextFrame` and
+uses the prior implementation which relied on async\_hooks. The previous model
+is retained for compatibility with Electron and for cases where the context
+flow may differ. However, if a difference in flow is found please report it.
+
 ### `--no-deprecation`
 
 <!-- YAML
@@ -3051,7 +3049,6 @@ one is included in the list below.
 * `--enable-source-maps`
 * `--entry-url`
 * `--experimental-abortcontroller`
-* `--experimental-async-context-frame`
 * `--experimental-default-type`
 * `--experimental-detect-module`
 * `--experimental-eventsource`
@@ -3097,6 +3094,7 @@ one is included in the list below.
 * `--napi-modules`
 * `--network-family-autoselection-attempt-timeout`
 * `--no-addons`
+* `--no-async-context-frame`
 * `--no-deprecation`
 * `--no-experimental-global-navigator`
 * `--no-experimental-repl-await`

filename: lib/internal/async_context_frame.js
patch: @@ -38,7 +38,7 @@ class ActiveAsyncContextFrame extends Map {
 
 function checkEnabled() {
   const enabled = require('internal/options')
-    .getOptionValue('--experimental-async-context-frame');
+    .getOptionValue('--async-context-frame');
 
   // If enabled, swap to active prototype so we don't need to check status
   // on every interaction with the async context frame.

filename: src/node_options.cc
patch: @@ -500,10 +500,11 @@ EnvironmentOptionsParser::EnvironmentOptionsParser() {
   AddOption(
       "--experimental-wasi-unstable-preview1", "", NoOp{}, kAllowedInEnvvar);
   AddOption("--expose-gc", "expose gc extension", V8Option{}, kAllowedInEnvvar);
-  AddOption("--experimental-async-context-frame",
+  AddOption("--async-context-frame",
             "Improve AsyncLocalStorage performance with AsyncContextFrame",
             &EnvironmentOptions::async_context_frame,
-            kAllowedInEnvvar);
+            kAllowedInEnvvar,
+            true);
   AddOption("--expose-internals", "", &EnvironmentOptions::expose_internals);
   AddOption("--frozen-intrinsics",
             "experimental frozen intrinsics support",

filename: test/parallel/test-without-async-context-frame.mjs
patch: @@ -41,15 +41,15 @@ const tests = testSets.reduce((m, v) => {
   return m;
 }, []);
 
-describe('AsyncContextFrame', {
+describe('without AsyncContextFrame', {
   // TODO(qard): I think high concurrency causes memory problems on Windows
   // concurrency: tests.length
 }, () => {
   for (const test of tests) {
     it(test, async () => {
       const proc = spawn(python, [
         testRunner,
-        '--node-args=--experimental-async-context-frame',
+        '--node-args=--no-async-context-frame',
         test,
       ], {
         stdio: ['ignore', 'ignore', 'inherit'],