Skip to content

Commit deb12d5

Browse files
authored
fix: Cleanup hooks when they are not used anymore (#12852)
Small optimization using the new hook cleanup capabilities to remove unused hooks. Ref PR to do this in angular: #12786
1 parent 17bf308 commit deb12d5

File tree

6 files changed

+62
-45
lines changed

6 files changed

+62
-45
lines changed

dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/navigation/test.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,14 @@ sentryTest('error after navigation has navigation traceId', async ({ getLocalTes
7474
sentryTest.skip();
7575
}
7676

77+
await page.route('https://dsn.ingest.sentry.io/**/*', route => {
78+
return route.fulfill({
79+
status: 200,
80+
contentType: 'application/json',
81+
body: JSON.stringify({ id: 'test-id' }),
82+
});
83+
});
84+
7785
const url = await getLocalTestUrl({ testDir: __dirname });
7886

7987
// ensure pageload transaction is finished

packages/browser/src/tracing/browserTracingIntegration.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -288,7 +288,7 @@ export const browserTracingIntegration = ((_options: Partial<BrowserTracingOptio
288288
return;
289289
}
290290

291-
if (activeSpan) {
291+
if (activeSpan && !spanToJSON(activeSpan).timestamp) {
292292
DEBUG_BUILD && logger.log(`[Tracing] Finishing current root span with op: ${spanToJSON(activeSpan).op}`);
293293
// If there's an open transaction on the scope, we need to finish it before creating an new one.
294294
activeSpan.end();
@@ -304,7 +304,7 @@ export const browserTracingIntegration = ((_options: Partial<BrowserTracingOptio
304304
return;
305305
}
306306

307-
if (activeSpan) {
307+
if (activeSpan && !spanToJSON(activeSpan).timestamp) {
308308
DEBUG_BUILD && logger.log(`[Tracing] Finishing current root span with op: ${spanToJSON(activeSpan).op}`);
309309
// If there's an open transaction on the scope, we need to finish it before creating an new one.
310310
activeSpan.end();

packages/core/src/baseclient.ts

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -459,27 +459,19 @@ export abstract class BaseClient<O extends ClientOptions> implements Client<O> {
459459

460460
/** @inheritdoc */
461461
public on(hook: string, callback: unknown): () => void {
462-
// Note that the code below, with nullish coalescing assignment,
463-
// may reduce the code, so it may be switched to when Node 14 support
464-
// is dropped (the `??=` operator is supported since Node 15).
465-
// (this._hooks[hook] ??= []).push(callback);
466-
if (!this._hooks[hook]) {
467-
this._hooks[hook] = [];
468-
}
462+
const hooks = (this._hooks[hook] = this._hooks[hook] || []);
469463

470464
// @ts-expect-error We assue the types are correct
471-
this._hooks[hook].push(callback);
465+
hooks.push(callback);
472466

473467
// This function returns a callback execution handler that, when invoked,
474468
// deregisters a callback. This is crucial for managing instances where callbacks
475469
// need to be unregistered to prevent self-referencing in callback closures,
476470
// ensuring proper garbage collection.
477471
return () => {
478-
const hooks = this._hooks[hook];
479-
480-
if (hooks) {
481-
// @ts-expect-error We assue the types are correct
482-
const cbIndex = hooks.indexOf(callback);
472+
// @ts-expect-error We assue the types are correct
473+
const cbIndex = hooks.indexOf(callback);
474+
if (cbIndex > -1) {
483475
hooks.splice(cbIndex, 1);
484476
}
485477
};

packages/core/src/tracing/idleSpan.ts

Lines changed: 38 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,8 @@ export function startIdleSpan(startSpanOptions: StartSpanOptions, options: Parti
9696

9797
let _autoFinishAllowed: boolean = !options.disableAutoFinish;
9898

99+
const _cleanupHooks: (() => void)[] = [];
100+
99101
const {
100102
idleTimeout = TRACING_DEFAULTS.idleTimeout,
101103
finalTimeout = TRACING_DEFAULTS.finalTimeout,
@@ -240,6 +242,8 @@ export function startIdleSpan(startSpanOptions: StartSpanOptions, options: Parti
240242
_finished = true;
241243
activities.clear();
242244

245+
_cleanupHooks.forEach(cleanup => cleanup());
246+
243247
_setSpanForScope(scope, previousActiveSpan);
244248

245249
const spanJSON = spanToJSON(span);
@@ -298,41 +302,47 @@ export function startIdleSpan(startSpanOptions: StartSpanOptions, options: Parti
298302
}
299303
}
300304

301-
client.on('spanStart', startedSpan => {
302-
// If we already finished the idle span,
303-
// or if this is the idle span itself being started,
304-
// or if the started span has already been closed,
305-
// we don't care about it for activity
306-
if (_finished || startedSpan === span || !!spanToJSON(startedSpan).timestamp) {
307-
return;
308-
}
305+
_cleanupHooks.push(
306+
client.on('spanStart', startedSpan => {
307+
// If we already finished the idle span,
308+
// or if this is the idle span itself being started,
309+
// or if the started span has already been closed,
310+
// we don't care about it for activity
311+
if (_finished || startedSpan === span || !!spanToJSON(startedSpan).timestamp) {
312+
return;
313+
}
309314

310-
const allSpans = getSpanDescendants(span);
315+
const allSpans = getSpanDescendants(span);
311316

312-
// If the span that was just started is a child of the idle span, we should track it
313-
if (allSpans.includes(startedSpan)) {
314-
_pushActivity(startedSpan.spanContext().spanId);
315-
}
316-
});
317+
// If the span that was just started is a child of the idle span, we should track it
318+
if (allSpans.includes(startedSpan)) {
319+
_pushActivity(startedSpan.spanContext().spanId);
320+
}
321+
}),
322+
);
317323

318-
client.on('spanEnd', endedSpan => {
319-
if (_finished) {
320-
return;
321-
}
324+
_cleanupHooks.push(
325+
client.on('spanEnd', endedSpan => {
326+
if (_finished) {
327+
return;
328+
}
322329

323-
_popActivity(endedSpan.spanContext().spanId);
324-
});
330+
_popActivity(endedSpan.spanContext().spanId);
331+
}),
332+
);
325333

326-
client.on('idleSpanEnableAutoFinish', spanToAllowAutoFinish => {
327-
if (spanToAllowAutoFinish === span) {
328-
_autoFinishAllowed = true;
329-
_restartIdleTimeout();
334+
_cleanupHooks.push(
335+
client.on('idleSpanEnableAutoFinish', spanToAllowAutoFinish => {
336+
if (spanToAllowAutoFinish === span) {
337+
_autoFinishAllowed = true;
338+
_restartIdleTimeout();
330339

331-
if (activities.size) {
332-
_restartChildSpanTimeout();
340+
if (activities.size) {
341+
_restartChildSpanTimeout();
342+
}
333343
}
334-
}
335-
});
344+
}),
345+
);
336346

337347
// We only start the initial idle timeout if we are not delaying the auto finish
338348
if (!options.disableAutoFinish) {

packages/feedback/src/core/sendFeedback.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,12 +40,13 @@ export const sendFeedback: SendFeedback = (
4040
// After 5s, we want to clear anyhow
4141
const timeout = setTimeout(() => reject('Unable to determine if Feedback was correctly sent.'), 5_000);
4242

43-
client.on('afterSendEvent', (event: Event, response: TransportMakeRequestResponse) => {
43+
const cleanup = client.on('afterSendEvent', (event: Event, response: TransportMakeRequestResponse) => {
4444
if (event.event_id !== eventId) {
4545
return;
4646
}
4747

4848
clearTimeout(timeout);
49+
cleanup();
4950

5051
// Require valid status codes, otherwise can assume feedback was not sent successfully
5152
if (

packages/react/src/errorboundary.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ class ErrorBoundary extends React.Component<ErrorBoundaryProps, ErrorBoundarySta
7777
private readonly _openFallbackReportDialog: boolean;
7878

7979
private _lastEventId?: string;
80+
private _cleanupHook?: () => void;
8081

8182
public constructor(props: ErrorBoundaryProps) {
8283
super(props);
@@ -87,7 +88,7 @@ class ErrorBoundary extends React.Component<ErrorBoundaryProps, ErrorBoundarySta
8788
const client = getClient();
8889
if (client && props.showDialog) {
8990
this._openFallbackReportDialog = false;
90-
client.on('afterSendEvent', event => {
91+
this._cleanupHook = client.on('afterSendEvent', event => {
9192
if (!event.type && this._lastEventId && event.event_id === this._lastEventId) {
9293
showReportDialog({ ...props.dialogOptions, eventId: this._lastEventId });
9394
}
@@ -137,6 +138,11 @@ class ErrorBoundary extends React.Component<ErrorBoundaryProps, ErrorBoundarySta
137138
if (onUnmount) {
138139
onUnmount(error, componentStack, eventId);
139140
}
141+
142+
if (this._cleanupHook) {
143+
this._cleanupHook();
144+
this._cleanupHook = undefined;
145+
}
140146
}
141147

142148
public resetErrorBoundary: () => void = () => {

0 commit comments

Comments
 (0)