Skip to content

Commit c106d05

Browse files
committed
grpc-js: Make backoff timer reset apply to the currently running timer
1 parent e583710 commit c106d05

File tree

1 file changed

+72
-11
lines changed

1 file changed

+72
-11
lines changed

packages/grpc-js/src/backoff-timeout.ts

Lines changed: 72 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -37,14 +37,47 @@ export interface BackoffOptions {
3737
}
3838

3939
export class BackoffTimeout {
40-
private initialDelay: number = INITIAL_BACKOFF_MS;
41-
private multiplier: number = BACKOFF_MULTIPLIER;
42-
private maxDelay: number = MAX_BACKOFF_MS;
43-
private jitter: number = BACKOFF_JITTER;
40+
/**
41+
* The delay time at the start, and after each reset.
42+
*/
43+
private readonly initialDelay: number = INITIAL_BACKOFF_MS;
44+
/**
45+
* The exponential backoff multiplier.
46+
*/
47+
private readonly multiplier: number = BACKOFF_MULTIPLIER;
48+
/**
49+
* The maximum delay time
50+
*/
51+
private readonly maxDelay: number = MAX_BACKOFF_MS;
52+
/**
53+
* The maximum fraction by which the delay time can randomly vary after
54+
* applying the multiplier.
55+
*/
56+
private readonly jitter: number = BACKOFF_JITTER;
57+
/**
58+
* The delay time for the next time the timer runs.
59+
*/
4460
private nextDelay: number;
61+
/**
62+
* The handle of the underlying timer. If running is false, this value refers
63+
* to an object representing a timer that has ended, but it can still be
64+
* interacted with without error.
65+
*/
4566
private timerId: NodeJS.Timer;
67+
/**
68+
* Indicates whether the timer is currently running.
69+
*/
4670
private running = false;
71+
/**
72+
* Indicates whether the timer should keep the Node process running if no
73+
* other async operation is doing so.
74+
*/
4775
private hasRef = true;
76+
/**
77+
* The time that the currently running timer was started. Only valid if
78+
* running is true.
79+
*/
80+
private startTime: Date = new Date();
4881

4982
constructor(private callback: () => void, options?: BackoffOptions) {
5083
if (options) {
@@ -66,18 +99,23 @@ export class BackoffTimeout {
6699
clearTimeout(this.timerId);
67100
}
68101

69-
/**
70-
* Call the callback after the current amount of delay time
71-
*/
72-
runOnce() {
73-
this.running = true;
102+
private runTimer(delay: number) {
74103
this.timerId = setTimeout(() => {
75104
this.callback();
76105
this.running = false;
77-
}, this.nextDelay);
106+
}, delay);
78107
if (!this.hasRef) {
79108
this.timerId.unref?.();
80109
}
110+
}
111+
112+
/**
113+
* Call the callback after the current amount of delay time
114+
*/
115+
runOnce() {
116+
this.running = true;
117+
this.startTime = new Date();
118+
this.runTimer(this.nextDelay);
81119
const nextBackoff = Math.min(
82120
this.nextDelay * this.multiplier,
83121
this.maxDelay
@@ -97,21 +135,44 @@ export class BackoffTimeout {
97135
}
98136

99137
/**
100-
* Reset the delay time to its initial value.
138+
* Reset the delay time to its initial value. If the timer is still running,
139+
* retroactively apply that reset to the current timer.
101140
*/
102141
reset() {
103142
this.nextDelay = this.initialDelay;
143+
if (this.running) {
144+
const now = new Date();
145+
const newEndTime = this.startTime;
146+
newEndTime.setMilliseconds(newEndTime.getMilliseconds() + this.nextDelay);
147+
clearTimeout(this.timerId);
148+
if (now < newEndTime) {
149+
this.runTimer(newEndTime.getTime() - now.getTime());
150+
} else {
151+
this.running = false;
152+
}
153+
}
104154
}
105155

156+
/**
157+
* Check whether the timer is currently running.
158+
*/
106159
isRunning() {
107160
return this.running;
108161
}
109162

163+
/**
164+
* Set that while the timer is running, it should keep the Node process
165+
* running.
166+
*/
110167
ref() {
111168
this.hasRef = true;
112169
this.timerId.ref?.();
113170
}
114171

172+
/**
173+
* Set that while the timer is running, it should not keep the Node process
174+
* running.
175+
*/
115176
unref() {
116177
this.hasRef = false;
117178
this.timerId.unref?.();

0 commit comments

Comments
 (0)