107 lines
3.9 KiB
TypeScript
107 lines
3.9 KiB
TypeScript
|
|
/**
|
|||
|
|
* @module tests/integration/vfs-root-sweep-once
|
|||
|
|
* @description THE OLD-ROOT SWEEP RUNS ONCE PER STORE, NOT ONCE PER OPEN.
|
|||
|
|
*
|
|||
|
|
* The VFS bootstrap ran a filtered `find()` over the whole store on EVERY
|
|||
|
|
* open, hunting for root directories created before the fixed root id existed
|
|||
|
|
* — duplicates a store has either always had or never will. MEASURED on a
|
|||
|
|
* 14,056-noun / 72,679-verb store: the phase it dominates cost 43–53 SECONDS
|
|||
|
|
* of every open, warm reopens included.
|
|||
|
|
*
|
|||
|
|
* The law: a migration sweep is caused by the store's state, not by the clock
|
|||
|
|
* or the open count. It runs behind the doors, records that it ran, and a
|
|||
|
|
* store carrying that record never sweeps again.
|
|||
|
|
*/
|
|||
|
|
|
|||
|
|
import { describe, it, expect, afterEach, vi } from 'vitest'
|
|||
|
|
import { mkdtempSync, rmSync, existsSync } from 'node:fs'
|
|||
|
|
import { tmpdir } from 'node:os'
|
|||
|
|
import { join } from 'node:path'
|
|||
|
|
import { Brainy } from '../../src/brainy.js'
|
|||
|
|
import { NounType } from '../../src/types/graphTypes.js'
|
|||
|
|
import { VirtualFileSystem } from '../../src/vfs/VirtualFileSystem.js'
|
|||
|
|
|
|||
|
|
describe('the VFS old-root sweep', () => {
|
|||
|
|
const dirs: string[] = []
|
|||
|
|
const brains: Brainy[] = []
|
|||
|
|
|
|||
|
|
afterEach(async () => {
|
|||
|
|
for (const b of brains.splice(0)) {
|
|||
|
|
try { await b.close() } catch { /* already closed */ }
|
|||
|
|
}
|
|||
|
|
for (const d of dirs.splice(0)) {
|
|||
|
|
try { rmSync(d, { recursive: true, force: true }) } catch { /* ignore */ }
|
|||
|
|
}
|
|||
|
|
vi.restoreAllMocks()
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
async function open(dir: string): Promise<Brainy> {
|
|||
|
|
const brain = new Brainy({ requireSubtype: false, storage: { type: 'filesystem', path: dir } })
|
|||
|
|
brains.push(brain)
|
|||
|
|
await brain.init()
|
|||
|
|
return brain
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
it('sweeps on the first open, records it, and never sweeps again', async () => {
|
|||
|
|
const dir = mkdtempSync(join(tmpdir(), 'brainy-root-sweep-'))
|
|||
|
|
dirs.push(dir)
|
|||
|
|
|
|||
|
|
const sweepSpy = vi.spyOn(
|
|||
|
|
VirtualFileSystem.prototype as unknown as { cleanupOldRoots: () => Promise<void> },
|
|||
|
|
'cleanupOldRoots'
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
const first = await open(dir)
|
|||
|
|
await (first.vfs as unknown as { whenRootSweepSettled: () => Promise<void> }).whenRootSweepSettled()
|
|||
|
|
expect(sweepSpy).toHaveBeenCalledTimes(1)
|
|||
|
|
// The record is durable engine plumbing under _system/, like every other marker.
|
|||
|
|
expect(
|
|||
|
|
existsSync(join(dir, '_system', 'vfs-root-sweep.json')) ||
|
|||
|
|
existsSync(join(dir, '_system', 'vfs-root-sweep.json.gz'))
|
|||
|
|
).toBe(true)
|
|||
|
|
|
|||
|
|
await first.add({ data: 'a row so the store is not trivially empty', type: NounType.Concept })
|
|||
|
|
await first.flush()
|
|||
|
|
await first.close()
|
|||
|
|
brains.splice(brains.indexOf(first), 1)
|
|||
|
|
|
|||
|
|
sweepSpy.mockClear()
|
|||
|
|
const second = await open(dir)
|
|||
|
|
await (second.vfs as unknown as { whenRootSweepSettled: () => Promise<void> }).whenRootSweepSettled()
|
|||
|
|
expect(sweepSpy).not.toHaveBeenCalled()
|
|||
|
|
|
|||
|
|
await second.close()
|
|||
|
|
brains.splice(brains.indexOf(second), 1)
|
|||
|
|
|
|||
|
|
// ...and a third open, to prove it is the record and not a one-off.
|
|||
|
|
sweepSpy.mockClear()
|
|||
|
|
const third = await open(dir)
|
|||
|
|
await (third.vfs as unknown as { whenRootSweepSettled: () => Promise<void> }).whenRootSweepSettled()
|
|||
|
|
expect(sweepSpy).not.toHaveBeenCalled()
|
|||
|
|
}, 180_000)
|
|||
|
|
|
|||
|
|
it('the open does not wait for the sweep', async () => {
|
|||
|
|
const dir = mkdtempSync(join(tmpdir(), 'brainy-root-sweep-async-'))
|
|||
|
|
dirs.push(dir)
|
|||
|
|
|
|||
|
|
const proto = VirtualFileSystem.prototype as unknown as Record<
|
|||
|
|
string,
|
|||
|
|
(...args: unknown[]) => Promise<unknown>
|
|||
|
|
>
|
|||
|
|
const real = proto.cleanupOldRoots
|
|||
|
|
proto.cleanupOldRoots = async function slow(this: unknown, ...args: unknown[]) {
|
|||
|
|
await new Promise((r) => setTimeout(r, 4_000))
|
|||
|
|
return real.apply(this, args)
|
|||
|
|
}
|
|||
|
|
try {
|
|||
|
|
const startedAt = Date.now()
|
|||
|
|
const brain = await open(dir)
|
|||
|
|
const openMs = Date.now() - startedAt
|
|||
|
|
expect(openMs).toBeLessThan(3_000)
|
|||
|
|
await (brain.vfs as unknown as { whenRootSweepSettled: () => Promise<void> }).whenRootSweepSettled()
|
|||
|
|
} finally {
|
|||
|
|
proto.cleanupOldRoots = real
|
|||
|
|
}
|
|||
|
|
}, 180_000)
|
|||
|
|
})
|