OverviewFilesCommitsIssuesDocsReleasesPeers

pgh-sidecar / test / overlay-mirror-batch.test.js

85 lines · javascript ref:
/**
 * 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')
})