Skip to content

Commit

Permalink
feat: Allow capture of more than 1 ANR event [v8] (#14713)
Browse files Browse the repository at this point in the history
  • Loading branch information
timfish authored Dec 16, 2024
1 parent 85952db commit c698ad9
Show file tree
Hide file tree
Showing 6 changed files with 67 additions and 9 deletions.
36 changes: 36 additions & 0 deletions dev-packages/node-integration-tests/suites/anr/basic-multiple.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import * as assert from 'assert';
import * as crypto from 'crypto';

import * as Sentry from '@sentry/node';

global._sentryDebugIds = { [new Error().stack]: 'aaaaaaaa-aaaa-4aaa-aaaa-aaaaaaaaaa' };

setTimeout(() => {
process.exit();
}, 10000);

Sentry.init({
dsn: process.env.SENTRY_DSN,
release: '1.0',
autoSessionTracking: false,
integrations: [Sentry.anrIntegration({ captureStackTrace: true, anrThreshold: 100, maxAnrEvents: 2 })],
});

Sentry.setUser({ email: 'person@home.com' });
Sentry.addBreadcrumb({ message: 'important message!' });

function longWork() {
for (let i = 0; i < 20; i++) {
const salt = crypto.randomBytes(128).toString('base64');
const hash = crypto.pbkdf2Sync('myPassword', salt, 10000, 512, 'sha512');
assert.ok(hash);
}
}

setTimeout(() => {
longWork();
}, 1000);

setTimeout(() => {
longWork();
}, 4000);
5 changes: 5 additions & 0 deletions dev-packages/node-integration-tests/suites/anr/basic.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,8 @@ function longWork() {
setTimeout(() => {
longWork();
}, 1000);

// Ensure we only send one event even with multiple blocking events
setTimeout(() => {
longWork();
}, 4000);
10 changes: 9 additions & 1 deletion dev-packages/node-integration-tests/suites/anr/test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ const ANR_EVENT_WITH_DEBUG_META: Event = {
{
type: 'sourcemap',
debug_id: 'aaaaaaaa-aaaa-4aaa-aaaa-aaaaaaaaaa',
code_file: expect.stringContaining('basic.'),
code_file: expect.stringContaining('basic'),
},
],
},
Expand All @@ -123,6 +123,14 @@ conditionalTest({ min: 16 })('should report ANR when event loop blocked', () =>
.start(done);
});

test('multiple events via maxAnrEvents', done => {
createRunner(__dirname, 'basic-multiple.mjs')
.withMockSentryServer()
.expect({ event: ANR_EVENT_WITH_DEBUG_META })
.expect({ event: ANR_EVENT_WITH_DEBUG_META })
.start(done);
});

test('blocked indefinitely', done => {
createRunner(__dirname, 'indefinite.mjs').withMockSentryServer().expect({ event: ANR_EVENT }).start(done);
});
Expand Down
6 changes: 6 additions & 0 deletions packages/node/src/integrations/anr/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@ export interface AnrIntegrationOptions {
* This uses the node debugger which enables the inspector API and opens the required ports.
*/
captureStackTrace: boolean;
/**
* Maximum number of ANR events to send.
*
* Defaults to 1.
*/
maxAnrEvents: number;
/**
* Tags to include with ANR events.
*/
Expand Down
1 change: 1 addition & 0 deletions packages/node/src/integrations/anr/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ async function _startWorker(
pollInterval: integrationOptions.pollInterval || DEFAULT_INTERVAL,
anrThreshold: integrationOptions.anrThreshold || DEFAULT_HANG_THRESHOLD,
captureStackTrace: !!integrationOptions.captureStackTrace,
maxAnrEvents: integrationOptions.maxAnrEvents || 1,
staticTags: integrationOptions.staticTags || {},
contexts,
};
Expand Down
18 changes: 10 additions & 8 deletions packages/node/src/integrations/anr/worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ type VoidFunction = () => void;

const options: WorkerStartData = workerData;
let session: Session | undefined;
let hasSentAnrEvent = false;
let sentAnrEvents = 0;
let mainDebugImages: Record<string, string> = {};

function log(msg: string): void {
Expand Down Expand Up @@ -134,11 +134,11 @@ function applyScopeToEvent(event: Event, scope: ScopeData): void {
}

async function sendAnrEvent(frames?: StackFrame[], scope?: ScopeData): Promise<void> {
if (hasSentAnrEvent) {
if (sentAnrEvents >= options.maxAnrEvents) {
return;
}

hasSentAnrEvent = true;
sentAnrEvents += 1;

await sendAbnormalSession();

Expand Down Expand Up @@ -179,11 +179,13 @@ async function sendAnrEvent(frames?: StackFrame[], scope?: ScopeData): Promise<v
await transport.send(envelope);
await transport.flush(2000);

// Delay for 5 seconds so that stdio can flush if the main event loop ever restarts.
// This is mainly for the benefit of logging or debugging.
setTimeout(() => {
process.exit(0);
}, 5_000);
if (sentAnrEvents >= options.maxAnrEvents) {
// Delay for 5 seconds so that stdio can flush if the main event loop ever restarts.
// This is mainly for the benefit of logging or debugging.
setTimeout(() => {
process.exit(0);
}, 5_000);
}
}

let debuggerPause: VoidFunction | undefined;
Expand Down

0 comments on commit c698ad9

Please sign in to comment.