fix: make abort() async to prevent race condition in service termination

- Changed BaseTerminalProcess.abort() signature to return Promise<void>
- 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.
This commit is contained in:
Roo Code 2025-11-16 11:04:12 +00:00
parent 2868365fae
commit d476095cb3
6 changed files with 10 additions and 10 deletions

View file

@ -1072,7 +1072,7 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
if (terminalOperation === "continue") {
this.terminalProcess?.continue()
} else if (terminalOperation === "abort") {
this.terminalProcess?.abort()
await this.terminalProcess?.abort()
}
}

View file

@ -507,9 +507,9 @@ export async function executeCommandInTerminal(
let isTimedOut = false
const timeoutPromise = new Promise<void>((_, 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)
})

View file

@ -122,7 +122,7 @@ export abstract class BaseTerminalProcess extends EventEmitter<RooTerminalProces
/**
* Aborts the process via a SIGINT.
*/
abstract abort(): void
abstract abort(): Promise<void>
/**
* Checks if this process has unretrieved output.

View file

@ -280,7 +280,7 @@ export class ExecaTerminalProcess extends BaseTerminalProcess {
this.emit("continue")
}
public override abort() {
public override async abort(): Promise<void> {
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()
}
}

View file

@ -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

View file

@ -264,7 +264,7 @@ export class TerminalProcess extends BaseTerminalProcess {
this.emit("continue")
}
public override abort() {
public override async abort(): Promise<void> {
if (this.isListening) {
// Send SIGINT using CTRL+C
this.terminal.terminal.sendText("\x03")