mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-11-11 05:11:49 +01:00
fix(server): preserve backlog deadlines after partial failure
Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
1 parent
fe1c886a3d
commit
d3187f76a4
7 files changed
+126
-6
No files matched your search
@@ -860,6 +860,27 @@ suite("Discord command ownership store (real PostgreSQL, no network)", () => {
|
||||
expect(errors).toEqual([]);
|
||||
});
|
||||
|
||||
it("drains healthy commands promptly while one entry remains in failure backoff", async () => {
|
||||
await reconcileDatabaseWork(db, DELIVERY_QUEUES.chatCommandRegistration);
|
||||
vi.useFakeTimers({ toFake: ["Date", "setTimeout", "clearTimeout"], shouldAdvanceTime: true });
|
||||
const entries = Array.from({ length: 300 }, (_, index) => ({
|
||||
id: `command-${index}`, companyId: `company-${index % 10}`, connectionId: `connection-${index}`, dueAt: 0,
|
||||
}));
|
||||
const check = vi.fn(async (id: string) => {
|
||||
if (id === "command-0") throw new Error("Unavailable credential");
|
||||
return { ...entries.find(entry => entry.id === id)!, dueAt: Date.now() + 300_000 };
|
||||
});
|
||||
const onError = vi.fn();
|
||||
coordinator = createDeliveryWorkCoordinator({ owner: db, canRun: () => true, onError });
|
||||
await registerDiscordCommandWork(coordinator, { load: async () => entries, check }, () => true).ready;
|
||||
await vi.advanceTimersByTimeAsync(15_000);
|
||||
expect(new Set(check.mock.calls.map(([id]) => id)).size).toBe(300);
|
||||
expect(check.mock.calls.filter(([id]) => id === "command-0")).toHaveLength(1);
|
||||
expect(onError).toHaveBeenCalledOnce();
|
||||
await vi.advanceTimersByTimeAsync(45_000);
|
||||
expect(check.mock.calls.filter(([id]) => id === "command-0")).toHaveLength(2);
|
||||
expect(check.mock.calls.filter(([id]) => id !== "command-0")).toHaveLength(299);
|
||||
});
|
||||
it("recovers queued scopes after an unknown hint instead of dropping the wake", async () => {
|
||||
await reconcileDatabaseWork(db, DELIVERY_QUEUES.chatCommandRegistration);
|
||||
vi.useFakeTimers({ toFake: ["Date", "setTimeout", "clearTimeout"], shouldAdvanceTime: true });
|
||||
|
||||
@@ -78,9 +78,16 @@ export function createDeliveryWorkCoordinator(input: {
|
||||
}
|
||||
}).catch(error => {
|
||||
schedule(task.retryMs);
|
||||
let failure = error;
|
||||
try {
|
||||
const next = input.canRun() ? task.nextRunAt?.() : null;
|
||||
if (next != null && next > Date.now()) schedule(0, next);
|
||||
} catch (deadlineError) {
|
||||
failure = new AggregateError([error, deadlineError], "Delivery failure deadline could not be read");
|
||||
}
|
||||
// Logging must never turn a recoverable sweep failure into an
|
||||
// unhandled rejection or prevent other queues from running.
|
||||
try { if (!workerStopped) input.onError(error, queue); } catch { /* Recovery remains scheduled. */ }
|
||||
try { if (!workerStopped) input.onError(failure, queue); } catch { /* Recovery remains scheduled. */ }
|
||||
}).finally(() => {
|
||||
running = null;
|
||||
controller = null;
|
||||
|
||||
Reference in new issue
Block a user