mirror of
https://github.com/stablyai/orca.git
synced 2026-09-29 16:02:50 +00:00
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:
@@ -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 })
|
||||
})
|
||||
})
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user