From c3e3f99c7d4942a7dc81dd9760fc1822ff1a73d2 Mon Sep 17 00:00:00 2001 From: shocknet-justin Date: Tue, 10 Feb 2026 17:36:37 -0500 Subject: [PATCH] fix subprocess restart issues - Fix event listener memory leak by removing all listeners before restart - Properly cleanup old process with SIGTERM before forking new one - Fix Stop() to actually kill process (was using kill(0) which only checks existence) - Store callbacks in instance to avoid re-attaching listeners on restart - Add isShuttingDown flag to prevent restart when stopping intentionally - Add 100ms delay before restart to ensure clean process termination - Check if process is killed before sending messages - Update Reset() to store new settings for future restarts Co-authored-by: Cursor --- src/services/nostr/index.ts | 65 ++++++++++++++++++++++++++++--------- 1 file changed, 49 insertions(+), 16 deletions(-) diff --git a/src/services/nostr/index.ts b/src/services/nostr/index.ts index d544a286..5b2f0ddc 100644 --- a/src/services/nostr/index.ts +++ b/src/services/nostr/index.ts @@ -12,39 +12,68 @@ export default class NostrSubprocess { awaitingPongs: (() => void)[] = [] log = getLogger({}) latestRestart = 0 + private settings: NostrSettings + private eventCallback: EventCallback + private beaconCallback: BeaconCallback + private isShuttingDown = false + constructor(settings: NostrSettings, utils: Utils, eventCallback: EventCallback, beaconCallback: BeaconCallback) { this.utils = utils - this.startSubProcess(settings, eventCallback, beaconCallback) + this.settings = settings + this.eventCallback = eventCallback + this.beaconCallback = beaconCallback + this.startSubProcess() } - startSubProcess(settings: NostrSettings, eventCallback: EventCallback, beaconCallback: BeaconCallback) { + private cleanupProcess() { + if (this.childProcess) { + this.childProcess.removeAllListeners() + if (!this.childProcess.killed) { + this.childProcess.kill('SIGTERM') + } + } + } + + private startSubProcess() { + this.cleanupProcess() + this.childProcess = fork("./build/src/services/nostr/handler") + this.childProcess.on("error", (error) => { this.log(ERROR, "nostr subprocess error", error) }) this.childProcess.on("exit", (code, signal) => { - if (code === 0) { - this.log("nostr subprocess exited") + if (this.isShuttingDown) { + this.log("nostr subprocess stopped") return } - this.log(ERROR, `nostr subprocess exited with code ${code} and signal ${signal}`) - const now = Date.now() - if (now - this.latestRestart < 1000 * 5) { - this.log(ERROR, "nostr subprocess exited too quickly") - throw new Error("nostr subprocess exited too quickly") + + if (code === 0) { + this.log("nostr subprocess exited cleanly") + return } + + this.log(ERROR, `nostr subprocess exited with code ${code} and signal ${signal}`) + + const now = Date.now() + if (now - this.latestRestart < 5000) { + this.log(ERROR, "nostr subprocess exited too quickly, not restarting") + throw new Error("nostr subprocess crashed repeatedly") + } + + this.log("restarting nostr subprocess...") this.latestRestart = now - this.startSubProcess(settings, eventCallback, beaconCallback) + setTimeout(() => this.startSubProcess(), 100) }) this.childProcess.on("message", (message: ChildProcessResponse) => { switch (message.type) { case 'ready': - this.sendToChildProcess({ type: 'settings', settings: settings }) + this.sendToChildProcess({ type: 'settings', settings: this.settings }) break; case 'event': - eventCallback(message.event) + this.eventCallback(message.event) break case 'processMetrics': this.utils.tlvStorageFactory.ProcessMetrics(message.metrics, 'nostr') @@ -54,7 +83,7 @@ export default class NostrSubprocess { this.awaitingPongs = [] break case 'beacon': - beaconCallback({ content: message.content, pub: message.pub }) + this.beaconCallback({ content: message.content, pub: message.pub }) break default: console.error("unknown nostr event response", message) @@ -63,12 +92,14 @@ export default class NostrSubprocess { }) } - sendToChildProcess(message: ChildProcessRequest) { - this.childProcess.send(message) + if (this.childProcess && !this.childProcess.killed) { + this.childProcess.send(message) + } } Reset(settings: NostrSettings) { + this.settings = settings this.sendToChildProcess({ type: 'settings', settings }) } @@ -82,7 +113,9 @@ export default class NostrSubprocess { Send(initiator: SendInitiator, data: SendData, relays?: string[]) { this.sendToChildProcess({ type: 'send', data, initiator, relays }) } + Stop() { - this.childProcess.kill(0) + this.isShuttingDown = true + this.cleanupProcess() } }