From 867c6327397eb6696b9c84bfe9c6ab922a4fe383 Mon Sep 17 00:00:00 2001 From: Randall Leeds Date: Wed, 12 Nov 2025 13:07:28 -0800 Subject: [PATCH] Fix race condition in functions discovery shutdown Attach event handlers to subprocesses before yielding to calling code to ensure that promises of shutting down discovery processes are fulfilled. --- src/deploy/functions/runtimes/node/index.ts | 18 +++++++++++------- src/deploy/functions/runtimes/python/index.ts | 14 ++++++++++---- 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/src/deploy/functions/runtimes/node/index.ts b/src/deploy/functions/runtimes/node/index.ts index 24bb8289349..1119e42dc98 100644 --- a/src/deploy/functions/runtimes/node/index.ts +++ b/src/deploy/functions/runtimes/node/index.ts @@ -265,24 +265,28 @@ export class Delegate { ): Promise<() => Promise> { const childProcess = this.spawnFunctionsProcess(config, { ...envs, PORT: port }); + const pExit = new Promise((resolve, reject) => { + childProcess.once("exit", resolve); + childProcess.once("error", reject); + }); + // TODO: Refactor return type to () => Promise to simplify nested promises return Promise.resolve(async () => { - const p = new Promise((resolve, reject) => { - childProcess.once("exit", resolve); - childProcess.once("error", reject); - }); - try { await fetch(`http://localhost:${port}/__/quitquitquit`); } catch (e) { logger.debug("Failed to call quitquitquit. This often means the server failed to start", e); } - setTimeout(() => { + + const quitTimeout = setTimeout(() => { if (!childProcess.killed) { childProcess.kill("SIGKILL"); } }, 10_000); - return p; + + return pExit.finally(() => { + clearTimeout(quitTimeout); + }); }); } diff --git a/src/deploy/functions/runtimes/python/index.ts b/src/deploy/functions/runtimes/python/index.ts index 70d18976990..13154b415a0 100644 --- a/src/deploy/functions/runtimes/python/index.ts +++ b/src/deploy/functions/runtimes/python/index.ts @@ -167,21 +167,27 @@ export class Delegate implements runtimes.RuntimeDelegate { childProcess.stderr?.on("data", (chunk: Buffer) => { logger.error(chunk.toString("utf8")); }); + + const pExit = new Promise((resolve, reject) => { + childProcess.once("exit", resolve); + childProcess.once("error", reject); + }); + return Promise.resolve(async () => { try { await fetch(`http://127.0.0.1:${port}/__/quitquitquit`); } catch (e) { logger.debug("Failed to call quitquitquit. This often means the server failed to start", e); } + const quitTimeout = setTimeout(() => { if (!childProcess.killed) { childProcess.kill("SIGKILL"); } }, 10_000); - clearTimeout(quitTimeout); - return new Promise((resolve, reject) => { - childProcess.once("exit", resolve); - childProcess.once("error", reject); + + return pExit.finally(() => { + clearTimeout(quitTimeout); }); }); }