From 0531fc1dbf2e1028b0caf0adb32403255f42e0d0 Mon Sep 17 00:00:00 2001 From: iliya Date: Sun, 5 Apr 2026 22:03:11 +0300 Subject: [PATCH] fix worker lifecycle edge cases --- src/main/ipc/teams.ts | 6 ++++ .../services/team/TeamDataWorkerClient.ts | 34 ++++++++++--------- 2 files changed, 24 insertions(+), 16 deletions(-) diff --git a/src/main/ipc/teams.ts b/src/main/ipc/teams.ts index cee6de39..47fe6315 100644 --- a/src/main/ipc/teams.ts +++ b/src/main/ipc/teams.ts @@ -577,6 +577,12 @@ async function handleGetData( if (getDataMs >= 1500) { logger.warn(`[teams:getData] slow team=${tn} ms=${getDataMs}`); } + const teamDataService = getTeamDataService(); + if (data.processes.some((process) => !process.stoppedAt)) { + teamDataService.trackProcessHealthForTeam?.(tn); + } else { + teamDataService.untrackProcessHealthForTeam?.(tn); + } const provisioning = getTeamProvisioningService(); const isAlive = provisioning.isTeamAlive(tn); diff --git a/src/main/services/team/TeamDataWorkerClient.ts b/src/main/services/team/TeamDataWorkerClient.ts index 14e10d7e..314fe1a0 100644 --- a/src/main/services/team/TeamDataWorkerClient.ts +++ b/src/main/services/team/TeamDataWorkerClient.ts @@ -60,6 +60,18 @@ export class TeamDataWorkerClient { private warnedUnavailable = false; private pending = new Map(); + private failWorker(worker: Worker, error: Error): void { + if (this.worker !== worker) return; + + this.worker = null; + const pendingEntries = Array.from(this.pending.values()); + this.pending.clear(); + + for (const entry of pendingEntries) { + entry.reject(error); + } + } + isAvailable(): boolean { if (!this.workerPath && !this.warnedUnavailable) { this.warnedUnavailable = true; @@ -90,23 +102,13 @@ export class TeamDataWorkerClient { // Without this guard, a stale worker's exit event can reject // pending requests that belong to a newer replacement worker. w.on('error', (err) => { - if (this.worker !== w) return; logger.error('Worker error', err); - for (const [, entry] of this.pending) { - entry.reject(err instanceof Error ? err : new Error(String(err))); - } - this.pending.clear(); - this.worker = null; + this.failWorker(w, err instanceof Error ? err : new Error(String(err))); }); w.on('exit', (code) => { - if (this.worker !== w) return; if (code !== 0) logger.warn(`Worker exited with code ${code}`); - for (const [, entry] of this.pending) { - entry.reject(new Error(`Worker exited with code ${code}`)); - } - this.pending.clear(); - this.worker = null; + this.failWorker(w, new Error(`Worker exited with code ${code}`)); }); return w; @@ -121,10 +123,10 @@ export class TeamDataWorkerClient { return new Promise((resolve, reject) => { const timeout = setTimeout(() => { - this.pending.delete(id); - this.worker?.terminate().catch(() => undefined); - this.worker = null; - reject(new Error(`Worker call timeout after ${WORKER_CALL_TIMEOUT_MS}ms`)); + const timeoutError = new Error(`Worker call timeout after ${WORKER_CALL_TIMEOUT_MS}ms`); + this.failWorker(worker, timeoutError); + worker.terminate().catch(() => undefined); + reject(timeoutError); }, WORKER_CALL_TIMEOUT_MS); this.pending.set(id, {