mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-30 13:20:54 +01:00
fix(server): release settled voice recovery intent during drain
Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
1 parent
1bf70185d2
commit
1b43332c74
3 files changed
+47
-2
No files matched your search
@@ -0,0 +1,34 @@
|
||||
# Voice recovery drain repair
|
||||
|
||||
Owner: slice 21, PR #17576. Reviewed baseline: 1bf70185d2.
|
||||
|
||||
Greptile finding 4370544173 is valid. The worker retained dirty scopes while
|
||||
paused, and reported those in-memory hints as pending work. After the producer
|
||||
committed, the coordinator therefore retained its intent activity hold. No SQL
|
||||
was required to reproduce the blocked drain; the earlier quiet assertion checked
|
||||
SQL only and missed the nonzero idle counter.
|
||||
|
||||
The worker now reports pending dirty scopes only while dispatch is permitted.
|
||||
It still retains every hint and next deadline. The coordinator independently
|
||||
checks databaseWorkPending, so an open/uncertain transaction retains its activity
|
||||
hold until transaction reconciliation establishes settlement. Paused timers then
|
||||
use the existing passive-maintenance path without changing the idle generation.
|
||||
Resume processes the retained first session without another producer signal.
|
||||
No provider behavior or external sleep eligibility rule changes.
|
||||
|
||||
The strengthened real-PostgreSQL test proves: an open write during drain retains
|
||||
an activity hold; commit releases it; a further minute causes neither SQL nor
|
||||
an idle-snapshot change; resume processes the one retained session exactly once.
|
||||
|
||||
- /tmp/sleep21-drain-before.log: original worker fails, active is 1 instead of 0.
|
||||
- /tmp/sleep21-drain-tests.log: all 11 voice recovery lifecycle tests pass.
|
||||
- /tmp/sleep21-drain-typecheck.log: production server typecheck passes.
|
||||
- Dedicated migrated database: paperclip_sleep21_drain_20261025_test.
|
||||
|
||||
Propagate this predicate into slice 22's extracted scoped-deadline worker and
|
||||
all descendants, then rerun its existing consumers. The earlier tool-health,
|
||||
Discord command, and receiver workers have the same predicate pattern; audit
|
||||
and repair those at their own parent PRs as a separate propagation pass. Do not
|
||||
report all stack drain behavior fixed from the voice test alone. The company
|
||||
inbound cleanup/deletion policy, older E2E failure, current chat milestone audit,
|
||||
and full original inventory remain outstanding.
|
||||
@@ -90,7 +90,16 @@ const support = external ? { supported: true } : await getEmbeddedPostgresTestSu
|
||||
await settled(); expect(inspect).toHaveBeenCalledOnce(); await quiet();
|
||||
});
|
||||
it("preserves the first session during drain without passive database reads", async () => {
|
||||
await start(); await settled(); enabled = false; await session(); await quiet(60_000); expect(inspect).not.toHaveBeenCalled();
|
||||
await start(); await settled(); enabled = false;
|
||||
await db.transaction(async tx => {
|
||||
await session({}, tx as unknown as typeof db);
|
||||
await vi.advanceTimersByTimeAsync(60_000);
|
||||
expect(idleWorkSnapshot().active).toBeGreaterThan(0);
|
||||
expect(inspect).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(inspect).not.toHaveBeenCalled();
|
||||
enabled = true; await settled(); expect(inspect).toHaveBeenCalledOnce(); await quiet();
|
||||
});
|
||||
it("retries unavailable authority without ending a valid provider call", async () => {
|
||||
|
||||
@@ -78,7 +78,9 @@ export function registerVoiceRecoveryWork(
|
||||
}
|
||||
if (errors.length) throw new AggregateError(errors, "Voice session recovery could not settle");
|
||||
},
|
||||
hasPending: async () => dirty.size > 0,
|
||||
// Retain scopes/deadlines for resume without retaining a settled write's
|
||||
// idle hold during drain. The coordinator still fences unresolved XIDs.
|
||||
hasPending: async () => canRun() && dirty.size > 0,
|
||||
nextRunAt: next,
|
||||
});
|
||||
return worker;
|
||||
|
||||
Reference in new issue
Block a user