perf(agent-hooks): find spool newlines with Buffer.indexOf, not a per-byte loop

readSpoolFile walked every byte of every spooled JSONL file in an interpreted
loop to locate newlines. drainAgentHookSpool runs inside start() before the hook
listener binds, over up to AGENT_HOOK_SPOOL_MAX_FILES files, so this sits on the
startup path.

Buffer.indexOf reaches the same newlines via memchr. A 5.3MB spool file drops
from 32.50ms to 9.26ms per read. The torn-trailing-line contract is unchanged:
a final line with no newline is still left unconsumed.
This commit is contained in:
Neil
2026-09-04 04:29:48 -07:00
parent 637dc30a32
commit b2613f430d
2 changed files with 71 additions and 8 deletions
+65
View File
@@ -0,0 +1,65 @@
import { mkdtempSync, rmSync, writeFileSync } from 'node:fs'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { afterEach, beforeEach, describe, expect, it } from 'vitest'
import { readSpoolFile } from './agent-hook-spool'
let dir: string
beforeEach(() => {
dir = mkdtempSync(join(tmpdir(), 'orca-spool-read-'))
})
afterEach(() => {
rmSync(dir, { recursive: true, force: true })
})
function write(contents: string): string {
const file = join(dir, 'spool.jsonl')
writeFileSync(file, contents)
return file
}
function record(paneKey: string): string {
return JSON.stringify({ paneKey, source: 'PreToolUse', payload: {}, receivedAt: Date.now() })
}
describe('readSpoolFile', () => {
it('leaves a trailing line without its newline unconsumed', () => {
const complete = `${record('a')}\n`
const result = readSpoolFile(write(`${complete}${record('b')}`))
expect(result.records.map((entry) => entry.paneKey)).toEqual(['a'])
// The in-flight final record must stay replayable: consumed stops at the last newline.
expect(result.consumed).toBe(Buffer.byteLength(complete))
})
it('consumes through the final newline when every line is complete', () => {
const contents = `${record('a')}\n${record('b')}\n`
const result = readSpoolFile(write(contents))
expect(result.records.map((entry) => entry.paneKey)).toEqual(['a', 'b'])
expect(result.consumed).toBe(Buffer.byteLength(contents))
})
it('skips blank lines without consuming less than the bytes they occupy', () => {
const contents = `${record('a')}\n\n\n${record('b')}\n`
const result = readSpoolFile(write(contents))
expect(result.records.map((entry) => entry.paneKey)).toEqual(['a', 'b'])
expect(result.consumed).toBe(Buffer.byteLength(contents))
})
it('returns nothing for an empty file', () => {
expect(readSpoolFile(write(''))).toEqual({ records: [], consumed: 0 })
})
it('returns nothing for a file that is one torn line', () => {
expect(readSpoolFile(write(record('a'))).records).toEqual([])
expect(readSpoolFile(write(record('a'))).consumed).toBe(0)
})
it('returns nothing for a missing file', () => {
expect(readSpoolFile(join(dir, 'absent.jsonl'))).toEqual({ records: [], consumed: 0 })
})
})
+6 -8
View File
@@ -67,19 +67,17 @@ export function readSpoolFile(
const records: SpoolRecord[] = []
let consumed = 0
let start = 0
for (let end = 0; end <= bytes.length; end += 1) {
if (end !== bytes.length && bytes[end] !== 0x0a) {
continue
}
// indexOf, not a per-byte loop: this runs over every spooled file before the hook listener binds,
// and Buffer.indexOf finds the newline with memchr instead of an interpreted scan.
for (;;) {
const end = bytes.indexOf(0x0a, start)
// A final line without its newline may still be in flight from a hook writer.
// Leave it untouched until the writer terminates the record explicitly.
if (end === bytes.length && (end === 0 || bytes[end - 1] !== 0x0a)) {
if (end === -1) {
break
}
const lineBytes = bytes.subarray(start, end)
if (end !== bytes.length) {
consumed = end + 1
}
consumed = end + 1
start = end + 1
if (lineBytes.length === 0) {
continue