From b607f1eafaa2914dc03f87f072a3842acfea3f74 Mon Sep 17 00:00:00 2001 From: Vladimir Date: Wed, 7 Feb 2024 12:17:42 +0100 Subject: [PATCH] fix(vitest): remove excessive listeners when running without isolation, don't reset the state (#5132) --- packages/vitest/src/runtime/execute.ts | 37 ++++++++++++++++++-------- packages/vitest/src/runtime/rpc.ts | 4 +++ packages/vitest/src/runtime/worker.ts | 9 +------ 3 files changed, 31 insertions(+), 19 deletions(-) diff --git a/packages/vitest/src/runtime/execute.ts b/packages/vitest/src/runtime/execute.ts index a8542517c9ab..9dbf8f577c3e 100644 --- a/packages/vitest/src/runtime/execute.ts +++ b/packages/vitest/src/runtime/execute.ts @@ -42,14 +42,11 @@ export interface ContextExecutorOptions { const bareVitestRegexp = /^@?vitest(\/|$)/ -export async function startVitestExecutor(options: ContextExecutorOptions) { - // @ts-expect-error injected untyped global - const state = (): WorkerGlobalState => globalThis.__vitest_worker__ || options.state - const rpc = () => state().rpc +const dispose: (() => void)[] = [] - process.exit = (code = process.exitCode || 0): never => { - throw new Error(`process.exit unexpectedly called with "${code}"`) - } +function listenForErrors(state: () => WorkerGlobalState) { + dispose.forEach(fn => fn()) + dispose.length = 0 function catchError(err: unknown, type: string) { const worker = state() @@ -60,13 +57,31 @@ export async function startVitestExecutor(options: ContextExecutorOptions) { error.VITEST_TEST_PATH = relative(state().config.root, worker.filepath) error.VITEST_AFTER_ENV_TEARDOWN = worker.environmentTeardownRun } - rpc().onUnhandledError(error, type) + state().rpc.onUnhandledError(error, type) } - process.setMaxListeners(25) + const uncaughtException = (e: Error) => catchError(e, 'Uncaught Exception') + const unhandledRejection = (e: Error) => catchError(e, 'Unhandled Rejection') + + process.on('uncaughtException', uncaughtException) + process.on('unhandledRejection', unhandledRejection) + + dispose.push(() => { + process.off('uncaughtException', uncaughtException) + process.off('unhandledRejection', unhandledRejection) + }) +} + +export async function startVitestExecutor(options: ContextExecutorOptions) { + // @ts-expect-error injected untyped global + const state = (): WorkerGlobalState => globalThis.__vitest_worker__ || options.state + const rpc = () => state().rpc + + process.exit = (code = process.exitCode || 0): never => { + throw new Error(`process.exit unexpectedly called with "${code}"`) + } - process.on('uncaughtException', e => catchError(e, 'Uncaught Exception')) - process.on('unhandledRejection', e => catchError(e, 'Unhandled Rejection')) + listenForErrors(state) const getTransformMode = () => { return state().environment.transformMode ?? 'ssr' diff --git a/packages/vitest/src/runtime/rpc.ts b/packages/vitest/src/runtime/rpc.ts index c5723f2d6b51..4957613d69d8 100644 --- a/packages/vitest/src/runtime/rpc.ts +++ b/packages/vitest/src/runtime/rpc.ts @@ -73,6 +73,10 @@ export function createRuntimeRpc(options: Pick, 'on' | if (functionName === 'fetch' || functionName === 'transform' || functionName === 'resolveId') message += ` with "${JSON.stringify(args)}"` + // JSON.stringify cannot serialize Error instances + if (functionName === 'onUnhandledError') + message += ` with "${args[0]?.message || args[0]}"` + throw new Error(message) }, ...options, diff --git a/packages/vitest/src/runtime/worker.ts b/packages/vitest/src/runtime/worker.ts index 4c35bdc72cd3..41a3cb9cd4a1 100644 --- a/packages/vitest/src/runtime/worker.ts +++ b/packages/vitest/src/runtime/worker.ts @@ -3,7 +3,6 @@ import { workerId as poolId } from 'tinypool' import { ModuleCacheMap } from 'vite-node/client' import type { ContextRPC } from '../types/rpc' import { loadEnvironment } from '../integrations/env/loader' -import type { WorkerGlobalState } from '../types/worker' import { isChildProcess, setProcessTitle } from '../utils/base' import { setupInspect } from './inspector' import { createRuntimeRpc, rpcDone } from './rpc' @@ -21,8 +20,6 @@ export async function run(ctx: ContextRPC) { process.env.VITEST_WORKER_ID = String(ctx.workerId) process.env.VITEST_POOL_ID = String(poolId) - let state: WorkerGlobalState | null = null - try { // worker is a filepath or URL to a file that exposes a default export with "getRpcOptions" and "runTests" methods if (ctx.worker[0] === '.') @@ -46,7 +43,7 @@ export async function run(ctx: ContextRPC) { if (ctx.environment.transformMode) environment.transformMode = ctx.environment.transformMode - state = { + const state = { ctx, // here we create a new one, workers can reassign this if they need to keep it non-isolated moduleCache: new ModuleCacheMap(), @@ -70,9 +67,5 @@ export async function run(ctx: ContextRPC) { finally { await rpcDone().catch(() => {}) inspectorCleanup() - if (state) { - state.environment = null as any - state = null - } } }