@@ -31,6 +31,8 @@ const LOG_TAG = 'ExponentialBackoff';
31
31
export class ExponentialBackoff {
32
32
private currentBaseMs : number ;
33
33
private timerPromise : CancelablePromise < void > | null = null ;
34
+ /** The last backoff attempt, as epoch milliseconds. */
35
+ private lastAttemptTime = Date . now ( ) ;
34
36
35
37
constructor (
36
38
/**
@@ -92,18 +94,36 @@ export class ExponentialBackoff {
92
94
93
95
// First schedule using the current base (which may be 0 and should be
94
96
// honored as such).
95
- const delayWithJitterMs = this . currentBaseMs + this . jitterDelayMs ( ) ;
97
+ const desiredDelayWithJitterMs = Math . floor (
98
+ this . currentBaseMs + this . jitterDelayMs ( )
99
+ ) ;
100
+
101
+ // Guard against lastAttemptTime being in the future due to a clock change.
102
+ const delaySoFarMs = Math . max ( 0 , Date . now ( ) - this . lastAttemptTime ) ;
103
+
104
+ // Guard against the backoff delay already being past.
105
+ const remainingDelayMs = Math . max (
106
+ 0 ,
107
+ desiredDelayWithJitterMs - delaySoFarMs
108
+ ) ;
109
+
96
110
if ( this . currentBaseMs > 0 ) {
97
111
log . debug (
98
112
LOG_TAG ,
99
- `Backing off for ${ delayWithJitterMs } ms ` +
100
- `(base delay: ${ this . currentBaseMs } ms)`
113
+ `Backing off for ${ remainingDelayMs } ms ` +
114
+ `(base delay: ${ this . currentBaseMs } ms, ` +
115
+ `delay with jitter: ${ desiredDelayWithJitterMs } ms, ` +
116
+ `last attempt: ${ delaySoFarMs } ms ago)`
101
117
) ;
102
118
}
119
+
103
120
this . timerPromise = this . queue . enqueueAfterDelay (
104
121
this . timerId ,
105
- delayWithJitterMs ,
106
- op
122
+ remainingDelayMs ,
123
+ ( ) => {
124
+ this . lastAttemptTime = Date . now ( ) ;
125
+ return op ( ) ;
126
+ }
107
127
) ;
108
128
109
129
// Apply backoff factor to determine next delay and ensure it is within
0 commit comments