Files
orca/src/shared/file-range-read.ts
Neil 99d9111653 fix(relay): fail an over-budget RPC response, not the connection (#17968)
The relay's control lane is a shared 1 MiB budget, and `sendResponse` admitted
responses onto it with the fatal default: once the lane was full, admission
closed the client. A ~900 KB `fs.listFiles` reply from a large remote workspace
therefore took down the whole remote session -- every terminal on it -- rather
than failing the one Quick Open request. The substitute `ResponseOverCapacity`
frame already there only covered the `legacy-response` lane, because the fatal
close beat it to the client.

A JSON-RPC response is the droppable class of control frame: it carries an id,
so one caller can be told and can retry. `pty.replay` and `notifyControl` keep
the fatal default -- they are never re-sent, and a silent drop there desyncs the
client with nothing to retry. Both response enqueues now pass
`controlOverflow: 'reject'`, so the substitute error is what the caller sees;
in the corner where even ~150 bytes will not fit, the caller's own 30s request
timeout settles it and the session survives.

Old clients are unaffected: they already decode this error code and message
generically (`ssh-channel-multiplexer.handleResponse` rejects the pending
promise with both), and the frame shape is unchanged. What changes is that a
listing which used to drop the connection now returns an error on it.
2026-09-02 01:29:26 -07:00

63 lines
3.1 KiB
TypeScript

/** Largest window one `fs.readFileRange` response may carry. Derived from the
* relay writer's admission budget, NOT from the 16 MiB frame cap.
*
* A response frame over `DISPATCHER_CONTROL_QUEUE_MAX_BYTES` (1 MiB) is demoted
* to the `legacy-response` lane, which is refused outright once the producer
* queue passes `DEFAULT_PRODUCER_QUEUE_MAX_BYTES` (2 MiB) -- an opaque
* `ResponseOverCapacity` that depends on unrelated load. 256 KiB of raw bytes
* frames to ~350 KB, so a full-cap response takes the control lane instead.
*
* The control lane is a shared budget, not a per-frame one: two full-cap
* responses fit alongside each other, and the third is refused. That refusal
* costs the one request -- `sendResponse` admits responses with
* `controlOverflow: 'reject'` and substitutes a `ResponseOverCapacity` error
* rather than closing the connection -- but it still turns on unrelated load,
* so the two-deep headroom that keeps it rare is pinned by a test. Widening
* the cap spends that headroom, so bigger transfers belong on the ack-paced
* bulk lane (`fs.readFileStream`) rather than on a wider window here.
*
* Raising this value is a wire change even though it is a constant: an older
* host still advertising `rangedReadVersion: 1` refuses a longer window with an
* opaque error, so a wider cap needs a version bump to negotiate against.
*
* Requests above it are REJECTED, never clamped: a clamped read is
* indistinguishable from EOF, and callers page by advancing `position`. */
export const MAX_FILE_RANGE_READ_BYTES = 256 * 1024
/** A range request the host will refuse. Separate from a read failure so a
* caller can tell "I asked for the wrong thing" from "the file is unreadable". */
export class FileRangeReadRequestError extends Error {
constructor(message: string) {
super(message)
this.name = 'FileRangeReadRequestError'
}
}
/** The single source of truth for what `fs.readFileRange` accepts. Both sides
* call it: the client so an invalid request never costs a round trip, the host
* because `position`/`length` land straight in an fd read and the relay has no
* request schema. Two independent copies would drift into a client that sends
* what the host rejects. */
export function validateFileRangeRequest(
position: unknown,
length: unknown
): { position: number; length: number } {
if (typeof position !== 'number' || !Number.isSafeInteger(position) || position < 0) {
throw new FileRangeReadRequestError(
'fs.readFileRange requires a non-negative safe-integer position'
)
}
if (typeof length !== 'number' || !Number.isSafeInteger(length) || length <= 0) {
throw new FileRangeReadRequestError('fs.readFileRange requires a positive integer length')
}
if (length > MAX_FILE_RANGE_READ_BYTES) {
throw new FileRangeReadRequestError(
`fs.readFileRange length ${length} exceeds the ${MAX_FILE_RANGE_READ_BYTES}-byte limit`
)
}
if (position > Number.MAX_SAFE_INTEGER - (length - 1)) {
throw new FileRangeReadRequestError('fs.readFileRange window exceeds safe-integer offsets')
}
return { position, length }
}