Skip to content

Commit b607f1e

Browse files
authoredFeb 7, 2024
fix(vitest): remove excessive listeners when running without isolation, don't reset the state (#5132)
1 parent 6f5b42b commit b607f1e

File tree

3 files changed

+31
-19
lines changed

3 files changed

+31
-19
lines changed
 

‎packages/vitest/src/runtime/execute.ts

+26-11
Original file line numberDiff line numberDiff line change
@@ -42,14 +42,11 @@ export interface ContextExecutorOptions {
4242

4343
const bareVitestRegexp = /^@?vitest(\/|$)/
4444

45-
export async function startVitestExecutor(options: ContextExecutorOptions) {
46-
// @ts-expect-error injected untyped global
47-
const state = (): WorkerGlobalState => globalThis.__vitest_worker__ || options.state
48-
const rpc = () => state().rpc
45+
const dispose: (() => void)[] = []
4946

50-
process.exit = (code = process.exitCode || 0): never => {
51-
throw new Error(`process.exit unexpectedly called with "${code}"`)
52-
}
47+
function listenForErrors(state: () => WorkerGlobalState) {
48+
dispose.forEach(fn => fn())
49+
dispose.length = 0
5350

5451
function catchError(err: unknown, type: string) {
5552
const worker = state()
@@ -60,13 +57,31 @@ export async function startVitestExecutor(options: ContextExecutorOptions) {
6057
error.VITEST_TEST_PATH = relative(state().config.root, worker.filepath)
6158
error.VITEST_AFTER_ENV_TEARDOWN = worker.environmentTeardownRun
6259
}
63-
rpc().onUnhandledError(error, type)
60+
state().rpc.onUnhandledError(error, type)
6461
}
6562

66-
process.setMaxListeners(25)
63+
const uncaughtException = (e: Error) => catchError(e, 'Uncaught Exception')
64+
const unhandledRejection = (e: Error) => catchError(e, 'Unhandled Rejection')
65+
66+
process.on('uncaughtException', uncaughtException)
67+
process.on('unhandledRejection', unhandledRejection)
68+
69+
dispose.push(() => {
70+
process.off('uncaughtException', uncaughtException)
71+
process.off('unhandledRejection', unhandledRejection)
72+
})
73+
}
74+
75+
export async function startVitestExecutor(options: ContextExecutorOptions) {
76+
// @ts-expect-error injected untyped global
77+
const state = (): WorkerGlobalState => globalThis.__vitest_worker__ || options.state
78+
const rpc = () => state().rpc
79+
80+
process.exit = (code = process.exitCode || 0): never => {
81+
throw new Error(`process.exit unexpectedly called with "${code}"`)
82+
}
6783

68-
process.on('uncaughtException', e => catchError(e, 'Uncaught Exception'))
69-
process.on('unhandledRejection', e => catchError(e, 'Unhandled Rejection'))
84+
listenForErrors(state)
7085

7186
const getTransformMode = () => {
7287
return state().environment.transformMode ?? 'ssr'

‎packages/vitest/src/runtime/rpc.ts

+4
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,10 @@ export function createRuntimeRpc(options: Pick<BirpcOptions<RuntimeRPC>, 'on' |
7373
if (functionName === 'fetch' || functionName === 'transform' || functionName === 'resolveId')
7474
message += ` with "${JSON.stringify(args)}"`
7575

76+
// JSON.stringify cannot serialize Error instances
77+
if (functionName === 'onUnhandledError')
78+
message += ` with "${args[0]?.message || args[0]}"`
79+
7680
throw new Error(message)
7781
},
7882
...options,

‎packages/vitest/src/runtime/worker.ts

+1-8
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import { workerId as poolId } from 'tinypool'
33
import { ModuleCacheMap } from 'vite-node/client'
44
import type { ContextRPC } from '../types/rpc'
55
import { loadEnvironment } from '../integrations/env/loader'
6-
import type { WorkerGlobalState } from '../types/worker'
76
import { isChildProcess, setProcessTitle } from '../utils/base'
87
import { setupInspect } from './inspector'
98
import { createRuntimeRpc, rpcDone } from './rpc'
@@ -21,8 +20,6 @@ export async function run(ctx: ContextRPC) {
2120
process.env.VITEST_WORKER_ID = String(ctx.workerId)
2221
process.env.VITEST_POOL_ID = String(poolId)
2322

24-
let state: WorkerGlobalState | null = null
25-
2623
try {
2724
// worker is a filepath or URL to a file that exposes a default export with "getRpcOptions" and "runTests" methods
2825
if (ctx.worker[0] === '.')
@@ -46,7 +43,7 @@ export async function run(ctx: ContextRPC) {
4643
if (ctx.environment.transformMode)
4744
environment.transformMode = ctx.environment.transformMode
4845

49-
state = {
46+
const state = {
5047
ctx,
5148
// here we create a new one, workers can reassign this if they need to keep it non-isolated
5249
moduleCache: new ModuleCacheMap(),
@@ -70,9 +67,5 @@ export async function run(ctx: ContextRPC) {
7067
finally {
7168
await rpcDone().catch(() => {})
7269
inspectorCleanup()
73-
if (state) {
74-
state.environment = null as any
75-
state = null
76-
}
7770
}
7871
}

0 commit comments

Comments
 (0)
Please sign in to comment.