Skip to content

Commit ab65d8f

Browse files
committed
fix(@angular/build): add bounded timeout to vitest executor disposal
When disposing the VitestExecutor in non-watch runs, `vitest.close()` can hang if a Vitest worker pool process or thread fails to terminate cleanly. Calling `process.exit()` in a builder runner is undesirable as it forcibly kills the host Node.js process and aborts any downstream Architect tasks or reporters. This change wraps `this.vitest.close()` in a `Promise.race` with an unref'd `setTimeout`. If `close()` stalls beyond 10 seconds, a warning is logged and disposal finishes cleanly, allowing host process execution and downstream logic to proceed. Fixes #32832
1 parent 10dc30f commit ab65d8f

1 file changed

Lines changed: 29 additions & 1 deletion

File tree

  • packages/angular/build/src/builders/unit-test/runners/vitest

packages/angular/build/src/builders/unit-test/runners/vitest/executor.ts

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import type { BuilderContext, BuilderOutput } from '@angular-devkit/architect';
1010
import assert from 'node:assert';
1111
import path from 'node:path';
12+
import { setTimeout } from 'node:timers/promises';
1213
import type * as Vite from 'vite' with {
1314
'resolution-mode': 'import',
1415
};
@@ -207,8 +208,35 @@ export class VitestExecutor implements TestExecutor {
207208
}
208209

209210
async [Symbol.asyncDispose](): Promise<void> {
211+
if (!this.vitest) {
212+
return;
213+
}
214+
210215
this.debugLog(DebugLogLevel.Info, 'Disposing VitestExecutor: Closing Vitest instance.');
211-
await this.vitest?.close();
216+
217+
const controller = new AbortController();
218+
const timeoutMs = 10_000;
219+
220+
try {
221+
await Promise.race([
222+
this.vitest.close(),
223+
setTimeout(timeoutMs, undefined, { signal: controller.signal, ref: false })
224+
.then(() => {
225+
this.logger.warn(
226+
`Vitest instance failed to close cleanly within ${timeoutMs}ms. Continuing teardown...`,
227+
);
228+
})
229+
.catch(() => {
230+
// Suppress AbortError triggered by controller.abort() when close() resolves first
231+
}),
232+
]);
233+
} catch (error: unknown) {
234+
assertIsError(error);
235+
this.logger.error(`An error occurred while closing Vitest instance: ${error.message}`);
236+
} finally {
237+
controller.abort();
238+
}
239+
212240
this.debugLog(DebugLogLevel.Info, 'Vitest instance closed.');
213241
}
214242

0 commit comments

Comments
 (0)