Skip to content

Commit 8443d33

Browse files
committed
PR review
1 parent a487e50 commit 8443d33

File tree

18 files changed

+82
-56
lines changed

18 files changed

+82
-56
lines changed

dev-packages/node-integration-tests/suites/thread-blocked-native/app-path.mjs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import * as Sentry from '@sentry/node';
2-
import { threadBlockedIntegration } from '@sentry/node-native';
2+
import { eventLoopBlockIntegration } from '@sentry/node-native';
33
import * as path from 'path';
44
import * as url from 'url';
55
import { longWork } from './long-work.js';
@@ -15,7 +15,7 @@ setTimeout(() => {
1515
Sentry.init({
1616
dsn: process.env.SENTRY_DSN,
1717
release: '1.0',
18-
integrations: [threadBlockedIntegration({ blockedThreshold: 100, appRootPath: __dirname })],
18+
integrations: [eventLoopBlockIntegration({ appRootPath: __dirname })],
1919
});
2020

2121
setTimeout(() => {

dev-packages/node-integration-tests/suites/thread-blocked-native/basic-multiple.mjs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import * as Sentry from '@sentry/node';
2-
import { threadBlockedIntegration } from '@sentry/node-native';
2+
import { eventLoopBlockIntegration } from '@sentry/node-native';
33
import { longWork } from './long-work.js';
44

55
global._sentryDebugIds = { [new Error().stack]: 'aaaaaaaa-aaaa-4aaa-aaaa-aaaaaaaaaa' };
@@ -11,7 +11,7 @@ setTimeout(() => {
1111
Sentry.init({
1212
dsn: process.env.SENTRY_DSN,
1313
release: '1.0',
14-
integrations: [threadBlockedIntegration({ blockedThreshold: 100, maxBlockedEvents: 2 })],
14+
integrations: [eventLoopBlockIntegration({ maxBlockedEvents: 2 })],
1515
});
1616

1717
setTimeout(() => {

dev-packages/node-integration-tests/suites/thread-blocked-native/basic.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
const Sentry = require('@sentry/node');
2-
const { threadBlockedIntegration } = require('@sentry/node-native');
2+
const { eventLoopBlockIntegration } = require('@sentry/node-native');
33
const { longWork } = require('./long-work.js');
44

55
global._sentryDebugIds = { [new Error().stack]: 'aaaaaaaa-aaaa-4aaa-aaaa-aaaaaaaaaa' };
@@ -11,7 +11,7 @@ setTimeout(() => {
1111
Sentry.init({
1212
dsn: process.env.SENTRY_DSN,
1313
release: '1.0',
14-
integrations: [threadBlockedIntegration({ blockedThreshold: 100 })],
14+
integrations: [eventLoopBlockIntegration()],
1515
});
1616

1717
setTimeout(() => {

dev-packages/node-integration-tests/suites/thread-blocked-native/basic.mjs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import * as Sentry from '@sentry/node';
2-
import { threadBlockedIntegration } from '@sentry/node-native';
2+
import { eventLoopBlockIntegration } from '@sentry/node-native';
33
import { longWork } from './long-work.js';
44

55
global._sentryDebugIds = { [new Error().stack]: 'aaaaaaaa-aaaa-4aaa-aaaa-aaaaaaaaaa' };
@@ -11,7 +11,7 @@ setTimeout(() => {
1111
Sentry.init({
1212
dsn: process.env.SENTRY_DSN,
1313
release: '1.0',
14-
integrations: [threadBlockedIntegration({ blockedThreshold: 100 })],
14+
integrations: [eventLoopBlockIntegration()],
1515
});
1616

1717
setTimeout(() => {

dev-packages/node-integration-tests/suites/thread-blocked-native/indefinite.mjs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import * as Sentry from '@sentry/node';
2-
import { threadBlockedIntegration } from '@sentry/node-native';
2+
import { eventLoopBlockIntegration } from '@sentry/node-native';
33
import * as assert from 'assert';
44
import * as crypto from 'crypto';
55

@@ -10,7 +10,7 @@ setTimeout(() => {
1010
Sentry.init({
1111
dsn: process.env.SENTRY_DSN,
1212
release: '1.0',
13-
integrations: [threadBlockedIntegration({ blockedThreshold: 100 })],
13+
integrations: [eventLoopBlockIntegration()],
1414
});
1515

1616
function longWork() {
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import * as Sentry from '@sentry/node';
2-
import { threadBlockedIntegration } from '@sentry/node-native';
2+
import { eventLoopBlockIntegration } from '@sentry/node-native';
33

44
Sentry.init({
55
debug: true,
66
dsn: process.env.SENTRY_DSN,
77
release: '1.0',
8-
integrations: [threadBlockedIntegration({ blockedThreshold: 100 })],
8+
integrations: [eventLoopBlockIntegration()],
99
});

dev-packages/node-integration-tests/suites/thread-blocked-native/long-work.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ const crypto = require('crypto');
22
const assert = require('assert');
33

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

dev-packages/node-integration-tests/suites/thread-blocked-native/should-exit-forced.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
const Sentry = require('@sentry/node');
2-
const { threadBlockedIntegration } = require('@sentry/node-native');
2+
const { eventLoopBlockIntegration } = require('@sentry/node-native');
33

44
function configureSentry() {
55
Sentry.init({
66
dsn: 'https://public@dsn.ingest.sentry.io/1337',
77
release: '1.0',
88
debug: true,
9-
integrations: [threadBlockedIntegration()],
9+
integrations: [eventLoopBlockIntegration()],
1010
});
1111
}
1212

dev-packages/node-integration-tests/suites/thread-blocked-native/should-exit.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
const Sentry = require('@sentry/node');
2-
const { threadBlockedIntegration } = require('@sentry/node-native');
2+
const { eventLoopBlockIntegration } = require('@sentry/node-native');
33

44
function configureSentry() {
55
Sentry.init({
66
dsn: 'https://public@dsn.ingest.sentry.io/1337',
77
release: '1.0',
88
debug: true,
9-
integrations: [threadBlockedIntegration()],
9+
integrations: [eventLoopBlockIntegration()],
1010
});
1111
}
1212

dev-packages/node-integration-tests/suites/thread-blocked-native/stop-and-start.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
const Sentry = require('@sentry/node');
2-
const { threadBlockedIntegration } = require('@sentry/node-native');
2+
const { eventLoopBlockIntegration } = require('@sentry/node-native');
33
const { longWork } = require('./long-work.js');
44
const crypto = require('crypto');
55
const assert = require('assert');
@@ -8,7 +8,7 @@ setTimeout(() => {
88
process.exit();
99
}, 10000);
1010

11-
const threadBlocked = threadBlockedIntegration({ blockedThreshold: 100 });
11+
const threadBlocked = eventLoopBlockIntegration();
1212

1313
Sentry.init({
1414
dsn: process.env.SENTRY_DSN,

dev-packages/node-integration-tests/suites/thread-blocked-native/test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ function EXCEPTION(thread_id = '0') {
88
values: [
99
{
1010
type: 'EventLoopBlocked',
11-
value: 'Event Loop Blocked for at least 100 ms',
11+
value: 'Event Loop Blocked for at least 1000 ms',
1212
mechanism: { type: 'ANR' },
1313
thread_id,
1414
stacktrace: {

packages/node-native/README.md

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,24 +20,25 @@ yarn add @sentry/node @sentry/node-native
2020
npm install --save @sentry/node @sentry/node-native
2121
```
2222

23-
## `threadBlockedIntegration`
23+
## `eventLoopBlockIntegration`
2424

25-
The `threadBlockedIntegration` can be used to monitor for blocked event loops in
25+
The `eventLoopBlockIntegration` can be used to monitor for blocked event loops in
2626
all threads of a Node.js application.
2727

28-
If you instrument your application via the Node.js `--import` flag, this
29-
instrumentation will be automatically applied to all worker threads.
28+
If you instrument your application via the Node.js `--import` flag, Sentry will
29+
be started and this instrumentation will be automatically applied to all worker
30+
threads.
3031

3132
`instrument.mjs`
3233

3334
```javascript
3435
import * as Sentry from '@sentry/node';
35-
import { threadBlockedIntegration } from '@sentry/node-native';
36+
import { eventLoopBlockIntegration } from '@sentry/node-native';
3637

3738
Sentry.init({
3839
dsn: '__YOUR_DSN__',
3940
// Capture stack traces when the event loop is blocked for more than 500ms
40-
integrations: [threadBlockedIntegration({ blockedThreshold: 500 })],
41+
integrations: [eventLoopBlockIntegration({ threshold: 500 })],
4142
});
4243
```
4344

packages/node-native/package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,12 @@
2121
"default": "./build/cjs/index.js"
2222
}
2323
},
24-
"./thread-blocked-watchdog": {
24+
"./event-loop-block-watchdog": {
2525
"import": {
26-
"default": "./build/esm/thread-blocked-watchdog.js"
26+
"default": "./build/esm/event-loop-block-watchdog.js"
2727
},
2828
"require": {
29-
"default": "./build/cjs/thread-blocked-watchdog.js"
29+
"default": "./build/cjs/event-loop-block-watchdog.js"
3030
}
3131
}
3232
},

packages/node-native/rollup.npm.config.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { makeBaseNPMConfig, makeNPMConfigVariants } from '@sentry-internal/rollu
22

33
export default makeNPMConfigVariants(
44
makeBaseNPMConfig({
5-
entrypoints: ['src/index.ts', 'src/thread-blocked-watchdog.ts'],
5+
entrypoints: ['src/index.ts', 'src/event-loop-block-watchdog.ts'],
66
packageSpecificConfig: {
77
output: {
88
dir: 'build',

packages/node-native/src/common.ts

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,14 @@
11
import type { Contexts, DsnComponents, Primitive, SdkMetadata, Session } from '@sentry/core';
22

3+
export const POLL_RATIO = 2;
4+
35
export interface ThreadBlockedIntegrationOptions {
46
/**
5-
* Interval to send heartbeat messages to the watchdog.
6-
*
7-
* Defaults to 50ms.
8-
*/
9-
pollInterval: number;
10-
/**
11-
* Threshold in milliseconds to trigger a blocked event.
7+
* Threshold in milliseconds to trigger an event.
128
*
13-
* Defaults to 5000ms.
9+
* Defaults to 1000ms.
1410
*/
15-
blockedThreshold: number;
11+
threshold: number;
1612
/**
1713
* Maximum number of blocked events to send.
1814
*

packages/node-native/src/thread-blocked-integration.ts renamed to packages/node-native/src/event-loop-block-integration.ts

Lines changed: 40 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,11 @@ import { defineIntegration, getClient, getFilenameToDebugIdMap, getIsolationScop
55
import type { NodeClient } from '@sentry/node';
66
import { registerThread, threadPoll } from '@sentry-internal/node-native-stacktrace';
77
import type { ThreadBlockedIntegrationOptions, WorkerStartData } from './common';
8+
import { POLL_RATIO } from './common';
89

910
const { isPromise } = types;
1011

11-
const DEFAULT_INTERVAL = 50;
12-
const DEFAULT_HANG_THRESHOLD = 5000;
12+
const DEFAULT_THRESHOLD = 1_000;
1313

1414
function log(message: string, ...args: unknown[]): void {
1515
logger.log(`[Thread Blocked] ${message}`, ...args);
@@ -34,7 +34,7 @@ const INTEGRATION_NAME = 'ThreadBlocked';
3434

3535
type ThreadBlockedInternal = { startWorker: () => void; stopWorker: () => void };
3636

37-
const _threadBlockedIntegration = ((options: Partial<ThreadBlockedIntegrationOptions> = {}) => {
37+
const _eventLoopBlockIntegration = ((options: Partial<ThreadBlockedIntegrationOptions> = {}) => {
3838
let worker: Promise<() => void> | undefined;
3939
let client: NodeClient | undefined;
4040

@@ -58,21 +58,48 @@ const _threadBlockedIntegration = ((options: Partial<ThreadBlockedIntegrationOpt
5858
});
5959
}
6060
},
61-
async setup(initClient: NodeClient) {
61+
async afterAllSetup(initClient: NodeClient) {
6262
client = initClient;
6363

6464
registerThread();
6565

66-
// setImmediate is used to ensure that all other integrations have had their setup called first.
67-
// This allows us to call into all integrations to fetch the full context
68-
setImmediate(() => this.startWorker());
66+
this.startWorker();
6967
},
7068
} as Integration & ThreadBlockedInternal;
7169
}) satisfies IntegrationFn;
7270

7371
type ThreadBlockedReturn = (options?: Partial<ThreadBlockedIntegrationOptions>) => Integration & ThreadBlockedInternal;
7472

75-
export const threadBlockedIntegration = defineIntegration(_threadBlockedIntegration) as ThreadBlockedReturn;
73+
/**
74+
* Monitors the Node.js event loop for blocking behavior and reports blocked events to Sentry.
75+
*
76+
* Uses a background worker thread to detect when the main thread is blocked for longer than
77+
* the configured threshold (default: 5 seconds).
78+
*
79+
* When instrumenting via the `--import` flag, this integration will
80+
* automatically monitor all worker threads as well.
81+
*
82+
* ```js
83+
* // instrument.mjs
84+
* import * as Sentry from '@sentry/node';
85+
* import { eventLoopBlockIntegration } from '@sentry/node-native';
86+
*
87+
* Sentry.init({
88+
* dsn: '__YOUR_DSN__',
89+
* integrations: [
90+
* eventLoopBlockIntegration({
91+
* threshold: 500, // Report blocks longer than 500ms
92+
* }),
93+
* ],
94+
* });
95+
* ```
96+
*
97+
* Start your application with:
98+
* ```bash
99+
* node --import instrument.mjs app.mjs
100+
* ```
101+
*/
102+
export const eventLoopBlockIntegration = defineIntegration(_eventLoopBlockIntegration) as ThreadBlockedReturn;
76103

77104
/**
78105
* Starts the worker thread
@@ -113,14 +140,15 @@ async function _startWorker(
113140
dist: initOptions.dist,
114141
sdkMetadata,
115142
appRootPath: integrationOptions.appRootPath,
116-
pollInterval: integrationOptions.pollInterval || DEFAULT_INTERVAL,
117-
blockedThreshold: integrationOptions.blockedThreshold || DEFAULT_HANG_THRESHOLD,
143+
threshold: integrationOptions.threshold || DEFAULT_THRESHOLD,
118144
maxBlockedEvents: integrationOptions.maxBlockedEvents || 1,
119145
staticTags: integrationOptions.staticTags || {},
120146
contexts,
121147
};
122148

123-
const worker = new Worker(new URL('./thread-blocked-watchdog.js', import.meta.url), {
149+
const pollInterval = options.threshold / POLL_RATIO
150+
151+
const worker = new Worker(new URL('./event-loop-block-watchdog.js', import.meta.url), {
124152
workerData: options,
125153
// We don't want any Node args like --import to be passed to the worker
126154
execArgv: [],
@@ -143,7 +171,7 @@ async function _startWorker(
143171
} catch (_) {
144172
//
145173
}
146-
}, options.pollInterval);
174+
}, pollInterval);
147175
// Timer should not block exit
148176
timer.unref();
149177

packages/node-native/src/thread-blocked-watchdog.ts renamed to packages/node-native/src/event-loop-block-watchdog.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,23 +14,24 @@ import {
1414
import { makeNodeTransport } from '@sentry/node';
1515
import { captureStackTrace, getThreadsLastSeen } from '@sentry-internal/node-native-stacktrace';
1616
import type { ThreadState, WorkerStartData } from './common';
17+
import { POLL_RATIO } from './common';
1718

1819
const {
19-
blockedThreshold,
20+
threshold,
2021
appRootPath,
2122
contexts,
2223
debug,
2324
dist,
2425
dsn,
2526
environment,
2627
maxBlockedEvents,
27-
pollInterval,
2828
release,
2929
sdkMetadata,
3030
staticTags: tags,
3131
tunnel,
3232
} = workerData as WorkerStartData;
3333

34+
const pollInterval = threshold / POLL_RATIO
3435
const triggeredThreads = new Set<string>();
3536
let sentAnrEvents = 0;
3637

@@ -161,7 +162,7 @@ function getExceptionAndThreads(
161162
values: [
162163
{
163164
type: 'EventLoopBlocked',
164-
value: `Event Loop Blocked for at least ${blockedThreshold} ms`,
165+
value: `Event Loop Blocked for at least ${threshold} ms`,
165166
stacktrace: { frames: prepareStackFrames(crashedThread?.frames) },
166167
// This ensures the UI doesn't say 'Crashed in' for the stack trace
167168
mechanism: { type: 'ANR' },
@@ -246,7 +247,7 @@ async function sendAnrEvent(crashedThreadId: string): Promise<void> {
246247

247248
setInterval(async () => {
248249
for (const [threadId, time] of Object.entries(getThreadsLastSeen())) {
249-
if (time > blockedThreshold) {
250+
if (time > threshold) {
250251
if (triggeredThreads.has(threadId)) {
251252
continue;
252253
}

packages/node-native/src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
export { threadBlockedIntegration, disableBlockedDetectionForCallback } from './thread-blocked-integration';
1+
export { eventLoopBlockIntegration, disableBlockedDetectionForCallback } from './event-loop-block-integration';

0 commit comments

Comments
 (0)