mirror of
https://github.com/stablyai/orca.git
synced 2026-09-23 16:02:24 +00:00
* 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.
173 lines
6.8 KiB
TypeScript
173 lines
6.8 KiB
TypeScript
export type RendererPhaseStamps = {
|
|
marker: string
|
|
sourcePaneId: number
|
|
sourcePtyId: string
|
|
newPaneId: number | null
|
|
newPtyId: string | null
|
|
rendererTimeOriginEpochMs: number
|
|
keydownAtMs: number | null
|
|
focusAtMs: number | null
|
|
cwdRequestAtMs: number | null
|
|
cwdSettledAtMs: number | null
|
|
ptySpawnRequestAtMs: number | null
|
|
ptySpawnResultAtMs: number | null
|
|
ptyBoundAtMs: number | null
|
|
fixtureUnlockRequestedAtMs: number | null
|
|
fixtureUnlockIpcWriteAtMs: number | null
|
|
fixtureUnlockIpcWriteChannel: 'pty:write' | 'pty:writeAccepted' | null
|
|
fixtureReadyParsedAtMs: number | null
|
|
inputAtMs: number | null
|
|
firstEchoAtMs: number | null
|
|
}
|
|
|
|
export type SplitLatencyMainProbeEvent = {
|
|
kind: 'cwd-request' | 'cwd-settled' | 'pty-spawn-request' | 'pty-spawn-result' | 'pty-write-cr'
|
|
operationId: number | null
|
|
atEpochMs: number
|
|
ptyId: string | null
|
|
writeChannel: 'pty:write' | 'pty:writeAccepted' | null
|
|
}
|
|
|
|
export type SplitLatencySample = RendererPhaseStamps & {
|
|
phase: 'warmup' | 'measured'
|
|
iteration: number
|
|
completedWithinTimeout: boolean
|
|
paneCountAfterProbe: number
|
|
ptyExitObserved: boolean
|
|
cleanupError: string | null
|
|
shortcutToFocusMs: number | null
|
|
shortcutToCwdRequestMs: number | null
|
|
cwdLookupMs: number | null
|
|
cwdSettleToPtySpawnRequestMs: number | null
|
|
ptySpawnRequestToResultMs: number | null
|
|
ptySpawnResultToBindMs: number | null
|
|
shortcutToPtyBindMs: number | null
|
|
ptyBindToFixtureUnlockRequestMs: number | null
|
|
fixtureUnlockRequestToIpcWriteMs: number | null
|
|
fixtureUnlockIpcWriteToReadyParseMs: number | null
|
|
fixtureReadyParseToInputMs: number | null
|
|
shortcutToFirstEchoMs: number | null
|
|
ptyBindToFirstEchoMs: number | null
|
|
inputToFirstEchoMs: number | null
|
|
missing: string[]
|
|
success: boolean
|
|
}
|
|
|
|
function elapsed(start: number | null, end: number | null): number | null {
|
|
return start === null || end === null ? null : Math.max(0, end - start)
|
|
}
|
|
|
|
export function mergeSplitLatencyMainProbeEvents(
|
|
stamps: RendererPhaseStamps,
|
|
events: readonly SplitLatencyMainProbeEvent[]
|
|
): RendererPhaseStamps {
|
|
const keydownEpochMs =
|
|
stamps.keydownAtMs === null
|
|
? Number.NEGATIVE_INFINITY
|
|
: stamps.rendererTimeOriginEpochMs + stamps.keydownAtMs
|
|
const afterKeydown = events.filter((event) => event.atEpochMs >= keydownEpochMs - 2)
|
|
const cwdRequest = afterKeydown.find(
|
|
(event) => event.kind === 'cwd-request' && event.ptyId === stamps.sourcePtyId
|
|
)
|
|
const cwdSettled = cwdRequest
|
|
? afterKeydown.find(
|
|
(event) => event.kind === 'cwd-settled' && event.operationId === cwdRequest.operationId
|
|
)
|
|
: undefined
|
|
const spawnResult = afterKeydown.find(
|
|
(event) => event.kind === 'pty-spawn-result' && event.ptyId === stamps.newPtyId
|
|
)
|
|
const spawnRequest = spawnResult
|
|
? afterKeydown.find(
|
|
(event) =>
|
|
event.kind === 'pty-spawn-request' && event.operationId === spawnResult.operationId
|
|
)
|
|
: undefined
|
|
const unlockRequestEpochMs =
|
|
stamps.fixtureUnlockRequestedAtMs === null
|
|
? Number.NEGATIVE_INFINITY
|
|
: stamps.rendererTimeOriginEpochMs + stamps.fixtureUnlockRequestedAtMs
|
|
const fixtureUnlockWrite = afterKeydown.find(
|
|
(event) =>
|
|
event.kind === 'pty-write-cr' &&
|
|
event.ptyId === stamps.newPtyId &&
|
|
event.atEpochMs >= unlockRequestEpochMs - 2
|
|
)
|
|
const toRendererTime = (event: SplitLatencyMainProbeEvent | undefined): number | null =>
|
|
event ? event.atEpochMs - stamps.rendererTimeOriginEpochMs : null
|
|
|
|
return {
|
|
...stamps,
|
|
cwdRequestAtMs: toRendererTime(cwdRequest),
|
|
cwdSettledAtMs: toRendererTime(cwdSettled),
|
|
ptySpawnRequestAtMs: toRendererTime(spawnRequest),
|
|
ptySpawnResultAtMs: toRendererTime(spawnResult),
|
|
fixtureUnlockIpcWriteAtMs: toRendererTime(fixtureUnlockWrite),
|
|
fixtureUnlockIpcWriteChannel: fixtureUnlockWrite?.writeChannel ?? null
|
|
}
|
|
}
|
|
|
|
export function createSplitLatencySample(args: {
|
|
phase: SplitLatencySample['phase']
|
|
iteration: number
|
|
stamps: RendererPhaseStamps
|
|
completedWithinTimeout: boolean
|
|
paneCountAfterProbe: number
|
|
ptyExitObserved: boolean
|
|
cleanupError: string | null
|
|
}): SplitLatencySample {
|
|
const { stamps } = args
|
|
const missing = [
|
|
...(stamps.keydownAtMs === null ? ['keydown'] : []),
|
|
...(stamps.focusAtMs === null ? ['focus'] : []),
|
|
...(stamps.cwdRequestAtMs === null ? ['cwd-request'] : []),
|
|
...(stamps.cwdSettledAtMs === null ? ['cwd-settled'] : []),
|
|
...(stamps.ptySpawnRequestAtMs === null ? ['pty-spawn-request'] : []),
|
|
...(stamps.ptySpawnResultAtMs === null ? ['pty-spawn-result'] : []),
|
|
...(stamps.ptyBoundAtMs === null ? ['pty-bind'] : []),
|
|
...(stamps.fixtureUnlockRequestedAtMs === null ? ['fixture-unlock-request'] : []),
|
|
...(stamps.fixtureUnlockIpcWriteAtMs === null ? ['fixture-unlock-ipc-write'] : []),
|
|
...(stamps.fixtureReadyParsedAtMs === null ? ['fixture-ready-parse'] : []),
|
|
...(stamps.inputAtMs === null ? ['input'] : []),
|
|
...(stamps.firstEchoAtMs === null ? ['first-echo'] : []),
|
|
...(stamps.newPtyId === stamps.sourcePtyId ? ['pty-identity'] : []),
|
|
...(args.paneCountAfterProbe !== 2 ? [`pane-count:${args.paneCountAfterProbe}`] : []),
|
|
...(!args.ptyExitObserved ? ['pty-exit'] : []),
|
|
...(args.cleanupError ? ['cleanup'] : [])
|
|
]
|
|
return {
|
|
...stamps,
|
|
phase: args.phase,
|
|
iteration: args.iteration,
|
|
completedWithinTimeout: args.completedWithinTimeout,
|
|
paneCountAfterProbe: args.paneCountAfterProbe,
|
|
ptyExitObserved: args.ptyExitObserved,
|
|
cleanupError: args.cleanupError,
|
|
shortcutToFocusMs: elapsed(stamps.keydownAtMs, stamps.focusAtMs),
|
|
shortcutToCwdRequestMs: elapsed(stamps.keydownAtMs, stamps.cwdRequestAtMs),
|
|
cwdLookupMs: elapsed(stamps.cwdRequestAtMs, stamps.cwdSettledAtMs),
|
|
cwdSettleToPtySpawnRequestMs: elapsed(stamps.cwdSettledAtMs, stamps.ptySpawnRequestAtMs),
|
|
ptySpawnRequestToResultMs: elapsed(stamps.ptySpawnRequestAtMs, stamps.ptySpawnResultAtMs),
|
|
ptySpawnResultToBindMs: elapsed(stamps.ptySpawnResultAtMs, stamps.ptyBoundAtMs),
|
|
shortcutToPtyBindMs: elapsed(stamps.keydownAtMs, stamps.ptyBoundAtMs),
|
|
ptyBindToFixtureUnlockRequestMs: elapsed(
|
|
stamps.ptyBoundAtMs,
|
|
stamps.fixtureUnlockRequestedAtMs
|
|
),
|
|
fixtureUnlockRequestToIpcWriteMs: elapsed(
|
|
stamps.fixtureUnlockRequestedAtMs,
|
|
stamps.fixtureUnlockIpcWriteAtMs
|
|
),
|
|
fixtureUnlockIpcWriteToReadyParseMs: elapsed(
|
|
stamps.fixtureUnlockIpcWriteAtMs,
|
|
stamps.fixtureReadyParsedAtMs
|
|
),
|
|
fixtureReadyParseToInputMs: elapsed(stamps.fixtureReadyParsedAtMs, stamps.inputAtMs),
|
|
shortcutToFirstEchoMs: elapsed(stamps.keydownAtMs, stamps.firstEchoAtMs),
|
|
ptyBindToFirstEchoMs: elapsed(stamps.ptyBoundAtMs, stamps.firstEchoAtMs),
|
|
inputToFirstEchoMs: elapsed(stamps.inputAtMs, stamps.firstEchoAtMs),
|
|
missing,
|
|
success: args.completedWithinTimeout && missing.length === 0
|
|
}
|
|
}
|