diff --git a/packages/build/src/plugins/spawn.ts b/packages/build/src/plugins/spawn.ts index 75073ad482..7e67a30a52 100644 --- a/packages/build/src/plugins/spawn.ts +++ b/packages/build/src/plugins/spawn.ts @@ -1,10 +1,11 @@ import { createRequire } from 'module' +import { platform } from 'os' import { fileURLToPath, pathToFileURL } from 'url' import { promisify } from 'util' import { trace } from '@opentelemetry/api' import { ExecaChildProcess, execaNode } from 'execa' -import { gte } from 'semver' +import { gte, satisfies } from 'semver' import { FeatureFlags } from '../core/feature_flags.js' import { addErrorInfo } from '../error/info.js' @@ -217,5 +218,17 @@ const stopPlugin = async function ({ }) childProcess.disconnect() } - childProcess.kill() + + // On Windows with Node 21+, there's a bug where attempting to kill a child process + // results in an EPERM error. Ignore the error in that case. + // See: https://github.com/nodejs/node/issues/51766 + // We also disable execa's `forceKillAfterTimeout` in this case + // which can cause unhandled rejection. + try { + childProcess.kill('SIGTERM', { + forceKillAfterTimeout: platform() === 'win32' && satisfies(process.version, '>=21') ? false : undefined, + }) + } catch { + // no-op + } } diff --git a/packages/edge-bundler/node/server/util.ts b/packages/edge-bundler/node/server/util.ts index 058018456a..e3c416c634 100644 --- a/packages/edge-bundler/node/server/util.ts +++ b/packages/edge-bundler/node/server/util.ts @@ -1,6 +1,9 @@ +import { platform } from 'os' + import { ExecaChildProcess } from 'execa' import fetch from 'node-fetch' import waitFor from 'p-wait-for' +import { satisfies } from 'semver' // 1 second const SERVER_KILL_TIMEOUT = 1e3 @@ -43,9 +46,19 @@ const killProcess = (ps: ExecaChildProcess) => { ps.on('close', resolve) ps.on('error', reject) - ps.kill('SIGTERM', { - forceKillAfterTimeout: SERVER_KILL_TIMEOUT, - }) + // On Windows with Node 21+, there's a bug where attempting to kill a child process + // results in an EPERM error. Ignore the error in that case. + // See: https://github.com/nodejs/node/issues/51766 + // We also disable execa's `forceKillAfterTimeout` in this case + // which can cause unhandled rejection. + try { + ps.kill('SIGTERM', { + forceKillAfterTimeout: + platform() === 'win32' && satisfies(process.version, '>=21') ? false : SERVER_KILL_TIMEOUT, + }) + } catch { + // no-op + } }) }