From 72998c69702d932196ea9256a98a7564b24f754f Mon Sep 17 00:00:00 2001 From: Ammar Date: Thu, 16 Oct 2025 19:01:22 -0500 Subject: [PATCH] Add 10s timeout to prevent stuck consumer calculations When the Web Worker hangs and never resolves/rejects the promise, the calculation gets stuck in 'Calculating...' state forever because pendingCalcs is never cleaned up. Add Promise.race with a 10-second timeout to guarantee the promise always settles. 10s is generous for large histories while remaining responsive (30s+ feels like a bug to users). Timeout is treated as an error (caches empty result to prevent retry spam). Original cancellation handling is preserved - cancellations don't cache so lazy trigger can naturally retry on next access. Net: +6 lines, 0 complexity increase --- src/stores/WorkspaceConsumerManager.ts | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/stores/WorkspaceConsumerManager.ts b/src/stores/WorkspaceConsumerManager.ts index 628bc7f86..b0cee2f20 100644 --- a/src/stores/WorkspaceConsumerManager.ts +++ b/src/stores/WorkspaceConsumerManager.ts @@ -2,6 +2,9 @@ import type { WorkspaceConsumersState } from "./WorkspaceStore"; import { TokenStatsWorker } from "@/utils/tokens/TokenStatsWorker"; import type { StreamingMessageAggregator } from "@/utils/messages/StreamingMessageAggregator"; +// Timeout for Web Worker calculations (10 seconds - generous but responsive) +const CALCULATION_TIMEOUT_MS = 10_000; + /** * Manages consumer token calculations for workspaces. * @@ -148,8 +151,15 @@ export class WorkspaceConsumerManager { const messages = aggregator.getAllMessages(); const model = aggregator.getCurrentModel() ?? "unknown"; - // Calculate in Web Worker (off main thread) - const fullStats = await this.tokenWorker.calculate(messages, model); + // Calculate in Web Worker with timeout protection + const timeoutPromise = new Promise((_, reject) => + setTimeout(() => reject(new Error("Calculation timeout")), CALCULATION_TIMEOUT_MS) + ); + + const fullStats = await Promise.race([ + this.tokenWorker.calculate(messages, model), + timeoutPromise, + ]); // Store result in cache this.cache.set(workspaceId, { @@ -168,7 +178,7 @@ export class WorkspaceConsumerManager { return; } - // Real errors: log and cache empty result + // Real errors (including timeout): log and cache empty result console.error(`[WorkspaceConsumerManager] Calculation failed for ${workspaceId}:`, error); this.cache.set(workspaceId, { consumers: [],