mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-03 18:04:06 +00:00
fix(telegram): handle ENOENT race in spool drain recovery rename
Handle the Telegram isolated-polling spool recovery race where a stale `.processing` claim can disappear between discovery and the final rename back to pending. Recovery now treats `ENOENT` as benign and mirrors the existing duplicate-pending cleanup path for `EEXIST`, avoiding noisy drain-failure logs and spurious failure counters without changing claim ownership semantics. Adds a regression test that removes the claim from inside `shouldRecover`, after recovery has discovered the entry and before the final rename path, so the old code would hit the reported `ENOENT` window. Fixes #87847 Co-authored-by: Sebastien Tardif <sebtardif@ncf.ca>
This commit is contained in:
@@ -242,6 +242,37 @@ describe("Telegram ingress spool", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("handles ENOENT race when processing file is removed before recovery rename", async () => {
|
||||
await withTempSpool(async (spoolDir) => {
|
||||
await writeTelegramSpooledUpdate({
|
||||
spoolDir,
|
||||
update: { update_id: 45, message: { text: "vanishes" } },
|
||||
});
|
||||
const update = (await listTelegramSpooledUpdates({ spoolDir }))[0];
|
||||
if (!update) {
|
||||
throw new Error("Expected a spooled update");
|
||||
}
|
||||
const claimed = await claimTelegramSpooledUpdate(update);
|
||||
if (!claimed) {
|
||||
throw new Error("Expected a claimed update");
|
||||
}
|
||||
let shouldRecoverCalls = 0;
|
||||
const recovered = await recoverStaleTelegramSpooledUpdateClaims({
|
||||
spoolDir,
|
||||
staleMs: 0,
|
||||
shouldRecover: async () => {
|
||||
shouldRecoverCalls += 1;
|
||||
await fs.unlink(claimed.path);
|
||||
return true;
|
||||
},
|
||||
});
|
||||
|
||||
expect(recovered).toBe(0);
|
||||
expect(shouldRecoverCalls).toBe(1);
|
||||
expect(await fs.readdir(spoolDir)).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
it("does not treat stale claims with reused pids as live-owned", () => {
|
||||
const now = Date.now();
|
||||
expect(
|
||||
|
||||
@@ -443,7 +443,19 @@ export async function recoverStaleTelegramSpooledUpdateClaims(params: {
|
||||
if (await pathExists(pendingPath)) {
|
||||
await unlinkIfPresent(claimedPath);
|
||||
} else {
|
||||
await fs.rename(claimedPath, pendingPath);
|
||||
try {
|
||||
await fs.rename(claimedPath, pendingPath);
|
||||
} catch (err) {
|
||||
const code = (err as { code?: string }).code;
|
||||
if (code === "ENOENT") {
|
||||
continue;
|
||||
}
|
||||
if (code === "EEXIST") {
|
||||
await unlinkIfPresent(claimedPath);
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
recovered += 1;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user