Skip to content

Commit 600df15

Browse files
authored
fix: Schedule the execution of the finish to let all the spans being closed first (#3022)
1 parent 40f5201 commit 600df15

File tree

2 files changed

+42
-8
lines changed

2 files changed

+42
-8
lines changed

packages/node/src/handlers.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,8 +63,11 @@ export function tracingHandler(): (
6363
(res as any).__sentry_transaction = transaction;
6464

6565
res.once('finish', () => {
66-
transaction.setHttpStatus(res.statusCode);
67-
transaction.finish();
66+
// We schedule the immediate execution of the `finish` to let all the spans being closed first.
67+
setImmediate(() => {
68+
transaction.setHttpStatus(res.statusCode);
69+
transaction.finish();
70+
});
6871
});
6972

7073
next();

packages/node/test/handlers.test.ts

Lines changed: 37 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -247,7 +247,7 @@ describe('tracingHandler', () => {
247247
expect(transaction).toEqual(expect.objectContaining({ name: `GET ${urlString}`, op: 'http.server' }));
248248
});
249249

250-
it('pulls status code from the response', async () => {
250+
it('pulls status code from the response', done => {
251251
const transaction = new Transaction({ name: 'mockTransaction' });
252252
jest.spyOn(sentryCore, 'startTransaction').mockReturnValue(transaction as TransactionType);
253253
const finishTransaction = jest.spyOn(transaction, 'finish');
@@ -256,9 +256,12 @@ describe('tracingHandler', () => {
256256
res.statusCode = 200;
257257
res.emit('finish');
258258

259-
expect(finishTransaction).toHaveBeenCalled();
260-
expect(transaction.status).toBe(SpanStatus.Ok);
261-
expect(transaction.tags).toEqual(expect.objectContaining({ 'http.status_code': '200' }));
259+
setImmediate(() => {
260+
expect(finishTransaction).toHaveBeenCalled();
261+
expect(transaction.status).toBe(SpanStatus.Ok);
262+
expect(transaction.tags).toEqual(expect.objectContaining({ 'http.status_code': '200' }));
263+
done();
264+
});
262265
});
263266

264267
it('strips query string from request path', () => {
@@ -291,14 +294,42 @@ describe('tracingHandler', () => {
291294
expect(transaction?.name).toBe(`GET ${urlString}`);
292295
});
293296

294-
it('closes the transaction when request processing is done', () => {
297+
it('closes the transaction when request processing is done', done => {
298+
const transaction = new Transaction({ name: 'mockTransaction' });
299+
jest.spyOn(sentryCore, 'startTransaction').mockReturnValue(transaction as TransactionType);
300+
const finishTransaction = jest.spyOn(transaction, 'finish');
301+
302+
sentryTracingMiddleware(req, res, next);
303+
res.emit('finish');
304+
305+
setImmediate(() => {
306+
expect(finishTransaction).toHaveBeenCalled();
307+
done();
308+
});
309+
});
310+
311+
it('lets all spans being finished before calling `finish` itself, despite being registered to `res.finish` event first', done => {
295312
const transaction = new Transaction({ name: 'mockTransaction' });
313+
transaction.initSpanRecorder();
314+
const span = transaction.startChild({
315+
description: 'reallyCoolHandler',
316+
op: 'middleware',
317+
});
296318
jest.spyOn(sentryCore, 'startTransaction').mockReturnValue(transaction as TransactionType);
319+
const finishSpan = jest.spyOn(span, 'finish');
297320
const finishTransaction = jest.spyOn(transaction, 'finish');
298321

299322
sentryTracingMiddleware(req, res, next);
323+
res.once('finish', () => {
324+
span.finish();
325+
});
300326
res.emit('finish');
301327

302-
expect(finishTransaction).toHaveBeenCalled();
328+
setImmediate(() => {
329+
expect(finishSpan).toHaveBeenCalled();
330+
expect(finishTransaction).toHaveBeenCalled();
331+
expect(span.endTimestamp).toBeLessThan(transaction.endTimestamp!);
332+
done();
333+
});
303334
});
304335
}); // end describe('tracingHandler')

0 commit comments

Comments
 (0)