Skip to content

Conversation

@feedthejim
Copy link
Contributor

@feedthejim feedthejim commented Jan 2, 2026

Summary

When a component suspends on a promise and that promise later resolves, pingTask() is called to continue rendering. Previously, pingTask() scheduled performWork without preserving the full async context that was active when the request started.

This caused third-party AsyncLocalStorage contexts (like Next.js's workUnitAsyncStorage) to be lost when rendering continued after promise resolution, leading to flaky test failures in Next.js: vercel/next.js#76066

The Problem

When a promise resolves after React's initial performWork has returned, pingTask() schedules a NEW performWork in a fresh microtask/macrotask. This new execution context doesn't have the original AsyncLocalStorage contexts.

The Fix

This PR uses AsyncLocalStorage.snapshot() (available in Node.js 18.2.0+) to capture the entire async context stack and restore it when pingTask() schedules continuation work.

Key insight: The snapshot must be captured inside requestStorage.run() so it includes both:

  1. Third-party AsyncLocalStorage contexts (like Next.js's workUnitAsyncStorage)
  2. React's own requestStorage context
// startWork() - capture snapshot INSIDE requestStorage.run()
if (supportsRequestStorage) {
  scheduleMicrotask(() =>
    requestStorage.run(request, () => {
      request.asyncContextSnapshot = createAsyncContextSnapshot();
      performWork(request);
    }),
  );
}

// pingTask() - restore snapshot (includes requestStorage, no need to wrap again)
if (runInAsyncContext !== null) {
  scheduleMicrotask(() => runInAsyncContext(performWork.bind(null, request)));
}

This ensures correct context nesting when restored, and simplifies pingTask() since requestStorage is already part of the captured snapshot.

Changes

  • Added asyncContextSnapshot field to Request type in both Fizz and Flight
  • Added createAsyncContextSnapshot() to all config forks (Node, Edge, Browser, Noop)
  • Modified startWork() to capture snapshot inside requestStorage.run()
  • Modified pingTask() to restore snapshot directly (no nested requestStorage.run() needed)
  • Added cleanup: asyncContextSnapshot = null when request closes
  • Added tests for context preservation across suspension and macrotask boundaries

How did you test this change?

Added tests in ReactDOMFizzServerNode-test.js:

  1. should preserve third-party AsyncLocalStorage context after promise resolution
  2. should preserve third-party AsyncLocalStorage context across macrotask boundaries
  3. should preserve nested AsyncLocalStorage contexts with correct nesting order
  4. should not leak async context between requests after cleanup

All tests verify that thirdPartyStorage.getStore() returns the expected value after suspension/resumption.

@meta-cla meta-cla bot added the CLA Signed label Jan 2, 2026
@feedthejim feedthejim force-pushed the preserve-async-context-snapshot branch from e73c02f to 042a634 Compare January 2, 2026 19:05
@react-sizebot
Copy link

react-sizebot commented Jan 2, 2026

Comparing: 65eec42...3f4b9d0

Critical size changes

Includes critical production bundles, as well as any change greater than 2%:

Name +/- Base Current +/- gzip Base gzip Current gzip
oss-stable/react-dom/cjs/react-dom.production.js = 6.84 kB 6.84 kB = 1.88 kB 1.88 kB
oss-stable/react-dom/cjs/react-dom-client.production.js = 607.60 kB 607.60 kB = 107.53 kB 107.54 kB
oss-experimental/react-dom/cjs/react-dom.production.js = 6.84 kB 6.84 kB = 1.88 kB 1.88 kB
oss-experimental/react-dom/cjs/react-dom-client.production.js = 666.83 kB 666.83 kB = 117.42 kB 117.42 kB
facebook-www/ReactDOM-prod.classic.js = 692.91 kB 692.91 kB = 121.92 kB 121.92 kB
facebook-www/ReactDOM-prod.modern.js = 683.34 kB 683.34 kB = 120.31 kB 120.31 kB
oss-experimental/react-noop-renderer/cjs/react-noop-renderer-flight-server.production.js +4.59% 2.44 kB 2.55 kB +3.94% 0.81 kB 0.85 kB
oss-stable-semver/react-noop-renderer/cjs/react-noop-renderer-flight-server.production.js +4.59% 2.44 kB 2.55 kB +3.94% 0.81 kB 0.85 kB
oss-stable/react-noop-renderer/cjs/react-noop-renderer-flight-server.production.js +4.59% 2.44 kB 2.55 kB +3.94% 0.81 kB 0.85 kB
oss-experimental/react-noop-renderer/cjs/react-noop-renderer-flight-server.development.js +4.26% 3.10 kB 3.23 kB +3.38% 0.95 kB 0.98 kB
oss-stable-semver/react-noop-renderer/cjs/react-noop-renderer-flight-server.development.js +4.26% 3.10 kB 3.23 kB +3.38% 0.95 kB 0.98 kB
oss-stable/react-noop-renderer/cjs/react-noop-renderer-flight-server.development.js +4.26% 3.10 kB 3.23 kB +3.38% 0.95 kB 0.98 kB

Significant size changes

Includes any change greater than 0.2%:

Expand to show
Name +/- Base Current +/- gzip Base gzip Current gzip
oss-experimental/react-noop-renderer/cjs/react-noop-renderer-flight-server.production.js +4.59% 2.44 kB 2.55 kB +3.94% 0.81 kB 0.85 kB
oss-stable-semver/react-noop-renderer/cjs/react-noop-renderer-flight-server.production.js +4.59% 2.44 kB 2.55 kB +3.94% 0.81 kB 0.85 kB
oss-stable/react-noop-renderer/cjs/react-noop-renderer-flight-server.production.js +4.59% 2.44 kB 2.55 kB +3.94% 0.81 kB 0.85 kB
oss-experimental/react-noop-renderer/cjs/react-noop-renderer-flight-server.development.js +4.26% 3.10 kB 3.23 kB +3.38% 0.95 kB 0.98 kB
oss-stable-semver/react-noop-renderer/cjs/react-noop-renderer-flight-server.development.js +4.26% 3.10 kB 3.23 kB +3.38% 0.95 kB 0.98 kB
oss-stable/react-noop-renderer/cjs/react-noop-renderer-flight-server.development.js +4.26% 3.10 kB 3.23 kB +3.38% 0.95 kB 0.98 kB
oss-experimental/react-noop-renderer/cjs/react-noop-renderer-server.development.js +1.65% 8.02 kB 8.15 kB +1.54% 1.69 kB 1.71 kB
oss-stable-semver/react-noop-renderer/cjs/react-noop-renderer-server.development.js +1.65% 8.02 kB 8.15 kB +1.54% 1.69 kB 1.71 kB
oss-stable/react-noop-renderer/cjs/react-noop-renderer-server.development.js +1.65% 8.02 kB 8.15 kB +1.54% 1.69 kB 1.71 kB
oss-experimental/react-noop-renderer/cjs/react-noop-renderer-server.production.js +1.53% 6.65 kB 6.75 kB +1.56% 1.60 kB 1.63 kB
oss-stable-semver/react-noop-renderer/cjs/react-noop-renderer-server.production.js +1.53% 6.65 kB 6.75 kB +1.56% 1.60 kB 1.63 kB
oss-stable/react-noop-renderer/cjs/react-noop-renderer-server.production.js +1.53% 6.65 kB 6.75 kB +1.56% 1.60 kB 1.63 kB
oss-stable-semver/react-server-dom-parcel/cjs/react-server-dom-parcel-server.edge.production.js +1.26% 104.74 kB 106.05 kB +0.86% 21.29 kB 21.47 kB
oss-stable/react-server-dom-parcel/cjs/react-server-dom-parcel-server.edge.production.js +1.26% 104.74 kB 106.05 kB +0.86% 21.29 kB 21.47 kB
oss-experimental/react-server-dom-parcel/cjs/react-server-dom-parcel-server.edge.production.js +1.24% 106.59 kB 107.91 kB +0.89% 21.68 kB 21.87 kB
oss-stable-semver/react-server-dom-webpack/cjs/react-server-dom-webpack-server.edge.production.js +1.18% 111.77 kB 113.09 kB +0.86% 22.42 kB 22.61 kB
oss-stable/react-server-dom-webpack/cjs/react-server-dom-webpack-server.edge.production.js +1.18% 111.77 kB 113.09 kB +0.86% 22.42 kB 22.61 kB
oss-stable-semver/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.edge.production.js +1.18% 111.77 kB 113.09 kB +0.86% 22.41 kB 22.60 kB
oss-stable/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.edge.production.js +1.18% 111.77 kB 113.09 kB +0.86% 22.41 kB 22.60 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-server.edge.production.js +1.16% 113.62 kB 114.94 kB +0.90% 22.80 kB 23.01 kB
oss-experimental/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.edge.production.js +1.16% 113.62 kB 114.94 kB +0.91% 22.80 kB 23.00 kB
oss-stable-semver/react-server/cjs/react-server-flight.production.js +1.07% 66.18 kB 66.89 kB +0.88% 13.08 kB 13.19 kB
oss-stable/react-server/cjs/react-server-flight.production.js +1.07% 66.18 kB 66.89 kB +0.88% 13.08 kB 13.19 kB
oss-experimental/react-server/cjs/react-server-flight.production.js +1.04% 68.13 kB 68.84 kB +0.73% 13.50 kB 13.60 kB
oss-stable-semver/react-server-dom-esm/cjs/react-server-dom-esm-server.node.production.js +0.90% 107.46 kB 108.42 kB +0.69% 21.79 kB 21.94 kB
oss-stable/react-server-dom-esm/cjs/react-server-dom-esm-server.node.production.js +0.90% 107.46 kB 108.42 kB +0.69% 21.79 kB 21.94 kB
oss-experimental/react-server-dom-esm/cjs/react-server-dom-esm-server.node.production.js +0.88% 109.31 kB 110.28 kB +0.69% 22.17 kB 22.32 kB
oss-stable-semver/react-server-dom-parcel/cjs/react-server-dom-parcel-server.node.production.js +0.86% 111.39 kB 112.35 kB +0.68% 22.35 kB 22.51 kB
oss-stable/react-server-dom-parcel/cjs/react-server-dom-parcel-server.node.production.js +0.86% 111.39 kB 112.35 kB +0.68% 22.35 kB 22.51 kB
oss-experimental/react-server-dom-parcel/cjs/react-server-dom-parcel-server.node.production.js +0.85% 113.24 kB 114.20 kB +0.70% 22.72 kB 22.88 kB
oss-stable-semver/react-server-dom-unbundled/cjs/react-server-dom-unbundled-server.node.production.js +0.82% 117.42 kB 118.39 kB +0.61% 23.27 kB 23.42 kB
oss-stable/react-server-dom-unbundled/cjs/react-server-dom-unbundled-server.node.production.js +0.82% 117.42 kB 118.39 kB +0.61% 23.27 kB 23.42 kB
oss-stable-semver/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.production.js +0.81% 118.47 kB 119.43 kB +0.61% 23.50 kB 23.64 kB
oss-stable/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.production.js +0.81% 118.47 kB 119.43 kB +0.61% 23.50 kB 23.64 kB
oss-stable-semver/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.node.production.js +0.81% 118.48 kB 119.45 kB +0.61% 23.49 kB 23.64 kB
oss-stable/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.node.production.js +0.81% 118.48 kB 119.45 kB +0.61% 23.49 kB 23.64 kB
oss-experimental/react-server-dom-unbundled/cjs/react-server-dom-unbundled-server.node.production.js +0.81% 119.28 kB 120.24 kB +0.69% 23.66 kB 23.82 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.production.js +0.80% 120.32 kB 121.28 kB +0.70% 23.88 kB 24.05 kB
oss-experimental/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.node.production.js +0.80% 120.34 kB 121.30 kB +0.70% 23.88 kB 24.04 kB
oss-stable-semver/react-server-dom-parcel/cjs/react-server-dom-parcel-server.edge.development.js +0.73% 200.66 kB 202.11 kB +0.68% 36.09 kB 36.33 kB
oss-stable/react-server-dom-parcel/cjs/react-server-dom-parcel-server.edge.development.js +0.73% 200.66 kB 202.11 kB +0.68% 36.09 kB 36.33 kB
oss-experimental/react-server-dom-parcel/cjs/react-server-dom-parcel-server.edge.development.js +0.72% 202.75 kB 204.20 kB +0.65% 36.52 kB 36.76 kB
oss-stable-semver/react-server-dom-webpack/cjs/react-server-dom-webpack-server.edge.development.js +0.70% 208.48 kB 209.94 kB +0.62% 37.40 kB 37.63 kB
oss-stable/react-server-dom-webpack/cjs/react-server-dom-webpack-server.edge.development.js +0.70% 208.48 kB 209.94 kB +0.62% 37.40 kB 37.63 kB
oss-stable-semver/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.edge.development.js +0.70% 208.48 kB 209.94 kB +0.61% 37.40 kB 37.63 kB
oss-stable/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.edge.development.js +0.70% 208.48 kB 209.94 kB +0.61% 37.40 kB 37.63 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-server.edge.development.js +0.69% 210.57 kB 212.03 kB +0.61% 37.82 kB 38.06 kB
oss-experimental/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.edge.development.js +0.69% 210.57 kB 212.03 kB +0.61% 37.83 kB 38.06 kB
oss-stable-semver/react-server-dom-parcel/cjs/react-server-dom-parcel-server.browser.production.js +0.65% 103.58 kB 104.25 kB +0.52% 21.02 kB 21.13 kB
oss-stable/react-server-dom-parcel/cjs/react-server-dom-parcel-server.browser.production.js +0.65% 103.58 kB 104.25 kB +0.52% 21.02 kB 21.13 kB
oss-experimental/react-server-dom-parcel/cjs/react-server-dom-parcel-server.browser.production.js +0.64% 105.43 kB 106.10 kB +0.61% 21.38 kB 21.52 kB
oss-stable-semver/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.browser.production.js +0.61% 110.59 kB 111.26 kB +0.57% 22.11 kB 22.24 kB
oss-stable/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.browser.production.js +0.61% 110.59 kB 111.26 kB +0.57% 22.11 kB 22.24 kB
oss-stable-semver/react-server-dom-webpack/cjs/react-server-dom-webpack-server.browser.production.js +0.60% 110.95 kB 111.62 kB +0.58% 22.22 kB 22.34 kB
oss-stable/react-server-dom-webpack/cjs/react-server-dom-webpack-server.browser.production.js +0.60% 110.95 kB 111.62 kB +0.58% 22.22 kB 22.34 kB
oss-experimental/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.browser.production.js +0.60% 112.45 kB 113.12 kB +0.54% 22.51 kB 22.63 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-server.browser.production.js +0.59% 112.80 kB 113.47 kB +0.54% 22.61 kB 22.73 kB
oss-stable-semver/react-server/cjs/react-server-flight.development.js +0.52% 143.70 kB 144.44 kB +0.47% 25.64 kB 25.76 kB
oss-stable/react-server/cjs/react-server-flight.development.js +0.52% 143.70 kB 144.44 kB +0.47% 25.64 kB 25.76 kB
oss-experimental/react-server/cjs/react-server-flight.development.js +0.51% 145.79 kB 146.53 kB +0.51% 26.10 kB 26.23 kB
oss-stable-semver/react-server/cjs/react-server.production.js +0.49% 145.28 kB 145.99 kB +0.46% 25.49 kB 25.61 kB
oss-stable/react-server/cjs/react-server.production.js +0.49% 145.28 kB 145.99 kB +0.46% 25.49 kB 25.61 kB
oss-experimental/react-server/cjs/react-server.production.js +0.48% 148.76 kB 149.48 kB +0.47% 26.28 kB 26.40 kB
oss-stable-semver/react-server-dom-esm/cjs/react-server-dom-esm-server.node.development.js +0.48% 223.50 kB 224.57 kB +0.48% 40.48 kB 40.68 kB
oss-stable/react-server-dom-esm/cjs/react-server-dom-esm-server.node.development.js +0.48% 223.50 kB 224.57 kB +0.48% 40.48 kB 40.68 kB
oss-experimental/react-server-dom-esm/cjs/react-server-dom-esm-server.node.development.js +0.47% 225.59 kB 226.66 kB +0.49% 40.93 kB 41.13 kB
oss-stable-semver/react-dom/cjs/react-dom-server.edge.production.js +0.46% 282.83 kB 284.14 kB +0.41% 51.44 kB 51.65 kB
oss-stable/react-dom/cjs/react-dom-server.edge.production.js +0.46% 282.90 kB 284.22 kB +0.40% 51.46 kB 51.67 kB
oss-stable-semver/react-server-dom-parcel/cjs/react-server-dom-parcel-server.node.development.js +0.46% 230.02 kB 231.09 kB +0.47% 41.08 kB 41.27 kB
oss-stable/react-server-dom-parcel/cjs/react-server-dom-parcel-server.node.development.js +0.46% 230.02 kB 231.09 kB +0.47% 41.08 kB 41.27 kB
oss-experimental/react-server-dom-parcel/cjs/react-server-dom-parcel-server.node.development.js +0.46% 232.11 kB 233.18 kB +0.46% 41.53 kB 41.73 kB
oss-stable-semver/react-server-dom-unbundled/cjs/react-server-dom-unbundled-server.node.development.js +0.45% 236.68 kB 237.75 kB +0.48% 42.13 kB 42.33 kB
oss-stable/react-server-dom-unbundled/cjs/react-server-dom-unbundled-server.node.development.js +0.45% 236.68 kB 237.75 kB +0.48% 42.13 kB 42.33 kB
oss-stable-semver/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.development.js +0.45% 237.87 kB 238.94 kB +0.45% 42.45 kB 42.64 kB
oss-stable/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.development.js +0.45% 237.87 kB 238.94 kB +0.45% 42.45 kB 42.64 kB
oss-stable-semver/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.node.development.js +0.45% 237.92 kB 238.99 kB +0.47% 42.43 kB 42.63 kB
oss-stable/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.node.development.js +0.45% 237.92 kB 238.99 kB +0.47% 42.43 kB 42.63 kB
oss-experimental/react-server-dom-unbundled/cjs/react-server-dom-unbundled-server.node.development.js +0.45% 238.77 kB 239.84 kB +0.51% 42.61 kB 42.82 kB
oss-experimental/react-dom/cjs/react-dom-server.edge.production.js +0.45% 294.91 kB 296.23 kB +0.38% 53.32 kB 53.52 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.development.js +0.45% 239.96 kB 241.03 kB +0.48% 42.92 kB 43.12 kB
oss-experimental/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.node.development.js +0.44% 240.01 kB 241.08 kB +0.47% 42.91 kB 43.11 kB
oss-stable-semver/react-server-dom-parcel/cjs/react-server-dom-parcel-server.browser.development.js +0.38% 196.96 kB 197.71 kB +0.36% 35.59 kB 35.72 kB
oss-stable/react-server-dom-parcel/cjs/react-server-dom-parcel-server.browser.development.js +0.38% 196.96 kB 197.71 kB +0.36% 35.59 kB 35.72 kB
oss-experimental/react-server-dom-parcel/cjs/react-server-dom-parcel-server.browser.development.js +0.37% 199.07 kB 199.81 kB +0.37% 36.03 kB 36.17 kB
oss-stable-semver/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.browser.development.js +0.36% 204.75 kB 205.49 kB +0.36% 36.90 kB 37.03 kB
oss-stable/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.browser.development.js +0.36% 204.75 kB 205.49 kB +0.36% 36.90 kB 37.03 kB
oss-stable-semver/react-server-dom-webpack/cjs/react-server-dom-webpack-server.browser.development.js +0.36% 205.22 kB 205.97 kB +0.36% 37.01 kB 37.14 kB
oss-stable/react-server-dom-webpack/cjs/react-server-dom-webpack-server.browser.development.js +0.36% 205.22 kB 205.97 kB +0.36% 37.01 kB 37.14 kB
oss-stable-semver/react-server/cjs/react-server.development.js +0.36% 205.20 kB 205.95 kB +0.34% 36.17 kB 36.29 kB
oss-stable/react-server/cjs/react-server.development.js +0.36% 205.20 kB 205.95 kB +0.34% 36.17 kB 36.29 kB
oss-experimental/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.browser.development.js +0.36% 206.85 kB 207.60 kB +0.37% 37.34 kB 37.48 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-server.browser.development.js +0.36% 207.33 kB 208.07 kB +0.38% 37.45 kB 37.59 kB
oss-experimental/react-server/cjs/react-server.development.js +0.35% 209.99 kB 210.74 kB +0.31% 37.10 kB 37.21 kB
oss-stable-semver/react-dom/cjs/react-dom-server.edge.development.js +0.33% 434.13 kB 435.57 kB +0.33% 77.37 kB 77.62 kB
oss-stable/react-dom/cjs/react-dom-server.edge.development.js +0.33% 434.21 kB 435.65 kB +0.33% 77.42 kB 77.67 kB
oss-stable-semver/react-dom/cjs/react-dom-server.node.production.js +0.33% 290.89 kB 291.85 kB +0.34% 51.30 kB 51.47 kB
oss-stable/react-dom/cjs/react-dom-server.node.production.js +0.33% 290.97 kB 291.92 kB +0.34% 51.32 kB 51.50 kB
oss-experimental/react-dom/cjs/react-dom-server.edge.development.js +0.32% 450.72 kB 452.16 kB +0.31% 79.73 kB 79.98 kB
oss-experimental/react-dom/cjs/react-dom-server.node.production.js +0.32% 302.64 kB 303.60 kB +0.30% 53.17 kB 53.33 kB
oss-stable-semver/react-dom/cjs/react-dom-server.browser.production.js +0.24% 277.21 kB 277.88 kB +0.28% 49.34 kB 49.48 kB
oss-stable/react-dom/cjs/react-dom-server.browser.production.js +0.24% 277.28 kB 277.96 kB +0.28% 49.37 kB 49.51 kB
oss-stable-semver/react-dom/cjs/react-dom-server.bun.production.js +0.24% 277.96 kB 278.64 kB +0.30% 48.99 kB 49.14 kB
oss-stable/react-dom/cjs/react-dom-server.bun.production.js +0.24% 278.04 kB 278.71 kB +0.29% 49.02 kB 49.16 kB
oss-stable-semver/react-dom/cjs/react-dom-server.node.development.js +0.24% 440.39 kB 441.44 kB +0.28% 77.16 kB 77.37 kB
oss-stable/react-dom/cjs/react-dom-server.node.development.js +0.24% 440.47 kB 441.52 kB +0.27% 77.21 kB 77.42 kB
oss-experimental/react-dom/cjs/react-dom-server.browser.production.js +0.23% 288.62 kB 289.29 kB +0.24% 50.99 kB 51.11 kB
oss-experimental/react-dom/cjs/react-dom-server.bun.production.js +0.23% 289.22 kB 289.90 kB +0.25% 50.77 kB 50.90 kB
oss-experimental/react-dom/cjs/react-dom-server.node.development.js +0.23% 456.48 kB 457.52 kB +0.23% 79.49 kB 79.68 kB
oss-experimental/react-markup/cjs/react-markup.react-server.production.js +0.23% 357.68 kB 358.49 kB +0.24% 66.35 kB 66.51 kB

Generated by 🚫 dangerJS against 3f4b9d0

@feedthejim feedthejim force-pushed the preserve-async-context-snapshot branch 2 times, most recently from 609e491 to 7ab1d0a Compare January 2, 2026 19:16
When a component suspends on a promise and that promise later resolves,
`pingTask()` is called to continue rendering. Previously, `pingTask()`
scheduled `performWork` without preserving the full async context that
was active when the request started. This caused third-party
AsyncLocalStorage contexts (like Next.js's `workUnitAsyncStorage`) to be
lost when rendering continued after promise resolution.

This fix uses `AsyncLocalStorage.snapshot()` (available in Node.js 18.2.0+)
to capture the entire async context stack when `startWork()` begins, and
restores it when `pingTask()` schedules continuation work.

The `snapshot()` API captures ALL async contexts in the stack, not just
React's own `requestStorage`. This ensures that frameworks like Next.js
can maintain their own AsyncLocalStorage contexts across React's
internal scheduling boundaries.

Changes:
- Added `asyncContextSnapshot` field to Request type
- Added `createAsyncContextSnapshot()` to config forks
- Modified `startWork()` to capture the snapshot
- Modified `pingTask()` to restore the snapshot when scheduling work
- Added test to verify third-party AsyncLocalStorage context is preserved

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <[email protected]>
@feedthejim feedthejim force-pushed the preserve-async-context-snapshot branch from 7ab1d0a to 9bfa965 Compare January 2, 2026 19:17
feedthejim and others added 3 commits January 2, 2026 20:26
- Add supportsRequestStorage check to pingTask conditions to prevent
  calling requestStorage.run() when requestStorage is null (browser env)
- Add createAsyncContextSnapshot to noop renderer configs for tests

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <[email protected]>
Null out the snapshot when the request is CLOSED to allow earlier
garbage collection of captured AsyncLocalStorage contexts.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <[email protected]>
Verifies that async context snapshots are properly cleaned up between
requests, preventing context leakage from one request to another.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <[email protected]>
Moved snapshot capture inside requestStorage.run() so the snapshot
includes both third-party AsyncLocalStorage contexts AND React's
own requestStorage context. This ensures correct context nesting
when restored in pingTask.

Also simplified pingTask to not need nested requestStorage.run()
when restoring from snapshot, since requestStorage is now part of
the captured context.

Added tests for:
- Macrotask boundary context preservation
- Nested AsyncLocalStorage contexts
@feedthejim feedthejim force-pushed the preserve-async-context-snapshot branch from 0cb9b72 to 3f4b9d0 Compare January 3, 2026 07:42
unstubbable added a commit to unstubbable/react that referenced this pull request Jan 5, 2026
Comment on lines +720 to +723
if (!hasLoaded) {
contextValueDuringRender = thirdPartyStorage.getStore();
throw promise;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

should probably just use(promise)?

@feedthejim feedthejim closed this Jan 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants