mirror of
https://github.com/stablyai/orca.git
synced 2026-09-22 08:02:28 +00:00
fix(runtime): cancel pending driver timers on desktop reclaim (#16337)
* fix(runtime): cancel pending driver timers on desktop reclaim * fix(runtime): centralize pending driver cancellation * fix(runtime): complete pending driver cancellation extraction * test(runtime): cover desktop reclaim mutation branches
This commit is contained in:
@@ -840,15 +840,80 @@ describe('mobile subscribe integration', () => {
|
||||
await runtime.handleMobileSubscribe('pty-1', 'client-a', { cols: 45, rows: 20 })
|
||||
runtime.handleMobileUnsubscribe('pty-1', 'client-a')
|
||||
|
||||
// No subscribers, indefinite hold — PTY stays at phone dims.
|
||||
await vi.advanceTimersByTimeAsync(60_000)
|
||||
expect(ptySizes.get('pty-1')).toEqual({ cols: 45, rows: 20 })
|
||||
expect(runtime.isMobileSubscriberActive('pty-1')).toBe(false)
|
||||
|
||||
// Manual reclaim: PTY restored to desktop dims via the held branch.
|
||||
const ok = await runtime.reclaimTerminalForDesktop('pty-1')
|
||||
expect(ok).toBe(true)
|
||||
await runtime.reclaimTerminalForDesktop('pty-1')
|
||||
expect(ptySizes.get('pty-1')).toEqual({ cols: 150, rows: 40 })
|
||||
|
||||
await vi.advanceTimersByTimeAsync(250)
|
||||
expect(runtime.getDriver('pty-1')).toEqual({ kind: 'desktop' })
|
||||
})
|
||||
|
||||
it('reclaim cancels the soft-leave timer in the remote-layout branch', async () => {
|
||||
const { runtime } = createRuntime()
|
||||
await runtime.updateRemoteDesktopViewer('pty-1', 'sub-a', 'viewer-a', 120, 32)
|
||||
await runtime.handleMobileSubscribe('pty-1', 'client-a', { cols: 45, rows: 20 })
|
||||
runtime.handleMobileUnsubscribe('pty-1', 'client-a')
|
||||
|
||||
expect(await runtime.reclaimTerminalForDesktop('pty-1')).toBe(true)
|
||||
await vi.advanceTimersByTimeAsync(250)
|
||||
expect(runtime.getDriver('pty-1')).toEqual({ kind: 'desktop' })
|
||||
})
|
||||
|
||||
it('reclaim cancels pending restore timers on the active-subscriber branch', async () => {
|
||||
const { runtime } = createRuntime()
|
||||
await runtime.handleMobileSubscribe('pty-1', 'client-a', { cols: 45, rows: 20 })
|
||||
runtime.handleMobileUnsubscribe('pty-1', 'client-a')
|
||||
await runtime.handleMobileSubscribe('pty-1', 'client-b', { cols: 40, rows: 18 })
|
||||
|
||||
const pendingRestore = Reflect.get(runtime, 'pendingRestoreTimers') as Map<string, unknown>
|
||||
pendingRestore.set('pty-1', { timer: setTimeout(() => {}, 60_000), clientId: 'client-b' })
|
||||
const pendingSoft = Reflect.get(runtime, 'pendingSoftLeavers') as Map<string, unknown>
|
||||
expect(pendingSoft.has('pty-1')).toBe(true)
|
||||
await runtime.reclaimTerminalForDesktop('pty-1')
|
||||
expect(pendingRestore.has('pty-1')).toBe(false)
|
||||
expect(pendingSoft.has('pty-1')).toBe(false)
|
||||
})
|
||||
|
||||
it('reclaim cancels pending restore timers on the orphan-driver branch', async () => {
|
||||
const { runtime } = createRuntime()
|
||||
await runtime.handleMobileSubscribe('pty-1', 'client-a', { cols: 45, rows: 20 })
|
||||
runtime.handleMobileUnsubscribe('pty-1', 'client-a')
|
||||
;(Reflect.get(runtime, 'terminalFitOverrides') as Map<string, unknown>).delete('pty-1')
|
||||
|
||||
const pendingRestore = Reflect.get(runtime, 'pendingRestoreTimers') as Map<string, unknown>
|
||||
const pendingSoft = Reflect.get(runtime, 'pendingSoftLeavers') as Map<string, unknown>
|
||||
await runtime.reclaimTerminalForDesktop('pty-1')
|
||||
expect(pendingRestore.has('pty-1')).toBe(false)
|
||||
expect(pendingSoft.has('pty-1')).toBe(false)
|
||||
})
|
||||
|
||||
it('reclaim cancels pending restore timers when no driver lock remains', async () => {
|
||||
const { runtime } = createRuntime()
|
||||
await runtime.handleMobileSubscribe('pty-1', 'client-a', { cols: 45, rows: 20 })
|
||||
runtime.handleMobileUnsubscribe('pty-1', 'client-a')
|
||||
;(Reflect.get(runtime, 'terminalFitOverrides') as Map<string, unknown>).delete('pty-1')
|
||||
;(Reflect.get(runtime, 'currentDriver') as Map<string, { kind: string }>).set('pty-1', {
|
||||
kind: 'idle'
|
||||
})
|
||||
|
||||
const pendingRestore = Reflect.get(runtime, 'pendingRestoreTimers') as Map<string, unknown>
|
||||
const pendingSoft = Reflect.get(runtime, 'pendingSoftLeavers') as Map<string, unknown>
|
||||
expect(await runtime.reclaimTerminalForDesktop('pty-1')).toBe(false)
|
||||
expect(pendingRestore.has('pty-1')).toBe(false)
|
||||
expect(pendingSoft.has('pty-1')).toBe(false)
|
||||
})
|
||||
|
||||
it('reclaim revokes soft-leave grace admission for mobile input', async () => {
|
||||
const { runtime } = createRuntime()
|
||||
await runtime.handleMobileSubscribe('pty-1', 'client-a', { cols: 45, rows: 20 })
|
||||
runtime.handleMobileUnsubscribe('pty-1', 'client-a')
|
||||
|
||||
const claim = runtime.beginMobileInputFloor('pty-1', 'client-a')
|
||||
expect(claim).not.toBeNull()
|
||||
claim?.rollback()
|
||||
|
||||
await runtime.reclaimTerminalForDesktop('pty-1')
|
||||
expect(runtime.getDriver('pty-1')).toEqual({ kind: 'desktop' })
|
||||
expect(runtime.beginMobileInputFloor('pty-1', 'client-a')).toBeNull()
|
||||
})
|
||||
|
||||
it('reclaimTerminalForDesktop prefers fresh desktop geometry for a held PTY', async () => {
|
||||
|
||||
@@ -15821,16 +15821,7 @@ export class OrcaRuntimeService {
|
||||
this.layouts.delete(ptyId)
|
||||
this.layoutQueues.delete(ptyId)
|
||||
this.freshSubscribeGuard.delete(ptyId)
|
||||
const pendingRestore = this.pendingRestoreTimers.get(ptyId)
|
||||
if (pendingRestore) {
|
||||
clearTimeout(pendingRestore.timer)
|
||||
this.pendingRestoreTimers.delete(ptyId)
|
||||
}
|
||||
const pendingSoft = this.pendingSoftLeavers.get(ptyId)
|
||||
if (pendingSoft) {
|
||||
clearTimeout(pendingSoft.timer)
|
||||
this.pendingSoftLeavers.delete(ptyId)
|
||||
}
|
||||
this.cancelPendingDriverMutations(ptyId)
|
||||
// Why: a cold restore can respawn under the same session id within the
|
||||
// delayed-Enter window; the armed Enter would inject \r into the
|
||||
// replacement and stamp rows it never received.
|
||||
@@ -16487,6 +16478,7 @@ export class OrcaRuntimeService {
|
||||
// frame. Returns `true` whenever there was a lock to reclaim, `false` only
|
||||
// when there was nothing to reclaim.
|
||||
async reclaimTerminalForDesktop(ptyId: string): Promise<boolean> {
|
||||
this.cancelPendingDriverMutations(ptyId)
|
||||
if (this.isMobileSubscriberActive(ptyId)) {
|
||||
this.setMobileDisplayMode(ptyId, 'desktop')
|
||||
await this.applyMobileDisplayMode(ptyId)
|
||||
@@ -16506,16 +16498,6 @@ export class OrcaRuntimeService {
|
||||
}
|
||||
const heldOverride = this.terminalFitOverrides.get(ptyId)
|
||||
if (heldOverride && this.hasRemoteDesktopLayoutState(ptyId)) {
|
||||
const pending = this.pendingRestoreTimers.get(ptyId)
|
||||
if (pending) {
|
||||
clearTimeout(pending.timer)
|
||||
this.pendingRestoreTimers.delete(ptyId)
|
||||
}
|
||||
const softLeaver = this.pendingSoftLeavers.get(ptyId)
|
||||
if (softLeaver) {
|
||||
clearTimeout(softLeaver.timer)
|
||||
this.pendingSoftLeavers.delete(ptyId)
|
||||
}
|
||||
// Why: applyRemoteDesktopLayout no-ops while the driver still reads mobile.
|
||||
this.setDriver(ptyId, { kind: 'idle' })
|
||||
// Why: best-effort, like the local held branch below. A host whose resize
|
||||
@@ -16528,11 +16510,6 @@ export class OrcaRuntimeService {
|
||||
return true
|
||||
}
|
||||
if (heldOverride) {
|
||||
const pending = this.pendingRestoreTimers.get(ptyId)
|
||||
if (pending) {
|
||||
clearTimeout(pending.timer)
|
||||
this.pendingRestoreTimers.delete(ptyId)
|
||||
}
|
||||
// Why: with no subscribers, resolveDesktopRestoreTarget can fall through
|
||||
// to current PTY size — which is at phone dims (wrong). Prefer a fresh
|
||||
// desktop renderer measurement when one exists; otherwise use the
|
||||
@@ -16557,6 +16534,21 @@ export class OrcaRuntimeService {
|
||||
return false
|
||||
}
|
||||
|
||||
// Why: teardown and desktop reclaim supersede delayed mobile mutations,
|
||||
// revoking soft-leave grace admission for input floors.
|
||||
private cancelPendingDriverMutations(ptyId: string): void {
|
||||
const pendingRestore = this.pendingRestoreTimers.get(ptyId)
|
||||
if (pendingRestore) {
|
||||
clearTimeout(pendingRestore.timer)
|
||||
this.pendingRestoreTimers.delete(ptyId)
|
||||
}
|
||||
const pendingSoft = this.pendingSoftLeavers.get(ptyId)
|
||||
if (pendingSoft) {
|
||||
clearTimeout(pendingSoft.timer)
|
||||
this.pendingSoftLeavers.delete(ptyId)
|
||||
}
|
||||
}
|
||||
|
||||
// Why: the shared "banner must be gone now" step for an explicit desktop
|
||||
// take-back. Releases the presence lock (driver → desktop) and, if the
|
||||
// best-effort resize left a fit-override held (resize didn't converge),
|
||||
|
||||
Reference in New Issue
Block a user