Skip to content

Commit 30e7dcc

Browse files
authored
fix(core): unref timer to not block node exit (#11430)
Add `unref` to make sure that the session flusher or metrics aggregator does not block nodejs exit. Especially important for serverless scenarios.
1 parent 38ab325 commit 30e7dcc

File tree

6 files changed

+77
-7
lines changed

6 files changed

+77
-7
lines changed
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
const Sentry = require('@sentry/node');
2+
3+
function configureSentry() {
4+
Sentry.init({
5+
dsn: 'https://[email protected]/1337',
6+
release: '1.0',
7+
autoSessionTracking: false,
8+
});
9+
10+
Sentry.metrics.increment('test');
11+
}
12+
13+
async function main() {
14+
configureSentry();
15+
await new Promise(resolve => setTimeout(resolve, 1000));
16+
process.exit(0);
17+
}
18+
19+
main();
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
const Sentry = require('@sentry/node');
2+
3+
function configureSentry() {
4+
Sentry.init({
5+
dsn: 'https://[email protected]/1337',
6+
release: '1.0',
7+
autoSessionTracking: false,
8+
});
9+
10+
Sentry.metrics.increment('test');
11+
}
12+
13+
async function main() {
14+
configureSentry();
15+
await new Promise(resolve => setTimeout(resolve, 1000));
16+
}
17+
18+
main();
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { createRunner } from '../../utils/runner';
2+
3+
describe('metrics', () => {
4+
test('should exit', done => {
5+
const runner = createRunner(__dirname, 'should-exit.js').start();
6+
7+
setTimeout(() => {
8+
expect(runner.childHasExited()).toBe(true);
9+
done();
10+
}, 5_000);
11+
});
12+
13+
test('should exit forced', done => {
14+
const runner = createRunner(__dirname, 'should-exit-forced.js').start();
15+
16+
setTimeout(() => {
17+
expect(runner.childHasExited()).toBe(true);
18+
done();
19+
}, 5_000);
20+
});
21+
});

dev-packages/node-integration-tests/suites/tracing/metric-summaries/scenario.js

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,6 @@ Sentry.init({
88
transport: loggingTransport,
99
});
1010

11-
// Stop the process from exiting before the transaction is sent
12-
setInterval(() => {}, 1000);
13-
1411
Sentry.startSpan(
1512
{
1613
name: 'Test Transaction',

packages/core/src/metrics/aggregator.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,9 @@ export class MetricsAggregator implements MetricsAggregatorBase {
2020
// that we store in memory.
2121
private _bucketsTotalWeight;
2222

23-
private readonly _interval: ReturnType<typeof setInterval>;
23+
// Cast to any so that it can use Node.js timeout
24+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
25+
private readonly _interval: any;
2426

2527
// SDKs are required to shift the flush interval by random() * rollup_in_seconds.
2628
// That shift is determined once per startup to create jittering.
@@ -37,7 +39,13 @@ export class MetricsAggregator implements MetricsAggregatorBase {
3739
public constructor(private readonly _client: Client) {
3840
this._buckets = new Map();
3941
this._bucketsTotalWeight = 0;
40-
this._interval = setInterval(() => this._flush(), DEFAULT_FLUSH_INTERVAL);
42+
43+
this._interval = setInterval(() => this._flush(), DEFAULT_FLUSH_INTERVAL) as any;
44+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
45+
if (this._interval.unref) {
46+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
47+
this._interval.unref();
48+
}
4149
this._flushShift = Math.floor((Math.random() * DEFAULT_FLUSH_INTERVAL) / 1000);
4250
this._forceFlush = false;
4351
}

packages/core/src/sessionflusher.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,9 @@ export class SessionFlusher implements SessionFlusherLike {
2020
public readonly flushTimeout: number;
2121
private _pendingAggregates: Record<number, AggregationCounts>;
2222
private _sessionAttrs: ReleaseHealthAttributes;
23-
private _intervalId: ReturnType<typeof setInterval>;
23+
// Cast to any so that it can use Node.js timeout
24+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
25+
private _intervalId: any;
2426
private _isEnabled: boolean;
2527
private _client: Client;
2628

@@ -30,8 +32,13 @@ export class SessionFlusher implements SessionFlusherLike {
3032
this._pendingAggregates = {};
3133
this._isEnabled = true;
3234

33-
// Call to setInterval, so that flush is called every 60 seconds
35+
// Call to setInterval, so that flush is called every 60 seconds.
3436
this._intervalId = setInterval(() => this.flush(), this.flushTimeout * 1000);
37+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
38+
if (this._intervalId.unref) {
39+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
40+
this._intervalId.unref();
41+
}
3542
this._sessionAttrs = attrs;
3643
}
3744

0 commit comments

Comments
 (0)