Skip to content
Draft
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
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,11 @@ setTimeout(() => {
setTimeout(() => {
anr.startWorker();

setTimeout(() => {
// Wait for the restarted worker to reconnect its debugger session before blocking the event
// loop. The main-thread inspector stays open across restarts, so there is no main-thread signal
// that the new worker is ready; without this, on slow CI `longWork` can run before the worker
// is sampling and the ANR is missed entirely.
anr.waitUntilWorkerReady().then(() => {
longWork();
});
}, 2000);
Expand Down
19 changes: 16 additions & 3 deletions packages/node/src/integrations/anr/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,12 +60,17 @@ async function getContexts(client: NodeClient): Promise<Contexts> {

const INTEGRATION_NAME = 'Anr' as const;

type AnrInternal = { startWorker: () => void; stopWorker: () => void };
type AnrInternal = {
startWorker: () => void;
stopWorker: () => void;
waitUntilWorkerReady: () => Promise<void>;
};

// eslint-disable-next-line typescript/no-deprecated
const _anrIntegration = ((options: Partial<AnrIntegrationOptions> = {}) => {
let worker: Promise<() => void> | undefined;
let client: NodeClient | undefined;
let workerReady: Promise<void> | undefined;

// Hookup the scope fetch function to the global object so that it can be called from the worker thread via the
// debugger when it pauses
Expand All @@ -79,19 +84,24 @@ const _anrIntegration = ((options: Partial<AnrIntegrationOptions> = {}) => {
return;
}

if (client) {
worker = _startWorker(client, options);
const initializedClient = client;
if (initializedClient) {
workerReady = new Promise<void>(resolve => {
worker = _startWorker(initializedClient, options, resolve);
});
}
},
stopWorker: () => {
if (worker) {
workerReady = undefined;
// eslint-disable-next-line @typescript-eslint/no-floating-promises
worker.then(stop => {
stop();
worker = undefined;
});
}
},
waitUntilWorkerReady: () => workerReady ?? Promise.resolve(),
async setup(initClient: NodeClient) {
client = initClient;

Expand Down Expand Up @@ -157,6 +167,7 @@ async function _startWorker(
client: NodeClient,
// eslint-disable-next-line typescript/no-deprecated
integrationOptions: Partial<AnrIntegrationOptions>,
onReady?: () => void,
): Promise<() => void> {
const dsn = client.getDsn();

Expand Down Expand Up @@ -234,6 +245,8 @@ async function _startWorker(
if (msg === 'session-ended') {
log('ANR event sent from ANR worker. Clearing session in this thread.');
getIsolationScope().setSession(undefined);
} else if (msg === 'worker-ready') {
onReady?.();
}
});

Expand Down
6 changes: 6 additions & 0 deletions packages/node/src/integrations/anr/worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -328,3 +328,9 @@ parentPort?.on('message', (msg: { session: Session | undefined; debugImages?: Re

poll();
});

// Signal that the worker is fully set up: the inspector session (when capturing stack traces) is
// connected to the main thread and the watchdog is armed. Consumers that restart the worker can wait
// for this before blocking the event loop, since the main-thread inspector stays open across restarts
// and gives no signal that the new worker has reconnected.
parentPort?.postMessage('worker-ready');
Loading