@sentry/node-native 10.53.1 → 10.55.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/cjs/event-loop-block-integration.js +25 -140
- package/build/cjs/event-loop-block-integration.js.map +1 -1
- package/build/cjs/event-loop-block-watchdog.js +39 -101
- package/build/cjs/event-loop-block-watchdog.js.map +1 -1
- package/build/esm/event-loop-block-integration.js +25 -140
- package/build/esm/event-loop-block-integration.js.map +1 -1
- package/build/esm/event-loop-block-watchdog.js +39 -101
- package/build/esm/event-loop-block-watchdog.js.map +1 -1
- package/build/esm/package.json +1 -1
- package/package.json +3 -3
|
@@ -7,112 +7,70 @@ const nodeNativeStacktrace = require('@sentry-internal/node-native-stacktrace');
|
|
|
7
7
|
const common = require('./common.js');
|
|
8
8
|
|
|
9
9
|
var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
|
|
10
|
-
const INTEGRATION_NAME =
|
|
11
|
-
const DEFAULT_THRESHOLD_MS =
|
|
12
|
-
|
|
10
|
+
const INTEGRATION_NAME = "ThreadBlocked";
|
|
11
|
+
const DEFAULT_THRESHOLD_MS = 1e3;
|
|
13
12
|
function log(message, ...args) {
|
|
14
13
|
core.debug.log(`[Sentry Event Loop Blocked] ${message}`, ...args);
|
|
15
14
|
}
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Gets contexts by calling all event processors. This shouldn't be called until all integrations are setup
|
|
19
|
-
*/
|
|
20
15
|
async function getContexts(client) {
|
|
21
16
|
let event = { message: INTEGRATION_NAME };
|
|
22
17
|
const eventHint = {};
|
|
23
|
-
|
|
24
18
|
for (const processor of client.getEventProcessors()) {
|
|
25
19
|
if (event === null) break;
|
|
26
20
|
event = await processor(event, eventHint);
|
|
27
21
|
}
|
|
28
|
-
|
|
29
22
|
return event?.contexts || {};
|
|
30
23
|
}
|
|
31
|
-
|
|
32
24
|
function getLocalScopeData() {
|
|
33
25
|
const globalScope = core.getGlobalScope().getScopeData();
|
|
34
26
|
const currentScope = core.getCurrentScope().getScopeData();
|
|
35
27
|
core.mergeScopeData(globalScope, currentScope);
|
|
36
28
|
return globalScope;
|
|
37
29
|
}
|
|
38
|
-
|
|
39
30
|
function poll(enabled, clientOptions) {
|
|
40
31
|
try {
|
|
41
32
|
const currentSession = core.getIsolationScope().getSession();
|
|
42
|
-
|
|
43
|
-
// serialized without making it a SerializedSession
|
|
44
|
-
const session = currentSession ? { ...currentSession, toJSON: undefined } : undefined;
|
|
33
|
+
const session = currentSession ? { ...currentSession, toJSON: void 0 } : void 0;
|
|
45
34
|
const scope = getLocalScopeData();
|
|
46
|
-
// message the worker to tell it the main event loop is still running
|
|
47
35
|
nodeNativeStacktrace.threadPoll(enabled, { session, scope, debugImages: core.getFilenameToDebugIdMap(clientOptions.stackParser) });
|
|
48
36
|
} catch {
|
|
49
|
-
// we ignore all errors
|
|
50
37
|
}
|
|
51
38
|
}
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Starts polling
|
|
55
|
-
*/
|
|
56
|
-
function startPolling(
|
|
57
|
-
client,
|
|
58
|
-
integrationOptions,
|
|
59
|
-
) {
|
|
39
|
+
function startPolling(client, integrationOptions) {
|
|
60
40
|
if (client.asyncLocalStorageLookup) {
|
|
61
41
|
const { asyncLocalStorage, contextSymbol } = client.asyncLocalStorageLookup;
|
|
62
|
-
nodeNativeStacktrace.registerThread({ asyncLocalStorage, stateLookup: [
|
|
42
|
+
nodeNativeStacktrace.registerThread({ asyncLocalStorage, stateLookup: ["_currentContext", contextSymbol] });
|
|
63
43
|
} else {
|
|
64
44
|
nodeNativeStacktrace.registerThread();
|
|
65
45
|
}
|
|
66
|
-
|
|
67
46
|
let enabled = true;
|
|
68
|
-
|
|
69
47
|
const initOptions = client.getOptions();
|
|
70
48
|
const pollInterval = (integrationOptions.threshold || DEFAULT_THRESHOLD_MS) / common.POLL_RATIO;
|
|
71
|
-
|
|
72
|
-
// unref so timer does not block exit
|
|
73
49
|
setInterval(() => poll(enabled, initOptions), pollInterval).unref();
|
|
74
|
-
|
|
75
50
|
return {
|
|
76
51
|
start: () => {
|
|
77
52
|
enabled = true;
|
|
78
53
|
},
|
|
79
54
|
stop: () => {
|
|
80
55
|
enabled = false;
|
|
81
|
-
// poll immediately because the timer above might not get a chance to run
|
|
82
|
-
// before the event loop gets blocked
|
|
83
56
|
poll(enabled, initOptions);
|
|
84
|
-
}
|
|
57
|
+
}
|
|
85
58
|
};
|
|
86
59
|
}
|
|
87
|
-
|
|
88
|
-
/**
|
|
89
|
-
* Starts the worker thread that will monitor the other threads.
|
|
90
|
-
*
|
|
91
|
-
* This function is only called in the main thread.
|
|
92
|
-
*/
|
|
93
|
-
async function startWorker(
|
|
94
|
-
dsn,
|
|
95
|
-
client,
|
|
96
|
-
integrationOptions,
|
|
97
|
-
) {
|
|
60
|
+
async function startWorker(dsn, client, integrationOptions) {
|
|
98
61
|
const contexts = await getContexts(client);
|
|
99
|
-
|
|
100
|
-
// These will not be accurate if sent later from the worker thread
|
|
101
62
|
delete contexts.app?.app_memory;
|
|
102
63
|
delete contexts.device?.free_memory;
|
|
103
|
-
|
|
104
64
|
const initOptions = client.getOptions();
|
|
105
|
-
|
|
106
65
|
const sdkMetadata = client.getSdkMetadata() || {};
|
|
107
66
|
if (sdkMetadata.sdk) {
|
|
108
|
-
sdkMetadata.sdk.integrations = initOptions.integrations.map(i => i.name);
|
|
67
|
+
sdkMetadata.sdk.integrations = initOptions.integrations.map((i) => i.name);
|
|
109
68
|
}
|
|
110
|
-
|
|
111
69
|
const options = {
|
|
112
70
|
debug: core.debug.isEnabled(),
|
|
113
71
|
dsn,
|
|
114
72
|
tunnel: initOptions.tunnel,
|
|
115
|
-
environment: initOptions.environment ||
|
|
73
|
+
environment: initOptions.environment || "production",
|
|
116
74
|
release: initOptions.release,
|
|
117
75
|
dist: initOptions.dist,
|
|
118
76
|
sdkMetadata,
|
|
@@ -120,58 +78,44 @@ async function startWorker(
|
|
|
120
78
|
threshold: integrationOptions.threshold || DEFAULT_THRESHOLD_MS,
|
|
121
79
|
maxEventsPerHour: integrationOptions.maxEventsPerHour || 1,
|
|
122
80
|
staticTags: integrationOptions.staticTags || {},
|
|
123
|
-
contexts
|
|
81
|
+
contexts
|
|
124
82
|
};
|
|
125
|
-
|
|
126
|
-
const worker = new node_worker_threads.Worker(new URL('./event-loop-block-watchdog.js', (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('event-loop-block-integration.js', document.baseURI).href))), {
|
|
83
|
+
const worker = new node_worker_threads.Worker(new URL("./event-loop-block-watchdog.js", (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('event-loop-block-integration.js', document.baseURI).href))), {
|
|
127
84
|
workerData: options,
|
|
128
85
|
// We don't want any Node args like --import to be passed to the worker
|
|
129
86
|
execArgv: [],
|
|
130
|
-
env: { ...process.env, NODE_OPTIONS:
|
|
87
|
+
env: { ...process.env, NODE_OPTIONS: void 0 }
|
|
131
88
|
});
|
|
132
|
-
|
|
133
|
-
process.on('exit', () => {
|
|
134
|
-
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
89
|
+
process.on("exit", () => {
|
|
135
90
|
worker.terminate();
|
|
136
91
|
});
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
log('watchdog worker error', err);
|
|
140
|
-
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
92
|
+
worker.once("error", (err) => {
|
|
93
|
+
log("watchdog worker error", err);
|
|
141
94
|
worker.terminate();
|
|
142
95
|
});
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
log('watchdog worker exit', code);
|
|
96
|
+
worker.once("exit", (code) => {
|
|
97
|
+
log("watchdog worker exit", code);
|
|
146
98
|
});
|
|
147
|
-
|
|
148
|
-
// Ensure this thread can't block app exit
|
|
149
99
|
worker.unref();
|
|
150
100
|
}
|
|
151
|
-
|
|
152
101
|
const _eventLoopBlockIntegration = ((options = {}) => {
|
|
153
102
|
let polling;
|
|
154
|
-
|
|
155
103
|
return {
|
|
156
104
|
name: INTEGRATION_NAME,
|
|
157
105
|
async afterAllSetup(client) {
|
|
158
106
|
const dsn = client.getDsn();
|
|
159
|
-
|
|
160
107
|
if (!dsn) {
|
|
161
|
-
log(
|
|
108
|
+
log("No DSN configured, skipping starting integration");
|
|
162
109
|
return;
|
|
163
110
|
}
|
|
164
|
-
|
|
165
|
-
// Otel is not setup until after afterAllSetup returns.
|
|
166
111
|
setImmediate(async () => {
|
|
167
112
|
try {
|
|
168
113
|
polling = startPolling(client, options);
|
|
169
|
-
|
|
170
114
|
if (node_worker_threads.isMainThread) {
|
|
171
115
|
await startWorker(dsn, client, options);
|
|
172
116
|
}
|
|
173
117
|
} catch (err) {
|
|
174
|
-
log(
|
|
118
|
+
log("Failed to start integration", err);
|
|
175
119
|
return;
|
|
176
120
|
}
|
|
177
121
|
});
|
|
@@ -181,64 +125,21 @@ const _eventLoopBlockIntegration = ((options = {}) => {
|
|
|
181
125
|
},
|
|
182
126
|
stop() {
|
|
183
127
|
polling?.stop();
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
})
|
|
187
|
-
|
|
188
|
-
/**
|
|
189
|
-
* Monitors the Node.js event loop for blocking behavior and reports blocked events to Sentry.
|
|
190
|
-
*
|
|
191
|
-
* Uses a background worker thread to detect when the main thread is blocked for longer than
|
|
192
|
-
* the configured threshold (default: 1 second).
|
|
193
|
-
*
|
|
194
|
-
* When instrumenting via the `--import` flag, this integration will
|
|
195
|
-
* automatically monitor all worker threads as well.
|
|
196
|
-
*
|
|
197
|
-
* ```js
|
|
198
|
-
* // instrument.mjs
|
|
199
|
-
* import * as Sentry from '@sentry/node';
|
|
200
|
-
* import { eventLoopBlockIntegration } from '@sentry/node-native';
|
|
201
|
-
*
|
|
202
|
-
* Sentry.init({
|
|
203
|
-
* dsn: '__YOUR_DSN__',
|
|
204
|
-
* integrations: [
|
|
205
|
-
* eventLoopBlockIntegration({
|
|
206
|
-
* threshold: 500, // Report blocks longer than 500ms
|
|
207
|
-
* }),
|
|
208
|
-
* ],
|
|
209
|
-
* });
|
|
210
|
-
* ```
|
|
211
|
-
*
|
|
212
|
-
* Start your application with:
|
|
213
|
-
* ```bash
|
|
214
|
-
* node --import instrument.mjs app.mjs
|
|
215
|
-
* ```
|
|
216
|
-
*/
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
});
|
|
217
131
|
const eventLoopBlockIntegration = core.defineIntegration(_eventLoopBlockIntegration);
|
|
218
|
-
|
|
219
|
-
/**
|
|
220
|
-
* Disables Event Loop Block detection for the current thread for the duration
|
|
221
|
-
* of the callback.
|
|
222
|
-
*
|
|
223
|
-
* This utility function allows you to disable block detection during operations that
|
|
224
|
-
* are expected to block the event loop, such as intensive computational tasks or
|
|
225
|
-
* synchronous I/O operations.
|
|
226
|
-
*/
|
|
227
132
|
function disableBlockDetectionForCallback(callback) {
|
|
228
|
-
const integration = core.getClient()?.getIntegrationByName(INTEGRATION_NAME)
|
|
229
|
-
|
|
133
|
+
const integration = core.getClient()?.getIntegrationByName(INTEGRATION_NAME);
|
|
230
134
|
if (!integration) {
|
|
231
135
|
return callback();
|
|
232
136
|
}
|
|
233
|
-
|
|
234
137
|
integration.stop();
|
|
235
|
-
|
|
236
138
|
try {
|
|
237
139
|
const result = callback();
|
|
238
140
|
if (types.isPromise(result)) {
|
|
239
141
|
return result.finally(() => integration.start());
|
|
240
142
|
}
|
|
241
|
-
|
|
242
143
|
integration.start();
|
|
243
144
|
return result;
|
|
244
145
|
} catch (error) {
|
|
@@ -246,34 +147,18 @@ function disableBlockDetectionForCallback(callback) {
|
|
|
246
147
|
throw error;
|
|
247
148
|
}
|
|
248
149
|
}
|
|
249
|
-
|
|
250
|
-
/**
|
|
251
|
-
* Pauses the block detection integration.
|
|
252
|
-
*
|
|
253
|
-
* This function pauses event loop block detection for the current thread.
|
|
254
|
-
*/
|
|
255
150
|
function pauseEventLoopBlockDetection() {
|
|
256
|
-
const integration = core.getClient()?.getIntegrationByName(INTEGRATION_NAME)
|
|
257
|
-
|
|
151
|
+
const integration = core.getClient()?.getIntegrationByName(INTEGRATION_NAME);
|
|
258
152
|
if (!integration) {
|
|
259
153
|
return;
|
|
260
154
|
}
|
|
261
|
-
|
|
262
155
|
integration.stop();
|
|
263
156
|
}
|
|
264
|
-
|
|
265
|
-
/**
|
|
266
|
-
* Restarts the block detection integration.
|
|
267
|
-
*
|
|
268
|
-
* This function restarts event loop block detection for the current thread.
|
|
269
|
-
*/
|
|
270
157
|
function restartEventLoopBlockDetection() {
|
|
271
|
-
const integration = core.getClient()?.getIntegrationByName(INTEGRATION_NAME)
|
|
272
|
-
|
|
158
|
+
const integration = core.getClient()?.getIntegrationByName(INTEGRATION_NAME);
|
|
273
159
|
if (!integration) {
|
|
274
160
|
return;
|
|
275
161
|
}
|
|
276
|
-
|
|
277
162
|
integration.start();
|
|
278
163
|
}
|
|
279
164
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"event-loop-block-integration.js","sources":["../../src/event-loop-block-integration.ts"],"sourcesContent":["import { isPromise } from 'node:util/types';\nimport { isMainThread, Worker } from 'node:worker_threads';\nimport type {\n ClientOptions,\n Contexts,\n DsnComponents,\n Event,\n EventHint,\n Integration,\n IntegrationFn,\n ScopeData,\n} from '@sentry/core';\nimport {\n debug,\n defineIntegration,\n getClient,\n getCurrentScope,\n getFilenameToDebugIdMap,\n getGlobalScope,\n getIsolationScope,\n mergeScopeData,\n} from '@sentry/core';\nimport type { NodeClient } from '@sentry/node';\nimport { registerThread, threadPoll } from '@sentry-internal/node-native-stacktrace';\nimport type { ThreadBlockedIntegrationOptions, WorkerStartData } from './common';\nimport { POLL_RATIO } from './common';\n\nconst INTEGRATION_NAME = 'ThreadBlocked';\nconst DEFAULT_THRESHOLD_MS = 1_000;\n\nfunction log(message: string, ...args: unknown[]): void {\n debug.log(`[Sentry Event Loop Blocked] ${message}`, ...args);\n}\n\n/**\n * Gets contexts by calling all event processors. This shouldn't be called until all integrations are setup\n */\nasync function getContexts(client: NodeClient): Promise<Contexts> {\n let event: Event | null = { message: INTEGRATION_NAME };\n const eventHint: EventHint = {};\n\n for (const processor of client.getEventProcessors()) {\n if (event === null) break;\n event = await processor(event, eventHint);\n }\n\n return event?.contexts || {};\n}\n\nfunction getLocalScopeData(): ScopeData {\n const globalScope = getGlobalScope().getScopeData();\n const currentScope = getCurrentScope().getScopeData();\n mergeScopeData(globalScope, currentScope);\n return globalScope;\n}\n\ntype IntegrationInternal = { start: () => void; stop: () => void };\n\nfunction poll(enabled: boolean, clientOptions: ClientOptions): void {\n try {\n const currentSession = getIsolationScope().getSession();\n // We need to copy the session object and remove the toJSON method so it can be sent to the worker\n // serialized without making it a SerializedSession\n const session = currentSession ? { ...currentSession, toJSON: undefined } : undefined;\n const scope = getLocalScopeData();\n // message the worker to tell it the main event loop is still running\n threadPoll(enabled, { session, scope, debugImages: getFilenameToDebugIdMap(clientOptions.stackParser) });\n } catch {\n // we ignore all errors\n }\n}\n\n/**\n * Starts polling\n */\nfunction startPolling(\n client: NodeClient,\n integrationOptions: Partial<ThreadBlockedIntegrationOptions>,\n): IntegrationInternal | undefined {\n if (client.asyncLocalStorageLookup) {\n const { asyncLocalStorage, contextSymbol } = client.asyncLocalStorageLookup;\n registerThread({ asyncLocalStorage, stateLookup: ['_currentContext', contextSymbol] });\n } else {\n registerThread();\n }\n\n let enabled = true;\n\n const initOptions = client.getOptions();\n const pollInterval = (integrationOptions.threshold || DEFAULT_THRESHOLD_MS) / POLL_RATIO;\n\n // unref so timer does not block exit\n setInterval(() => poll(enabled, initOptions), pollInterval).unref();\n\n return {\n start: () => {\n enabled = true;\n },\n stop: () => {\n enabled = false;\n // poll immediately because the timer above might not get a chance to run\n // before the event loop gets blocked\n poll(enabled, initOptions);\n },\n };\n}\n\n/**\n * Starts the worker thread that will monitor the other threads.\n *\n * This function is only called in the main thread.\n */\nasync function startWorker(\n dsn: DsnComponents,\n client: NodeClient,\n integrationOptions: Partial<ThreadBlockedIntegrationOptions>,\n): Promise<void> {\n const contexts = await getContexts(client);\n\n // These will not be accurate if sent later from the worker thread\n delete contexts.app?.app_memory;\n delete contexts.device?.free_memory;\n\n const initOptions = client.getOptions();\n\n const sdkMetadata = client.getSdkMetadata() || {};\n if (sdkMetadata.sdk) {\n sdkMetadata.sdk.integrations = initOptions.integrations.map(i => i.name);\n }\n\n const options: WorkerStartData = {\n debug: debug.isEnabled(),\n dsn,\n tunnel: initOptions.tunnel,\n environment: initOptions.environment || 'production',\n release: initOptions.release,\n dist: initOptions.dist,\n sdkMetadata,\n appRootPath: integrationOptions.appRootPath,\n threshold: integrationOptions.threshold || DEFAULT_THRESHOLD_MS,\n maxEventsPerHour: integrationOptions.maxEventsPerHour || 1,\n staticTags: integrationOptions.staticTags || {},\n contexts,\n };\n\n const worker = new Worker(new URL('./event-loop-block-watchdog.js', import.meta.url), {\n workerData: options,\n // We don't want any Node args like --import to be passed to the worker\n execArgv: [],\n env: { ...process.env, NODE_OPTIONS: undefined },\n });\n\n process.on('exit', () => {\n // eslint-disable-next-line @typescript-eslint/no-floating-promises\n worker.terminate();\n });\n\n worker.once('error', (err: Error) => {\n log('watchdog worker error', err);\n // eslint-disable-next-line @typescript-eslint/no-floating-promises\n worker.terminate();\n });\n\n worker.once('exit', (code: number) => {\n log('watchdog worker exit', code);\n });\n\n // Ensure this thread can't block app exit\n worker.unref();\n}\n\nconst _eventLoopBlockIntegration = ((options: Partial<ThreadBlockedIntegrationOptions> = {}) => {\n let polling: IntegrationInternal | undefined;\n\n return {\n name: INTEGRATION_NAME,\n async afterAllSetup(client: NodeClient): Promise<void> {\n const dsn = client.getDsn();\n\n if (!dsn) {\n log('No DSN configured, skipping starting integration');\n return;\n }\n\n // Otel is not setup until after afterAllSetup returns.\n setImmediate(async () => {\n try {\n polling = startPolling(client, options);\n\n if (isMainThread) {\n await startWorker(dsn, client, options);\n }\n } catch (err) {\n log('Failed to start integration', err);\n return;\n }\n });\n },\n start() {\n polling?.start();\n },\n stop() {\n polling?.stop();\n },\n } as Integration & IntegrationInternal;\n}) satisfies IntegrationFn;\n\n/**\n * Monitors the Node.js event loop for blocking behavior and reports blocked events to Sentry.\n *\n * Uses a background worker thread to detect when the main thread is blocked for longer than\n * the configured threshold (default: 1 second).\n *\n * When instrumenting via the `--import` flag, this integration will\n * automatically monitor all worker threads as well.\n *\n * ```js\n * // instrument.mjs\n * import * as Sentry from '@sentry/node';\n * import { eventLoopBlockIntegration } from '@sentry/node-native';\n *\n * Sentry.init({\n * dsn: '__YOUR_DSN__',\n * integrations: [\n * eventLoopBlockIntegration({\n * threshold: 500, // Report blocks longer than 500ms\n * }),\n * ],\n * });\n * ```\n *\n * Start your application with:\n * ```bash\n * node --import instrument.mjs app.mjs\n * ```\n */\nexport const eventLoopBlockIntegration = defineIntegration(_eventLoopBlockIntegration);\n\nexport function disableBlockDetectionForCallback<T>(callback: () => T): T;\nexport function disableBlockDetectionForCallback<T>(callback: () => Promise<T>): Promise<T>;\n/**\n * Disables Event Loop Block detection for the current thread for the duration\n * of the callback.\n *\n * This utility function allows you to disable block detection during operations that\n * are expected to block the event loop, such as intensive computational tasks or\n * synchronous I/O operations.\n */\nexport function disableBlockDetectionForCallback<T>(callback: () => T | Promise<T>): T | Promise<T> {\n const integration = getClient()?.getIntegrationByName(INTEGRATION_NAME) as IntegrationInternal | undefined;\n\n if (!integration) {\n return callback();\n }\n\n integration.stop();\n\n try {\n const result = callback();\n if (isPromise(result)) {\n return result.finally(() => integration.start());\n }\n\n integration.start();\n return result;\n } catch (error) {\n integration.start();\n throw error;\n }\n}\n\n/**\n * Pauses the block detection integration.\n *\n * This function pauses event loop block detection for the current thread.\n */\nexport function pauseEventLoopBlockDetection(): void {\n const integration = getClient()?.getIntegrationByName(INTEGRATION_NAME) as IntegrationInternal | undefined;\n\n if (!integration) {\n return;\n }\n\n integration.stop();\n}\n\n/**\n * Restarts the block detection integration.\n *\n * This function restarts event loop block detection for the current thread.\n */\nexport function restartEventLoopBlockDetection(): void {\n const integration = getClient()?.getIntegrationByName(INTEGRATION_NAME) as IntegrationInternal | undefined;\n\n if (!integration) {\n return;\n }\n\n integration.start();\n}\n"],"names":["debug","getGlobalScope","getCurrentScope","mergeScopeData","getIsolationScope","threadPoll","getFilenameToDebugIdMap","registerThread","POLL_RATIO","Worker","isMainThread","defineIntegration","getClient","isPromise"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"event-loop-block-integration.js","sources":["../../src/event-loop-block-integration.ts"],"sourcesContent":["import { isPromise } from 'node:util/types';\nimport { isMainThread, Worker } from 'node:worker_threads';\nimport type {\n ClientOptions,\n Contexts,\n DsnComponents,\n Event,\n EventHint,\n Integration,\n IntegrationFn,\n ScopeData,\n} from '@sentry/core';\nimport {\n debug,\n defineIntegration,\n getClient,\n getCurrentScope,\n getFilenameToDebugIdMap,\n getGlobalScope,\n getIsolationScope,\n mergeScopeData,\n} from '@sentry/core';\nimport type { NodeClient } from '@sentry/node';\nimport { registerThread, threadPoll } from '@sentry-internal/node-native-stacktrace';\nimport type { ThreadBlockedIntegrationOptions, WorkerStartData } from './common';\nimport { POLL_RATIO } from './common';\n\nconst INTEGRATION_NAME = 'ThreadBlocked';\nconst DEFAULT_THRESHOLD_MS = 1_000;\n\nfunction log(message: string, ...args: unknown[]): void {\n debug.log(`[Sentry Event Loop Blocked] ${message}`, ...args);\n}\n\n/**\n * Gets contexts by calling all event processors. This shouldn't be called until all integrations are setup\n */\nasync function getContexts(client: NodeClient): Promise<Contexts> {\n let event: Event | null = { message: INTEGRATION_NAME };\n const eventHint: EventHint = {};\n\n for (const processor of client.getEventProcessors()) {\n if (event === null) break;\n event = await processor(event, eventHint);\n }\n\n return event?.contexts || {};\n}\n\nfunction getLocalScopeData(): ScopeData {\n const globalScope = getGlobalScope().getScopeData();\n const currentScope = getCurrentScope().getScopeData();\n mergeScopeData(globalScope, currentScope);\n return globalScope;\n}\n\ntype IntegrationInternal = { start: () => void; stop: () => void };\n\nfunction poll(enabled: boolean, clientOptions: ClientOptions): void {\n try {\n const currentSession = getIsolationScope().getSession();\n // We need to copy the session object and remove the toJSON method so it can be sent to the worker\n // serialized without making it a SerializedSession\n const session = currentSession ? { ...currentSession, toJSON: undefined } : undefined;\n const scope = getLocalScopeData();\n // message the worker to tell it the main event loop is still running\n threadPoll(enabled, { session, scope, debugImages: getFilenameToDebugIdMap(clientOptions.stackParser) });\n } catch {\n // we ignore all errors\n }\n}\n\n/**\n * Starts polling\n */\nfunction startPolling(\n client: NodeClient,\n integrationOptions: Partial<ThreadBlockedIntegrationOptions>,\n): IntegrationInternal | undefined {\n if (client.asyncLocalStorageLookup) {\n const { asyncLocalStorage, contextSymbol } = client.asyncLocalStorageLookup;\n registerThread({ asyncLocalStorage, stateLookup: ['_currentContext', contextSymbol] });\n } else {\n registerThread();\n }\n\n let enabled = true;\n\n const initOptions = client.getOptions();\n const pollInterval = (integrationOptions.threshold || DEFAULT_THRESHOLD_MS) / POLL_RATIO;\n\n // unref so timer does not block exit\n setInterval(() => poll(enabled, initOptions), pollInterval).unref();\n\n return {\n start: () => {\n enabled = true;\n },\n stop: () => {\n enabled = false;\n // poll immediately because the timer above might not get a chance to run\n // before the event loop gets blocked\n poll(enabled, initOptions);\n },\n };\n}\n\n/**\n * Starts the worker thread that will monitor the other threads.\n *\n * This function is only called in the main thread.\n */\nasync function startWorker(\n dsn: DsnComponents,\n client: NodeClient,\n integrationOptions: Partial<ThreadBlockedIntegrationOptions>,\n): Promise<void> {\n const contexts = await getContexts(client);\n\n // These will not be accurate if sent later from the worker thread\n delete contexts.app?.app_memory;\n delete contexts.device?.free_memory;\n\n const initOptions = client.getOptions();\n\n const sdkMetadata = client.getSdkMetadata() || {};\n if (sdkMetadata.sdk) {\n sdkMetadata.sdk.integrations = initOptions.integrations.map(i => i.name);\n }\n\n const options: WorkerStartData = {\n debug: debug.isEnabled(),\n dsn,\n tunnel: initOptions.tunnel,\n environment: initOptions.environment || 'production',\n release: initOptions.release,\n dist: initOptions.dist,\n sdkMetadata,\n appRootPath: integrationOptions.appRootPath,\n threshold: integrationOptions.threshold || DEFAULT_THRESHOLD_MS,\n maxEventsPerHour: integrationOptions.maxEventsPerHour || 1,\n staticTags: integrationOptions.staticTags || {},\n contexts,\n };\n\n const worker = new Worker(new URL('./event-loop-block-watchdog.js', import.meta.url), {\n workerData: options,\n // We don't want any Node args like --import to be passed to the worker\n execArgv: [],\n env: { ...process.env, NODE_OPTIONS: undefined },\n });\n\n process.on('exit', () => {\n // eslint-disable-next-line @typescript-eslint/no-floating-promises\n worker.terminate();\n });\n\n worker.once('error', (err: Error) => {\n log('watchdog worker error', err);\n // eslint-disable-next-line @typescript-eslint/no-floating-promises\n worker.terminate();\n });\n\n worker.once('exit', (code: number) => {\n log('watchdog worker exit', code);\n });\n\n // Ensure this thread can't block app exit\n worker.unref();\n}\n\nconst _eventLoopBlockIntegration = ((options: Partial<ThreadBlockedIntegrationOptions> = {}) => {\n let polling: IntegrationInternal | undefined;\n\n return {\n name: INTEGRATION_NAME,\n async afterAllSetup(client: NodeClient): Promise<void> {\n const dsn = client.getDsn();\n\n if (!dsn) {\n log('No DSN configured, skipping starting integration');\n return;\n }\n\n // Otel is not setup until after afterAllSetup returns.\n setImmediate(async () => {\n try {\n polling = startPolling(client, options);\n\n if (isMainThread) {\n await startWorker(dsn, client, options);\n }\n } catch (err) {\n log('Failed to start integration', err);\n return;\n }\n });\n },\n start() {\n polling?.start();\n },\n stop() {\n polling?.stop();\n },\n } as Integration & IntegrationInternal;\n}) satisfies IntegrationFn;\n\n/**\n * Monitors the Node.js event loop for blocking behavior and reports blocked events to Sentry.\n *\n * Uses a background worker thread to detect when the main thread is blocked for longer than\n * the configured threshold (default: 1 second).\n *\n * When instrumenting via the `--import` flag, this integration will\n * automatically monitor all worker threads as well.\n *\n * ```js\n * // instrument.mjs\n * import * as Sentry from '@sentry/node';\n * import { eventLoopBlockIntegration } from '@sentry/node-native';\n *\n * Sentry.init({\n * dsn: '__YOUR_DSN__',\n * integrations: [\n * eventLoopBlockIntegration({\n * threshold: 500, // Report blocks longer than 500ms\n * }),\n * ],\n * });\n * ```\n *\n * Start your application with:\n * ```bash\n * node --import instrument.mjs app.mjs\n * ```\n */\nexport const eventLoopBlockIntegration = defineIntegration(_eventLoopBlockIntegration);\n\nexport function disableBlockDetectionForCallback<T>(callback: () => T): T;\nexport function disableBlockDetectionForCallback<T>(callback: () => Promise<T>): Promise<T>;\n/**\n * Disables Event Loop Block detection for the current thread for the duration\n * of the callback.\n *\n * This utility function allows you to disable block detection during operations that\n * are expected to block the event loop, such as intensive computational tasks or\n * synchronous I/O operations.\n */\nexport function disableBlockDetectionForCallback<T>(callback: () => T | Promise<T>): T | Promise<T> {\n const integration = getClient()?.getIntegrationByName(INTEGRATION_NAME) as IntegrationInternal | undefined;\n\n if (!integration) {\n return callback();\n }\n\n integration.stop();\n\n try {\n const result = callback();\n if (isPromise(result)) {\n return result.finally(() => integration.start());\n }\n\n integration.start();\n return result;\n } catch (error) {\n integration.start();\n throw error;\n }\n}\n\n/**\n * Pauses the block detection integration.\n *\n * This function pauses event loop block detection for the current thread.\n */\nexport function pauseEventLoopBlockDetection(): void {\n const integration = getClient()?.getIntegrationByName(INTEGRATION_NAME) as IntegrationInternal | undefined;\n\n if (!integration) {\n return;\n }\n\n integration.stop();\n}\n\n/**\n * Restarts the block detection integration.\n *\n * This function restarts event loop block detection for the current thread.\n */\nexport function restartEventLoopBlockDetection(): void {\n const integration = getClient()?.getIntegrationByName(INTEGRATION_NAME) as IntegrationInternal | undefined;\n\n if (!integration) {\n return;\n }\n\n integration.start();\n}\n"],"names":["debug","getGlobalScope","getCurrentScope","mergeScopeData","getIsolationScope","threadPoll","getFilenameToDebugIdMap","registerThread","POLL_RATIO","Worker","isMainThread","defineIntegration","getClient","isPromise"],"mappings":"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"}
|
|
@@ -16,123 +16,92 @@ const {
|
|
|
16
16
|
release,
|
|
17
17
|
sdkMetadata,
|
|
18
18
|
staticTags: tags,
|
|
19
|
-
tunnel
|
|
20
|
-
} = node_worker_threads.workerData
|
|
21
|
-
|
|
19
|
+
tunnel
|
|
20
|
+
} = node_worker_threads.workerData;
|
|
22
21
|
const pollInterval = threshold / common.POLL_RATIO;
|
|
23
|
-
const triggeredThreads = new Set();
|
|
24
|
-
|
|
22
|
+
const triggeredThreads = /* @__PURE__ */ new Set();
|
|
25
23
|
function log(...msg) {
|
|
26
24
|
if (debug) {
|
|
27
|
-
|
|
28
|
-
console.log('[Sentry Event Loop Blocked Watchdog]', ...msg);
|
|
25
|
+
console.log("[Sentry Event Loop Blocked Watchdog]", ...msg);
|
|
29
26
|
}
|
|
30
27
|
}
|
|
31
|
-
|
|
32
|
-
function createRateLimiter(maxEventsPerHour) {
|
|
28
|
+
function createRateLimiter(maxEventsPerHour2) {
|
|
33
29
|
let currentHour = 0;
|
|
34
30
|
let currentCount = 0;
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
const hour = new Date().getHours();
|
|
38
|
-
|
|
31
|
+
return function isRateLimited2() {
|
|
32
|
+
const hour = (/* @__PURE__ */ new Date()).getHours();
|
|
39
33
|
if (hour !== currentHour) {
|
|
40
34
|
currentHour = hour;
|
|
41
35
|
currentCount = 0;
|
|
42
36
|
}
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
if (currentCount === maxEventsPerHour) {
|
|
37
|
+
if (currentCount >= maxEventsPerHour2) {
|
|
38
|
+
if (currentCount === maxEventsPerHour2) {
|
|
46
39
|
currentCount += 1;
|
|
47
40
|
log(`Rate limit reached: ${currentCount} events in this hour`);
|
|
48
41
|
}
|
|
49
42
|
return true;
|
|
50
43
|
}
|
|
51
|
-
|
|
52
44
|
currentCount += 1;
|
|
53
45
|
return false;
|
|
54
46
|
};
|
|
55
47
|
}
|
|
56
|
-
|
|
57
48
|
const url = core.getEnvelopeEndpointWithUrlEncodedAuth(dsn, tunnel, sdkMetadata.sdk);
|
|
58
49
|
const transport = node.makeNodeTransport({
|
|
59
50
|
url,
|
|
60
51
|
recordDroppedEvent: () => {
|
|
61
|
-
|
|
62
|
-
},
|
|
52
|
+
}
|
|
63
53
|
});
|
|
64
54
|
const isRateLimited = createRateLimiter(maxEventsPerHour);
|
|
65
|
-
|
|
66
55
|
async function sendAbnormalSession(serializedSession) {
|
|
67
56
|
if (!serializedSession) {
|
|
68
57
|
return;
|
|
69
58
|
}
|
|
70
|
-
|
|
71
|
-
log('Sending abnormal session');
|
|
59
|
+
log("Sending abnormal session");
|
|
72
60
|
const session = core.makeSession(serializedSession);
|
|
73
|
-
|
|
74
61
|
core.updateSession(session, {
|
|
75
|
-
status:
|
|
76
|
-
abnormal_mechanism:
|
|
62
|
+
status: "abnormal",
|
|
63
|
+
abnormal_mechanism: "anr_foreground",
|
|
77
64
|
release,
|
|
78
|
-
environment
|
|
65
|
+
environment
|
|
79
66
|
});
|
|
80
|
-
|
|
81
67
|
const envelope = core.createSessionEnvelope(session, dsn, sdkMetadata, tunnel);
|
|
82
|
-
// Log the envelope so to aid in testing
|
|
83
68
|
log(JSON.stringify(envelope));
|
|
84
|
-
|
|
85
69
|
await transport.send(envelope);
|
|
86
70
|
}
|
|
87
|
-
|
|
88
|
-
log('Started');
|
|
89
|
-
|
|
71
|
+
log("Started");
|
|
90
72
|
function prepareStackFrames(stackFrames) {
|
|
91
73
|
if (!stackFrames) {
|
|
92
|
-
return
|
|
74
|
+
return void 0;
|
|
93
75
|
}
|
|
94
|
-
|
|
95
|
-
// Strip Sentry frames and reverse the stack frames so they are in the correct order
|
|
96
76
|
const strippedFrames = core.stripSentryFramesAndReverse(stackFrames);
|
|
97
|
-
|
|
98
77
|
for (const frame of strippedFrames) {
|
|
99
78
|
if (!frame.filename) {
|
|
100
79
|
continue;
|
|
101
80
|
}
|
|
102
|
-
|
|
103
81
|
frame.in_app = core.filenameIsInApp(frame.filename);
|
|
104
|
-
|
|
105
|
-
// If we have an app root path, rewrite the filenames to be relative to the app root
|
|
106
82
|
if (appRootPath) {
|
|
107
83
|
frame.filename = core.normalizeUrlToBase(frame.filename, appRootPath);
|
|
108
84
|
}
|
|
109
85
|
}
|
|
110
|
-
|
|
111
86
|
return strippedFrames;
|
|
112
87
|
}
|
|
113
|
-
|
|
114
88
|
function stripFileProtocol(filename) {
|
|
115
89
|
if (!filename) {
|
|
116
|
-
return
|
|
90
|
+
return void 0;
|
|
117
91
|
}
|
|
118
|
-
return filename.replace(/^file:\/\//,
|
|
92
|
+
return filename.replace(/^file:\/\//, "");
|
|
119
93
|
}
|
|
120
|
-
|
|
121
|
-
// eslint-disable-next-line complexity
|
|
122
94
|
function applyDebugMeta(event, debugImages) {
|
|
123
95
|
if (Object.keys(debugImages).length === 0) {
|
|
124
96
|
return;
|
|
125
97
|
}
|
|
126
|
-
|
|
127
98
|
const normalisedDebugImages = appRootPath ? {} : debugImages;
|
|
128
99
|
if (appRootPath) {
|
|
129
100
|
for (const [path, debugId] of Object.entries(debugImages)) {
|
|
130
101
|
normalisedDebugImages[core.normalizeUrlToBase(path, appRootPath)] = debugId;
|
|
131
102
|
}
|
|
132
103
|
}
|
|
133
|
-
|
|
134
|
-
const filenameToDebugId = new Map();
|
|
135
|
-
|
|
104
|
+
const filenameToDebugId = /* @__PURE__ */ new Map();
|
|
136
105
|
for (const exception of event.exception?.values || []) {
|
|
137
106
|
for (const frame of exception.stacktrace?.frames || []) {
|
|
138
107
|
const filename = stripFileProtocol(frame.abs_path || frame.filename);
|
|
@@ -141,7 +110,6 @@ function applyDebugMeta(event, debugImages) {
|
|
|
141
110
|
}
|
|
142
111
|
}
|
|
143
112
|
}
|
|
144
|
-
|
|
145
113
|
for (const thread of event.threads?.values || []) {
|
|
146
114
|
for (const frame of thread.stacktrace?.frames || []) {
|
|
147
115
|
const filename = stripFileProtocol(frame.abs_path || frame.filename);
|
|
@@ -150,145 +118,115 @@ function applyDebugMeta(event, debugImages) {
|
|
|
150
118
|
}
|
|
151
119
|
}
|
|
152
120
|
}
|
|
153
|
-
|
|
154
121
|
if (filenameToDebugId.size > 0) {
|
|
155
122
|
const images = [];
|
|
156
123
|
for (const [code_file, debug_id] of filenameToDebugId.entries()) {
|
|
157
124
|
images.push({
|
|
158
|
-
type:
|
|
125
|
+
type: "sourcemap",
|
|
159
126
|
code_file,
|
|
160
|
-
debug_id
|
|
127
|
+
debug_id
|
|
161
128
|
});
|
|
162
129
|
}
|
|
163
130
|
event.debug_meta = { images };
|
|
164
131
|
}
|
|
165
132
|
}
|
|
166
|
-
|
|
167
|
-
function getExceptionAndThreads(
|
|
168
|
-
crashedThreadId,
|
|
169
|
-
threads,
|
|
170
|
-
) {
|
|
133
|
+
function getExceptionAndThreads(crashedThreadId, threads) {
|
|
171
134
|
const crashedThread = threads[crashedThreadId];
|
|
172
|
-
|
|
173
135
|
return {
|
|
174
136
|
exception: {
|
|
175
137
|
values: [
|
|
176
138
|
{
|
|
177
|
-
type:
|
|
139
|
+
type: "EventLoopBlocked",
|
|
178
140
|
value: `Event Loop Blocked for at least ${threshold} ms`,
|
|
179
141
|
stacktrace: { frames: prepareStackFrames(crashedThread?.frames) },
|
|
180
142
|
// This ensures the UI doesn't say 'Crashed in' for the stack trace
|
|
181
|
-
mechanism: { type:
|
|
182
|
-
thread_id: crashedThreadId
|
|
183
|
-
}
|
|
184
|
-
]
|
|
143
|
+
mechanism: { type: "ANR" },
|
|
144
|
+
thread_id: crashedThreadId
|
|
145
|
+
}
|
|
146
|
+
]
|
|
185
147
|
},
|
|
186
148
|
threads: {
|
|
187
149
|
values: Object.entries(threads).map(([threadId, threadState]) => {
|
|
188
150
|
const crashed = threadId === crashedThreadId;
|
|
189
|
-
|
|
190
151
|
const thread = {
|
|
191
152
|
id: threadId,
|
|
192
|
-
name: threadId ===
|
|
153
|
+
name: threadId === "0" ? "main" : `worker-${threadId}`,
|
|
193
154
|
crashed,
|
|
194
155
|
current: true,
|
|
195
|
-
main: threadId ===
|
|
156
|
+
main: threadId === "0"
|
|
196
157
|
};
|
|
197
|
-
|
|
198
158
|
if (!crashed) {
|
|
199
159
|
thread.stacktrace = { frames: prepareStackFrames(threadState.frames) };
|
|
200
160
|
}
|
|
201
|
-
|
|
202
161
|
return thread;
|
|
203
|
-
})
|
|
204
|
-
}
|
|
162
|
+
})
|
|
163
|
+
}
|
|
205
164
|
};
|
|
206
165
|
}
|
|
207
|
-
|
|
208
166
|
function applyScopeToEvent(event, scope) {
|
|
209
167
|
core.applyScopeDataToEvent(event, scope);
|
|
210
|
-
|
|
211
168
|
if (!event.contexts?.trace) {
|
|
212
169
|
const { traceId, parentSpanId, propagationSpanId } = scope.propagationContext;
|
|
213
170
|
event.contexts = {
|
|
214
171
|
trace: {
|
|
215
172
|
trace_id: traceId,
|
|
216
173
|
span_id: propagationSpanId || core.generateSpanId(),
|
|
217
|
-
parent_span_id: parentSpanId
|
|
174
|
+
parent_span_id: parentSpanId
|
|
218
175
|
},
|
|
219
|
-
...event.contexts
|
|
176
|
+
...event.contexts
|
|
220
177
|
};
|
|
221
178
|
}
|
|
222
179
|
}
|
|
223
|
-
|
|
224
180
|
async function sendBlockEvent(crashedThreadId) {
|
|
225
181
|
if (isRateLimited()) {
|
|
226
182
|
return;
|
|
227
183
|
}
|
|
228
|
-
|
|
229
184
|
const threads = nodeNativeStacktrace.captureStackTrace();
|
|
230
185
|
const crashedThread = threads[crashedThreadId];
|
|
231
|
-
|
|
232
186
|
if (!crashedThread) {
|
|
233
187
|
log(`No thread found with ID '${crashedThreadId}'`);
|
|
234
188
|
return;
|
|
235
189
|
}
|
|
236
|
-
|
|
237
190
|
try {
|
|
238
191
|
await sendAbnormalSession(crashedThread.pollState?.session);
|
|
239
192
|
} catch (error) {
|
|
240
193
|
log(`Failed to send abnormal session for thread '${crashedThreadId}':`, error);
|
|
241
194
|
}
|
|
242
|
-
|
|
243
|
-
log('Sending event');
|
|
244
|
-
|
|
195
|
+
log("Sending event");
|
|
245
196
|
const event = {
|
|
246
197
|
event_id: core.uuid4(),
|
|
247
198
|
contexts,
|
|
248
199
|
release,
|
|
249
200
|
environment,
|
|
250
201
|
dist,
|
|
251
|
-
platform:
|
|
252
|
-
level:
|
|
202
|
+
platform: "node",
|
|
203
|
+
level: "error",
|
|
253
204
|
tags,
|
|
254
|
-
...getExceptionAndThreads(crashedThreadId, threads)
|
|
205
|
+
...getExceptionAndThreads(crashedThreadId, threads)
|
|
255
206
|
};
|
|
256
|
-
|
|
257
|
-
const scope = crashedThread.pollState?.scope
|
|
258
|
-
? new core.Scope().update(crashedThread.pollState.scope).getScopeData()
|
|
259
|
-
: new core.Scope().getScopeData();
|
|
260
|
-
|
|
207
|
+
const scope = crashedThread.pollState?.scope ? new core.Scope().update(crashedThread.pollState.scope).getScopeData() : new core.Scope().getScopeData();
|
|
261
208
|
if (crashedThread?.asyncState?.isolationScope) {
|
|
262
|
-
// We need to rehydrate the scope from the serialized object with properties beginning with _user, etc
|
|
263
209
|
const isolationScope = Object.assign(new core.Scope(), crashedThread.asyncState.isolationScope).getScopeData();
|
|
264
210
|
core.mergeScopeData(scope, isolationScope);
|
|
265
211
|
}
|
|
266
212
|
applyScopeToEvent(event, scope);
|
|
267
|
-
|
|
268
213
|
const allDebugImages = Object.values(threads).reduce((acc, threadState) => {
|
|
269
214
|
return { ...acc, ...threadState.pollState?.debugImages };
|
|
270
215
|
}, {});
|
|
271
|
-
|
|
272
216
|
applyDebugMeta(event, allDebugImages);
|
|
273
|
-
|
|
274
217
|
const envelope = core.createEventEnvelope(event, dsn, sdkMetadata, tunnel);
|
|
275
|
-
// Log the envelope to aid in testing
|
|
276
218
|
log(JSON.stringify(envelope));
|
|
277
|
-
|
|
278
219
|
await transport.send(envelope);
|
|
279
|
-
await transport.flush(
|
|
220
|
+
await transport.flush(2e3);
|
|
280
221
|
}
|
|
281
|
-
|
|
282
222
|
setInterval(async () => {
|
|
283
223
|
for (const [threadId, time] of Object.entries(nodeNativeStacktrace.getThreadsLastSeen())) {
|
|
284
224
|
if (time > threshold) {
|
|
285
225
|
if (triggeredThreads.has(threadId)) {
|
|
286
226
|
continue;
|
|
287
227
|
}
|
|
288
|
-
|
|
289
228
|
log(`Blocked thread detected '${threadId}' last polled ${time} ms ago.`);
|
|
290
229
|
triggeredThreads.add(threadId);
|
|
291
|
-
|
|
292
230
|
try {
|
|
293
231
|
await sendBlockEvent(threadId);
|
|
294
232
|
} catch (error) {
|