Repository navigation
@angular/ssr: an aborted request rejects handle() but the render keeps running #34567
Description
Which @angular/* package(s) are the source of the bug?
platform-server
Is this a regression?
No
Description
AngularServerApp.handle() wraps handleRendering() in promiseWithAbort(…, request.signal, …) (packages/angular/ssr/src/app.ts). When the signal aborts, the returned promise rejects, but nothing stops the render: the platform renderAngular() created stays alive, its HTTP calls keep going, and the app goes on rendering a page nobody will receive.
This makes request.signal unusable as a render timeout. A server that caps renders (to answer 503 before the proxy's own timeout, and to limit how many renders run at once) gets its promise back on time while the work piles up in the background. With a slow backend, every abandoned render is still running when the next batch starts.
Reproduction: a route whose component issues an HttpClient GET to a backend that answers after 60 s. Call engine.handle(new Request(url, { signal: AbortSignal.timeout(5_000) })). handle() rejects at 5 s; the backend still sees the request run to completion, and any request the app makes after that response still arrives. On our app (22.2.2), three renders aborted at 50 s made 8 more backend calls once the backend answered.
Suggested fix
Pass the signal into renderAngular() and destroy the platform on abort, the same way it is destroyed after a normal render:
// utils/ng.ts, inside renderAngular(), after platformServer(...)
const onAbort = () => void asyncDestroyPlatform(platformRef);
signal?.addEventListener('abort', onAbort, { once: true });
// ...and remove the listener in the existing finally / destroy pathsThe rest already works: destroying the app clears PendingTasks, so whenStable() settles; the applicationRef.destroyed check added in 730ae66 ("handle platform destruction during rendering") returns { hasNavigationError: true }; and FetchBackend aborts its fetch when the destroyed app's subscriptions are torn down.
Workaround (what we ship now), in app.config.server.ts:
provideEnvironmentInitializer(() => {
const signal = inject(REQUEST, { optional: true })?.signal;
if (!signal) return;
const platform = inject(PlatformRef);
const destroy = () => setTimeout(() => platform.destroyed || platform.destroy());
if (signal.aborted) return destroy();
signal.addEventListener('abort', destroy, { once: true });
inject(DestroyRef).onDestroy(() => signal.removeEventListener('abort', destroy));
});Please provide a link to a minimal reproduction of the bug
No response
Please provide the exception or error you saw
Please provide the environment you discovered this bug in (run ng version)
Angular CLI : 22.2.2
Angular : 22.2.2
Node.js : 24.22.1
Package Manager : npm 11.19.1
Operating System : darwin arm64
┌───────────────────────────────────┬───────────────────┬───────────────────┐
│ Package │ Installed Version │ Requested Version │
├───────────────────────────────────┼───────────────────┼───────────────────┤
│ @angular/aria │ 22.2.2 │ ^22.2.2 │
│ @angular/build │ 22.2.2 │ ^22.2.2 │
│ @angular/cdk │ 22.2.2 │ ^22.2.2 │
│ @angular/cli │ 22.2.2 │ ^22.2.2 │
│ @angular/common │ 22.2.2 │ ^22.2.2 │
│ @angular/compiler │ 22.2.2 │ ^22.2.2 │
│ @angular/compiler-cli │ 22.2.2 │ ^22.2.2 │
│ @angular/core │ 22.2.2 │ ^22.2.2 │
│ @angular/elements │ 22.2.2 │ ^22.2.2 │
│ @angular/forms │ 22.2.2 │ ^22.2.2 │
│ @angular/platform-browser │ 22.2.2 │ ^22.2.2 │
│ @angular/platform-browser-dynamic │ 22.2.2 │ ^22.2.2 │
│ @angular/platform-server │ 22.2.2 │ ^22.2.2 │
│ @angular/router │ 22.2.2 │ ^22.2.2 │
│ @angular/ssr │ 22.2.2 │ ^22.2.2 │
│ ng-packagr │ 22.1.1 │ ^22.1.1 │
│ rxjs │ 7.8.2 │ ~7.8.2 │
│ typescript │ 6.0.3 │ ~6.0.3 │
│ vitest │ 4.1.9 │ ^4.1.9 │
└───────────────────────────────────┴───────────────────┴───────────────────┘
Anything else?
No response
Activity
fix(@angular/ssr): destroy platform when request is aborted during re…