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 da9911f2b4)
This commit is contained in:
JoostK 2025-08-01 21:55:02 +02:00 committed by Jessica Janiuk
parent 7ada2519f1
commit 780e37241f
2 changed files with 31 additions and 8 deletions

View file

@ -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,

View file

@ -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();
}
});
});
});