mirror of
https://github.com/stablyai/orca
synced 2026-04-21 14:17:16 +00:00
fix(terminal-host): avoid reattach to terminating session, force-kill on dispose (#801)
Reattaching to a session where kill() has been called but the subprocess hasn't exited yet races the in-flight exit. Treat terminating sessions the same as fully-exited ones in createOrAttach, and have Session.dispose() force-kill a stuck subprocess and notify attached clients so we don't leak the process when the killTimer is cleared mid-flight.
This commit is contained in:
parent
bbf38cce54
commit
c2ed4fbd3b
2 changed files with 25 additions and 1 deletions
|
|
@ -178,6 +178,21 @@ export class Session {
|
|||
if (this._disposed) {
|
||||
return
|
||||
}
|
||||
|
||||
// Why: if kill() was called but the subprocess hasn't exited yet, our
|
||||
// killTimer is the only thing that would forceKill a stuck subprocess.
|
||||
// Clearing it below without force-killing would leak an orphaned process,
|
||||
// so mirror forceDispose(): issue the force-kill, notify attached clients
|
||||
// (handleSubprocessExit is guarded by _disposed and won't broadcast later),
|
||||
// and clear the terminating flag for a consistent final state.
|
||||
const wasTerminating = this._isTerminating && this._state !== 'exited'
|
||||
const clientsToNotify = wasTerminating ? this.attachedClients.slice() : []
|
||||
if (wasTerminating) {
|
||||
this.subprocess.forceKill()
|
||||
this._exitCode = -1
|
||||
this._isTerminating = false
|
||||
}
|
||||
|
||||
this._disposed = true
|
||||
this._state = 'exited'
|
||||
|
||||
|
|
@ -193,6 +208,10 @@ export class Session {
|
|||
this.attachedClients = []
|
||||
this.preReadyStdinQueue = []
|
||||
this.emulator.dispose()
|
||||
|
||||
for (const client of clientsToNotify) {
|
||||
client.onExit(-1)
|
||||
}
|
||||
}
|
||||
|
||||
private handleSubprocessData(data: string): void {
|
||||
|
|
|
|||
|
|
@ -46,7 +46,12 @@ export class TerminalHost {
|
|||
async createOrAttach(opts: CreateOrAttachOptions): Promise<CreateOrAttachResult> {
|
||||
const existing = this.sessions.get(opts.sessionId)
|
||||
|
||||
if (existing && existing.isAlive) {
|
||||
// Why: a session that has been asked to terminate (kill() called but the
|
||||
// subprocess hasn't exited yet) must not be reattached. Reattaching would
|
||||
// hand the caller a handle that races with the in-flight exit, and any
|
||||
// subsequent operation (write/kill/resize) would fail once the subprocess
|
||||
// finally exits. Treat terminating sessions the same as fully-exited ones.
|
||||
if (existing && existing.isAlive && !existing.isTerminating) {
|
||||
const snapshot = existing.getSnapshot()
|
||||
existing.detachAllClients()
|
||||
const token = existing.attachClient(opts.streamClient)
|
||||
|
|
|
|||
Loading…
Reference in a new issue