From 780e37241fc6dc69da355704e85ea40670ee134e Mon Sep 17 00:00:00 2001 From: JoostK Date: Fri, 1 Aug 2025 21:55:02 +0200 Subject: [PATCH] refactor(core): let the profiler handle asymmetric events leniently Although the prior commit has made more profiler events guaranteed symmetric through the use of finally-blocks, there continue to be some situations that could potentially result in asymmetric events, e.g. application bootstrap doesn't guarantee symmetric events. This commit makes the profiler lenient to these situations by unrolling the stack past the asymmetric event data, eventually reaching the expected start event. (cherry picked from commit da9911f2b434d3f6269e1e4fb90ea3fdbe57a38e) --- .../debug/chrome_dev_tools_performance.ts | 14 +++++------ .../chrome_dev_tools_performance_spec.ts | 25 ++++++++++++++++++- 2 files changed, 31 insertions(+), 8 deletions(-) diff --git a/packages/core/src/render3/debug/chrome_dev_tools_performance.ts b/packages/core/src/render3/debug/chrome_dev_tools_performance.ts index d9312981ea0..7b72292a1bc 100644 --- a/packages/core/src/render3/debug/chrome_dev_tools_performance.ts +++ b/packages/core/src/render3/debug/chrome_dev_tools_performance.ts @@ -71,14 +71,14 @@ function measureEnd( entryName: string, color: DevToolsColor, ) { - const top = eventsStack.pop(); + let top: stackEntry | undefined; - assertDefined(top, 'Profiling error: could not find start event entry ' + startEvent); - assertEqual( - top[0], - startEvent, - `Profiling error: expected to see ${startEvent} event but got ${top[0]}`, - ); + // The stack may be asymmetric when an end event for a prior start event is missing (e.g. when an exception + // has occurred), unroll the stack until a matching item has been found in that case. + do { + top = eventsStack.pop(); + assertDefined(top, 'Profiling error: could not find start event entry ' + startEvent); + } while (top[0] !== startEvent); console.timeStamp( entryName, diff --git a/packages/core/test/acceptance/chrome_dev_tools_performance_spec.ts b/packages/core/test/acceptance/chrome_dev_tools_performance_spec.ts index d6b2c2c102b..df500959a72 100644 --- a/packages/core/test/acceptance/chrome_dev_tools_performance_spec.ts +++ b/packages/core/test/acceptance/chrome_dev_tools_performance_spec.ts @@ -6,8 +6,10 @@ * found in the LICENSE file at https://angular.dev/license */ -import {enableProfiling} from '../../src/render3/debug/chrome_dev_tools_performance'; import {Component, HostAttributeToken, inject, Inject} from '../../src/core'; +import {enableProfiling} from '../../src/render3/debug/chrome_dev_tools_performance'; +import {profiler} from '../../src/render3/profiler'; +import {ProfilerEvent} from '../../src/render3/profiler_types'; import {TestBed} from '../../testing'; describe('Chrome DevTools Performance integration', () => { @@ -59,5 +61,26 @@ describe('Chrome DevTools Performance integration', () => { stopProfiling(); } }); + + it('should not crash when asymmetric events are processed', () => { + const timeStampSpy = spyOn(console, 'timeStamp'); + const stopProfiling = enableProfiling(); + + try { + profiler(ProfilerEvent.ChangeDetectionSyncStart); + profiler(ProfilerEvent.ChangeDetectionStart); + profiler(ProfilerEvent.ChangeDetectionSyncEnd); + + const calls = timeStampSpy.calls.all(); + expect(calls.length).toBe(3); + + const [syncStart, cdStart, syncEnd] = calls; + expect(syncStart.args[0]).toMatch(/^Event_/); + expect(cdStart.args[0]).toMatch(/^Event_/); + expect(syncEnd.args[0]).toMatch(/^Synchronization /); + } finally { + stopProfiling(); + } + }); }); });