@@ -17,17 +17,27 @@ import {
17
17
WorkerEvents ,
18
18
} from "./interfaces" ;
19
19
import {
20
+ calculateDelay ,
20
21
coerceError ,
21
22
CompiledOptions ,
22
23
CompiledSharedOptions ,
23
24
Releasers ,
25
+ RetryOptions ,
26
+ sleep ,
24
27
} from "./lib" ;
25
28
26
29
interface CronRequirements {
27
30
pgPool : Pool ;
28
31
events : WorkerEvents ;
29
32
}
30
33
34
+ const CRON_RETRY : RetryOptions = {
35
+ maxAttempts : Infinity ,
36
+ minDelay : 200 ,
37
+ maxDelay : 60_000 ,
38
+ multiplier : 1.5 ,
39
+ } ;
40
+
31
41
/**
32
42
* This function looks through all the cron items we have (e.g. from our
33
43
* crontab file) and compares them to the items we already know about. If the
@@ -347,7 +357,7 @@ export const runCron = (
347
357
if ( ! stopCalled ) {
348
358
stopCalled = true ;
349
359
if ( e ) {
350
- promise . reject ( e ) ;
360
+ promise . reject ( coerceError ( e ) ) ;
351
361
} else {
352
362
promise . resolve ( ) ;
353
363
}
@@ -358,6 +368,17 @@ export const runCron = (
358
368
}
359
369
}
360
370
371
+ let attempts = 0 ;
372
+ function restartCronAfterDelay ( error : Error ) {
373
+ ++ attempts ;
374
+ const delay = calculateDelay ( attempts - 1 , CRON_RETRY ) ;
375
+ logger . error (
376
+ `Cron hit an error; restarting in ${ delay } ms (attempt ${ attempts } ): ${ error } ` ,
377
+ { error, attempts } ,
378
+ ) ;
379
+ sleep ( delay ) . then ( cronMain ) . catch ( restartCronAfterDelay ) ;
380
+ }
381
+
361
382
async function cronMain ( ) {
362
383
if ( ! cron . _active ) {
363
384
return stop ( ) ;
@@ -411,6 +432,8 @@ export const runCron = (
411
432
if ( ! cron . _active ) {
412
433
return stop ( ) ;
413
434
}
435
+ // Healthy!
436
+ attempts = 0 ;
414
437
415
438
// THIS MUST COME BEFORE nextTimestamp IS MUTATED
416
439
const digest = digestTimestamp ( nextTimestamp ) ;
@@ -516,9 +539,10 @@ export const runCron = (
516
539
// timestamps on error).
517
540
scheduleNextLoop ( ) ;
518
541
} catch ( e ) {
519
- // If something goes wrong; abort. The calling code should re-schedule
520
- // which will re-trigger the backfilling code.
521
- return stop ( coerceError ( e ) ) ;
542
+ // If something goes wrong; abort the current loop and restart cron
543
+ // after an exponential back-off. This is essential because we need to
544
+ // re-trigger the backfilling code.
545
+ return restartCronAfterDelay ( coerceError ( e ) ) ;
522
546
}
523
547
}
524
548
@@ -540,7 +564,7 @@ export const runCron = (
540
564
promise,
541
565
} ;
542
566
543
- cronMain ( ) . catch ( stop ) ;
567
+ cronMain ( ) . catch ( restartCronAfterDelay ) ;
544
568
545
569
return cron ;
546
570
} ;
0 commit comments