@@ -123,15 +123,55 @@ export async function runReliableQueue(
123123}
124124
125125/**
126- * Runs an async function on a loop, waiting the delay between each iteration
126+ * Runs an async function on a loop, waiting the delay between each iteration.
127+ *
128+ * On failure, logs and sleeps with exponential backoff + jitter (capped at 30s)
129+ * before retrying, so a sick dependency (e.g. Postgres at max_connections) is
130+ * not hammered by tight-loop restarts.
131+ *
132+ * After MAX_CONSECUTIVE_FAILURES in a row, exits the process so PM2 can do a
133+ * clean-slate restart (fresh Knex pool, cleared in-memory state).
134+ *
135+ * The lastRun health beacon is only refreshed on success, so the existing
136+ * HEALTH_TIMEOUT alerting still detects a stuck worker before exit.
137+ *
127138 * @param func
128139 * @param delay
129140 */
130141export async function runInLoop ( func : ( ) => Promise < void > , delay : number ) {
142+ const BASE_BACKOFF_MS = 250 ;
143+ const MAX_BACKOFF_MS = 30_000 ;
144+ const MAX_CONSECUTIVE_FAILURES = 10 ;
145+ let consecutiveFailures = 0 ;
131146 while ( true ) {
132147 console . log ( "running %s" , func . name ) ;
133148 const start = Date . now ( ) ;
134- await func ( ) ;
149+ try {
150+ await func ( ) ;
151+ } catch ( e ) {
152+ consecutiveFailures += 1 ;
153+ console . error (
154+ "%s failed (consecutive failures: %d):" ,
155+ func . name ,
156+ consecutiveFailures ,
157+ e ,
158+ ) ;
159+ if ( consecutiveFailures >= MAX_CONSECUTIVE_FAILURES ) {
160+ console . error (
161+ "%s exceeded %d consecutive failures, exiting for PM2 restart" ,
162+ func . name ,
163+ MAX_CONSECUTIVE_FAILURES ,
164+ ) ;
165+ process . exit ( 1 ) ;
166+ }
167+ // 2 ** capped so the exponent doesn't overflow on long outages
168+ const exp = Math . min ( consecutiveFailures , 10 ) ;
169+ const backoff = Math . min ( MAX_BACKOFF_MS , BASE_BACKOFF_MS * 2 ** exp ) ;
170+ const jitter = Math . random ( ) * 250 ;
171+ await new Promise ( ( resolve ) => setTimeout ( resolve , backoff + jitter ) ) ;
172+ continue ;
173+ }
174+ consecutiveFailures = 0 ;
135175 const end = Date . now ( ) ;
136176 console . log ( "%s: %dms" , func . name , end - start ) ;
137177 await redis . setex (
0 commit comments