Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Flight] Wire up async_hooks in Node.js DEV for inspecting Promises #27840

Merged
merged 4 commits into from
Dec 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -532,6 +532,7 @@ module.exports = {
trustedTypes: 'readonly',
IS_REACT_ACT_ENVIRONMENT: 'readonly',
AsyncLocalStorage: 'readonly',
async_hooks: 'readonly',
globalThis: 'readonly',
},
};
3 changes: 3 additions & 0 deletions packages/react-server/src/ReactFlightServer.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ import {
requestStorage,
prepareHostDispatcher,
createHints,
initAsyncDebugInfo,
} from './ReactFlightServerConfig';

import {
Expand Down Expand Up @@ -117,6 +118,8 @@ import binaryToComparableString from 'shared/binaryToComparableString';

import {SuspenseException, getSuspendedThenable} from './ReactFlightThenable';

initAsyncDebugInfo();
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

The explicit init here is really only due to the module cycle between the implementation and the config since we need createAsyncHook to have been resolved already.


const ObjectPrototype = Object.prototype;

type JSONValue =
Expand Down
33 changes: 33 additions & 0 deletions packages/react-server/src/ReactFlightServerConfigDebugNode.js
Original file line number Diff line number Diff line change
@@ -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();
}
}
11 changes: 11 additions & 0 deletions packages/react-server/src/ReactFlightServerConfigDebugNoop.js
Original file line number Diff line number Diff line change
@@ -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 {}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,5 @@ export * from 'react-dom-bindings/src/server/ReactFlightServerConfigDOM';
export const supportsRequestStorage = true;
export const requestStorage: AsyncLocalStorage<Request> =
new AsyncLocalStorage();

export * from '../ReactFlightServerConfigDebugNoop';
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,5 @@ export * from 'react-dom-bindings/src/server/ReactFlightServerConfigDOM';

export const supportsRequestStorage = false;
export const requestStorage: AsyncLocalStorage<Request> = (null: any);

export * from '../ReactFlightServerConfigDebugNoop';
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,5 @@ export * from 'react-dom-bindings/src/server/ReactFlightServerConfigDOM';

export const supportsRequestStorage = false;
export const requestStorage: AsyncLocalStorage<Request> = (null: any);

export * from '../ReactFlightServerConfigDebugNoop';
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,5 @@ export * from 'react-dom-bindings/src/server/ReactFlightServerConfigDOM';

export const supportsRequestStorage = false;
export const requestStorage: AsyncLocalStorage<Request> = (null: any);

export * from '../ReactFlightServerConfigDebugNoop';
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,18 @@ export const supportsRequestStorage = typeof AsyncLocalStorage === 'function';
export const requestStorage: AsyncLocalStorage<Request> = 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';
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,18 @@ export const supportsRequestStorage = typeof AsyncLocalStorage === 'function';
export const requestStorage: AsyncLocalStorage<Request> = 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';
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,5 @@ export * from 'react-dom-bindings/src/server/ReactFlightServerConfigDOM';

export const supportsRequestStorage = false;
export const requestStorage: AsyncLocalStorage<Request> = (null: any);

export * from '../ReactFlightServerConfigDebugNoop';
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,5 @@ export * from 'react-dom-bindings/src/server/ReactFlightServerConfigDOM';

export const supportsRequestStorage = false;
export const requestStorage: AsyncLocalStorage<Request> = (null: any);

export * from '../ReactFlightServerConfigDebugNoop';
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,6 @@ export * from 'react-dom-bindings/src/server/ReactFlightServerConfigDOM';
export const supportsRequestStorage = true;
export const requestStorage: AsyncLocalStorage<Request> =
new AsyncLocalStorage();

export {createHook as createAsyncHook, executionAsyncId} from 'async_hooks';
export * from '../ReactFlightServerConfigDebugNode';
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,6 @@ export * from 'react-dom-bindings/src/server/ReactFlightServerConfigDOM';
export const supportsRequestStorage = true;
export const requestStorage: AsyncLocalStorage<Request> =
new AsyncLocalStorage();

export {createHook as createAsyncHook, executionAsyncId} from 'async_hooks';
export * from '../ReactFlightServerConfigDebugNode';
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,6 @@ export * from 'react-dom-bindings/src/server/ReactFlightServerConfigDOM';
export const supportsRequestStorage = true;
export const requestStorage: AsyncLocalStorage<Request> =
new AsyncLocalStorage();

export {createHook as createAsyncHook, executionAsyncId} from 'async_hooks';
export * from '../ReactFlightServerConfigDebugNode';
2 changes: 2 additions & 0 deletions packages/shared/ReactFeatureFlags.js
Original file line number Diff line number Diff line change
Expand Up @@ -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__;

