diff --git a/.eslintrc.js b/.eslintrc.js index 29ffa942550d7..7c7bc560c2445 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -532,6 +532,7 @@ module.exports = { trustedTypes: 'readonly', IS_REACT_ACT_ENVIRONMENT: 'readonly', AsyncLocalStorage: 'readonly', + async_hooks: 'readonly', globalThis: 'readonly', }, }; diff --git a/packages/react-server/src/ReactFlightServer.js b/packages/react-server/src/ReactFlightServer.js index cc65f746f42e8..2ce2260a4852f 100644 --- a/packages/react-server/src/ReactFlightServer.js +++ b/packages/react-server/src/ReactFlightServer.js @@ -70,6 +70,7 @@ import { requestStorage, prepareHostDispatcher, createHints, + initAsyncDebugInfo, } from './ReactFlightServerConfig'; import { @@ -117,6 +118,8 @@ import binaryToComparableString from 'shared/binaryToComparableString'; import {SuspenseException, getSuspendedThenable} from './ReactFlightThenable'; +initAsyncDebugInfo(); + const ObjectPrototype = Object.prototype; type JSONValue = diff --git a/packages/react-server/src/ReactFlightServerConfigDebugNode.js b/packages/react-server/src/ReactFlightServerConfigDebugNode.js new file mode 100644 index 0000000000000..f9f4d9cef3f89 --- /dev/null +++ b/packages/react-server/src/ReactFlightServerConfigDebugNode.js @@ -0,0 +1,33 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + */ + +import {createAsyncHook, executionAsyncId} from './ReactFlightServerConfig'; +import {enableAsyncDebugInfo} from 'shared/ReactFeatureFlags'; + +// Initialize the tracing of async operations. +// We do this globally since the async work can potentially eagerly +// start before the first request and once requests start they can interleave. +// In theory we could enable and disable using a ref count of active requests +// but given that typically this is just a live server, it doesn't really matter. +export function initAsyncDebugInfo(): void { + if (__DEV__ && enableAsyncDebugInfo) { + createAsyncHook({ + init(asyncId: number, type: string, triggerAsyncId: number): void { + // TODO + }, + promiseResolve(asyncId: number): void { + // TODO + executionAsyncId(); + }, + destroy(asyncId: number): void { + // TODO + }, + }).enable(); + } +} diff --git a/packages/react-server/src/ReactFlightServerConfigDebugNoop.js b/packages/react-server/src/ReactFlightServerConfigDebugNoop.js new file mode 100644 index 0000000000000..55661479583e0 --- /dev/null +++ b/packages/react-server/src/ReactFlightServerConfigDebugNoop.js @@ -0,0 +1,11 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + */ + +// Exported for runtimes that don't support Promise instrumentation for async debugging. +export function initAsyncDebugInfo(): void {} diff --git a/packages/react-server/src/forks/ReactFlightServerConfig.custom.js b/packages/react-server/src/forks/ReactFlightServerConfig.custom.js index 4133395a1036e..9c00e67bb67b1 100644 --- a/packages/react-server/src/forks/ReactFlightServerConfig.custom.js +++ b/packages/react-server/src/forks/ReactFlightServerConfig.custom.js @@ -11,6 +11,8 @@ import type {Request} from 'react-server/src/ReactFlightServer'; export * from '../ReactFlightServerConfigBundlerCustom'; +export * from '../ReactFlightServerConfigDebugNoop'; + export type Hints = any; export type HintCode = any; // eslint-disable-next-line no-unused-vars diff --git a/packages/react-server/src/forks/ReactFlightServerConfig.dom-browser-esm.js b/packages/react-server/src/forks/ReactFlightServerConfig.dom-browser-esm.js index d52bf8ad7451e..ffcf103c6a0e8 100644 --- a/packages/react-server/src/forks/ReactFlightServerConfig.dom-browser-esm.js +++ b/packages/react-server/src/forks/ReactFlightServerConfig.dom-browser-esm.js @@ -16,3 +16,5 @@ export * from 'react-dom-bindings/src/server/ReactFlightServerConfigDOM'; export const supportsRequestStorage = true; export const requestStorage: AsyncLocalStorage = new AsyncLocalStorage(); + +export * from '../ReactFlightServerConfigDebugNoop'; diff --git a/packages/react-server/src/forks/ReactFlightServerConfig.dom-browser-turbopack.js b/packages/react-server/src/forks/ReactFlightServerConfig.dom-browser-turbopack.js index 3fdeac94f5775..4773085ba5c2b 100644 --- a/packages/react-server/src/forks/ReactFlightServerConfig.dom-browser-turbopack.js +++ b/packages/react-server/src/forks/ReactFlightServerConfig.dom-browser-turbopack.js @@ -14,3 +14,5 @@ export * from 'react-dom-bindings/src/server/ReactFlightServerConfigDOM'; export const supportsRequestStorage = false; export const requestStorage: AsyncLocalStorage = (null: any); + +export * from '../ReactFlightServerConfigDebugNoop'; diff --git a/packages/react-server/src/forks/ReactFlightServerConfig.dom-browser.js b/packages/react-server/src/forks/ReactFlightServerConfig.dom-browser.js index 212bd8c89a66e..6f209caaaf590 100644 --- a/packages/react-server/src/forks/ReactFlightServerConfig.dom-browser.js +++ b/packages/react-server/src/forks/ReactFlightServerConfig.dom-browser.js @@ -14,3 +14,5 @@ export * from 'react-dom-bindings/src/server/ReactFlightServerConfigDOM'; export const supportsRequestStorage = false; export const requestStorage: AsyncLocalStorage = (null: any); + +export * from '../ReactFlightServerConfigDebugNoop'; diff --git a/packages/react-server/src/forks/ReactFlightServerConfig.dom-bun.js b/packages/react-server/src/forks/ReactFlightServerConfig.dom-bun.js index 2e7273d82d696..cfdb89861ebff 100644 --- a/packages/react-server/src/forks/ReactFlightServerConfig.dom-bun.js +++ b/packages/react-server/src/forks/ReactFlightServerConfig.dom-bun.js @@ -14,3 +14,5 @@ export * from 'react-dom-bindings/src/server/ReactFlightServerConfigDOM'; export const supportsRequestStorage = false; export const requestStorage: AsyncLocalStorage = (null: any); + +export * from '../ReactFlightServerConfigDebugNoop'; diff --git a/packages/react-server/src/forks/ReactFlightServerConfig.dom-edge-turbopack.js b/packages/react-server/src/forks/ReactFlightServerConfig.dom-edge-turbopack.js index 2beb3986d0726..64a329a341f11 100644 --- a/packages/react-server/src/forks/ReactFlightServerConfig.dom-edge-turbopack.js +++ b/packages/react-server/src/forks/ReactFlightServerConfig.dom-edge-turbopack.js @@ -16,3 +16,18 @@ export const supportsRequestStorage = typeof AsyncLocalStorage === 'function'; export const requestStorage: AsyncLocalStorage = supportsRequestStorage ? new AsyncLocalStorage() : (null: any); + +// We use the Node version but get access to async_hooks from a global. +import type {HookCallbacks, AsyncHook} from 'async_hooks'; +export const createAsyncHook: HookCallbacks => AsyncHook = + typeof async_hooks === 'object' + ? async_hooks.createHook + : function () { + return ({ + enable() {}, + disable() {}, + }: any); + }; +export const executionAsyncId: () => number = + typeof async_hooks === 'object' ? async_hooks.executionAsyncId : (null: any); +export * from '../ReactFlightServerConfigDebugNode'; diff --git a/packages/react-server/src/forks/ReactFlightServerConfig.dom-edge.js b/packages/react-server/src/forks/ReactFlightServerConfig.dom-edge.js index 79c5d9c946fa5..b15e88ed11e46 100644 --- a/packages/react-server/src/forks/ReactFlightServerConfig.dom-edge.js +++ b/packages/react-server/src/forks/ReactFlightServerConfig.dom-edge.js @@ -16,3 +16,18 @@ export const supportsRequestStorage = typeof AsyncLocalStorage === 'function'; export const requestStorage: AsyncLocalStorage = supportsRequestStorage ? new AsyncLocalStorage() : (null: any); + +// We use the Node version but get access to async_hooks from a global. +import type {HookCallbacks, AsyncHook} from 'async_hooks'; +export const createAsyncHook: HookCallbacks => AsyncHook = + typeof async_hooks === 'object' + ? async_hooks.createHook + : function () { + return ({ + enable() {}, + disable() {}, + }: any); + }; +export const executionAsyncId: () => number = + typeof async_hooks === 'object' ? async_hooks.executionAsyncId : (null: any); +export * from '../ReactFlightServerConfigDebugNode'; diff --git a/packages/react-server/src/forks/ReactFlightServerConfig.dom-fb-experimental.js b/packages/react-server/src/forks/ReactFlightServerConfig.dom-fb-experimental.js index 0c265c7c11a84..5239248d677f0 100644 --- a/packages/react-server/src/forks/ReactFlightServerConfig.dom-fb-experimental.js +++ b/packages/react-server/src/forks/ReactFlightServerConfig.dom-fb-experimental.js @@ -14,3 +14,5 @@ export * from 'react-dom-bindings/src/server/ReactFlightServerConfigDOM'; export const supportsRequestStorage = false; export const requestStorage: AsyncLocalStorage = (null: any); + +export * from '../ReactFlightServerConfigDebugNoop'; diff --git a/packages/react-server/src/forks/ReactFlightServerConfig.dom-legacy.js b/packages/react-server/src/forks/ReactFlightServerConfig.dom-legacy.js index 2e7273d82d696..cfdb89861ebff 100644 --- a/packages/react-server/src/forks/ReactFlightServerConfig.dom-legacy.js +++ b/packages/react-server/src/forks/ReactFlightServerConfig.dom-legacy.js @@ -14,3 +14,5 @@ export * from 'react-dom-bindings/src/server/ReactFlightServerConfigDOM'; export const supportsRequestStorage = false; export const requestStorage: AsyncLocalStorage = (null: any); + +export * from '../ReactFlightServerConfigDebugNoop'; diff --git a/packages/react-server/src/forks/ReactFlightServerConfig.dom-node-esm.js b/packages/react-server/src/forks/ReactFlightServerConfig.dom-node-esm.js index d52bf8ad7451e..f3460fd71b925 100644 --- a/packages/react-server/src/forks/ReactFlightServerConfig.dom-node-esm.js +++ b/packages/react-server/src/forks/ReactFlightServerConfig.dom-node-esm.js @@ -16,3 +16,6 @@ export * from 'react-dom-bindings/src/server/ReactFlightServerConfigDOM'; export const supportsRequestStorage = true; export const requestStorage: AsyncLocalStorage = new AsyncLocalStorage(); + +export {createHook as createAsyncHook, executionAsyncId} from 'async_hooks'; +export * from '../ReactFlightServerConfigDebugNode'; diff --git a/packages/react-server/src/forks/ReactFlightServerConfig.dom-node-turbopack.js b/packages/react-server/src/forks/ReactFlightServerConfig.dom-node-turbopack.js index f85af8f3c296c..d9eb6a46e4e71 100644 --- a/packages/react-server/src/forks/ReactFlightServerConfig.dom-node-turbopack.js +++ b/packages/react-server/src/forks/ReactFlightServerConfig.dom-node-turbopack.js @@ -17,3 +17,6 @@ export * from 'react-dom-bindings/src/server/ReactFlightServerConfigDOM'; export const supportsRequestStorage = true; export const requestStorage: AsyncLocalStorage = new AsyncLocalStorage(); + +export {createHook as createAsyncHook, executionAsyncId} from 'async_hooks'; +export * from '../ReactFlightServerConfigDebugNode'; diff --git a/packages/react-server/src/forks/ReactFlightServerConfig.dom-node.js b/packages/react-server/src/forks/ReactFlightServerConfig.dom-node.js index 5d917f6f7868c..d716d502f7598 100644 --- a/packages/react-server/src/forks/ReactFlightServerConfig.dom-node.js +++ b/packages/react-server/src/forks/ReactFlightServerConfig.dom-node.js @@ -17,3 +17,6 @@ export * from 'react-dom-bindings/src/server/ReactFlightServerConfigDOM'; export const supportsRequestStorage = true; export const requestStorage: AsyncLocalStorage = new AsyncLocalStorage(); + +export {createHook as createAsyncHook, executionAsyncId} from 'async_hooks'; +export * from '../ReactFlightServerConfigDebugNode'; diff --git a/packages/shared/ReactFeatureFlags.js b/packages/shared/ReactFeatureFlags.js index acf2e6e9b9dcc..8841a7bc6a4b5 100644 --- a/packages/shared/ReactFeatureFlags.js +++ b/packages/shared/ReactFeatureFlags.js @@ -229,6 +229,8 @@ export const enableProfilerNestedUpdatePhase = __PROFILE__; // issues in DEV builds. export const enableDebugTracing = false; +export const enableAsyncDebugInfo = __EXPERIMENTAL__; + // Track which Fiber(s) schedule render work. export const enableUpdaterTracking = __PROFILE__; diff --git a/packages/shared/forks/ReactFeatureFlags.native-fb.js b/packages/shared/forks/ReactFeatureFlags.native-fb.js index 8c8ff2cbf97bb..eaf68191aa6f5 100644 --- a/packages/shared/forks/ReactFeatureFlags.native-fb.js +++ b/packages/shared/forks/ReactFeatureFlags.native-fb.js @@ -29,6 +29,7 @@ export const { // The rest of the flags are static for better dead code elimination. export const disableModulePatternComponents = true; export const enableDebugTracing = false; +export const enableAsyncDebugInfo = false; export const enableSchedulingProfiler = __PROFILE__; export const enableProfilerTimer = __PROFILE__; export const enableProfilerCommitHooks = __PROFILE__; diff --git a/packages/shared/forks/ReactFeatureFlags.native-oss.js b/packages/shared/forks/ReactFeatureFlags.native-oss.js index 795fab334ee80..d3ef3621d1dbb 100644 --- a/packages/shared/forks/ReactFeatureFlags.native-oss.js +++ b/packages/shared/forks/ReactFeatureFlags.native-oss.js @@ -12,6 +12,7 @@ import typeof * as ExportsType from './ReactFeatureFlags.native-oss'; export const debugRenderPhaseSideEffectsForStrictMode = false; export const enableDebugTracing = false; +export const enableAsyncDebugInfo = false; export const enableSchedulingProfiler = false; export const replayFailedUnitOfWorkWithInvokeGuardedCallback = __DEV__; export const enableProfilerTimer = __PROFILE__; diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.js index e0216e3f57535..20fa7bb3ff1d1 100644 --- a/packages/shared/forks/ReactFeatureFlags.test-renderer.js +++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.js @@ -12,6 +12,7 @@ import typeof * as ExportsType from './ReactFeatureFlags.test-renderer'; export const debugRenderPhaseSideEffectsForStrictMode = false; export const enableDebugTracing = false; +export const enableAsyncDebugInfo = false; export const enableSchedulingProfiler = false; export const replayFailedUnitOfWorkWithInvokeGuardedCallback = false; export const enableProfilerTimer = __PROFILE__; diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.native.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.native.js index 93d09f741601e..af184d191bd04 100644 --- a/packages/shared/forks/ReactFeatureFlags.test-renderer.native.js +++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.native.js @@ -12,6 +12,7 @@ import typeof * as ExportsType from './ReactFeatureFlags.test-renderer'; export const debugRenderPhaseSideEffectsForStrictMode = false; export const enableDebugTracing = false; +export const enableAsyncDebugInfo = false; export const enableSchedulingProfiler = false; export const replayFailedUnitOfWorkWithInvokeGuardedCallback = false; export const enableProfilerTimer = __PROFILE__; diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js index d5fa0454eaaf0..8a31dbca6bcf7 100644 --- a/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js +++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js @@ -12,6 +12,7 @@ import typeof * as ExportsType from './ReactFeatureFlags.test-renderer.www'; export const debugRenderPhaseSideEffectsForStrictMode = false; export const enableDebugTracing = false; +export const enableAsyncDebugInfo = false; export const enableSchedulingProfiler = false; export const replayFailedUnitOfWorkWithInvokeGuardedCallback = false; export const enableProfilerTimer = __PROFILE__; diff --git a/packages/shared/forks/ReactFeatureFlags.www.js b/packages/shared/forks/ReactFeatureFlags.www.js index a76277e43b864..4850d8236c661 100644 --- a/packages/shared/forks/ReactFeatureFlags.www.js +++ b/packages/shared/forks/ReactFeatureFlags.www.js @@ -116,5 +116,7 @@ export const forceConcurrentByDefaultForTesting = false; export const useMicrotasksForSchedulingInFabric = false; export const passChildrenWhenCloningPersistedNodes = false; +export const enableAsyncDebugInfo = false; + // Flow magic to verify the exports of this file match the original version. ((((null: any): ExportsType): FeatureFlagsType): ExportsType); diff --git a/scripts/flow/environment.js b/scripts/flow/environment.js index fe12baf98906c..19adb73dd7334 100644 --- a/scripts/flow/environment.js +++ b/scripts/flow/environment.js @@ -281,13 +281,7 @@ declare module 'pg/lib/utils' { }; } -declare class AsyncLocalStorage { - disable(): void; - getStore(): T | void; - run(store: T, callback: (...args: any[]) => void, ...args: any[]): void; - enterWith(store: T): void; -} - +// Node declare module 'async_hooks' { declare class AsyncLocalStorage { disable(): void; @@ -295,8 +289,42 @@ declare module 'async_hooks' { run(store: T, callback: (...args: any[]) => void, ...args: any[]): void; enterWith(store: T): void; } + declare interface AsyncResource {} + declare function executionAsyncId(): number; + declare function executionAsyncResource(): AsyncResource; + declare function triggerAsyncId(): number; + declare type HookCallbacks = { + init?: ( + asyncId: number, + type: string, + triggerAsyncId: number, + resource: AsyncResource, + ) => void, + before?: (asyncId: number) => void, + after?: (asyncId: number) => void, + promiseResolve?: (asyncId: number) => void, + destroy?: (asyncId: number) => void, + }; + declare class AsyncHook { + enable(): this; + disable(): this; + } + declare function createHook(callbacks: HookCallbacks): AsyncHook; } +// Edge +declare class AsyncLocalStorage { + disable(): void; + getStore(): T | void; + run(store: T, callback: (...args: any[]) => void, ...args: any[]): void; + enterWith(store: T): void; +} + +declare var async_hooks: { + createHook(callbacks: any): any, + executionAsyncId(): number, +}; + declare module 'node:worker_threads' { declare class MessageChannel { port1: MessagePort; diff --git a/scripts/rollup/validate/eslintrc.cjs.js b/scripts/rollup/validate/eslintrc.cjs.js index 7bb549cc6087e..fa4b9cb979cf5 100644 --- a/scripts/rollup/validate/eslintrc.cjs.js +++ b/scripts/rollup/validate/eslintrc.cjs.js @@ -54,6 +54,7 @@ module.exports = { // Temp AsyncLocalStorage: 'readonly', + async_hooks: 'readonly', // Flight Webpack __webpack_chunk_load__: 'readonly', diff --git a/scripts/rollup/validate/eslintrc.cjs2015.js b/scripts/rollup/validate/eslintrc.cjs2015.js index 4f00ddc6f1e04..64d83ff90c241 100644 --- a/scripts/rollup/validate/eslintrc.cjs2015.js +++ b/scripts/rollup/validate/eslintrc.cjs2015.js @@ -52,6 +52,7 @@ module.exports = { // Temp AsyncLocalStorage: 'readonly', + async_hooks: 'readonly', // Flight Webpack __webpack_chunk_load__: 'readonly', diff --git a/scripts/rollup/validate/eslintrc.esm.js b/scripts/rollup/validate/eslintrc.esm.js index 0de98a0c7ce20..80572e85cd8c3 100644 --- a/scripts/rollup/validate/eslintrc.esm.js +++ b/scripts/rollup/validate/eslintrc.esm.js @@ -54,6 +54,7 @@ module.exports = { // Temp AsyncLocalStorage: 'readonly', + async_hooks: 'readonly', // Flight Webpack __webpack_chunk_load__: 'readonly', diff --git a/scripts/rollup/validate/eslintrc.fb.js b/scripts/rollup/validate/eslintrc.fb.js index ef6ed5d43674c..a3f58fc25d928 100644 --- a/scripts/rollup/validate/eslintrc.fb.js +++ b/scripts/rollup/validate/eslintrc.fb.js @@ -55,6 +55,7 @@ module.exports = { // Temp AsyncLocalStorage: 'readonly', + async_hooks: 'readonly', // jest jest: 'readonly', diff --git a/scripts/rollup/validate/eslintrc.rn.js b/scripts/rollup/validate/eslintrc.rn.js index efc2a62a098c6..ff956ec5e746c 100644 --- a/scripts/rollup/validate/eslintrc.rn.js +++ b/scripts/rollup/validate/eslintrc.rn.js @@ -53,6 +53,7 @@ module.exports = { // Temp AsyncLocalStorage: 'readonly', + async_hooks: 'readonly', // jest jest: 'readonly', diff --git a/scripts/rollup/validate/eslintrc.umd.js b/scripts/rollup/validate/eslintrc.umd.js index a8e6de45a22d7..800fd645ae614 100644 --- a/scripts/rollup/validate/eslintrc.umd.js +++ b/scripts/rollup/validate/eslintrc.umd.js @@ -59,6 +59,7 @@ module.exports = { // Temp AsyncLocalStorage: 'readonly', + async_hooks: 'readonly', // Flight Webpack __webpack_chunk_load__: 'readonly', diff --git a/scripts/shared/inlinedHostConfigs.js b/scripts/shared/inlinedHostConfigs.js index adbc4bdabe93e..00353682abf23 100644 --- a/scripts/shared/inlinedHostConfigs.js +++ b/scripts/shared/inlinedHostConfigs.js @@ -45,6 +45,7 @@ module.exports = [ 'react-devtools-shared', 'react-interactions', 'shared/ReactDOMSharedInternals', + 'react-server/src/ReactFlightServerConfigDebugNode.js', ], isFlowTyped: true, isServerSupported: true, @@ -81,6 +82,7 @@ module.exports = [ 'react-devtools-shared', 'react-interactions', 'shared/ReactDOMSharedInternals', + 'react-server/src/ReactFlightServerConfigDebugNode.js', ], isFlowTyped: true, isServerSupported: true, @@ -117,6 +119,7 @@ module.exports = [ 'react-devtools-shared', 'react-interactions', 'shared/ReactDOMSharedInternals', + 'react-server/src/ReactFlightServerConfigDebugNode.js', ], isFlowTyped: true, isServerSupported: true, @@ -154,6 +157,7 @@ module.exports = [ 'react-devtools-shared', 'react-interactions', 'shared/ReactDOMSharedInternals', + 'react-server/src/ReactFlightServerConfigDebugNode.js', ], isFlowTyped: true, isServerSupported: true, @@ -297,6 +301,7 @@ module.exports = [ 'react-devtools-shell', 'react-devtools-shared', 'shared/ReactDOMSharedInternals', + 'react-server/src/ReactFlightServerConfigDebugNode.js', ], isFlowTyped: true, isServerSupported: true, @@ -330,6 +335,7 @@ module.exports = [ 'react-devtools-shell', 'react-devtools-shared', 'shared/ReactDOMSharedInternals', + 'react-server/src/ReactFlightServerConfigDebugNode.js', ], isFlowTyped: true, isServerSupported: true, @@ -364,6 +370,7 @@ module.exports = [ 'react-devtools-shared', 'react-interactions', 'shared/ReactDOMSharedInternals', + 'react-server/src/ReactFlightServerConfigDebugNode.js', ], isFlowTyped: true, isServerSupported: true,