fix(06): WR-06 bound post-write targeted resync with 10s timeout so a hang cannot wedge drain
This commit is contained in:
@@ -155,6 +155,14 @@ export function assembleRruleString(
|
|||||||
*/
|
*/
|
||||||
let isDraining = false
|
let isDraining = false
|
||||||
|
|
||||||
|
/**
|
||||||
|
* WR-06: max time to wait on the post-write targeted re-sync before marking the
|
||||||
|
* outbox row 'done'. A stalled Fastmail connection cannot wedge the single-process
|
||||||
|
* drain loop beyond this cap; the PWA's next sync-status poll reconciles any cache
|
||||||
|
* that the timed-out re-sync did not refresh.
|
||||||
|
*/
|
||||||
|
const RESYNC_TIMEOUT_MS = 10_000
|
||||||
|
|
||||||
// ── Credential + client loading ──────────────────────────────────────────────
|
// ── Credential + client loading ──────────────────────────────────────────────
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -690,7 +698,20 @@ export async function runOutboxDrain(): Promise<void> {
|
|||||||
// raced the re-sync and returned stale cache (deleted event still
|
// raced the re-sync and returned stale cache (deleted event still
|
||||||
// present, edit not yet applied) — forcing a manual refresh. Re-syncing
|
// present, edit not yet applied) — forcing a manual refresh. Re-syncing
|
||||||
// first means 'done' guarantees the cache already reflects the write.
|
// first means 'done' guarantees the cache already reflects the write.
|
||||||
await triggerTargetedResync(row.calendarUrl, row.userId, clientCache)
|
//
|
||||||
|
// WR-06: bound the re-sync with a timeout. triggerTargetedResync does
|
||||||
|
// unbounded network I/O against Fastmail (fetchCalendars + syncCalendar);
|
||||||
|
// a hang would leave this row 'pending' from the DB's view for the full
|
||||||
|
// duration, the 15s isDraining guard would no-op the next cycle, and the
|
||||||
|
// PWA would poll 'pending' indefinitely — wedging the single-process
|
||||||
|
// drain loop. On timeout we proceed to mark 'done' and let the PWA's next
|
||||||
|
// poll/refetch reconcile (the same documented refetch path the eager
|
||||||
|
// re-sync was optimizing). triggerTargetedResync already swallows its own
|
||||||
|
// errors, so the race only needs to cap the wait.
|
||||||
|
await Promise.race([
|
||||||
|
triggerTargetedResync(row.calendarUrl, row.userId, clientCache),
|
||||||
|
new Promise<void>((resolve) => setTimeout(resolve, RESYNC_TIMEOUT_MS)),
|
||||||
|
])
|
||||||
await db
|
await db
|
||||||
.update(calendarOutbox)
|
.update(calendarOutbox)
|
||||||
.set({ status: 'done' })
|
.set({ status: 'done' })
|
||||||
|
|||||||
Reference in New Issue
Block a user