Skip to content

Commit 5e4e719

Browse files
feat(tracing): Expose cancelIdleTimeout and add option to make it permanent (#7236)
1 parent 14b6fca commit 5e4e719

File tree

2 files changed

+74
-11
lines changed

2 files changed

+74
-11
lines changed

packages/core/src/tracing/idletransaction.ts

Lines changed: 35 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,9 @@ export class IdleTransaction extends Transaction {
6969
// We should not use heartbeat if we finished a transaction
7070
private _finished: boolean = false;
7171

72+
// Idle timeout was canceled and we should finish the transaction with the last span end.
73+
private _idleTimeoutCanceledPermanently: boolean = false;
74+
7275
private readonly _beforeFinishCallbacks: BeforeFinishCallback[] = [];
7376

7477
/**
@@ -104,7 +107,7 @@ export class IdleTransaction extends Transaction {
104107
_idleHub.configureScope(scope => scope.setSpan(this));
105108
}
106109

107-
this._startIdleTimeout();
110+
this._restartIdleTimeout();
108111
setTimeout(() => {
109112
if (!this._finished) {
110113
this.setStatus('deadline_exceeded');
@@ -203,20 +206,37 @@ export class IdleTransaction extends Transaction {
203206
}
204207

205208
/**
206-
* Cancels the existing idletimeout, if there is one
209+
* Cancels the existing idle timeout, if there is one.
210+
* @param restartOnChildSpanChange Default is `true`.
211+
* If set to false the transaction will end
212+
* with the last child span.
207213
*/
208-
private _cancelIdleTimeout(): void {
214+
public cancelIdleTimeout(
215+
endTimestamp?: Parameters<IdleTransaction['finish']>[0],
216+
{
217+
restartOnChildSpanChange,
218+
}: {
219+
restartOnChildSpanChange?: boolean;
220+
} = {
221+
restartOnChildSpanChange: true,
222+
},
223+
): void {
209224
if (this._idleTimeoutID) {
210225
clearTimeout(this._idleTimeoutID);
211226
this._idleTimeoutID = undefined;
227+
this._idleTimeoutCanceledPermanently = restartOnChildSpanChange === false;
228+
229+
if (Object.keys(this.activities).length === 0 && this._idleTimeoutCanceledPermanently) {
230+
this.finish(endTimestamp);
231+
}
212232
}
213233
}
214234

215235
/**
216-
* Creates an idletimeout
236+
* Restarts idle timeout, if there is no running idle timeout it will start one.
217237
*/
218-
private _startIdleTimeout(endTimestamp?: Parameters<IdleTransaction['finish']>[0]): void {
219-
this._cancelIdleTimeout();
238+
private _restartIdleTimeout(endTimestamp?: Parameters<IdleTransaction['finish']>[0]): void {
239+
this.cancelIdleTimeout();
220240
this._idleTimeoutID = setTimeout(() => {
221241
if (!this._finished && Object.keys(this.activities).length === 0) {
222242
this.finish(endTimestamp);
@@ -229,7 +249,7 @@ export class IdleTransaction extends Transaction {
229249
* @param spanId The span id that represents the activity
230250
*/
231251
private _pushActivity(spanId: string): void {
232-
this._cancelIdleTimeout();
252+
this.cancelIdleTimeout();
233253
__DEBUG_BUILD__ && logger.log(`[Tracing] pushActivity: ${spanId}`);
234254
this.activities[spanId] = true;
235255
__DEBUG_BUILD__ && logger.log('[Tracing] new activities count', Object.keys(this.activities).length);
@@ -248,10 +268,14 @@ export class IdleTransaction extends Transaction {
248268
}
249269

250270
if (Object.keys(this.activities).length === 0) {
251-
// We need to add the timeout here to have the real endtimestamp of the transaction
252-
// Remember timestampWithMs is in seconds, timeout is in ms
253-
const endTimestamp = timestampWithMs() + this._idleTimeout / 1000;
254-
this._startIdleTimeout(endTimestamp);
271+
const endTimestamp = timestampWithMs();
272+
if (this._idleTimeoutCanceledPermanently) {
273+
this.finish(endTimestamp);
274+
} else {
275+
// We need to add the timeout here to have the real endtimestamp of the transaction
276+
// Remember timestampWithMs is in seconds, timeout is in ms
277+
this._restartIdleTimeout(endTimestamp + this._idleTimeout / 1000);
278+
}
255279
}
256280
}
257281

packages/tracing/test/idletransaction.test.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,45 @@ describe('IdleTransaction', () => {
247247
});
248248
});
249249

250+
describe('cancelIdleTimeout', () => {
251+
it('permanent idle timeout cancel finishes transaction if there are no activities', () => {
252+
const idleTimeout = 10;
253+
const transaction = new IdleTransaction({ name: 'foo', startTimestamp: 1234 }, hub, idleTimeout);
254+
transaction.initSpanRecorder(10);
255+
256+
const span = transaction.startChild({});
257+
span.finish();
258+
259+
jest.advanceTimersByTime(2);
260+
261+
transaction.cancelIdleTimeout(undefined, { restartOnChildSpanChange: false });
262+
263+
expect(transaction.endTimestamp).toBeDefined();
264+
});
265+
266+
it('default idle cancel timeout is restarted by child span change', () => {
267+
const idleTimeout = 10;
268+
const transaction = new IdleTransaction({ name: 'foo', startTimestamp: 1234 }, hub, idleTimeout);
269+
transaction.initSpanRecorder(10);
270+
271+
const span = transaction.startChild({});
272+
span.finish();
273+
274+
jest.advanceTimersByTime(2);
275+
276+
transaction.cancelIdleTimeout();
277+
278+
const span2 = transaction.startChild({});
279+
span2.finish();
280+
281+
jest.advanceTimersByTime(8);
282+
expect(transaction.endTimestamp).toBeUndefined();
283+
284+
jest.advanceTimersByTime(2);
285+
expect(transaction.endTimestamp).toBeDefined();
286+
});
287+
});
288+
250289
describe('heartbeat', () => {
251290
it('does not mark transaction as `DeadlineExceeded` if idle timeout has not been reached', () => {
252291
// 20s to exceed 3 heartbeats

0 commit comments

Comments
 (0)