Skip to content

Commit 0cc4658

Browse files
authored
Cron: drain pending writes before reading run log (#25416)
* Cron: drain pending writes before reading run log * Retrigger CI
1 parent 29a5594 commit 0cc4658

File tree

2 files changed

+37
-0
lines changed

2 files changed

+37
-0
lines changed

src/cron/run-log.test.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,4 +245,30 @@ describe("cron run log", () => {
245245
expect(getPendingCronRunLogWriteCountForTests()).toBe(0);
246246
});
247247
});
248+
249+
it("read drains pending fire-and-forget writes", async () => {
250+
await withRunLogDir("openclaw-cron-log-drain-", async (dir) => {
251+
const logPath = path.join(dir, "runs", "job-drain.jsonl");
252+
253+
// Fire-and-forget write (simulates the `void appendCronRunLog(...)` pattern
254+
// in server-cron.ts). Do NOT await.
255+
const writePromise = appendCronRunLog(logPath, {
256+
ts: 42,
257+
jobId: "job-drain",
258+
action: "finished",
259+
status: "ok",
260+
summary: "drain-test",
261+
});
262+
void writePromise.catch(() => undefined);
263+
264+
// Read should see the entry because it drains pending writes.
265+
const entries = await readCronRunLogEntries(logPath, { limit: 10 });
266+
expect(entries).toHaveLength(1);
267+
expect(entries[0]?.ts).toBe(42);
268+
expect(entries[0]?.summary).toBe("drain-test");
269+
270+
// Clean up
271+
await writePromise.catch(() => undefined);
272+
});
273+
});
248274
});

src/cron/run-log.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,14 @@ export function getPendingCronRunLogWriteCountForTests() {
103103
return writesByPath.size;
104104
}
105105

106+
async function drainPendingWrite(filePath: string): Promise<void> {
107+
const resolved = path.resolve(filePath);
108+
const pending = writesByPath.get(resolved);
109+
if (pending) {
110+
await pending.catch(() => undefined);
111+
}
112+
}
113+
106114
async function pruneIfNeeded(filePath: string, opts: { maxBytes: number; keepLines: number }) {
107115
const stat = await fs.stat(filePath).catch(() => null);
108116
if (!stat || stat.size <= opts.maxBytes) {
@@ -152,6 +160,7 @@ export async function readCronRunLogEntries(
152160
filePath: string,
153161
opts?: { limit?: number; jobId?: string },
154162
): Promise<CronRunLogEntry[]> {
163+
await drainPendingWrite(filePath);
155164
const limit = Math.max(1, Math.min(5000, Math.floor(opts?.limit ?? 200)));
156165
const page = await readCronRunLogEntriesPage(filePath, {
157166
jobId: opts?.jobId,
@@ -334,6 +343,7 @@ export async function readCronRunLogEntriesPage(
334343
filePath: string,
335344
opts?: ReadCronRunLogPageOptions,
336345
): Promise<CronRunLogPageResult> {
346+
await drainPendingWrite(filePath);
337347
const limit = Math.max(1, Math.min(200, Math.floor(opts?.limit ?? 50)));
338348
const raw = await fs.readFile(path.resolve(filePath), "utf-8").catch(() => "");
339349
const statuses = normalizeRunStatuses(opts);
@@ -388,6 +398,7 @@ export async function readCronRunLogEntriesPageAll(
388398
nextOffset: null,
389399
};
390400
}
401+
await Promise.all(jsonlFiles.map((f) => drainPendingWrite(f)));
391402
const chunks = await Promise.all(
392403
jsonlFiles.map(async (filePath) => {
393404
const raw = await fs.readFile(filePath, "utf-8").catch(() => "");

0 commit comments

Comments
 (0)