mirror of
https://github.com/stablyai/orca.git
synced 2026-09-27 00:02:37 +00:00
* perf(terminal): release oversized backing strings behind pending controls * test(terminal): record reproducible pending-storage gate evidence * perf(terminal): own retained control fragments with a fast copy primitive The pending-control ownership landed with a charCodeAt block copier (10 us at 4 Ki, 170 us at 64 Ki), so it needed a "copy only when discarded output dominates the tail" gate to stay affordable. That gate was the whole cost problem: on adversarial streams it fires every chunk and pays the slow copy (+45% on 16 Ki ANSI chunks, +81..111% on 194 Ki status chunks), and it also skipped ownership on fragments too small to be sliced strings anyway. ownRetainedString replaces it with a Buffer utf16le round trip (0.57 us at 4 Ki, 21.9 us at 64 Ki) and returns anything below V8's SlicedString kMinLength unchanged. Buffer is absent in the renderer and on mobile, so the copier is resolved once behind a lone-surrogate round-trip self-check and falls back to the block copier. With a ~1 us copy the gate is unnecessary: ownership is now unconditional at all three retention sites and the adversarial cases land within noise of the un-owned parsers. The three forced-GC threshold fixtures are replaced by one forced-GC test for the primitive plus deterministic spy assertions that each site routes its retained value through ownRetainedString. All fidelity and differential coverage is kept. * fix(terminal): escape the NUL in the round-trip probe A raw NUL byte in the source made git treat the file as binary, so its diffs and blame were unreadable. Escapes are equivalent at runtime.
51 lines
1.7 KiB
TypeScript
51 lines
1.7 KiB
TypeScript
import { copyUtf16SuffixToOwnedString } from './owned-utf16-suffix'
|
|
|
|
// V8 SlicedString::kMinLength. Shorter slices are already flat, so copying them is pure waste.
|
|
const MIN_SLICED_STRING_LENGTH = 13
|
|
|
|
/** Lone surrogates and an unpaired trail must survive the round trip byte-for-byte. */
|
|
const ROUND_TRIP_PROBE = '\ud800a\udfff\u0000\u6f22'
|
|
|
|
let copyString: ((value: string) => string) | null = null
|
|
|
|
function detectBufferCopier(): ((value: string) => string) | null {
|
|
const buffer = (globalThis as { Buffer?: typeof Buffer }).Buffer
|
|
if (typeof buffer?.from !== 'function') {
|
|
return null
|
|
}
|
|
try {
|
|
if (buffer.from(ROUND_TRIP_PROBE, 'utf16le').toString('utf16le') !== ROUND_TRIP_PROBE) {
|
|
return null
|
|
}
|
|
} catch {
|
|
return null
|
|
}
|
|
return (value) => buffer.from(value, 'utf16le').toString('utf16le')
|
|
}
|
|
|
|
function resolveCopyString(): (value: string) => string {
|
|
if (!copyString) {
|
|
// Buffer is absent in the renderer and on mobile; fall back to the code-unit block copier.
|
|
copyString =
|
|
detectBufferCopier() ?? ((value: string) => copyUtf16SuffixToOwnedString(value, value.length))
|
|
}
|
|
return copyString
|
|
}
|
|
|
|
/**
|
|
* Return a standalone copy of a string that outlives the chunk it came from.
|
|
* Why: `bigChunk.slice(a, b)` is a V8 SlicedString that pins the whole parent,
|
|
* so retaining a few KiB of tail can pin megabytes of already-consumed output.
|
|
*/
|
|
export function ownRetainedString(value: string): string {
|
|
if (value.length < MIN_SLICED_STRING_LENGTH) {
|
|
return value
|
|
}
|
|
return resolveCopyString()(value)
|
|
}
|
|
|
|
/** Test-only: drop the memoized copier so a fallback path can be exercised. */
|
|
export function resetOwnRetainedStringCopier(): void {
|
|
copyString = null
|
|
}
|