diff --git a/packages/rum/src/domain/profiling/profiler.spec.ts b/packages/rum/src/domain/profiling/profiler.spec.ts index 1e69b1b92b..67f520cea7 100644 --- a/packages/rum/src/domain/profiling/profiler.spec.ts +++ b/packages/rum/src/domain/profiling/profiler.spec.ts @@ -631,6 +631,35 @@ describe('profiler', () => { expect(profiler.isStopped()).toBe(true) }) + it('should not restart profiling on session renewal if user called stop after session expiration', async () => { + const { profiler, profilingContextManager } = setupProfiler() + + profiler.start() + + // Wait for start of collection. + await waitForBoolean(() => profiler.isRunning()) + + expect(profilingContextManager.get()?.status).toBe('running') + + // Session expires (sync - state changes immediately) + lifeCycle.notify(LifeCycleEventType.SESSION_EXPIRED) + + expect(profiler.isStopped()).toBe(true) + expect(profilingContextManager.get()?.status).toBe('stopped') + + // User explicitly stops the profiler after session expiration + profiler.stop() + + expect(profiler.isStopped()).toBe(true) + + // Session is renewed — start() is called synchronously, so no need to wait + lifeCycle.notify(LifeCycleEventType.SESSION_RENEWED) + + // Profiler should remain stopped - user's explicit stop should take priority over session expiration + expect(profiler.isStopped()).toBe(true) + expect(profilingContextManager.get()?.status).toBe('stopped') + }) + it('should restart profiling when session expires while paused and then renews', async () => { const { profiler, profilingContextManager } = setupProfiler() diff --git a/packages/rum/src/domain/profiling/profiler.ts b/packages/rum/src/domain/profiling/profiler.ts index aed29e78e2..61b82213c2 100644 --- a/packages/rum/src/domain/profiling/profiler.ts +++ b/packages/rum/src/domain/profiling/profiler.ts @@ -111,8 +111,9 @@ export function createRumProfiler( // Stop current profiler instance (data collection happens async in background) stopProfilerInstance(reason) - // Cleanup global listeners + // Cleanup global listeners and reset the array to prevent accumulation across start/stop cycles globalCleanupTasks.forEach((task) => task()) + globalCleanupTasks.length = 0 // Update Profiling status once the Profiler has been stopped. profilingContextManager.set({ status: 'stopped', error_reason: undefined }) @@ -254,12 +255,17 @@ export function createRumProfiler( } function stopProfilerInstance(stateReason: RumProfilerStoppedInstance['stateReason']) { - if (instance.state === 'paused') { - // If paused, profiler data was already collected during pause, just update state - instance = { state: 'stopped', stateReason } - return - } if (instance.state !== 'running') { + if ( + // If paused, profiler data was already collected during pause, just update state + instance.state === 'paused' || + // Update stateReason when already stopped and the user explicitly stops the profiler, + // so that SESSION_RENEWED does not override the user's intent. + (instance.state === 'stopped' && stateReason === 'stopped-by-user') + ) { + instance = { state: 'stopped', stateReason } + } + return }