mirror of
https://github.com/stablyai/orca.git
synced 2026-11-03 10:37:35 +00:00
* fix(memory): release consumed stream buffers and abandoned request state * fix(memory): preserve queued cancellation for shared promise callers * fix(memory): keep workspace cache cleanup free of browser side effects * fix(memory): follow renamed workspace owners in cached diff views * fix(memory): a viewer unmounting after its workspace is removed cannot re-create cached diff entries Combined-diff view state, scroll offset and scroll anchor now live in one owner-tagged entry per view key. A mounted viewer claims its key; writes only update claimed entries, so the late unmount write that follows a workspace removal is a no-op instead of re-adding orphan scroll entries. * fix(memory): tighten retention fixes and let evicted diff views heal - Combined diff: writes go through a per-mount lease. Forgetting a workspace revokes its leases, so a late unmount write cannot resurrect entries, while an LRU-evicted entry of a still-mounted viewer is re-created on its next write. Drops the duplicated view scrollTop. - NDJSON framer: copy a retained tail only when it is a strict slice of its chunk, copy an unfinished line once, and copy every rejected-line prefix. - Binary frame reader: single exit path for the end-of-feed leftover copy. - Relay readiness reuses waitForPromiseWithSignal instead of re-implementing it. - PTY preconnect input: pass the listener-removing settle into createInput instead of mutating the input afterwards. * docs(memory): state the deferSettled ordering guarantee precisely --------- Co-authored-by: m4air <m4air@Mac.localdomain> Co-authored-by: Neil <neil@stably.ai>
81 lines
2.6 KiB
TypeScript
81 lines
2.6 KiB
TypeScript
import { describe, expect, it } from 'vitest'
|
|
import { createIncrementalNdjsonFramer } from './main-process-ndjson-framer'
|
|
|
|
const CHUNK_CHARS = 1024 * 1024
|
|
const READERS = 32
|
|
const TAIL = 'tail'.repeat(1024)
|
|
|
|
function collectHeap(): number {
|
|
if (!globalThis.gc) {
|
|
throw new Error('Run this regression with --expose-gc')
|
|
}
|
|
// V8's last regexp input is a separate owner of the record under test.
|
|
void /reset/.test('reset')
|
|
globalThis.gc()
|
|
globalThis.gc()
|
|
return process.memoryUsage().heapUsed
|
|
}
|
|
|
|
describe('NDJSON retained chunk memory', () => {
|
|
it.each(['tail', 'paused', 'rejected'] as const)(
|
|
'releases consumed megabyte records while retaining %s content exactly',
|
|
(mode) => {
|
|
const held: { finish: () => void; result: () => string }[] = []
|
|
const baseline = collectHeap()
|
|
for (let index = 0; index < READERS; index += 1) {
|
|
let paused = false
|
|
let result = ''
|
|
const framer = createIncrementalNdjsonFramer(
|
|
(record) => {
|
|
if (record && typeof record === 'object' && 'tail' in record) {
|
|
result = typeof record.tail === 'string' ? record.tail : ''
|
|
}
|
|
if (mode === 'paused') {
|
|
paused = true
|
|
}
|
|
},
|
|
(rejected) => {
|
|
if (rejected.kind === 'line-too-long') {
|
|
result = rejected.prefix
|
|
}
|
|
},
|
|
{
|
|
maxLineBytes: mode === 'rejected' ? 1024 : CHUNK_CHARS * 2,
|
|
shouldPause: () => paused
|
|
}
|
|
)
|
|
const payload = String.fromCharCode(65 + (index % 26)).repeat(CHUNK_CHARS)
|
|
const chunk = `{"data":"${payload}"}\n${mode === 'paused' ? '{}\n' : ''}${mode === 'rejected' ? '' : `{"tail":"${TAIL}`}`
|
|
framer.feed(chunk)
|
|
held.push({
|
|
finish: () => {
|
|
if (mode === 'paused') {
|
|
paused = false
|
|
framer.resume()
|
|
// Delivering the queued record pauses again before the unfinished tail.
|
|
paused = false
|
|
framer.resume()
|
|
}
|
|
if (mode !== 'rejected') {
|
|
framer.feed('"}\n')
|
|
}
|
|
},
|
|
result: () => result
|
|
})
|
|
}
|
|
const retainedBytes = collectHeap() - baseline
|
|
expect(held).toHaveLength(READERS)
|
|
expect(retainedBytes).toBeLessThan(8 * 1024 * 1024)
|
|
for (const reader of held) {
|
|
reader.finish()
|
|
if (mode === 'rejected') {
|
|
expect(reader.result()).toHaveLength(64 * 1024)
|
|
expect(reader.result().startsWith('{"data":"')).toBe(true)
|
|
} else {
|
|
expect(reader.result()).toBe(TAIL)
|
|
}
|
|
}
|
|
}
|
|
)
|
|
})
|