Repository navigation
perf(core): avoid unnecessary aborts and reuse abort reason during resource teardown - #71557
Conversation
| options.injector ?? inject(Injector), | ||
| options.id as StateKey<T>, | ||
| undefined, | ||
| isStreamingResourceOptions(options), |
There was a problem hiding this comment.
Should we set this for httpResource() as well? https://github.com/angular/angular/blob/main/packages/common/http/src/resource.ts#L272
There was a problem hiding this comment.
HttpResourceImpl extends ResourceImpl this is already being taken care of.
There was a problem hiding this comment.
I meant should we not pass in isStreaming (since it defaults to false) to super() in the HttpResourceImpl constructor?
There was a problem hiding this comment.
The httpResource isn't a streaming resource per se, so false is the right value.
There was a problem hiding this comment.
I see. I was confused by the naming because it implements and passes a getInitialStream calback, but does that refer to a different kind of stream?
…source teardown
When tearing down component trees containing resources, `abortInProgressLoad` previously invoked `pendingController.abort()` without arguments for every resource.
This caused two performance issues:
1. Omitting the reason parameter in `abort()` instantiates a new `DOMException` ('AbortError') each time. In engines like V8, this triggers a full call stack capture (`CaptureStackTrace()`), whose cost scales linearly with call stack depth. On deep component tree teardown, this can take up to 20-40ms.
2. `ResourceImpl` retained its `pendingController` even after a non-streaming load had completed, unnecessarily executing `abort()` for operations that were no longer in flight.
This change:
- Reuses a shared `AbortError` `DOMException` reason when aborting loads, preventing repeated V8 stack captures while maintaining full compatibility with consumers expecting a standard `DOMException`.
- Clears `pendingController` upon completion for non-streaming resources, avoiding unnecessary abort work when tearing down already settled resources. Streaming resources (such as `rxResource`) continue to receive abort signals on destroy to clean up active subscriptions.
Fixes angular#71422
8cee1fc to
09fe7ec
Compare
PR Checklist
Please check if your PR fulfills the following requirements:
PR Type
What kind of change does this PR introduce?
What is the current behavior?
When tearing down a view tree containing resources (e.g. during route navigation),
ResourceImpl.destroy()invokesabortInProgressLoad(), callingpendingController.abort()without arguments.This leads to significant teardown latency due to two factors:
abort()with no reason instantiates a new"AbortError"DOMException. In V8/Chromium,DOMExceptionconstruction triggersv8::Exception::CaptureStackTrace(). The cost of walking execution stack frames scales linearly with call stack depth (~140 µs per call at depth 300 on throttled CPUs). When destroying dozens of resources in deep view trees, this adds 20–40ms of main thread blockage.ResourceImplretains itspendingControllereven after a one-shot (non-streaming) loader has settled (resolved or errored). When the component is eventually destroyed,abort()is executed on controllers whose operations finished long ago.Issue Number: #71422
What is the new behavior?
abortInProgressLoad()now passes a shared, lazily-initializedDOMException('The operation was aborted.', 'AbortError'). Passing an existingDOMExceptionprevents V8 from capturing a fresh call stack on every call while preserving complete Web API and consumer compatibility (err.name === 'AbortError',err instanceof DOMException).For non-streaming resources (
PromiseResourceOptions), once the loader promise settles (infinally),pendingControlleris cleared if it belongs to that settled load. When the resource is destroyed later, teardown does not perform unnecessary abort work or event dispatch. Streaming resources (likerxResourceorStreamingResourceOptions) retain their controller so active subscriptions and streams continue to be cleanly aborted on destroy.Does this PR introduce a breaking change?
Other information
Draft PR proposing the two-pronged optimization discussed in #71422 and #71423.