From 7697d7b08e56f869b852c1a311b3290849441946 Mon Sep 17 00:00:00 2001 From: Tim Fish Date: Thu, 12 Dec 2024 14:46:19 +0100 Subject: [PATCH 1/2] feat: Allow capture of more than 1 ANR event --- packages/node/src/integrations/anr/common.ts | 6 ++++++ packages/node/src/integrations/anr/index.ts | 1 + packages/node/src/integrations/anr/worker.ts | 18 ++++++++++-------- 3 files changed, 17 insertions(+), 8 deletions(-) diff --git a/packages/node/src/integrations/anr/common.ts b/packages/node/src/integrations/anr/common.ts index e2666e3ecd3e..fc1b23e35b1d 100644 --- a/packages/node/src/integrations/anr/common.ts +++ b/packages/node/src/integrations/anr/common.ts @@ -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. */ diff --git a/packages/node/src/integrations/anr/index.ts b/packages/node/src/integrations/anr/index.ts index b93fcfd66612..82bb67d5efc8 100644 --- a/packages/node/src/integrations/anr/index.ts +++ b/packages/node/src/integrations/anr/index.ts @@ -154,6 +154,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, }; diff --git a/packages/node/src/integrations/anr/worker.ts b/packages/node/src/integrations/anr/worker.ts index 354cea514618..a1ad39294c3e 100644 --- a/packages/node/src/integrations/anr/worker.ts +++ b/packages/node/src/integrations/anr/worker.ts @@ -23,7 +23,7 @@ type VoidFunction = () => void; const options: WorkerStartData = workerData; let session: Session | undefined; -let hasSentAnrEvent = false; +let sentAnrEvents = 0; let mainDebugImages: Record = {}; function log(msg: string): void { @@ -134,11 +134,11 @@ function applyScopeToEvent(event: Event, scope: ScopeData): void { } async function sendAnrEvent(frames?: StackFrame[], scope?: ScopeData): Promise { - if (hasSentAnrEvent) { + if (sentAnrEvents >= options.maxAnrEvents) { return; } - hasSentAnrEvent = true; + sentAnrEvents += 1; await sendAbnormalSession(); @@ -179,11 +179,13 @@ async function sendAnrEvent(frames?: StackFrame[], scope?: ScopeData): Promise { - 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; From f30481111128ad51b22e4157e0e501069041f41a Mon Sep 17 00:00:00 2001 From: Tim Fish Date: Thu, 12 Dec 2024 18:24:53 +0000 Subject: [PATCH 2/2] Add tests --- .../suites/anr/basic-multiple.mjs | 36 +++++++++++++++++++ .../suites/anr/basic.mjs | 5 +++ .../node-integration-tests/suites/anr/test.ts | 10 +++++- 3 files changed, 50 insertions(+), 1 deletion(-) create mode 100644 dev-packages/node-integration-tests/suites/anr/basic-multiple.mjs diff --git a/dev-packages/node-integration-tests/suites/anr/basic-multiple.mjs b/dev-packages/node-integration-tests/suites/anr/basic-multiple.mjs new file mode 100644 index 000000000000..f58eb87f8237 --- /dev/null +++ b/dev-packages/node-integration-tests/suites/anr/basic-multiple.mjs @@ -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); diff --git a/dev-packages/node-integration-tests/suites/anr/basic.mjs b/dev-packages/node-integration-tests/suites/anr/basic.mjs index 18777e5ecdbd..454a35605925 100644 --- a/dev-packages/node-integration-tests/suites/anr/basic.mjs +++ b/dev-packages/node-integration-tests/suites/anr/basic.mjs @@ -30,3 +30,8 @@ function longWork() { setTimeout(() => { longWork(); }, 1000); + +// Ensure we only send one event even with multiple blocking events +setTimeout(() => { + longWork(); +}, 4000); diff --git a/dev-packages/node-integration-tests/suites/anr/test.ts b/dev-packages/node-integration-tests/suites/anr/test.ts index fd15df4bd0b8..ecda151c1e21 100644 --- a/dev-packages/node-integration-tests/suites/anr/test.ts +++ b/dev-packages/node-integration-tests/suites/anr/test.ts @@ -72,7 +72,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'), }, ], }, @@ -94,6 +94,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); });