From 65840c67f07e841be24f82e9009e57c905c2547e Mon Sep 17 00:00:00 2001 From: Merlin Beutlberger Date: Fri, 7 Aug 2026 11:44:28 +0200 Subject: [PATCH] fix(server): Sequence destroy() callback after native teardown Supervisor.destroy() passed the caller's callback to http.Server.close(), firing it on socket close. The BuildServer's teardown (the @parcel/watcher unsubscribe and the node:sqlite handle, which maps a 256 MB region of the database into memory) runs after that call and was not sequenced ahead of the callback, so server.close(resolve) resolved with SQLite still mid-close. The next test.serial opening a fresh DatabaseSync, or the worker exiting, then races the finalizing handle and raises an access violation on Windows (0xC0000005), surfacing as `exited with a non-zero exit code: 3221225477` in the reinitialize suite. Start the socket close, then await the definition watcher and BuildServer teardown, and await the socket last. The callback fires only once every native handle is closed. --- packages/server/lib/serve/Supervisor.js | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/packages/server/lib/serve/Supervisor.js b/packages/server/lib/serve/Supervisor.js index 091b7db0285..4d7c4c38a97 100644 --- a/packages/server/lib/serve/Supervisor.js +++ b/packages/server/lib/serve/Supervisor.js @@ -560,8 +560,19 @@ class Supervisor extends EventEmitter { this.#definitionWatcher = null; this.#liveReloadHandle?.close(); this.#detachRelay(); - this.#httpServer?.close(callback); this.#clearRecoveryTimer(); + // The callback fires only after every native handle is closed, not on socket close. The + // BuildServer's teardown closes the @parcel/watcher subscriptions and the node:sqlite handle + // (which maps a 256 MB region of the database into memory); a caller resuming while SQLite is + // mid-close raises an access violation on Windows (0xC0000005). So start the socket close here + // but await it last. + const httpClosed = new Promise((resolve) => { + if (!this.#httpServer) { + resolve(); + return; + } + this.#httpServer.close(() => resolve()); + }); try { await definitionWatcher?.destroy(); } catch (err) { @@ -572,6 +583,8 @@ class Supervisor extends EventEmitter { } catch (err) { log.verbose(`Error while destroying BuildServer: ${err?.message ?? err}`); } + await httpClosed; + callback?.(); } }