From d476095cb36a9bd8d840bb8a629acd8ddd290d1e Mon Sep 17 00:00:00 2001 From: Roo Code Date: Sun, 16 Nov 2025 11:04:12 +0000 Subject: [PATCH] fix: make abort() async to prevent race condition in service termination - Changed BaseTerminalProcess.abort() signature to return Promise - Made ExecaTerminalProcess.abort() properly await performKill() - Made TerminalProcess.abort() async for consistency - Updated ServiceManager.stopService() to await abort() call - Updated ExecuteCommandTool timeout handler to await abort() - Updated Task.handleTerminalOperation() to await abort() This fixes the race condition where abort() returned immediately before process termination completed, causing false timeout failures in ServiceManager.stopService() when checking process status. --- src/core/task/Task.ts | 2 +- src/core/tools/ExecuteCommandTool.ts | 4 ++-- src/integrations/terminal/BaseTerminalProcess.ts | 2 +- src/integrations/terminal/ExecaTerminalProcess.ts | 6 +++--- src/integrations/terminal/ServiceManager.ts | 4 ++-- src/integrations/terminal/TerminalProcess.ts | 2 +- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/core/task/Task.ts b/src/core/task/Task.ts index cc4558a74b..85eb6cb24f 100644 --- a/src/core/task/Task.ts +++ b/src/core/task/Task.ts @@ -1072,7 +1072,7 @@ export class Task extends EventEmitter implements TaskLike { if (terminalOperation === "continue") { this.terminalProcess?.continue() } else if (terminalOperation === "abort") { - this.terminalProcess?.abort() + await this.terminalProcess?.abort() } } diff --git a/src/core/tools/ExecuteCommandTool.ts b/src/core/tools/ExecuteCommandTool.ts index baf9a9289c..ffc75e4a9f 100644 --- a/src/core/tools/ExecuteCommandTool.ts +++ b/src/core/tools/ExecuteCommandTool.ts @@ -507,9 +507,9 @@ export async function executeCommandInTerminal( let isTimedOut = false const timeoutPromise = new Promise((_, reject) => { - timeoutId = setTimeout(() => { + timeoutId = setTimeout(async () => { isTimedOut = true - task.terminalProcess?.abort() + await task.terminalProcess?.abort() reject(new Error(`Command execution timed out after ${commandExecutionTimeout}ms`)) }, commandExecutionTimeout) }) diff --git a/src/integrations/terminal/BaseTerminalProcess.ts b/src/integrations/terminal/BaseTerminalProcess.ts index 3474f6de1a..35bbd358fb 100644 --- a/src/integrations/terminal/BaseTerminalProcess.ts +++ b/src/integrations/terminal/BaseTerminalProcess.ts @@ -122,7 +122,7 @@ export abstract class BaseTerminalProcess extends EventEmitter /** * Checks if this process has unretrieved output. diff --git a/src/integrations/terminal/ExecaTerminalProcess.ts b/src/integrations/terminal/ExecaTerminalProcess.ts index fbcd26cc39..4181e4cd39 100644 --- a/src/integrations/terminal/ExecaTerminalProcess.ts +++ b/src/integrations/terminal/ExecaTerminalProcess.ts @@ -280,7 +280,7 @@ export class ExecaTerminalProcess extends BaseTerminalProcess { this.emit("continue") } - public override abort() { + public override async abort(): Promise { this.aborted = true // Simplified process termination function: directly use process group kill (most reliable method) @@ -348,9 +348,9 @@ export class ExecaTerminalProcess extends BaseTerminalProcess { // If PID update is in progress, wait for it before killing if (this.pidUpdatePromise) { - this.pidUpdatePromise.finally(performKill) + await this.pidUpdatePromise.then(() => performKill()).catch(() => performKill()) } else { - performKill() + await performKill() } } diff --git a/src/integrations/terminal/ServiceManager.ts b/src/integrations/terminal/ServiceManager.ts index e411f57ae0..65bd52568e 100644 --- a/src/integrations/terminal/ServiceManager.ts +++ b/src/integrations/terminal/ServiceManager.ts @@ -165,8 +165,8 @@ export class ServiceManager { service.healthCheckIntervalId = undefined } - // Terminate process (multiple attempts to ensure process is terminated) - service.process.abort() + // Terminate process and wait for it to complete + await service.process.abort() // Wait for process to actually stop, maximum wait 10 seconds const maxWaitTime = 10000 // 10 seconds diff --git a/src/integrations/terminal/TerminalProcess.ts b/src/integrations/terminal/TerminalProcess.ts index eb0424fe8d..0400167ad3 100644 --- a/src/integrations/terminal/TerminalProcess.ts +++ b/src/integrations/terminal/TerminalProcess.ts @@ -264,7 +264,7 @@ export class TerminalProcess extends BaseTerminalProcess { this.emit("continue") } - public override abort() { + public override async abort(): Promise { if (this.isListening) { // Send SIGINT using CTRL+C this.terminal.terminal.sendText("\x03")