From 8815dfad3845413fd583c2220ddd8bc83828ce07 Mon Sep 17 00:00:00 2001 From: Dotta Date: Sun, 25 Oct 2026 10:03:39 -0500 Subject: [PATCH] fix(server): release settled receiver work during drain Co-Authored-By: Paperclip --- ...26-10-25-configuration-worker-drain-repair.md | 16 ++++++++++++++++ server/src/__tests__/chat-receiver-work.test.ts | 7 ++++++- .../chat-channels/runtime/receiver-work.ts | 4 +++- 3 files changed, 25 insertions(+), 2 deletions(-) diff --git a/doc/plans/2026-10-25-configuration-worker-drain-repair.md b/doc/plans/2026-10-25-configuration-worker-drain-repair.md index 9b2d8c64483..03501ef329e 100644 --- a/doc/plans/2026-10-25-configuration-worker-drain-repair.md +++ b/doc/plans/2026-10-25-configuration-worker-drain-repair.md @@ -43,3 +43,19 @@ followed by registration on resume without another producer. Production server typecheck passes in /tmp/sleep19-drain-typecheck.log. These tests use the migrated paperclip_sleep19_drain_20261025_test database. Remote CI/review must run on the new head; this is not evidence of provider suspension. + +## Receiver discovery — slice 20 / #17572 + +The strengthened first-receiver drain case fails with active=1 after commit in +/tmp/sleep20-drain-before.log. Apply the same owner-level pending predicate while +retaining all dirty scopes and lease/retry deadlines. All 19 receiver lifecycle +cases pass in /tmp/sleep20-drain-tests.log, including no SQL and an unchanged idle +snapshot for a minute after settlement, then receiver acquisition on resume. +Production server typecheck passes in /tmp/sleep20-drain-typecheck.log. The fresh +migrated database is paperclip_sleep20_drain_20261025_test. + +The three configuration owners now have measured drain activity as well as traffic +checks. Their retained timers remain memory-only while paused; they do not provide +external wake coverage. Fresh remote CI/review and downstream propagation remain +required. Origin/master was refreshed to e335265568; the latest change removes +idle task-run UI polling and does not replace these worker repairs. diff --git a/server/src/__tests__/chat-receiver-work.test.ts b/server/src/__tests__/chat-receiver-work.test.ts index 8cbfe0e3577..83aff022265 100644 --- a/server/src/__tests__/chat-receiver-work.test.ts +++ b/server/src/__tests__/chat-receiver-work.test.ts @@ -96,7 +96,12 @@ const support = external ? { supported: true } : await getEmbeddedPostgresTestSu }); it("preserves the first receiver written during drain without passive SQL", async () => { await start(); await settled(); enabled = false; const row = await endpoint(); - await quiet(60_000); expect(reconcile).not.toHaveBeenCalled(); + await vi.waitFor(async () => { + await vi.advanceTimersByTimeAsync(1000); + expect(idleWorkSnapshot().active).toBe(0); + }); + const drained = idleWorkSnapshot(); + await quiet(60_000); expect(idleWorkSnapshot()).toEqual(drained); expect(reconcile).not.toHaveBeenCalled(); enabled = true; await settled(); expect(reconcile).toHaveBeenCalledWith(row.id); }); it("reconstructs after standby becomes primary without restarting the worker", async () => { diff --git a/server/src/services/chat-channels/runtime/receiver-work.ts b/server/src/services/chat-channels/runtime/receiver-work.ts index 5cb664d87ab..ad62f0baa06 100644 --- a/server/src/services/chat-channels/runtime/receiver-work.ts +++ b/server/src/services/chat-channels/runtime/receiver-work.ts @@ -79,7 +79,9 @@ export function registerChatReceiverWork( } if (errors.length) throw new AggregateError(errors, "Chat receiver discovery could not settle"); }, - hasPending: async () => dirty.size > 0, + // Preserve hints/deadlines for resume without holding settled intent during drain. + // The coordinator independently fences unresolved database transactions. + hasPending: async () => canRun() && dirty.size > 0, nextRunAt: next, }); return { ready: worker.ready, changed(id: string) {