Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 9 additions & 5 deletions packages/core/src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ import { safeMathRandom } from './utils/randomSafeContext';
import { reparentChildSpans, shouldIgnoreSpan } from './utils/should-ignore-span';
import { showSpanDropWarning } from './utils/spanUtils';
import { rejectedSyncPromise } from './utils/syncpromise';
import { safeUnref } from './utils/timer';
import { convertSpanJsonToTransactionEvent, convertTransactionEventToSpanJson } from './utils/transactionEvent';

const ALREADY_SEEN_ERROR = "Not capturing exception because it's already been captured.";
Expand Down Expand Up @@ -137,11 +138,14 @@ function setupWeightBasedFlushing<
// This prevents flushing being delayed by items that arrive close to the timeout limit
// and thus resetting the flushing timeout and delaying items being flushed.
isTimerActive = true;
flushTimeout = setTimeout(() => {
flushFn(client);
// Note: isTimerActive is reset by the flushHook handler above, not here,
// to avoid race conditions when new items arrive during the flush.
}, DEFAULT_FLUSH_INTERVAL);
// Use safeUnref so the timer doesn't prevent the process from exiting
flushTimeout = safeUnref(
setTimeout(() => {
flushFn(client);
// Note: isTimerActive is reset by the flushHook handler above, not here,
// to avoid race conditions when new items arrive during the flush.
}, DEFAULT_FLUSH_INTERVAL),
);
}
});

Expand Down
42 changes: 42 additions & 0 deletions packages/core/test/lib/client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import type { SpanJSON } from '../../src/types-hoist/span';
import * as debugLoggerModule from '../../src/utils/debug-logger';
import * as miscModule from '../../src/utils/misc';
import * as timeModule from '../../src/utils/time';
import * as timerModule from '../../src/utils/timer';
import { getDefaultTestClientOptions, TestClient } from '../mocks/client';
import { AdHocIntegration, AsyncTestIntegration, TestIntegration } from '../mocks/integration';
import { makeFakeTransport } from '../mocks/transport';
Expand Down Expand Up @@ -3076,6 +3077,27 @@ describe('Client', () => {

expect(sendEnvelopeSpy).not.toHaveBeenCalled();
});

it('uses safeUnref on flush timer to not block process exit', () => {
const safeUnrefSpy = vi.spyOn(timerModule, 'safeUnref');

const options = getDefaultTestClientOptions({
dsn: PUBLIC_DSN,
enableLogs: true,
});
const client = new TestClient(options);
const scope = new Scope();
scope.setClient(client);

// Capture a log which will start the flush timer
_INTERNAL_captureLog({ message: 'test log', level: 'info' }, scope);

// Verify safeUnref was called on the timer
expect(safeUnrefSpy).toHaveBeenCalledTimes(1);
expect(safeUnrefSpy).toHaveBeenCalledWith(expect.anything());

safeUnrefSpy.mockRestore();
});
});

describe('metric weight-based flushing', () => {
Expand Down Expand Up @@ -3142,6 +3164,26 @@ describe('Client', () => {

expect(sendEnvelopeSpy).toHaveBeenCalledTimes(1);
});

it('uses safeUnref on flush timer to not block process exit', () => {
const safeUnrefSpy = vi.spyOn(timerModule, 'safeUnref');

const options = getDefaultTestClientOptions({
dsn: PUBLIC_DSN,
});
const client = new TestClient(options);
const scope = new Scope();
scope.setClient(client);

// Capture a metric which will start the flush timer
_INTERNAL_captureMetric({ name: 'test_metric', value: 42, type: 'counter', attributes: {} }, { scope });

// Verify safeUnref was called on the timer
expect(safeUnrefSpy).toHaveBeenCalledTimes(1);
expect(safeUnrefSpy).toHaveBeenCalledWith(expect.anything());

safeUnrefSpy.mockRestore();
});
});

describe('promise buffer usage', () => {
Expand Down
Loading