From 5ccd1c422b22c9b5468dbc7653f4b68d564dec51 Mon Sep 17 00:00:00 2001 From: Ingo Fischer Date: Sat, 4 Apr 2026 18:21:04 +0200 Subject: [PATCH] Fix shutdown hang when SIGINT arrives during startup When SIGINT/SIGTERM arrives while the server is still starting up (e.g. during DCL certificate fetching), stop() and start() race against each other. start() continues running after stop() completes, leaving timers and resources alive that keep the process from exiting. Three changes: - stop() now awaits start() completion before tearing down resources - stop() guards against re-entrant calls from repeated SIGINT signals - CustomClusterPoller.stop() is always called in close(), not guarded behind the #started flag that may not be set yet during startup Addresses #454 Co-Authored-By: Claude Opus 4.6 (1M context) --- packages/matter-server/src/MatterServer.ts | 21 +++++++++++++++++-- .../controller/ControllerCommandHandler.ts | 2 +- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/packages/matter-server/src/MatterServer.ts b/packages/matter-server/src/MatterServer.ts index d708dd66..920e40ff 100644 --- a/packages/matter-server/src/MatterServer.ts +++ b/packages/matter-server/src/MatterServer.ts @@ -78,6 +78,8 @@ let config: ConfigStorage; let legacyData: LegacyData; let legacyDataWriter: LegacyDataWriter | undefined; let fileLoggerClose: (() => Promise) | undefined; +let stopping = false; +let startCompleted: Promise = Promise.resolve(); async function start() { // Set up file logging additionally to the console if configured @@ -190,6 +192,19 @@ async function start() { } async function stop() { + if (stopping) { + return; + } + stopping = true; + + // Wait for start() to finish (or fail) before tearing down, so we don't + // race against in-flight initialization that could re-create resources. + try { + await startCompleted; + } catch { + // start() failed - that's fine, we still need to clean up + } + try { await server?.stop(); } catch (err) { @@ -229,8 +244,10 @@ async function stop() { } } -start().catch(async err => { - console.error(err); +startCompleted = start().catch(async err => { + if (!stopping) { + console.error(err); + } await config?.close(); }); diff --git a/packages/ws-controller/src/controller/ControllerCommandHandler.ts b/packages/ws-controller/src/controller/ControllerCommandHandler.ts index ce3def0d..52462bb9 100644 --- a/packages/ws-controller/src/controller/ControllerCommandHandler.ts +++ b/packages/ws-controller/src/controller/ControllerCommandHandler.ts @@ -273,10 +273,10 @@ export class ControllerCommandHandler { } async close() { + await this.#customClusterPoller.stop(); if (!this.#started) { return; } - await this.#customClusterPoller.stop(); return this.#controller.close(); }