Skip to content

Commit ce9efc7

Browse files
authored
feat(node): Rework ANR to use worker script via an integration (#9823)
This PR reworks Node ANR detection to use a worker thread. Workers are usually started via a path to a source file but this can cause issues when bundlers are used. Instead, the worker code is bundled and included in the source as a base64 string which can be used to launch a worker via a data URL. The base64 code comes in at around 45KB. Positives 👍 - No extra processes - Only 10-15MB memory overhead (vs at least 50MB for a child process) - Uses inspector API so we can remove the websockets implementation - Doesn't require special cases/setup for Electron main process - No longer runs the app entry point again as the child process code - Closes some outstanding Electron ANR issues - ANR becomes just an integration since we don't need to intercept app execution in the child - Less confusing setup and less chance of running the app twice Negatives 👎 - Minimum supported Node version for ANR detection becomes v16 because Node 14 does not support data URLs for workers 😢 ## Usage ```ts import * as Sentry from '@sentry/node'; Sentry.init({ dsn: 'https://[email protected]/1337', integrations: [new Sentry.Integrations.Anr({ captureStackTrace: true, anrThreshold: 200 })], }); ```
1 parent 0563412 commit ce9efc7

File tree

25 files changed

+602
-894
lines changed

25 files changed

+602
-894
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,9 @@ tmp.js
5050
.eslintcache
5151
**/eslintcache/*
5252

53+
# node worker scripts
54+
packages/node/src/integrations/anr/worker-script.*
55+
5356
# deno
5457
packages/deno/build-types
5558
packages/deno/build-test

packages/core/src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ export type { ServerRuntimeClientOptions } from './server-runtime-client';
55
export type { RequestDataIntegrationOptions } from './integrations/requestdata';
66

77
export * from './tracing';
8-
export { createEventEnvelope } from './envelope';
8+
export { createEventEnvelope, createSessionEnvelope } from './envelope';
99
export {
1010
addBreadcrumb,
1111
captureCheckIn,

packages/node-experimental/src/sdk/init.ts

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import {
44
defaultIntegrations as defaultNodeIntegrations,
55
defaultStackParser,
66
getSentryRelease,
7-
isAnrChildProcess,
87
makeNodeTransport,
98
} from '@sentry/node';
109
import type { Integration } from '@sentry/types';
@@ -113,15 +112,14 @@ function getClientOptions(options: NodeExperimentalOptions): NodeExperimentalCli
113112

114113
const release = getRelease(options.release);
115114

116-
// If there is no release, or we are in an ANR child process, we disable autoSessionTracking by default
117115
const autoSessionTracking =
118-
typeof release !== 'string' || isAnrChildProcess()
116+
typeof release !== 'string'
119117
? false
120118
: options.autoSessionTracking === undefined
121119
? true
122120
: options.autoSessionTracking;
123-
// We enforce tracesSampleRate = 0 in ANR child processes
124-
const tracesSampleRate = isAnrChildProcess() ? 0 : getTracesSampleRate(options.tracesSampleRate);
121+
122+
const tracesSampleRate = getTracesSampleRate(options.tracesSampleRate);
125123

126124
const baseOptions = dropUndefinedKeys({
127125
transport: makeNodeTransport,
Lines changed: 13 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,28 @@
11
const crypto = require('crypto');
2+
const assert = require('assert');
23

34
const Sentry = require('@sentry/node');
45

5-
const { transport } = require('./test-transport.js');
6-
7-
// close both processes after 5 seconds
86
setTimeout(() => {
97
process.exit();
10-
}, 5000);
8+
}, 10000);
119

1210
Sentry.init({
1311
dsn: 'https://[email protected]/1337',
1412
release: '1.0',
1513
debug: true,
16-
transport,
14+
integrations: [new Sentry.Integrations.Anr({ captureStackTrace: true, anrThreshold: 200 })],
1715
});
1816

19-
Sentry.enableAnrDetection({ captureStackTrace: true, anrThreshold: 200 }).then(() => {
20-
function longWork() {
21-
for (let i = 0; i < 100; i++) {
22-
const salt = crypto.randomBytes(128).toString('base64');
23-
// eslint-disable-next-line no-unused-vars
24-
const hash = crypto.pbkdf2Sync('myPassword', salt, 10000, 512, 'sha512');
25-
}
17+
function longWork() {
18+
for (let i = 0; i < 100; i++) {
19+
const salt = crypto.randomBytes(128).toString('base64');
20+
// eslint-disable-next-line no-unused-vars
21+
const hash = crypto.pbkdf2Sync('myPassword', salt, 10000, 512, 'sha512');
22+
assert.ok(hash);
2623
}
24+
}
2725

28-
setTimeout(() => {
29-
longWork();
30-
}, 1000);
31-
});
26+
setTimeout(() => {
27+
longWork();
28+
}, 1000);
Lines changed: 13 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,29 @@
11
const crypto = require('crypto');
2+
const assert = require('assert');
23

34
const Sentry = require('@sentry/node');
45

5-
const { transport } = require('./test-transport.js');
6-
7-
// close both processes after 5 seconds
86
setTimeout(() => {
97
process.exit();
10-
}, 5000);
8+
}, 10000);
119

1210
Sentry.init({
1311
dsn: 'https://[email protected]/1337',
1412
release: '1.0',
1513
debug: true,
1614
autoSessionTracking: false,
17-
transport,
15+
integrations: [new Sentry.Integrations.Anr({ captureStackTrace: true, anrThreshold: 200 })],
1816
});
1917

20-
Sentry.enableAnrDetection({ captureStackTrace: true, anrThreshold: 200 }).then(() => {
21-
function longWork() {
22-
for (let i = 0; i < 100; i++) {
23-
const salt = crypto.randomBytes(128).toString('base64');
24-
// eslint-disable-next-line no-unused-vars
25-
const hash = crypto.pbkdf2Sync('myPassword', salt, 10000, 512, 'sha512');
26-
}
18+
function longWork() {
19+
for (let i = 0; i < 100; i++) {
20+
const salt = crypto.randomBytes(128).toString('base64');
21+
// eslint-disable-next-line no-unused-vars
22+
const hash = crypto.pbkdf2Sync('myPassword', salt, 10000, 512, 'sha512');
23+
assert.ok(hash);
2724
}
25+
}
2826

29-
setTimeout(() => {
30-
longWork();
31-
}, 1000);
32-
});
27+
setTimeout(() => {
28+
longWork();
29+
}, 1000);

packages/node-integration-tests/suites/anr/basic.mjs

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,26 @@
1+
import * as assert from 'assert';
12
import * as crypto from 'crypto';
23

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

5-
const { transport } = await import('./test-transport.js');
6-
7-
// close both processes after 5 seconds
86
setTimeout(() => {
97
process.exit();
10-
}, 5000);
8+
}, 10000);
119

1210
Sentry.init({
1311
dsn: 'https://[email protected]/1337',
1412
release: '1.0',
1513
debug: true,
1614
autoSessionTracking: false,
17-
transport,
15+
integrations: [new Sentry.Integrations.Anr({ captureStackTrace: true, anrThreshold: 200 })],
1816
});
1917

20-
await Sentry.enableAnrDetection({ captureStackTrace: true, anrThreshold: 200 });
21-
2218
function longWork() {
2319
for (let i = 0; i < 100; i++) {
2420
const salt = crypto.randomBytes(128).toString('base64');
2521
// eslint-disable-next-line no-unused-vars
2622
const hash = crypto.pbkdf2Sync('myPassword', salt, 10000, 512, 'sha512');
23+
assert.ok(hash);
2724
}
2825
}
2926

Lines changed: 13 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,29 @@
11
const crypto = require('crypto');
2+
const assert = require('assert');
23

34
const Sentry = require('@sentry/node');
45

5-
const { transport } = require('./test-transport.js');
6-
7-
// close both processes after 5 seconds
86
setTimeout(() => {
97
process.exit();
10-
}, 5000);
8+
}, 10000);
119

1210
Sentry.init({
1311
dsn: 'https://[email protected]/1337',
1412
release: '1.0',
1513
debug: true,
1614
autoSessionTracking: false,
17-
transport,
15+
integrations: [new Sentry.Integrations.Anr({ captureStackTrace: true, anrThreshold: 200 })],
1816
});
1917

20-
Sentry.enableAnrDetection({ captureStackTrace: true, anrThreshold: 200 }).then(() => {
21-
function longWork() {
22-
for (let i = 0; i < 100; i++) {
23-
const salt = crypto.randomBytes(128).toString('base64');
24-
// eslint-disable-next-line no-unused-vars
25-
const hash = crypto.pbkdf2Sync('myPassword', salt, 10000, 512, 'sha512');
26-
}
18+
function longWork() {
19+
for (let i = 0; i < 100; i++) {
20+
const salt = crypto.randomBytes(128).toString('base64');
21+
// eslint-disable-next-line no-unused-vars
22+
const hash = crypto.pbkdf2Sync('myPassword', salt, 10000, 512, 'sha512');
23+
assert.ok(hash);
2724
}
25+
}
2826

29-
setTimeout(() => {
30-
longWork();
31-
}, 1000);
32-
});
27+
setTimeout(() => {
28+
longWork();
29+
}, 1000);
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
const crypto = require('crypto');
2+
const assert = require('assert');
3+
4+
const Sentry = require('@sentry/node');
5+
6+
setTimeout(() => {
7+
process.exit();
8+
}, 10000);
9+
10+
Sentry.init({
11+
dsn: 'https://[email protected]/1337',
12+
release: '1.0',
13+
debug: true,
14+
autoSessionTracking: false,
15+
});
16+
17+
// eslint-disable-next-line deprecation/deprecation
18+
Sentry.enableAnrDetection({ captureStackTrace: true, anrThreshold: 200 }).then(() => {
19+
function longWork() {
20+
for (let i = 0; i < 100; i++) {
21+
const salt = crypto.randomBytes(128).toString('base64');
22+
// eslint-disable-next-line no-unused-vars
23+
const hash = crypto.pbkdf2Sync('myPassword', salt, 10000, 512, 'sha512');
24+
assert.ok(hash);
25+
}
26+
}
27+
28+
setTimeout(() => {
29+
longWork();
30+
}, 1000);
31+
});

packages/node-integration-tests/suites/anr/test-transport.js

Lines changed: 0 additions & 17 deletions
This file was deleted.

0 commit comments

Comments
 (0)