perf(tui): batch persistence work off the render path
This commit is contained in:
@@ -2,6 +2,10 @@
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Changed
|
||||
|
||||
- Batched same-turn file-session appends behind the session writer's microtask boundary. Render and tool paths can now record several transcript entries without paying one synchronous file write per entry, while explicit `flush()` and `close()` still make queued lines durable in order.
|
||||
|
||||
## [17.2.1] - 2026-07-30
|
||||
|
||||
### Added
|
||||
|
||||
@@ -90,6 +90,8 @@ class FileSessionStorageWriter implements SessionStorageWriter {
|
||||
#closed = false;
|
||||
#error: Error | undefined;
|
||||
#onError: ((err: Error) => void) | undefined;
|
||||
#pending = "";
|
||||
#flushScheduled = false;
|
||||
|
||||
constructor(fpath: string, options?: { flags?: "a" | "w"; onError?: (err: Error) => void }) {
|
||||
this.#onError = options?.onError;
|
||||
@@ -112,25 +114,41 @@ class FileSessionStorageWriter implements SessionStorageWriter {
|
||||
return error;
|
||||
}
|
||||
|
||||
async append(line: string): Promise<void> {
|
||||
if (this.#closed) throw new Error("Writer closed");
|
||||
if (this.#error) throw this.#error;
|
||||
try {
|
||||
const buf = Buffer.from(line, "utf-8");
|
||||
let offset = 0;
|
||||
while (offset < buf.length) {
|
||||
const written = fs.writeSync(this.#fd, buf, offset, buf.length - offset);
|
||||
if (written === 0) {
|
||||
throw new Error("Short write");
|
||||
}
|
||||
offset += written;
|
||||
#writeNow(line: string): void {
|
||||
const buf = Buffer.from(line, "utf-8");
|
||||
let offset = 0;
|
||||
while (offset < buf.length) {
|
||||
const written = fs.writeSync(this.#fd, buf, offset, buf.length - offset);
|
||||
if (written === 0) {
|
||||
throw new Error("Short write");
|
||||
}
|
||||
} catch (err) {
|
||||
throw this.#recordError(err);
|
||||
offset += written;
|
||||
}
|
||||
}
|
||||
|
||||
#flushPendingNow(): void {
|
||||
this.#flushScheduled = false;
|
||||
if (this.#pending.length === 0) return;
|
||||
const pending = this.#pending;
|
||||
this.#pending = "";
|
||||
try {
|
||||
this.#writeNow(pending);
|
||||
} catch (err) {
|
||||
this.#recordError(err);
|
||||
}
|
||||
}
|
||||
|
||||
async append(line: string): Promise<void> {
|
||||
if (this.#closed) throw new Error("Writer closed");
|
||||
if (this.#error) throw this.#error;
|
||||
this.#pending += line;
|
||||
if (this.#flushScheduled) return;
|
||||
this.#flushScheduled = true;
|
||||
queueMicrotask(() => this.#flushPendingNow());
|
||||
}
|
||||
|
||||
async flush(): Promise<void> {
|
||||
this.#flushPendingNow();
|
||||
if (this.#error) throw this.#error;
|
||||
}
|
||||
|
||||
@@ -140,6 +158,7 @@ class FileSessionStorageWriter implements SessionStorageWriter {
|
||||
|
||||
async close(): Promise<void> {
|
||||
if (this.#closed) return;
|
||||
this.#flushPendingNow();
|
||||
this.#closed = true;
|
||||
// Unregister from finalization - we're closing properly
|
||||
writerRegistry.unregister(this);
|
||||
|
||||
@@ -87,6 +87,41 @@ class ControlledTitleUpdateBackend implements SessionStorageBackend {
|
||||
this.#firstUpdate.reject(error);
|
||||
}
|
||||
}
|
||||
describe("FileSessionStorage writer", () => {
|
||||
let tempDir: string;
|
||||
let storage: FileSessionStorage;
|
||||
|
||||
beforeEach(async () => {
|
||||
tempDir = await fsp.mkdtemp(path.join(os.tmpdir(), "omp-session-writer-"));
|
||||
storage = new FileSessionStorage();
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await fsp.rm(tempDir, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
it("preserves append order through flush and close", async () => {
|
||||
const sessionPath = path.join(tempDir, "batched.jsonl");
|
||||
const writer = storage.openWriter(sessionPath, { flags: "w" });
|
||||
await writer.append("one\n");
|
||||
await writer.append("two\n");
|
||||
|
||||
await writer.flush();
|
||||
expect(fs.readFileSync(sessionPath, "utf8")).toBe("one\ntwo\n");
|
||||
await writer.close();
|
||||
});
|
||||
|
||||
it("flushes queued appends before closing", async () => {
|
||||
const sessionPath = path.join(tempDir, "closed.jsonl");
|
||||
const writer = storage.openWriter(sessionPath, { flags: "w" });
|
||||
await writer.append("one\n");
|
||||
await writer.append("two\n");
|
||||
await writer.close();
|
||||
|
||||
expect(fs.readFileSync(sessionPath, "utf8")).toBe("one\ntwo\n");
|
||||
});
|
||||
});
|
||||
|
||||
describe("FileSessionStorage.deleteSessionWithArtifacts", () => {
|
||||
let tempDir: string;
|
||||
let storage: FileSessionStorage;
|
||||
|
||||
Reference in New Issue
Block a user