Files
orca/tests/e2e/terminal-split-activation-latency-main-probe.ts
Neil c558d7e083 Activate terminal splits before inherited CWD resolution (#17601)
* perf(terminal): activate splits before cwd resolution

* test(terminal): prove split focus before cwd publish

* fix(terminal): release stale split cwd fence

* test(terminal): add visible split activation latency benchmark

* docs(reliability): clarify split benchmark provenance

* fix: preserve deferred split handoffs across remounts

* fix: fence late deferred split closes

* docs(reliability): record exact split benchmark runs

* test(reliability): fail benchmark on artifact write errors

* test(reliability): attribute split activation phases

* docs(reliability): record schema-v2 split benchmark

* refactor(terminal): collapse duplicated split-handoff and write-queue paths

- Drop the discardDeferredSplitPaneHandoff alias for its identical clear twin.
- Fold the deferred-cwd resolve/reject settle handlers into one applier.
- Extract settlePaneCwdDeferredSpawn for the repeated read-clear-write pattern.
- Share one head-index FIFO primitive between the ordinary and reply queues.

* fix(terminal): stop retaining a promise reaction per acknowledged write

Racing every accepted write against one queue-lifetime cancel promise kept a
reaction record alive until that promise settled: 200k acknowledged writes
retained 88.6MB, now 0.1MB. Give each in-flight write its own cancel, and
split the shared FIFO primitive into its own module.

Also sanitize the split-latency benchmark report at its single serialization
point so shared artifacts no longer carry the machine-local repo path or
unbounded cleanup error text.

* fix(terminal): settle deferred split input when the spawn is abandoned

An abandoned deferred spawn returns before transport.connect(), so nothing
drained the pre-connect buffer: sendInputAccepted's promise never settled and
a paste into that pane hung forever. Clear the buffer on the abandon fence.

Also re-derive the pre-connect retention cap from the clipboard-paste ceiling
rather than the 16MB single-write ceiling; it is held twice per pane across up
to 64 deferred splits, so 5.59M code units guarded the wrong thing.

* fix(terminal): release the deferred cwd fence on a rejected reattach

A daemon createOrAttach can turn an apparent fresh spawn into a reattach; when
that reattach is refused the spawn ends with deferredSplitSpawn/pendingCwd
still set, permanently arming the pre-bind detach refusal. The release no-ops
when a PTY did bind, so it only fires where the fence would otherwise leak.

The stale-generation return above is deliberately left alone: a newer connect
already owns the pane there, and the fence is not generation-scoped.
2026-08-31 16:45:36 -07:00

194 lines
6.3 KiB
TypeScript

import type { ElectronApplication } from '@stablyai/playwright-test'
import type { SplitLatencyMainProbeEvent } from './terminal-split-activation-latency-phases'
type MainProbeInvokeHandler = (event: unknown, args: Record<string, unknown>) => unknown
type SplitLatencyMainProbeState = {
events: SplitLatencyMainProbeEvent[]
nextOperationId: number
cwdHandler: MainProbeInvokeHandler
spawnHandler: MainProbeInvokeHandler
writeAcceptedHandler: MainProbeInvokeHandler
originalCwdHandler: MainProbeInvokeHandler
originalSpawnHandler: MainProbeInvokeHandler
originalWriteAcceptedHandler: MainProbeInvokeHandler
writeListener: (event: unknown, args: { id?: unknown; data?: unknown }) => void
}
export async function installSplitLatencyMainProbe(
electronApp: ElectronApplication
): Promise<void> {
await electronApp.evaluate(({ ipcMain }) => {
const scope = globalThis as typeof globalThis & {
__terminalSplitLatencyMainProbe?: SplitLatencyMainProbeState
}
if (scope.__terminalSplitLatencyMainProbe) {
throw new Error('Terminal split latency main probe is already installed')
}
const handlers = (
ipcMain as unknown as { _invokeHandlers?: Map<string, MainProbeInvokeHandler> }
)._invokeHandlers
const originalCwdHandler = handlers?.get('pty:getCwd')
const originalSpawnHandler = handlers?.get('pty:spawn')
const originalWriteAcceptedHandler = handlers?.get('pty:writeAccepted')
if (
!handlers ||
!originalCwdHandler ||
!originalSpawnHandler ||
!originalWriteAcceptedHandler
) {
throw new Error('Terminal split latency main probe could not find PTY invoke handlers')
}
const state = {
events: [],
nextOperationId: 1,
originalCwdHandler,
originalSpawnHandler,
originalWriteAcceptedHandler
} as unknown as SplitLatencyMainProbeState
state.cwdHandler = async (event, args) => {
const operationId = state.nextOperationId++
const ptyId = typeof args?.id === 'string' ? args.id : null
state.events.push({
kind: 'cwd-request',
operationId,
atEpochMs: Date.now(),
ptyId,
writeChannel: null
})
try {
return await state.originalCwdHandler(event, args)
} finally {
state.events.push({
kind: 'cwd-settled',
operationId,
atEpochMs: Date.now(),
ptyId,
writeChannel: null
})
}
}
state.spawnHandler = async (event, args) => {
const operationId = state.nextOperationId++
state.events.push({
kind: 'pty-spawn-request',
operationId,
atEpochMs: Date.now(),
ptyId: null,
writeChannel: null
})
try {
const result = await state.originalSpawnHandler(event, args)
const ptyId =
result && typeof result === 'object' && 'id' in result && typeof result.id === 'string'
? result.id
: null
state.events.push({
kind: 'pty-spawn-result',
operationId,
atEpochMs: Date.now(),
ptyId,
writeChannel: null
})
return result
} catch (error) {
state.events.push({
kind: 'pty-spawn-result',
operationId,
atEpochMs: Date.now(),
ptyId: null,
writeChannel: null
})
throw error
}
}
state.writeListener = (_event, args) => {
if (args?.data !== '\r') {
return
}
state.events.push({
kind: 'pty-write-cr',
operationId: null,
atEpochMs: Date.now(),
ptyId: typeof args.id === 'string' ? args.id : null,
writeChannel: 'pty:write'
})
}
state.writeAcceptedHandler = (event, args) => {
if (args?.data === '\r') {
state.events.push({
kind: 'pty-write-cr',
operationId: null,
atEpochMs: Date.now(),
ptyId: typeof args.id === 'string' ? args.id : null,
writeChannel: 'pty:writeAccepted'
})
}
return state.originalWriteAcceptedHandler(event, args)
}
handlers.set('pty:getCwd', state.cwdHandler)
handlers.set('pty:spawn', state.spawnHandler)
handlers.set('pty:writeAccepted', state.writeAcceptedHandler)
ipcMain.prependListener('pty:write', state.writeListener)
scope.__terminalSplitLatencyMainProbe = state
})
}
export async function resetSplitLatencyMainProbe(electronApp: ElectronApplication): Promise<void> {
await electronApp.evaluate(() => {
const state = (
globalThis as typeof globalThis & {
__terminalSplitLatencyMainProbe?: SplitLatencyMainProbeState
}
).__terminalSplitLatencyMainProbe
if (!state) {
throw new Error('Terminal split latency main probe is not installed')
}
state.events.length = 0
})
}
export async function readSplitLatencyMainProbe(
electronApp: ElectronApplication
): Promise<SplitLatencyMainProbeEvent[]> {
return electronApp.evaluate(() => {
const state = (
globalThis as typeof globalThis & {
__terminalSplitLatencyMainProbe?: SplitLatencyMainProbeState
}
).__terminalSplitLatencyMainProbe
if (!state) {
throw new Error('Terminal split latency main probe is not installed')
}
return [...state.events]
})
}
export async function disposeSplitLatencyMainProbe(
electronApp: ElectronApplication
): Promise<void> {
await electronApp.evaluate(({ ipcMain }) => {
const scope = globalThis as typeof globalThis & {
__terminalSplitLatencyMainProbe?: SplitLatencyMainProbeState
}
const state = scope.__terminalSplitLatencyMainProbe
if (!state) {
return
}
const handlers = (
ipcMain as unknown as { _invokeHandlers?: Map<string, MainProbeInvokeHandler> }
)._invokeHandlers
if (handlers?.get('pty:getCwd') === state.cwdHandler) {
handlers.set('pty:getCwd', state.originalCwdHandler)
}
if (handlers?.get('pty:spawn') === state.spawnHandler) {
handlers.set('pty:spawn', state.originalSpawnHandler)
}
if (handlers?.get('pty:writeAccepted') === state.writeAcceptedHandler) {
handlers.set('pty:writeAccepted', state.originalWriteAcceptedHandler)
}
ipcMain.removeListener('pty:write', state.writeListener)
delete scope.__terminalSplitLatencyMainProbe
})
}