From 8de202ba094750543473573940a55777e150e7ff Mon Sep 17 00:00:00 2001 From: Eric Wheeler Date: Sat, 8 Mar 2025 18:05:57 -0800 Subject: [PATCH] fix: prevent UI freeze when terminal stream is unavailable Add timeout and error handling to terminal stream initialization to prevent UI from freezing when a stream is unavailable or never starts. This ensures that if the VSCE shell integration stream does not start within 3 seconds: - The streamAvailable promise is rejected with a clear error - Event listeners are cleaned up to prevent memory leaks - Terminal state is properly reset - Execution continues rather than hanging indefinitely This fixes a potential deadlock where the UI could freeze waiting for a stream that never becomes available. Signed-off-by: Eric Wheeler --- src/integrations/terminal/TerminalProcess.ts | 41 +++++++++++++++++++- 1 file changed, 39 insertions(+), 2 deletions(-) diff --git a/src/integrations/terminal/TerminalProcess.ts b/src/integrations/terminal/TerminalProcess.ts index edbfcd80c7..c80014f0cb 100644 --- a/src/integrations/terminal/TerminalProcess.ts +++ b/src/integrations/terminal/TerminalProcess.ts @@ -246,8 +246,24 @@ export class TerminalProcess extends EventEmitter { if (terminal.shellIntegration && terminal.shellIntegration.executeCommand) { // Create a promise that resolves when the stream becomes available - const streamAvailable = new Promise>((resolve) => { + const streamAvailable = new Promise>((resolve, reject) => { + const timeoutId = setTimeout(() => { + // Remove event listener to prevent memory leaks + this.removeAllListeners("stream_available") + + // Emit no_shell_integration event with descriptive message + this.emit( + "no_shell_integration", + "VSCE shell integration stream did not start within 3 seconds. Terminal problem?", + ) + + // Reject with descriptive error + reject(new Error("VSCE shell integration stream did not start within 3 seconds.")) + }, 3000) + + // Clean up timeout if stream becomes available this.once("stream_available", (stream: AsyncIterable) => { + clearTimeout(timeoutId) resolve(stream) }) }) @@ -264,7 +280,28 @@ export class TerminalProcess extends EventEmitter { this.isHot = true // Wait for stream to be available - const stream = await streamAvailable + let stream: AsyncIterable + try { + stream = await streamAvailable + } catch (error) { + // Stream timeout or other error occurred + console.error("[Terminal Process] Stream error:", error.message) + + // Emit completed event with error message + this.emit( + "completed", + "", + ) + + // Ensure terminal is marked as not busy + if (this.terminalInfo) { + this.terminalInfo.busy = false + } + + // Emit continue event to allow execution to proceed + this.emit("continue") + return + } let preOutput = "" let commandOutputStarted = false