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 <cursoragent@cursor.com>
This commit is contained in:
shocknet-justin 2026-02-10 17:36:37 -05:00
parent e271d35c2d
commit c3e3f99c7d
No known key found for this signature in database

View file

@ -12,39 +12,68 @@ export default class NostrSubprocess {
awaitingPongs: (() => void)[] = [] awaitingPongs: (() => void)[] = []
log = getLogger({}) log = getLogger({})
latestRestart = 0 latestRestart = 0
private settings: NostrSettings
private eventCallback: EventCallback
private beaconCallback: BeaconCallback
private isShuttingDown = false
constructor(settings: NostrSettings, utils: Utils, eventCallback: EventCallback, beaconCallback: BeaconCallback) { constructor(settings: NostrSettings, utils: Utils, eventCallback: EventCallback, beaconCallback: BeaconCallback) {
this.utils = utils 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 = fork("./build/src/services/nostr/handler")
this.childProcess.on("error", (error) => { this.childProcess.on("error", (error) => {
this.log(ERROR, "nostr subprocess error", error) this.log(ERROR, "nostr subprocess error", error)
}) })
this.childProcess.on("exit", (code, signal) => { this.childProcess.on("exit", (code, signal) => {
if (code === 0) { if (this.isShuttingDown) {
this.log("nostr subprocess exited") this.log("nostr subprocess stopped")
return return
} }
this.log(ERROR, `nostr subprocess exited with code ${code} and signal ${signal}`)
const now = Date.now() if (code === 0) {
if (now - this.latestRestart < 1000 * 5) { this.log("nostr subprocess exited cleanly")
this.log(ERROR, "nostr subprocess exited too quickly") return
throw new Error("nostr subprocess exited too quickly")
} }
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.latestRestart = now
this.startSubProcess(settings, eventCallback, beaconCallback) setTimeout(() => this.startSubProcess(), 100)
}) })
this.childProcess.on("message", (message: ChildProcessResponse) => { this.childProcess.on("message", (message: ChildProcessResponse) => {
switch (message.type) { switch (message.type) {
case 'ready': case 'ready':
this.sendToChildProcess({ type: 'settings', settings: settings }) this.sendToChildProcess({ type: 'settings', settings: this.settings })
break; break;
case 'event': case 'event':
eventCallback(message.event) this.eventCallback(message.event)
break break
case 'processMetrics': case 'processMetrics':
this.utils.tlvStorageFactory.ProcessMetrics(message.metrics, 'nostr') this.utils.tlvStorageFactory.ProcessMetrics(message.metrics, 'nostr')
@ -54,7 +83,7 @@ export default class NostrSubprocess {
this.awaitingPongs = [] this.awaitingPongs = []
break break
case 'beacon': case 'beacon':
beaconCallback({ content: message.content, pub: message.pub }) this.beaconCallback({ content: message.content, pub: message.pub })
break break
default: default:
console.error("unknown nostr event response", message) console.error("unknown nostr event response", message)
@ -63,12 +92,14 @@ export default class NostrSubprocess {
}) })
} }
sendToChildProcess(message: ChildProcessRequest) { sendToChildProcess(message: ChildProcessRequest) {
if (this.childProcess && !this.childProcess.killed) {
this.childProcess.send(message) this.childProcess.send(message)
} }
}
Reset(settings: NostrSettings) { Reset(settings: NostrSettings) {
this.settings = settings
this.sendToChildProcess({ type: 'settings', settings }) this.sendToChildProcess({ type: 'settings', settings })
} }
@ -82,7 +113,9 @@ export default class NostrSubprocess {
Send(initiator: SendInitiator, data: SendData, relays?: string[]) { Send(initiator: SendInitiator, data: SendData, relays?: string[]) {
this.sendToChildProcess({ type: 'send', data, initiator, relays }) this.sendToChildProcess({ type: 'send', data, initiator, relays })
} }
Stop() { Stop() {
this.childProcess.kill(0) this.isShuttingDown = true
this.cleanupProcess()
} }
} }