From 6838e70f181d1bbbaee07ba46fd731e402b82221 Mon Sep 17 00:00:00 2001 From: Griffin Long Date: Thu, 27 Aug 2026 21:58:54 -0400 Subject: [PATCH] perf(subagent-log): memoize mkdirSync per sessionLabel to avoid redundant syscalls (#1320) --- src/agent/subagent/log.test.ts | 48 ++++++++++++++++++++++++++++++++++ src/agent/subagent/log.ts | 15 ++++++++--- 2 files changed, 59 insertions(+), 4 deletions(-) diff --git a/src/agent/subagent/log.test.ts b/src/agent/subagent/log.test.ts index a7adb239..ab224d6a 100644 --- a/src/agent/subagent/log.test.ts +++ b/src/agent/subagent/log.test.ts @@ -208,6 +208,54 @@ describe('stream-open-window concurrent writes', () => { }); }); +// --------------------------------------------------------------------------- +// mkdirSync memoization — multiple writers with the same sessionLabel +// --------------------------------------------------------------------------- + +describe('mkdirSync memoization', () => { + it('two writers sharing a sessionLabel both produce correct logs', async () => { + const session = 'test-memo-session'; + + const w1 = new SubagentLogWriter(session, 'sub-memo-1'); + const w2 = new SubagentLogWriter(session, 'sub-memo-2'); + + w1.write(makeChunkEvent('from-w1')); + w2.write(makeChunkEvent('from-w2')); + await Promise.all([w1.close(), w2.close()]); + + // Both log files must be readable — verifies the memoised path does not + // suppress directory creation for the first writer or break the second. + const events1: OutputEvent[] = []; + for await (const e of SubagentLogReader.readEvents(session, 'sub-memo-1')) { + events1.push(e); + } + const events2: OutputEvent[] = []; + for await (const e of SubagentLogReader.readEvents(session, 'sub-memo-2')) { + events2.push(e); + } + expect(events1).toHaveLength(1); + expect(events2).toHaveLength(1); + }); + + it('N writers sharing a sessionLabel all produce correct logs (stress)', async () => { + const session = 'test-memo-stress'; + const N = 8; + const writers = Array.from({ length: N }, (_, i) => + new SubagentLogWriter(session, `sub-stress-${i}`), + ); + for (const [i, w] of writers.entries()) w.write(makeChunkEvent(`msg-${i}`)); + await Promise.all(writers.map(w => w.close())); + + for (let i = 0; i < N; i++) { + const events: OutputEvent[] = []; + for await (const e of SubagentLogReader.readEvents(session, `sub-stress-${i}`)) { + events.push(e); + } + expect(events).toHaveLength(1); + } + }); +}); + // --------------------------------------------------------------------------- // MAX_LOG_BYTES cap // --------------------------------------------------------------------------- diff --git a/src/agent/subagent/log.ts b/src/agent/subagent/log.ts index 67772f3c..12b6605b 100644 --- a/src/agent/subagent/log.ts +++ b/src/agent/subagent/log.ts @@ -33,6 +33,9 @@ import type { OutputEvent } from '../types/session-types.js'; /** Maximum bytes per subagent log file (1 MB). Writes beyond this are dropped. */ const MAX_LOG_BYTES = 1_048_576; +/** Session-log directories already created this process — avoids redundant mkdirSync syscalls. */ +const createdDirs = new Set(); + // --------------------------------------------------------------------------- // Writer // --------------------------------------------------------------------------- @@ -53,10 +56,14 @@ export class SubagentLogWriter { readonly subagentId: string, ) { this.logPath = getSubagentLogPath(sessionLabel, subagentId); - try { - fs.mkdirSync(getSubagentLogSessionDir(sessionLabel), { recursive: true }); - } catch { - this.errored = true; + const dir = getSubagentLogSessionDir(sessionLabel); + if (!createdDirs.has(dir)) { + try { + fs.mkdirSync(dir, { recursive: true }); + createdDirs.add(dir); + } catch { + this.errored = true; + } } }