Skip to content

Commit 8b8d265

Browse files
authored
[Flight] Wire up async_hooks in Node.js DEV for inspecting Promises (#27840)
This wires up the use of `async_hooks` in the Node build (as well as the Edge build when a global is available) in DEV mode only. This will be used to track debug info about what suspended during an RSC pass. Enabled behind a flag for now.
1 parent 63310df commit 8b8d265

31 files changed

+158
-7
lines changed

.eslintrc.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -532,6 +532,7 @@ module.exports = {
532532
trustedTypes: 'readonly',
533533
IS_REACT_ACT_ENVIRONMENT: 'readonly',
534534
AsyncLocalStorage: 'readonly',
535+
async_hooks: 'readonly',
535536
globalThis: 'readonly',
536537
},
537538
};

packages/react-server/src/ReactFlightServer.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ import {
7070
requestStorage,
7171
prepareHostDispatcher,
7272
createHints,
73+
initAsyncDebugInfo,
7374
} from './ReactFlightServerConfig';
7475

7576
import {
@@ -117,6 +118,8 @@ import binaryToComparableString from 'shared/binaryToComparableString';
117118

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

121+
initAsyncDebugInfo();
122+
120123
const ObjectPrototype = Object.prototype;
121124

122125
type JSONValue =
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/**
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
* @flow
8+
*/
9+
10+
import {createAsyncHook, executionAsyncId} from './ReactFlightServerConfig';
11+
import {enableAsyncDebugInfo} from 'shared/ReactFeatureFlags';
12+
13+
// Initialize the tracing of async operations.
14+
// We do this globally since the async work can potentially eagerly
15+
// start before the first request and once requests start they can interleave.
16+
// In theory we could enable and disable using a ref count of active requests
17+
// but given that typically this is just a live server, it doesn't really matter.
18+
export function initAsyncDebugInfo(): void {
19+
if (__DEV__ && enableAsyncDebugInfo) {
20+
createAsyncHook({
21+
init(asyncId: number, type: string, triggerAsyncId: number): void {
22+
// TODO
23+
},
24+
promiseResolve(asyncId: number): void {
25+
// TODO
26+
executionAsyncId();
27+
},
28+
destroy(asyncId: number): void {
29+
// TODO
30+
},
31+
}).enable();
32+
}
33+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
/**
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
* @flow
8+
*/
9+
10+
// Exported for runtimes that don't support Promise instrumentation for async debugging.
11+
export function initAsyncDebugInfo(): void {}

packages/react-server/src/forks/ReactFlightServerConfig.custom.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ import type {Request} from 'react-server/src/ReactFlightServer';
1111

1212
export * from '../ReactFlightServerConfigBundlerCustom';
1313

14+
export * from '../ReactFlightServerConfigDebugNoop';
15+
1416
export type Hints = any;
1517
export type HintCode = any;
1618
// eslint-disable-next-line no-unused-vars

packages/react-server/src/forks/ReactFlightServerConfig.dom-browser-esm.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,5 @@ export * from 'react-dom-bindings/src/server/ReactFlightServerConfigDOM';
1616
export const supportsRequestStorage = true;
1717
export const requestStorage: AsyncLocalStorage<Request> =
1818
new AsyncLocalStorage();
19+
20+
export * from '../ReactFlightServerConfigDebugNoop';

packages/react-server/src/forks/ReactFlightServerConfig.dom-browser-turbopack.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,5 @@ export * from 'react-dom-bindings/src/server/ReactFlightServerConfigDOM';
1414

1515
export const supportsRequestStorage = false;
1616
export const requestStorage: AsyncLocalStorage<Request> = (null: any);
17+
18+
export * from '../ReactFlightServerConfigDebugNoop';

packages/react-server/src/forks/ReactFlightServerConfig.dom-browser.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,5 @@ export * from 'react-dom-bindings/src/server/ReactFlightServerConfigDOM';
1414

1515
export const supportsRequestStorage = false;
1616
export const requestStorage: AsyncLocalStorage<Request> = (null: any);
17+
18+
export * from '../ReactFlightServerConfigDebugNoop';

packages/react-server/src/forks/ReactFlightServerConfig.dom-bun.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,5 @@ export * from 'react-dom-bindings/src/server/ReactFlightServerConfigDOM';
1414

1515
export const supportsRequestStorage = false;
1616
export const requestStorage: AsyncLocalStorage<Request> = (null: any);
17+
18+
export * from '../ReactFlightServerConfigDebugNoop';

packages/react-server/src/forks/ReactFlightServerConfig.dom-edge-turbopack.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,18 @@ export const supportsRequestStorage = typeof AsyncLocalStorage === 'function';
1616
export const requestStorage: AsyncLocalStorage<Request> = supportsRequestStorage
1717
? new AsyncLocalStorage()
1818
: (null: any);
19+
20+
// We use the Node version but get access to async_hooks from a global.
21+
import type {HookCallbacks, AsyncHook} from 'async_hooks';
22+
export const createAsyncHook: HookCallbacks => AsyncHook =
23+
typeof async_hooks === 'object'
24+
? async_hooks.createHook
25+
: function () {
26+
return ({
27+
enable() {},
28+
disable() {},
29+
}: any);
30+
};
31+
export const executionAsyncId: () => number =
32+
typeof async_hooks === 'object' ? async_hooks.executionAsyncId : (null: any);
33+
export * from '../ReactFlightServerConfigDebugNode';

0 commit comments

Comments
 (0)