fix(pty): close provider cancellation races

This commit is contained in:
Tianyi Cui
2026-07-29 23:11:49 +08:00
parent b71dbbe766
commit ff86795b15
17 changed files with 131 additions and 36 deletions

View File

@@ -82,6 +82,22 @@ function spawnArgv(ctx: Context, config: ResolvedConfig, spec: PtyBackendSpawnSp
}).argv
}
async function initializeSession(session: LocalPtySession, signal?: AbortSignal): Promise<void> {
if (signal === undefined) {
await session.initialize(signal)
return
}
const aborted = Promise.withResolvers<never>()
const onAbort = (): void => { aborted.reject(signal.reason) }
signal.addEventListener('abort', onAbort, { once: true })
try {
signal.throwIfAborted()
await Promise.race([session.initialize(signal), aborted.promise])
} finally {
signal.removeEventListener('abort', onAbort)
}
}
/** Local shell backend registered under the configured type. */
export class LocalPtyBackend implements PtyBackend {
readonly type: string
@@ -116,7 +132,7 @@ export class LocalPtyBackend implements PtyBackend {
})
const session = this.createSession(terminal, this.config)
try {
await session.initialize(spec.signal)
await initializeSession(session, spec.signal)
return session
} catch (error) {
try {

View File

@@ -6,6 +6,7 @@ import type {
SubprocessTerminalForeground,
SubprocessTerminalHandle,
} from '@deepseek-ai/dsh-subprocess'
import { PtyError } from '@deepseek-ai/dsh-pty'
import type {
PtyBackendSession,
PtyReadRequest,
@@ -219,7 +220,7 @@ export class LocalPtySession implements PtyBackendSession {
startSend(request: PtySendRequest): PtySendOperation {
if (this.closing) throw new Error('PTY session is closing')
if (this.statusValue.kind === 'exited') throw new Error('PTY session has exited')
if (this.active !== undefined) throw new Error('PTY session already has an active send')
if (this.active !== undefined) throw new PtyError('PTY session already has an active send or draining provider operation', 'SEND_ACTIVE')
if (request.signal?.aborted === true) throw new Error('PTY send aborted before write')
const operation = new LocalSendOperation(
@@ -247,7 +248,7 @@ export class LocalPtySession implements PtyBackendSession {
private async beginSend(operation: LocalSendOperation, request: PtySendRequest): Promise<void> {
try {
const foreground = await this.terminal.inspectForeground()
if (this.active !== operation || this.closing) return
if (this.active !== operation || this.closing || this.interrupting === operation) return
operation.setInitialForeground(foreground)
const input = `${request.text}${request.submit ? '\r' : ''}`
if (input.length > 0 && !operation.cancelRequested) {