/**
* overlay-mirror must not fork the daemon once per blob, and passes must not overlap.
*
* 2026-10-11 stortron: listPointers ran one `git cat-file -p` per small blob
* (~4,000 spawns a pass for hypercore-kb + hyper-tps), a pass outlived its 5-min
* setInterval, and passes piled up. Fork cost grows with the daemon's RSS, so it
* fed itself: the main thread spent 98% of 44 h of CPU in the kernel, the UDS
* accept backlog filled (511/511), and the box ran out of RAM + swap.
*/
import test from 'brittle'
import { spawn } from '#node:child_process'
import { parseCatFileBatch, listPointersFromGit, singleFlight } from '../lib/overlay-mirror.js'
import { spawnCaptureNoLeak } from '../lib/index.js'
const OID = 'a'.repeat(64)
const OID2 = 'b'.repeat(64)
const ptr = (oid, size) => 'hyper-overlay v1\noid blake2b:' + oid + '\nsize ' + size + '\n'
test('parseCatFileBatch — oid -> content, contents may contain newlines; missing objects skipped', (t) => {
const a = ptr(OID, 10)
const b = 'just text\nwith lines'
const out = '1111 blob ' + Buffer.byteLength(a) + '\n' + a + '\n' +
'2222 missing\n' +
'3333 blob ' + Buffer.byteLength(b) + '\n' + b + '\n'
const m = parseCatFileBatch(out)
t.is(m.get('1111'), a)
t.is(m.get('3333'), b)
t.absent(m.has('2222'))
})
test('parseCatFileBatch — parses by BYTES: a binary blob does not misalign the next entry', (t) => {
const bin = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0xff, 0xfe, 0x0a, 0x00, 0xc3])
const p = Buffer.from(ptr(OID, 3))
const out = Buffer.concat([
Buffer.from('1111 blob ' + bin.length + '\n'), bin, Buffer.from('\n'),
Buffer.from('2222 blob ' + p.length + '\n'), p, Buffer.from('\n')
])
const m = parseCatFileBatch(out)
t.is(m.get('2222'), p.toString('utf8'), 'pointer after a binary blob parsed intact')
})
test('listPointersFromGit — ONE batch call for all small blobs; big blobs never read', async (t) => {
const lsTree = [
'100644 blob 1111 ' + Buffer.byteLength(ptr(OID, 5)) + '\ttex/a.png',
'100644 blob 2222 12\tREADME.md',
'100644 blob 3333 ' + Buffer.byteLength(ptr(OID2, 7)) + '\ttex/b.png',
'100644 blob 4444 900000\tbig/in-git.bin',
'040000 tree 5555 -\tdir'
].join('\n')
const objects = { 1111: ptr(OID, 5), 2222: 'hello world\n', 3333: ptr(OID2, 7) }
const batches = []
const pointers = await listPointersFromGit({
lsTree: async () => lsTree,
catFileBatch: async (oids) => {
batches.push(oids)
return oids.map(o => o + ' blob ' + Buffer.byteLength(objects[o]) + '\n' + objects[o] + '\n').join('')
}
})
t.is(batches.length, 1, 'one batch, not one spawn per blob')
t.alike(batches[0], ['1111', '2222', '3333'], 'only blobs small enough to be pointers')
t.alike(pointers, [{ path: 'tex/a.png', oid: OID, size: 5 }, { path: 'tex/b.png', oid: OID2, size: 7 }])
})
test('singleFlight — a call while one is running does not start a second run', async (t) => {
let runs = 0
let release
const gate = new Promise(r => { release = r })
const pass = singleFlight(async () => { runs++; await gate; return runs })
const p1 = pass()
const p2 = pass()
t.is(runs, 1, 'second call joined the first')
release()
t.is(await p1, 1)
t.is(await p2, 1)
await pass()
t.is(runs, 2, 'a later call runs again')
})
test('spawnCaptureNoLeak feeds stdin when given input', async (t) => {
const out = await spawnCaptureNoLeak(spawn, 'cat', [], 'line one\nline two\n')
t.is(out, 'line one\nline two\n')
})