Skip to content

Commit b5ff6b2

Browse files
Merge pull request #1527 from corbitsdev/cl-10797-session-store-access-probes-regressed-67x-between-b5a452f6
fix(session): stop per-turn access probes on the base log
2 parents 60aff13 + 8a61b62 commit b5ff6b2

2 files changed

Lines changed: 81 additions & 5 deletions

File tree

‎src/session/optimized-context-store.test.ts‎

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2242,3 +2242,59 @@ describe("createOptimizedContextStore metadata ownership (CL-10667)", () => {
22422242
expect((await resumed.load()).tokenUsage).toEqual(USAGE_TWO);
22432243
});
22442244
});
2245+
2246+
describe("createOptimizedContextStore base-log access probes", () => {
2247+
test("a missing base loads as empty history", async () => {
2248+
const dir = tempDir();
2249+
const store = await createOptimizedContextStore(dir);
2250+
const loaded = await store.load();
2251+
expect(loaded.turns).toEqual([]);
2252+
expect(loaded.pendingOperations).toEqual([]);
2253+
});
2254+
2255+
test("access calls stay flat across turn counts", async () => {
2256+
const lowTurns = 3;
2257+
const highTurns = 9;
2258+
const low = await accessCallsForTurnCycles(lowTurns);
2259+
const high = await accessCallsForTurnCycles(highTurns);
2260+
const slope = (high - low) / (highTurns - lowTurns);
2261+
// A per-turn existence probe is slope 1. The directory listing already
2262+
// knows whether turns.jsonl is there, so extra turns must not add access.
2263+
expect(slope).toBe(0);
2264+
});
2265+
});
2266+
2267+
/**
2268+
* Counts `fs.promises.access` the way `scripts/context-store-perf.ts`
2269+
* `installCounters` does, for one fresh store's load/write/commit cycles.
2270+
*/
2271+
async function accessCallsForTurnCycles(turnCount: number): Promise<number> {
2272+
const target = fs.promises as unknown as Record<
2273+
string,
2274+
(...args: unknown[]) => unknown
2275+
>;
2276+
const original = target.access;
2277+
if (typeof original !== "function") {
2278+
throw new Error("fs.promises.access missing");
2279+
}
2280+
let access = 0;
2281+
target.access = function counted(this: unknown, ...args: unknown[]) {
2282+
access += 1;
2283+
return original.apply(this, args);
2284+
};
2285+
try {
2286+
const dir = tempDir();
2287+
const store = await createOptimizedContextStore(dir);
2288+
const history: ConversationTurn[] = [];
2289+
for (let index = 0; index < turnCount; index++) {
2290+
history.push(turn(`turn ${index}`));
2291+
await store.load();
2292+
await store.writePrompt(history);
2293+
await store.writeTurns(history);
2294+
await store.commit({ message: `checkpoint ${index}` });
2295+
}
2296+
return access;
2297+
} finally {
2298+
target.access = original;
2299+
}
2300+
}

‎src/session/optimized-context-store.ts‎

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -517,11 +517,28 @@ interface ParsedLogSegment {
517517
/**
518518
* Resilient parse of `turns.jsonl` alone (extras merged by the caller), so a
519519
* torn or poisoned base cannot block the healing write.
520+
*
521+
* `baseListed` is the directory listing `turnSegmentStats` already paid for.
522+
* A missing base is empty history (the same answer `pathExists` gave). A
523+
* listed base is read directly: `access` only repeated that listing, and a
524+
* changed base fingerprint was doing it once per appended turn. ENOENT
525+
* between the listing and the read is still empty history, not a throw.
520526
*/
521-
async function readBaseLogFromDisk(dir: string): Promise<ParsedLogSegment> {
527+
async function readBaseLogFromDisk(
528+
dir: string,
529+
baseListed: boolean,
530+
): Promise<ParsedLogSegment> {
531+
if (!baseListed) return { turns: [], approvals: [] };
522532
const basePath = path.join(dir, TURNS_FILE);
523-
if (!(await pathExists(basePath))) return { turns: [], approvals: [] };
524-
const text = await fs.promises.readFile(basePath, "utf-8");
533+
let text: string;
534+
try {
535+
text = await fs.promises.readFile(basePath, "utf-8");
536+
} catch (cause) {
537+
if (cause instanceof Error && "code" in cause && cause.code === "ENOENT") {
538+
return { turns: [], approvals: [] };
539+
}
540+
throw cause;
541+
}
525542
const approvals: PendingOperation[] = [];
526543
const turns = parseSegmentTurns(text, true, TURNS_FILE, true, approvals);
527544
return { turns, approvals };
@@ -1618,11 +1635,14 @@ export async function createSessionStores(
16181635
// Otherwise parse turns.jsonl here rather than through the isogit base's
16191636
// load(): that also validates metadata.json against the vendor shape,
16201637
// which this store no longer writes (tokenUsage lives on the sidecar).
1621-
// Metadata is read separately by readSessionMetadata.
1638+
// Metadata is read separately by readSessionMetadata. The segment list
1639+
// already says whether the base file is present, so the parse does not
1640+
// probe it again.
1641+
const baseListed = segments.at(0)?.name === TURNS_FILE;
16221642
const baseLog =
16231643
reused >= 1 && turnsCache !== null
16241644
? turnsCache.base
1625-
: await readBaseLogFromDisk(dir);
1645+
: await readBaseLogFromDisk(dir, baseListed);
16261646
const parsedExtras: ParsedLogSegment[] = [];
16271647
for (let index = 1; index < segments.length; index++) {
16281648
const cached =

0 commit comments

Comments
 (0)