Skip to content

Commit 8e5ef17

Browse files
authored
v3: registry maintenance (#1146)
* retry checkpoints with backoff and optional failover registry for deploys * never abort checkpoint cleanup * simulate checkpoint failure for 5 minutes * add flag to simulate checkpoint push failure * add flag to control push failure simulation duration * backoff with helper * handle all coordinator errors * improve stop retrying * increase cleanup ipc timeout * improve webapp socket.io handler error logging * remove unused backoff function
1 parent 58b6b1a commit 8e5ef17

File tree

8 files changed

+491
-39
lines changed

8 files changed

+491
-39
lines changed

.changeset/young-snails-sell.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"trigger.dev": patch
3+
---
4+
5+
Increase cleanup IPC timeout

apps/coordinator/src/backoff.ts

Lines changed: 247 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,247 @@
1+
type ExponentialBackoffType = "NoJitter" | "FullJitter" | "EqualJitter";
2+
3+
type ExponentialBackoffOptions = {
4+
base: number;
5+
factor: number;
6+
min: number;
7+
max: number;
8+
maxRetries: number;
9+
maxElapsed: number;
10+
};
11+
12+
class StopRetrying extends Error {
13+
constructor(message?: string) {
14+
super(message);
15+
this.name = "StopRetrying";
16+
}
17+
}
18+
19+
export class ExponentialBackoff {
20+
#retries: number = 0;
21+
22+
#type: ExponentialBackoffType;
23+
#base: number;
24+
#factor: number;
25+
26+
#min: number;
27+
#max: number;
28+
29+
#maxRetries: number;
30+
#maxElapsed: number;
31+
32+
constructor(type?: ExponentialBackoffType, opts: Partial<ExponentialBackoffOptions> = {}) {
33+
this.#type = type ?? "NoJitter";
34+
this.#base = opts.base ?? 2;
35+
this.#factor = opts.factor ?? 1;
36+
37+
this.#min = opts.min ?? -Infinity;
38+
this.#max = opts.max ?? Infinity;
39+
40+
this.#maxRetries = opts.maxRetries ?? Infinity;
41+
this.#maxElapsed = opts.maxElapsed ?? Infinity;
42+
}
43+
44+
#clone() {
45+
return new ExponentialBackoff(this.#type, {
46+
base: this.#base,
47+
factor: this.#factor,
48+
min: this.#min,
49+
max: this.#max,
50+
maxRetries: this.#maxRetries,
51+
maxElapsed: this.#maxElapsed,
52+
});
53+
}
54+
55+
type(type?: ExponentialBackoffType) {
56+
if (typeof type !== "undefined") {
57+
this.#type = type;
58+
}
59+
return this.#clone();
60+
}
61+
62+
base(base?: number) {
63+
if (typeof base !== "undefined") {
64+
this.#base = base;
65+
}
66+
return this.#clone();
67+
}
68+
69+
factor(factor?: number) {
70+
if (typeof factor !== "undefined") {
71+
this.#factor = factor;
72+
}
73+
return this.#clone();
74+
}
75+
76+
min(min?: number) {
77+
if (typeof min !== "undefined") {
78+
this.#min = min;
79+
}
80+
return this.#clone();
81+
}
82+
83+
max(max?: number) {
84+
if (typeof max !== "undefined") {
85+
this.#max = max;
86+
}
87+
return this.#clone();
88+
}
89+
90+
maxRetries(maxRetries?: number) {
91+
if (typeof maxRetries !== "undefined") {
92+
this.#maxRetries = maxRetries;
93+
}
94+
return this.#clone();
95+
}
96+
97+
maxElapsed(maxElapsed?: number) {
98+
if (typeof maxElapsed !== "undefined") {
99+
this.#maxElapsed = maxElapsed;
100+
}
101+
return this.#clone();
102+
}
103+
104+
retries(retries?: number) {
105+
if (typeof retries !== "undefined") {
106+
if (retries > this.#maxRetries) {
107+
console.error(
108+
`Can't set retries ${retries} higher than maxRetries (${
109+
this.#maxRetries
110+
}), setting to maxRetries instead.`
111+
);
112+
this.#retries = this.#maxRetries;
113+
} else {
114+
this.#retries = retries;
115+
}
116+
}
117+
return this.#clone();
118+
}
119+
120+
async *retryAsync(maxRetries: number = this.#maxRetries ?? Infinity) {
121+
let elapsed = 0;
122+
let retry = 0;
123+
124+
while (retry <= maxRetries) {
125+
const delay = this.delay(retry);
126+
elapsed += delay;
127+
128+
if (elapsed > this.#maxElapsed) {
129+
break;
130+
}
131+
132+
yield {
133+
delay: {
134+
seconds: delay,
135+
milliseconds: delay * 1000,
136+
},
137+
retry,
138+
};
139+
140+
retry++;
141+
}
142+
}
143+
144+
async *[Symbol.asyncIterator]() {
145+
yield* this.retryAsync();
146+
}
147+
148+
delay(retries: number = this.#retries, jitter: boolean = true) {
149+
if (retries > this.#maxRetries) {
150+
console.error(
151+
`Can't set retries ${retries} higher than maxRetries (${
152+
this.#maxRetries
153+
}), setting to maxRetries instead.`
154+
);
155+
retries = this.#maxRetries;
156+
}
157+
158+
let delay = this.#factor * this.#base ** retries;
159+
160+
switch (this.#type) {
161+
case "NoJitter": {
162+
break;
163+
}
164+
case "FullJitter": {
165+
if (!jitter) {
166+
delay = 0;
167+
break;
168+
}
169+
170+
delay *= Math.random();
171+
break;
172+
}
173+
case "EqualJitter": {
174+
if (!jitter) {
175+
delay *= 0.5;
176+
break;
177+
}
178+
179+
delay *= 0.5 * (1 + Math.random());
180+
break;
181+
}
182+
default: {
183+
throw new Error(`Unknown backoff type: ${this.#type}`);
184+
}
185+
}
186+
187+
delay = Math.min(delay, this.#max);
188+
delay = Math.max(delay, this.#min);
189+
delay = Math.round(delay);
190+
191+
return delay;
192+
}
193+
194+
elapsed(retries: number = this.#retries, jitter: boolean = true) {
195+
let elapsed = 0;
196+
197+
for (let i = 0; i <= retries; i++) {
198+
elapsed += this.delay(i, jitter);
199+
}
200+
201+
const total = elapsed;
202+
203+
let days = 0;
204+
if (elapsed > 3600 * 24) {
205+
days = Math.floor(elapsed / 3600 / 24);
206+
elapsed -= days * 3600 * 24;
207+
}
208+
209+
let hours = 0;
210+
if (elapsed > 3600) {
211+
hours = Math.floor(elapsed / 3600);
212+
elapsed -= hours * 3600;
213+
}
214+
215+
let minutes = 0;
216+
if (elapsed > 60) {
217+
minutes = Math.floor(elapsed / 60);
218+
elapsed -= minutes * 60;
219+
}
220+
221+
const seconds = elapsed;
222+
223+
return {
224+
seconds,
225+
minutes,
226+
hours,
227+
days,
228+
total,
229+
};
230+
}
231+
232+
reset() {
233+
this.#retries = 0;
234+
return this;
235+
}
236+
237+
next() {
238+
this.#retries++;
239+
return this.delay();
240+
}
241+
242+
stop() {
243+
throw new StopRetrying();
244+
}
245+
246+
static StopRetrying = StopRetrying;
247+
}

0 commit comments

Comments
 (0)