perf(terminal): avoid duplicate recovery snapshot replay

This commit is contained in:
Brennan Benson
2026-08-12 18:06:18 -07:00
parent 58e355aa0c
commit 6774dfd07f
3 changed files with 53 additions and 1 deletions
@@ -32,6 +32,7 @@ function leafIdForPane(paneId: number): string {
type ConnectCallbacks = {
onReattachDetermined?: () => void
onConnect?: () => void
onStreamRecovered?: () => void
onData?: (
data: string,
meta?: { seq?: number; rawLength?: number; background?: boolean; droppedOutput?: boolean }
@@ -379,6 +380,7 @@ type RemotePaneDrive = {
disposable: { dispose: () => void }
deliver: (data: string, seq: number) => void
setOutputPaused: (paused: boolean) => void
recoverStream: () => void
writtenChunks: () => string[]
}
@@ -396,9 +398,11 @@ async function connectHiddenRemoteAgentPane(
const capturedOutputPauseCallback: {
current: ((paused: boolean, supported: boolean) => void) | null
} = { current: null }
const capturedStreamRecoveredCallback: { current: (() => void) | null } = { current: null }
transport.connect.mockImplementation(async ({ callbacks }: { callbacks?: ConnectCallbacks }) => {
capturedDataCallback.current = callbacks?.onData ?? null
capturedOutputPauseCallback.current = callbacks?.onOutputPauseChanged ?? null
capturedStreamRecoveredCallback.current = callbacks?.onStreamRecovered ?? null
return REMOTE_PTY_ID
})
transportFactoryQueue.push(transport)
@@ -415,6 +419,7 @@ async function connectHiddenRemoteAgentPane(
disposable,
deliver: (data, seq) => capturedDataCallback.current?.(data, { seq, rawLength: data.length }),
setOutputPaused: (paused) => capturedOutputPauseCallback.current?.(paused, true),
recoverStream: () => capturedStreamRecoveredCallback.current?.(),
writtenChunks: () => pane.terminal.write.mock.calls.map(([data]) => String(data))
}
}
@@ -618,6 +623,24 @@ describe('remote hidden-output restore outcomes', () => {
resetAgentStartupDelayedDeliveryForTests()
})
it('[modern] repaints a recovered visible pane from the retained host buffer', async () => {
const serializeBuffer = vi.fn()
const serializeBufferOutcome = vi.fn().mockResolvedValue({
availability: { kind: 'snapshot' },
snapshot: HOST_SNAPSHOT
})
const drive = await connectHiddenRemoteAgentPane(serializeBuffer, serializeBufferOutcome)
;(drive.deps.isVisibleRef as { current: boolean }).current = true
drive.recoverStream()
await flushAsyncTicks(20)
expect(serializeBufferOutcome).toHaveBeenCalledTimes(1)
expect(drive.writtenChunks().join('')).toContain(HOST_SNAPSHOT_MARKER)
expect(serializeBuffer).not.toHaveBeenCalled()
drive.disposable.dispose()
})
it('[modern] accepts an empty snapshot as successful recovery without a loss banner', async () => {
const serializeBuffer = vi.fn()
const serializeBufferOutcome = vi.fn().mockResolvedValue({
@@ -5109,6 +5109,33 @@ describe('createRemoteRuntimePtyTransport', () => {
await vi.waitFor(() => expect(onStreamRecovered).toHaveBeenCalledTimes(1))
expect(onReplayData.mock.calls.map((call) => call[0])).toEqual(['INITIAL_SNAPSHOT'])
subscriptionCallbacks?.onClose?.()
await vi.waitFor(() => expect(runtimeSubscribe).toHaveBeenCalledTimes(3))
await vi.waitFor(() =>
expect(
subscriptionSendBinary.mock.calls
.map((call) => decodeTerminalStreamFrame(call[0]))
.filter((frame) => frame?.opcode === TerminalStreamOpcode.Subscribe)
).toHaveLength(3)
)
const populatedReconnectStreamId = latestSubscribePayload().streamId
emitSnapshot(populatedReconnectStreamId, 'RECOVERY_SNAPSHOT')
subscriptionCallbacks?.onResponse({
ok: true,
result: {
type: 'subscribed',
streamId: populatedReconnectStreamId,
capabilities: { outputPause: 1 }
}
})
await vi.waitFor(() => expect(onConnect).toHaveBeenCalledTimes(3))
expect(onStreamRecovered).toHaveBeenCalledTimes(1)
expect(onReplayData.mock.calls.map((call) => call[0])).toEqual([
'INITIAL_SNAPSHOT',
'RECOVERY_SNAPSHOT'
])
transport.destroy?.()
})
@@ -1735,6 +1735,7 @@ export function createRemoteRuntimePtyTransport(
setAttachmentReady(false)
let transportClosed = false
let subscriptionAttached = false
let subscriptionSnapshotHadContent = false
// Why: viewport handed to subscribe; a resize during the round-trip falls back to the refresh-only one-shot RPC, replayed through the stream below once current.
const subscribedViewport = desiredViewport
const isCurrentSubscription = (): boolean =>
@@ -1760,6 +1761,7 @@ export function createRemoteRuntimePtyTransport(
onSnapshot: (data, meta) => {
// Why: an empty snapshot can still carry a pending mid-escape tail that must replay so the next live chunk completes it.
if ((data || meta?.pendingEscapeTailAnsi) && isCurrentSubscription()) {
subscriptionSnapshotHadContent = true
if (subscribedPtyId && bufferPtyShutdownReplayData(subscribedPtyId, data)) {
return
}
@@ -1809,7 +1811,7 @@ export function createRemoteRuntimePtyTransport(
// Why: a recovery subscribe replays nothing when the host's push snapshot is
// empty (idle or exited pane), so ask for the retained buffer instead of
// waiting for bytes that an exited process will never send.
if (expectedRecoveryEpoch !== undefined) {
if (expectedRecoveryEpoch !== undefined && !subscriptionSnapshotHadContent) {
storedCallbacks.onStreamRecovered?.()
}
storedCallbacks.onStatus?.('shell')