Expand Down
1 change: 1 addition & 0 deletions packages/shared/forks/ReactFeatureFlags.native-fb.js
Original file line number Diff line number Diff line change
Expand Up @@ -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__;
Expand Down
1 change: 1 addition & 0 deletions packages/shared/forks/ReactFeatureFlags.native-oss.js
Original file line number Diff line number Diff line change
Expand Up @@ -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__;
Expand Down
1 change: 1 addition & 0 deletions packages/shared/forks/ReactFeatureFlags.test-renderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -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__;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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__;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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__;
Expand Down
2 changes: 2 additions & 0 deletions packages/shared/forks/ReactFeatureFlags.www.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
42 changes: 35 additions & 7 deletions scripts/flow/environment.js
Original file line number Diff line number Diff line change
Expand Up @@ -281,22 +281,50 @@ declare module 'pg/lib/utils' {
};
}

declare class AsyncLocalStorage<T> {
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<T> {
disable(): void;
getStore(): T | void;
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<T> {
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;
Expand Down
1 change: 1 addition & 0 deletions scripts/rollup/validate/eslintrc.cjs.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ module.exports = {

// Temp
AsyncLocalStorage: 'readonly',
async_hooks: 'readonly',

// Flight Webpack
__webpack_chunk_load__: 'readonly',
Expand Down
1 change: 1 addition & 0 deletions scripts/rollup/validate/eslintrc.cjs2015.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ module.exports = {

// Temp
AsyncLocalStorage: 'readonly',
async_hooks: 'readonly',

// Flight Webpack
__webpack_chunk_load__: 'readonly',
Expand Down
1 change: 1 addition & 0 deletions scripts/rollup/validate/eslintrc.esm.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ module.exports = {

// Temp
AsyncLocalStorage: 'readonly',
async_hooks: 'readonly',

// Flight Webpack
__webpack_chunk_load__: 'readonly',
Expand Down
1 change: 1 addition & 0 deletions scripts/rollup/validate/eslintrc.fb.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ module.exports = {

// Temp
AsyncLocalStorage: 'readonly',
async_hooks: 'readonly',

// jest
jest: 'readonly',
Expand Down
1 change: 1 addition & 0 deletions scripts/rollup/validate/eslintrc.rn.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ module.exports = {

// Temp
AsyncLocalStorage: 'readonly',
async_hooks: 'readonly',

// jest
jest: 'readonly',
Expand Down
1 change: 1 addition & 0 deletions scripts/rollup/validate/eslintrc.umd.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ module.exports = {

// Temp
AsyncLocalStorage: 'readonly',
async_hooks: 'readonly',

// Flight Webpack
__webpack_chunk_load__: 'readonly',
Expand Down
7 changes: 7 additions & 0 deletions scripts/shared/inlinedHostConfigs.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ module.exports = [
'react-devtools-shared',
'react-interactions',
'shared/ReactDOMSharedInternals',
'react-server/src/ReactFlightServerConfigDebugNode.js',
],
isFlowTyped: true,
isServerSupported: true,
Expand Down Expand Up @@ -81,6 +82,7 @@ module.exports = [
'react-devtools-shared',
'react-interactions',
'shared/ReactDOMSharedInternals',
'react-server/src/ReactFlightServerConfigDebugNode.js',
],
isFlowTyped: true,
isServerSupported: true,
Expand Down Expand Up @@ -117,6 +119,7 @@ module.exports = [
'react-devtools-shared',
'react-interactions',
'shared/ReactDOMSharedInternals',
'react-server/src/ReactFlightServerConfigDebugNode.js',
],
isFlowTyped: true,
isServerSupported: true,
Expand Down Expand Up @@ -154,6 +157,7 @@ module.exports = [
'react-devtools-shared',
'react-interactions',
'shared/ReactDOMSharedInternals',
'react-server/src/ReactFlightServerConfigDebugNode.js',
],
isFlowTyped: true,
isServerSupported: true,
Expand Down Expand Up @@ -297,6 +301,7 @@ module.exports = [
'react-devtools-shell',
'react-devtools-shared',
'shared/ReactDOMSharedInternals',
'react-server/src/ReactFlightServerConfigDebugNode.js',
],
isFlowTyped: true,
isServerSupported: true,
Expand Down Expand Up @@ -330,6 +335,7 @@ module.exports = [
'react-devtools-shell',
'react-devtools-shared',
'shared/ReactDOMSharedInternals',
'react-server/src/ReactFlightServerConfigDebugNode.js',
],
isFlowTyped: true,
isServerSupported: true,
Expand Down Expand Up @@ -364,6 +370,7 @@ module.exports = [
'react-devtools-shared',
'react-interactions',
'shared/ReactDOMSharedInternals',
'react-server/src/ReactFlightServerConfigDebugNode.js',
],
isFlowTyped: true,
isServerSupported: true,
Expand Down