Files
2bdf281433 fix: avoid retaining foreign SSH file frames before metadata (#21167)
* fix: avoid retaining foreign SSH file frames before metadata

* test(ssh): exercise empty metadata through the streaming mux fixture

* fix(ssh): fail the file read when beforeResolve never runs

Moving the metadata install from .then() to beforeResolve moved it from a
mandatory callback to an optional one, and handleResponse clears the request
timer before beforeResolve runs. That left "response fulfilled, metadata never
installed" with no deadline: the read never settled, holding its notification
and dispose closures until mux disposal. Before this PR the same state failed
after the 60s inactivity deadline.

Unreachable with the concrete mux, which calls resolve on the line after
beforeResolve, but the hook is optional in the type and nothing enforces the
pairing. The guard is a no-op on every real path: empty, missing streamId,
cap-exceeded and alloc-failure all settle first, and the success path sets
metadataReady.

Found during review of #21167; raised at
https://github.com/stablyai/orca/pull/21167#issuecomment-5726058832

---------

Co-authored-by: m4air <m4air@Mac.localdomain>
Co-authored-by: Claude <noreply@anthropic.com>
2026-09-17 23:59:42 -07:00

85 lines
3.3 KiB
JavaScript

const assert = require('node:assert/strict')
const { readFileSync } = require('node:fs')
const { createHash } = require('node:crypto')
const path = require('node:path')
const { applyPatch, parsePatch, reversePatch } = require('diff')
const root = path.resolve(__dirname, '../../..')
const canonicalLf = (text) => text.replaceAll('\r\n', '\n')
const sha256 = (text) => createHash('sha256').update(text).digest('hex')
const readText = (filename) => canonicalLf(readFileSync(filename, 'utf8'))
const versions = JSON.parse(readText(path.join(__dirname, 'source-versions.json')))
function checkedPatch(name, expectedPath, read) {
const patches = parsePatch(canonicalLf(read(path.join(__dirname, name))))
assert.equal(patches.length, 1)
assert.equal(patches[0].newFileName, `b/${expectedPath}`)
assert.equal(patches[0].oldFileName, `a/${expectedPath}`)
return patches[0]
}
function observePending(source) {
return source.replace(
' const pending: PendingFrame[] = []',
' const pending: PendingFrame[] = []; globalThis.__sshPendingReaders.set(filePath, new WeakRef(pending))'
)
}
function loadSources({
graph = process.env.ORCA_SSH_READER_GRAPH ?? 'worktree',
variant = process.env.ORCA_SSH_READER_VARIANT ?? 'fixed',
read = readText
} = {}) {
assert.ok(graph === 'worktree' || graph === 'main')
assert.ok(variant === 'before' || variant === 'fixed')
const targetPatch = checkedPatch('fix.patch', versions.sourcePath, read)
const contextPatch = checkedPatch('main-context.patch', versions.contextPath, read)
const sources = new Map()
const hashes = {}
const selected = graph === 'main' ? versions.mainGraph : versions.worktreeGraph
for (const [relative, expected] of Object.entries(selected)) {
const filename = path.join(root, relative)
let text = canonicalLf(read(filename))
if (relative === versions.sourcePath) {
assert.equal(sha256(text), versions.fixedSha256, 'Fixed reader drift')
if (variant === 'before') {
text = applyPatch(text, reversePatch(targetPatch))
assert.notEqual(text, false, 'Reader patch no longer reverses')
assert.equal(sha256(text), versions.baselineSha256)
}
} else if (relative === versions.contextPath) {
const actual = sha256(text)
assert.ok(
actual === versions.worktreeGraph[relative] || actual === versions.mainGraph[relative],
'Unaudited writer context'
)
if (actual !== expected) {
text = applyPatch(text, graph === 'main' ? contextPatch : reversePatch(contextPatch))
assert.notEqual(text, false, 'Writer context no longer reconstructs')
}
assert.equal(sha256(text), expected)
} else {
assert.equal(sha256(text), expected, `Graph source drift: ${relative}`)
}
hashes[relative] = sha256(text)
sources.set(filename, text)
}
for (const [relative, expected] of Object.entries(versions.callerHashes)) {
assert.equal(
sha256(canonicalLf(read(path.join(root, relative)))),
expected,
`Caller drift: ${relative}`
)
}
const reader = sources.get(path.join(root, versions.sourcePath))
return {
root,
sources,
hashes,
observedReaderSha256: sha256(observePending(reader)),
graph,
variant
}
}
module.exports = { loadSources, observePending, readText, versions }