@rivetkit/engine-runner 2.0.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +22 -0
- package/.turbo/turbo-check-types.log +4 -0
- package/LICENSE +203 -0
- package/benches/actor-lifecycle.bench.ts +190 -0
- package/benches/utils.ts +143 -0
- package/dist/mod.cjs +2027 -0
- package/dist/mod.cjs.map +1 -0
- package/dist/mod.d.cts +72 -0
- package/dist/mod.d.ts +72 -0
- package/dist/mod.js +2027 -0
- package/dist/mod.js.map +1 -0
- package/package.json +37 -0
- package/src/log.ts +11 -0
- package/src/mod.ts +1485 -0
- package/src/tunnel.ts +613 -0
- package/src/utils.ts +31 -0
- package/src/websocket-tunnel-adapter.ts +538 -0
- package/src/websocket.ts +43 -0
- package/tests/lifecycle.test.ts +596 -0
- package/tsconfig.json +11 -0
- package/tsup.config.ts +4 -0
- package/turbo.json +4 -0
- package/vitest.config.ts +16 -0
package/dist/mod.js
ADDED
|
@@ -0,0 +1,2027 @@
|
|
|
1
|
+
// src/mod.ts
|
|
2
|
+
import * as protocol from "@rivetkit/engine-runner-protocol";
|
|
3
|
+
|
|
4
|
+
// src/log.ts
|
|
5
|
+
var LOGGER;
|
|
6
|
+
function setLogger(logger2) {
|
|
7
|
+
LOGGER = logger2;
|
|
8
|
+
}
|
|
9
|
+
function logger() {
|
|
10
|
+
return LOGGER;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// src/tunnel.ts
|
|
14
|
+
import { v4 as uuidv4 } from "uuid";
|
|
15
|
+
|
|
16
|
+
// src/utils.ts
|
|
17
|
+
function unreachable(x) {
|
|
18
|
+
throw `Unreachable: ${x}`;
|
|
19
|
+
}
|
|
20
|
+
function calculateBackoff(attempt, options = {}) {
|
|
21
|
+
const {
|
|
22
|
+
initialDelay = 1e3,
|
|
23
|
+
maxDelay = 3e4,
|
|
24
|
+
multiplier = 2,
|
|
25
|
+
jitter = true
|
|
26
|
+
} = options;
|
|
27
|
+
let delay = Math.min(initialDelay * multiplier ** attempt, maxDelay);
|
|
28
|
+
if (jitter) {
|
|
29
|
+
delay = delay * (1 + Math.random() * 0.25);
|
|
30
|
+
}
|
|
31
|
+
return Math.floor(delay);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// src/websocket-tunnel-adapter.ts
|
|
35
|
+
var WebSocketTunnelAdapter = class {
|
|
36
|
+
#webSocketId;
|
|
37
|
+
#readyState = 0;
|
|
38
|
+
// CONNECTING
|
|
39
|
+
#eventListeners = /* @__PURE__ */ new Map();
|
|
40
|
+
#onopen = null;
|
|
41
|
+
#onclose = null;
|
|
42
|
+
#onerror = null;
|
|
43
|
+
#onmessage = null;
|
|
44
|
+
#bufferedAmount = 0;
|
|
45
|
+
#binaryType = "nodebuffer";
|
|
46
|
+
#extensions = "";
|
|
47
|
+
#protocol = "";
|
|
48
|
+
#url = "";
|
|
49
|
+
#sendCallback;
|
|
50
|
+
#closeCallback;
|
|
51
|
+
// Event buffering for events fired before listeners are attached
|
|
52
|
+
#bufferedEvents = [];
|
|
53
|
+
constructor(webSocketId, sendCallback, closeCallback) {
|
|
54
|
+
this.#webSocketId = webSocketId;
|
|
55
|
+
this.#sendCallback = sendCallback;
|
|
56
|
+
this.#closeCallback = closeCallback;
|
|
57
|
+
}
|
|
58
|
+
get readyState() {
|
|
59
|
+
return this.#readyState;
|
|
60
|
+
}
|
|
61
|
+
get bufferedAmount() {
|
|
62
|
+
return this.#bufferedAmount;
|
|
63
|
+
}
|
|
64
|
+
get binaryType() {
|
|
65
|
+
return this.#binaryType;
|
|
66
|
+
}
|
|
67
|
+
set binaryType(value) {
|
|
68
|
+
if (value === "nodebuffer" || value === "arraybuffer" || value === "blob") {
|
|
69
|
+
this.#binaryType = value;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
get extensions() {
|
|
73
|
+
return this.#extensions;
|
|
74
|
+
}
|
|
75
|
+
get protocol() {
|
|
76
|
+
return this.#protocol;
|
|
77
|
+
}
|
|
78
|
+
get url() {
|
|
79
|
+
return this.#url;
|
|
80
|
+
}
|
|
81
|
+
get onopen() {
|
|
82
|
+
return this.#onopen;
|
|
83
|
+
}
|
|
84
|
+
set onopen(value) {
|
|
85
|
+
this.#onopen = value;
|
|
86
|
+
if (value) {
|
|
87
|
+
this.#flushBufferedEvents("open");
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
get onclose() {
|
|
91
|
+
return this.#onclose;
|
|
92
|
+
}
|
|
93
|
+
set onclose(value) {
|
|
94
|
+
this.#onclose = value;
|
|
95
|
+
if (value) {
|
|
96
|
+
this.#flushBufferedEvents("close");
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
get onerror() {
|
|
100
|
+
return this.#onerror;
|
|
101
|
+
}
|
|
102
|
+
set onerror(value) {
|
|
103
|
+
this.#onerror = value;
|
|
104
|
+
if (value) {
|
|
105
|
+
this.#flushBufferedEvents("error");
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
get onmessage() {
|
|
109
|
+
return this.#onmessage;
|
|
110
|
+
}
|
|
111
|
+
set onmessage(value) {
|
|
112
|
+
this.#onmessage = value;
|
|
113
|
+
if (value) {
|
|
114
|
+
this.#flushBufferedEvents("message");
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
send(data) {
|
|
118
|
+
if (this.#readyState !== 1) {
|
|
119
|
+
throw new Error("WebSocket is not open");
|
|
120
|
+
}
|
|
121
|
+
let isBinary = false;
|
|
122
|
+
let messageData;
|
|
123
|
+
if (typeof data === "string") {
|
|
124
|
+
messageData = data;
|
|
125
|
+
} else if (data instanceof ArrayBuffer) {
|
|
126
|
+
isBinary = true;
|
|
127
|
+
messageData = data;
|
|
128
|
+
} else if (ArrayBuffer.isView(data)) {
|
|
129
|
+
isBinary = true;
|
|
130
|
+
const view = data;
|
|
131
|
+
if (view.buffer instanceof SharedArrayBuffer) {
|
|
132
|
+
const bytes = new Uint8Array(
|
|
133
|
+
view.buffer,
|
|
134
|
+
view.byteOffset,
|
|
135
|
+
view.byteLength
|
|
136
|
+
);
|
|
137
|
+
messageData = bytes.buffer.slice(
|
|
138
|
+
bytes.byteOffset,
|
|
139
|
+
bytes.byteOffset + bytes.byteLength
|
|
140
|
+
);
|
|
141
|
+
} else {
|
|
142
|
+
messageData = view.buffer.slice(
|
|
143
|
+
view.byteOffset,
|
|
144
|
+
view.byteOffset + view.byteLength
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
} else if (data instanceof Blob) {
|
|
148
|
+
throw new Error("Blob sending not implemented in tunnel adapter");
|
|
149
|
+
} else if (typeof Buffer !== "undefined" && Buffer.isBuffer(data)) {
|
|
150
|
+
isBinary = true;
|
|
151
|
+
const buf = data;
|
|
152
|
+
if (buf.buffer instanceof SharedArrayBuffer) {
|
|
153
|
+
const bytes = new Uint8Array(
|
|
154
|
+
buf.buffer,
|
|
155
|
+
buf.byteOffset,
|
|
156
|
+
buf.byteLength
|
|
157
|
+
);
|
|
158
|
+
messageData = bytes.buffer.slice(
|
|
159
|
+
bytes.byteOffset,
|
|
160
|
+
bytes.byteOffset + bytes.byteLength
|
|
161
|
+
);
|
|
162
|
+
} else {
|
|
163
|
+
messageData = buf.buffer.slice(
|
|
164
|
+
buf.byteOffset,
|
|
165
|
+
buf.byteOffset + buf.byteLength
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
} else {
|
|
169
|
+
throw new Error("Invalid data type");
|
|
170
|
+
}
|
|
171
|
+
this.#sendCallback(messageData, isBinary);
|
|
172
|
+
}
|
|
173
|
+
close(code, reason) {
|
|
174
|
+
if (this.#readyState === 2 || // CLOSING
|
|
175
|
+
this.#readyState === 3) {
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
this.#readyState = 2;
|
|
179
|
+
this.#closeCallback(code, reason);
|
|
180
|
+
this.#readyState = 3;
|
|
181
|
+
const closeEvent = {
|
|
182
|
+
wasClean: true,
|
|
183
|
+
code: code || 1e3,
|
|
184
|
+
reason: reason || "",
|
|
185
|
+
type: "close",
|
|
186
|
+
target: this
|
|
187
|
+
};
|
|
188
|
+
this.#fireEvent("close", closeEvent);
|
|
189
|
+
}
|
|
190
|
+
addEventListener(type, listener, options) {
|
|
191
|
+
if (typeof listener === "function") {
|
|
192
|
+
let listeners = this.#eventListeners.get(type);
|
|
193
|
+
if (!listeners) {
|
|
194
|
+
listeners = /* @__PURE__ */ new Set();
|
|
195
|
+
this.#eventListeners.set(type, listeners);
|
|
196
|
+
}
|
|
197
|
+
listeners.add(listener);
|
|
198
|
+
this.#flushBufferedEvents(type);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
removeEventListener(type, listener, options) {
|
|
202
|
+
if (typeof listener === "function") {
|
|
203
|
+
const listeners = this.#eventListeners.get(type);
|
|
204
|
+
if (listeners) {
|
|
205
|
+
listeners.delete(listener);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
dispatchEvent(event) {
|
|
210
|
+
return true;
|
|
211
|
+
}
|
|
212
|
+
#fireEvent(type, event) {
|
|
213
|
+
var _a, _b, _c, _d, _e;
|
|
214
|
+
const listeners = this.#eventListeners.get(type);
|
|
215
|
+
let hasListeners = false;
|
|
216
|
+
if (listeners && listeners.size > 0) {
|
|
217
|
+
hasListeners = true;
|
|
218
|
+
for (const listener of listeners) {
|
|
219
|
+
try {
|
|
220
|
+
listener.call(this, event);
|
|
221
|
+
} catch (error) {
|
|
222
|
+
(_a = logger()) == null ? void 0 : _a.error({
|
|
223
|
+
msg: "error in websocket event listener",
|
|
224
|
+
error,
|
|
225
|
+
type
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
switch (type) {
|
|
231
|
+
case "open":
|
|
232
|
+
if (this.#onopen) {
|
|
233
|
+
hasListeners = true;
|
|
234
|
+
try {
|
|
235
|
+
this.#onopen.call(this, event);
|
|
236
|
+
} catch (error) {
|
|
237
|
+
(_b = logger()) == null ? void 0 : _b.error({
|
|
238
|
+
msg: "error in onopen handler",
|
|
239
|
+
error
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
break;
|
|
244
|
+
case "close":
|
|
245
|
+
if (this.#onclose) {
|
|
246
|
+
hasListeners = true;
|
|
247
|
+
try {
|
|
248
|
+
this.#onclose.call(this, event);
|
|
249
|
+
} catch (error) {
|
|
250
|
+
(_c = logger()) == null ? void 0 : _c.error({
|
|
251
|
+
msg: "error in onclose handler",
|
|
252
|
+
error
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
break;
|
|
257
|
+
case "error":
|
|
258
|
+
if (this.#onerror) {
|
|
259
|
+
hasListeners = true;
|
|
260
|
+
try {
|
|
261
|
+
this.#onerror.call(this, event);
|
|
262
|
+
} catch (error) {
|
|
263
|
+
(_d = logger()) == null ? void 0 : _d.error({
|
|
264
|
+
msg: "error in onerror handler",
|
|
265
|
+
error
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
break;
|
|
270
|
+
case "message":
|
|
271
|
+
if (this.#onmessage) {
|
|
272
|
+
hasListeners = true;
|
|
273
|
+
try {
|
|
274
|
+
this.#onmessage.call(this, event);
|
|
275
|
+
} catch (error) {
|
|
276
|
+
(_e = logger()) == null ? void 0 : _e.error({
|
|
277
|
+
msg: "error in onmessage handler",
|
|
278
|
+
error
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
break;
|
|
283
|
+
}
|
|
284
|
+
if (!hasListeners) {
|
|
285
|
+
this.#bufferedEvents.push({ type, event });
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
#flushBufferedEvents(type) {
|
|
289
|
+
var _a, _b, _c, _d, _e;
|
|
290
|
+
const eventsToFlush = this.#bufferedEvents.filter(
|
|
291
|
+
(buffered) => buffered.type === type
|
|
292
|
+
);
|
|
293
|
+
this.#bufferedEvents = this.#bufferedEvents.filter(
|
|
294
|
+
(buffered) => buffered.type !== type
|
|
295
|
+
);
|
|
296
|
+
for (const { event } of eventsToFlush) {
|
|
297
|
+
const listeners = this.#eventListeners.get(type);
|
|
298
|
+
if (listeners) {
|
|
299
|
+
for (const listener of listeners) {
|
|
300
|
+
try {
|
|
301
|
+
listener.call(this, event);
|
|
302
|
+
} catch (error) {
|
|
303
|
+
(_a = logger()) == null ? void 0 : _a.error({
|
|
304
|
+
msg: "error in websocket event listener",
|
|
305
|
+
error,
|
|
306
|
+
type
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
switch (type) {
|
|
312
|
+
case "open":
|
|
313
|
+
if (this.#onopen) {
|
|
314
|
+
try {
|
|
315
|
+
this.#onopen.call(this, event);
|
|
316
|
+
} catch (error) {
|
|
317
|
+
(_b = logger()) == null ? void 0 : _b.error({
|
|
318
|
+
msg: "error in onopen handler",
|
|
319
|
+
error
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
break;
|
|
324
|
+
case "close":
|
|
325
|
+
if (this.#onclose) {
|
|
326
|
+
try {
|
|
327
|
+
this.#onclose.call(this, event);
|
|
328
|
+
} catch (error) {
|
|
329
|
+
(_c = logger()) == null ? void 0 : _c.error({
|
|
330
|
+
msg: "error in onclose handler",
|
|
331
|
+
error
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
break;
|
|
336
|
+
case "error":
|
|
337
|
+
if (this.#onerror) {
|
|
338
|
+
try {
|
|
339
|
+
this.#onerror.call(this, event);
|
|
340
|
+
} catch (error) {
|
|
341
|
+
(_d = logger()) == null ? void 0 : _d.error({
|
|
342
|
+
msg: "error in onerror handler",
|
|
343
|
+
error
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
break;
|
|
348
|
+
case "message":
|
|
349
|
+
if (this.#onmessage) {
|
|
350
|
+
try {
|
|
351
|
+
this.#onmessage.call(this, event);
|
|
352
|
+
} catch (error) {
|
|
353
|
+
(_e = logger()) == null ? void 0 : _e.error({
|
|
354
|
+
msg: "error in onmessage handler",
|
|
355
|
+
error
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
break;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
// Internal methods called by the Tunnel class
|
|
364
|
+
_handleOpen() {
|
|
365
|
+
if (this.#readyState !== 0) {
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
this.#readyState = 1;
|
|
369
|
+
const event = {
|
|
370
|
+
type: "open",
|
|
371
|
+
target: this
|
|
372
|
+
};
|
|
373
|
+
this.#fireEvent("open", event);
|
|
374
|
+
}
|
|
375
|
+
_handleMessage(data, isBinary) {
|
|
376
|
+
if (this.#readyState !== 1) {
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
let messageData;
|
|
380
|
+
if (isBinary) {
|
|
381
|
+
if (this.#binaryType === "nodebuffer") {
|
|
382
|
+
messageData = Buffer.from(data);
|
|
383
|
+
} else if (this.#binaryType === "arraybuffer") {
|
|
384
|
+
if (data instanceof Uint8Array) {
|
|
385
|
+
messageData = data.buffer.slice(
|
|
386
|
+
data.byteOffset,
|
|
387
|
+
data.byteOffset + data.byteLength
|
|
388
|
+
);
|
|
389
|
+
} else {
|
|
390
|
+
messageData = data;
|
|
391
|
+
}
|
|
392
|
+
} else {
|
|
393
|
+
throw new Error(
|
|
394
|
+
"Blob binaryType not supported in tunnel adapter"
|
|
395
|
+
);
|
|
396
|
+
}
|
|
397
|
+
} else {
|
|
398
|
+
messageData = data;
|
|
399
|
+
}
|
|
400
|
+
const event = {
|
|
401
|
+
data: messageData,
|
|
402
|
+
type: "message",
|
|
403
|
+
target: this
|
|
404
|
+
};
|
|
405
|
+
this.#fireEvent("message", event);
|
|
406
|
+
}
|
|
407
|
+
_handleClose(code, reason) {
|
|
408
|
+
if (this.#readyState === 3) {
|
|
409
|
+
return;
|
|
410
|
+
}
|
|
411
|
+
this.#readyState = 3;
|
|
412
|
+
const event = {
|
|
413
|
+
wasClean: true,
|
|
414
|
+
code: code || 1e3,
|
|
415
|
+
reason: reason || "",
|
|
416
|
+
type: "close",
|
|
417
|
+
target: this
|
|
418
|
+
};
|
|
419
|
+
this.#fireEvent("close", event);
|
|
420
|
+
}
|
|
421
|
+
_handleError(error) {
|
|
422
|
+
const event = {
|
|
423
|
+
type: "error",
|
|
424
|
+
target: this,
|
|
425
|
+
error
|
|
426
|
+
};
|
|
427
|
+
this.#fireEvent("error", event);
|
|
428
|
+
}
|
|
429
|
+
// WebSocket constants for compatibility
|
|
430
|
+
static CONNECTING = 0;
|
|
431
|
+
static OPEN = 1;
|
|
432
|
+
static CLOSING = 2;
|
|
433
|
+
static CLOSED = 3;
|
|
434
|
+
// Instance constants
|
|
435
|
+
CONNECTING = 0;
|
|
436
|
+
OPEN = 1;
|
|
437
|
+
CLOSING = 2;
|
|
438
|
+
CLOSED = 3;
|
|
439
|
+
// Additional methods for compatibility
|
|
440
|
+
ping(data, mask, cb) {
|
|
441
|
+
if (cb) cb(new Error("Ping not supported in tunnel adapter"));
|
|
442
|
+
}
|
|
443
|
+
pong(data, mask, cb) {
|
|
444
|
+
if (cb) cb(new Error("Pong not supported in tunnel adapter"));
|
|
445
|
+
}
|
|
446
|
+
terminate() {
|
|
447
|
+
this.#readyState = 3;
|
|
448
|
+
this.#closeCallback(1006, "Abnormal Closure");
|
|
449
|
+
const event = {
|
|
450
|
+
wasClean: false,
|
|
451
|
+
code: 1006,
|
|
452
|
+
reason: "Abnormal Closure",
|
|
453
|
+
type: "close",
|
|
454
|
+
target: this
|
|
455
|
+
};
|
|
456
|
+
this.#fireEvent("close", event);
|
|
457
|
+
}
|
|
458
|
+
};
|
|
459
|
+
|
|
460
|
+
// src/tunnel.ts
|
|
461
|
+
var GC_INTERVAL = 6e4;
|
|
462
|
+
var MESSAGE_ACK_TIMEOUT = 5e3;
|
|
463
|
+
var Tunnel = class {
|
|
464
|
+
#runner;
|
|
465
|
+
/** Requests over the tunnel to the actor that are in flight. */
|
|
466
|
+
#actorPendingRequests = /* @__PURE__ */ new Map();
|
|
467
|
+
/** WebSockets over the tunnel to the actor that are in flight. */
|
|
468
|
+
#actorWebSockets = /* @__PURE__ */ new Map();
|
|
469
|
+
/** Messages sent from the actor over the tunnel that have not been acked by the gateway. */
|
|
470
|
+
#pendingTunnelMessages = /* @__PURE__ */ new Map();
|
|
471
|
+
#gcInterval;
|
|
472
|
+
constructor(runner) {
|
|
473
|
+
this.#runner = runner;
|
|
474
|
+
}
|
|
475
|
+
start() {
|
|
476
|
+
this.#startGarbageCollector();
|
|
477
|
+
}
|
|
478
|
+
shutdown() {
|
|
479
|
+
if (this.#gcInterval) {
|
|
480
|
+
clearInterval(this.#gcInterval);
|
|
481
|
+
this.#gcInterval = void 0;
|
|
482
|
+
}
|
|
483
|
+
for (const [_, request] of this.#actorPendingRequests) {
|
|
484
|
+
request.reject(new Error("Tunnel shutting down"));
|
|
485
|
+
}
|
|
486
|
+
this.#actorPendingRequests.clear();
|
|
487
|
+
for (const [_, ws] of this.#actorWebSockets) {
|
|
488
|
+
ws.close();
|
|
489
|
+
}
|
|
490
|
+
this.#actorWebSockets.clear();
|
|
491
|
+
}
|
|
492
|
+
#sendMessage(requestId, messageKind) {
|
|
493
|
+
var _a;
|
|
494
|
+
if (!this.#runner.__webSocketReady()) {
|
|
495
|
+
(_a = logger()) == null ? void 0 : _a.warn(
|
|
496
|
+
"cannot send tunnel message, socket not connected to engine"
|
|
497
|
+
);
|
|
498
|
+
return;
|
|
499
|
+
}
|
|
500
|
+
const messageId = generateUuidBuffer();
|
|
501
|
+
const requestIdStr = bufferToString(requestId);
|
|
502
|
+
this.#pendingTunnelMessages.set(bufferToString(messageId), {
|
|
503
|
+
sentAt: Date.now(),
|
|
504
|
+
requestIdStr
|
|
505
|
+
});
|
|
506
|
+
const message = {
|
|
507
|
+
tag: "ToServerTunnelMessage",
|
|
508
|
+
val: {
|
|
509
|
+
requestId,
|
|
510
|
+
messageId,
|
|
511
|
+
messageKind
|
|
512
|
+
}
|
|
513
|
+
};
|
|
514
|
+
this.#runner.__sendToServer(message);
|
|
515
|
+
}
|
|
516
|
+
#sendAck(requestId, messageId) {
|
|
517
|
+
if (!this.#runner.__webSocketReady()) {
|
|
518
|
+
return;
|
|
519
|
+
}
|
|
520
|
+
const message = {
|
|
521
|
+
tag: "ToServerTunnelMessage",
|
|
522
|
+
val: {
|
|
523
|
+
requestId,
|
|
524
|
+
messageId,
|
|
525
|
+
messageKind: { tag: "TunnelAck", val: null }
|
|
526
|
+
}
|
|
527
|
+
};
|
|
528
|
+
this.#runner.__sendToServer(message);
|
|
529
|
+
}
|
|
530
|
+
#startGarbageCollector() {
|
|
531
|
+
if (this.#gcInterval) {
|
|
532
|
+
clearInterval(this.#gcInterval);
|
|
533
|
+
}
|
|
534
|
+
this.#gcInterval = setInterval(() => {
|
|
535
|
+
this.#gc();
|
|
536
|
+
}, GC_INTERVAL);
|
|
537
|
+
}
|
|
538
|
+
#gc() {
|
|
539
|
+
var _a;
|
|
540
|
+
const now = Date.now();
|
|
541
|
+
const messagesToDelete = [];
|
|
542
|
+
for (const [messageId, pendingMessage] of this.#pendingTunnelMessages) {
|
|
543
|
+
if (now - pendingMessage.sentAt > MESSAGE_ACK_TIMEOUT) {
|
|
544
|
+
messagesToDelete.push(messageId);
|
|
545
|
+
const requestIdStr = pendingMessage.requestIdStr;
|
|
546
|
+
const pendingRequest = this.#actorPendingRequests.get(requestIdStr);
|
|
547
|
+
if (pendingRequest) {
|
|
548
|
+
pendingRequest.reject(
|
|
549
|
+
new Error("Message acknowledgment timeout")
|
|
550
|
+
);
|
|
551
|
+
if (pendingRequest.streamController) {
|
|
552
|
+
pendingRequest.streamController.error(
|
|
553
|
+
new Error("Message acknowledgment timeout")
|
|
554
|
+
);
|
|
555
|
+
}
|
|
556
|
+
this.#actorPendingRequests.delete(requestIdStr);
|
|
557
|
+
}
|
|
558
|
+
const webSocket = this.#actorWebSockets.get(requestIdStr);
|
|
559
|
+
if (webSocket) {
|
|
560
|
+
webSocket.close(1e3, "Message acknowledgment timeout");
|
|
561
|
+
this.#actorWebSockets.delete(requestIdStr);
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
if (messagesToDelete.length > 0) {
|
|
566
|
+
(_a = logger()) == null ? void 0 : _a.warn({
|
|
567
|
+
msg: "purging unacked tunnel messages, this indicates that the Rivet Engine is disconnected or not responding",
|
|
568
|
+
count: messagesToDelete.length
|
|
569
|
+
});
|
|
570
|
+
for (const messageId of messagesToDelete) {
|
|
571
|
+
this.#pendingTunnelMessages.delete(messageId);
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
unregisterActor(actor) {
|
|
576
|
+
const actorId = actor.actorId;
|
|
577
|
+
for (const requestId of actor.requests) {
|
|
578
|
+
const pending = this.#actorPendingRequests.get(requestId);
|
|
579
|
+
if (pending) {
|
|
580
|
+
pending.reject(new Error(`Actor ${actorId} stopped`));
|
|
581
|
+
this.#actorPendingRequests.delete(requestId);
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
actor.requests.clear();
|
|
585
|
+
for (const webSocketId of actor.webSockets) {
|
|
586
|
+
const ws = this.#actorWebSockets.get(webSocketId);
|
|
587
|
+
if (ws) {
|
|
588
|
+
ws.close(1e3, "Actor stopped");
|
|
589
|
+
this.#actorWebSockets.delete(webSocketId);
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
actor.webSockets.clear();
|
|
593
|
+
}
|
|
594
|
+
async #fetch(actorId, request) {
|
|
595
|
+
var _a;
|
|
596
|
+
if (!this.#runner.hasActor(actorId)) {
|
|
597
|
+
(_a = logger()) == null ? void 0 : _a.warn({
|
|
598
|
+
msg: "ignoring request for unknown actor",
|
|
599
|
+
actorId
|
|
600
|
+
});
|
|
601
|
+
return new Response("Actor not found", { status: 404 });
|
|
602
|
+
}
|
|
603
|
+
const fetchHandler = this.#runner.config.fetch(
|
|
604
|
+
this.#runner,
|
|
605
|
+
actorId,
|
|
606
|
+
request
|
|
607
|
+
);
|
|
608
|
+
if (!fetchHandler) {
|
|
609
|
+
return new Response("Not Implemented", { status: 501 });
|
|
610
|
+
}
|
|
611
|
+
return fetchHandler;
|
|
612
|
+
}
|
|
613
|
+
async handleTunnelMessage(message) {
|
|
614
|
+
if (message.messageKind.tag === "TunnelAck") {
|
|
615
|
+
const msgIdStr = bufferToString(message.messageId);
|
|
616
|
+
const pending = this.#pendingTunnelMessages.get(msgIdStr);
|
|
617
|
+
if (pending) {
|
|
618
|
+
this.#pendingTunnelMessages.delete(msgIdStr);
|
|
619
|
+
}
|
|
620
|
+
} else {
|
|
621
|
+
this.#sendAck(message.requestId, message.messageId);
|
|
622
|
+
switch (message.messageKind.tag) {
|
|
623
|
+
case "ToClientRequestStart":
|
|
624
|
+
await this.#handleRequestStart(
|
|
625
|
+
message.requestId,
|
|
626
|
+
message.messageKind.val
|
|
627
|
+
);
|
|
628
|
+
break;
|
|
629
|
+
case "ToClientRequestChunk":
|
|
630
|
+
await this.#handleRequestChunk(
|
|
631
|
+
message.requestId,
|
|
632
|
+
message.messageKind.val
|
|
633
|
+
);
|
|
634
|
+
break;
|
|
635
|
+
case "ToClientRequestAbort":
|
|
636
|
+
await this.#handleRequestAbort(message.requestId);
|
|
637
|
+
break;
|
|
638
|
+
case "ToClientWebSocketOpen":
|
|
639
|
+
await this.#handleWebSocketOpen(
|
|
640
|
+
message.requestId,
|
|
641
|
+
message.messageKind.val
|
|
642
|
+
);
|
|
643
|
+
break;
|
|
644
|
+
case "ToClientWebSocketMessage":
|
|
645
|
+
await this.#handleWebSocketMessage(
|
|
646
|
+
message.requestId,
|
|
647
|
+
message.messageKind.val
|
|
648
|
+
);
|
|
649
|
+
break;
|
|
650
|
+
case "ToClientWebSocketClose":
|
|
651
|
+
await this.#handleWebSocketClose(
|
|
652
|
+
message.requestId,
|
|
653
|
+
message.messageKind.val
|
|
654
|
+
);
|
|
655
|
+
break;
|
|
656
|
+
default:
|
|
657
|
+
unreachable(message.messageKind);
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
async #handleRequestStart(requestId, req) {
|
|
662
|
+
var _a;
|
|
663
|
+
const requestIdStr = bufferToString(requestId);
|
|
664
|
+
const actor = this.#runner.getActor(req.actorId);
|
|
665
|
+
if (actor) {
|
|
666
|
+
actor.requests.add(requestIdStr);
|
|
667
|
+
}
|
|
668
|
+
try {
|
|
669
|
+
const headers = new Headers();
|
|
670
|
+
for (const [key, value] of req.headers) {
|
|
671
|
+
headers.append(key, value);
|
|
672
|
+
}
|
|
673
|
+
const request = new Request(`http://localhost${req.path}`, {
|
|
674
|
+
method: req.method,
|
|
675
|
+
headers,
|
|
676
|
+
body: req.body ? new Uint8Array(req.body) : void 0
|
|
677
|
+
});
|
|
678
|
+
if (req.stream) {
|
|
679
|
+
const stream = new ReadableStream({
|
|
680
|
+
start: (controller) => {
|
|
681
|
+
const existing = this.#actorPendingRequests.get(requestIdStr);
|
|
682
|
+
if (existing) {
|
|
683
|
+
existing.streamController = controller;
|
|
684
|
+
existing.actorId = req.actorId;
|
|
685
|
+
} else {
|
|
686
|
+
this.#actorPendingRequests.set(requestIdStr, {
|
|
687
|
+
resolve: () => {
|
|
688
|
+
},
|
|
689
|
+
reject: () => {
|
|
690
|
+
},
|
|
691
|
+
streamController: controller,
|
|
692
|
+
actorId: req.actorId
|
|
693
|
+
});
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
});
|
|
697
|
+
const streamingRequest = new Request(request, {
|
|
698
|
+
body: stream,
|
|
699
|
+
duplex: "half"
|
|
700
|
+
});
|
|
701
|
+
const response = await this.#fetch(
|
|
702
|
+
req.actorId,
|
|
703
|
+
streamingRequest
|
|
704
|
+
);
|
|
705
|
+
await this.#sendResponse(requestId, response);
|
|
706
|
+
} else {
|
|
707
|
+
const response = await this.#fetch(req.actorId, request);
|
|
708
|
+
await this.#sendResponse(requestId, response);
|
|
709
|
+
}
|
|
710
|
+
} catch (error) {
|
|
711
|
+
(_a = logger()) == null ? void 0 : _a.error({ msg: "error handling request", error });
|
|
712
|
+
this.#sendResponseError(requestId, 500, "Internal Server Error");
|
|
713
|
+
} finally {
|
|
714
|
+
const actor2 = this.#runner.getActor(req.actorId);
|
|
715
|
+
if (actor2) {
|
|
716
|
+
actor2.requests.delete(requestIdStr);
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
async #handleRequestChunk(requestId, chunk) {
|
|
721
|
+
const requestIdStr = bufferToString(requestId);
|
|
722
|
+
const pending = this.#actorPendingRequests.get(requestIdStr);
|
|
723
|
+
if (pending == null ? void 0 : pending.streamController) {
|
|
724
|
+
pending.streamController.enqueue(new Uint8Array(chunk.body));
|
|
725
|
+
if (chunk.finish) {
|
|
726
|
+
pending.streamController.close();
|
|
727
|
+
this.#actorPendingRequests.delete(requestIdStr);
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
async #handleRequestAbort(requestId) {
|
|
732
|
+
const requestIdStr = bufferToString(requestId);
|
|
733
|
+
const pending = this.#actorPendingRequests.get(requestIdStr);
|
|
734
|
+
if (pending == null ? void 0 : pending.streamController) {
|
|
735
|
+
pending.streamController.error(new Error("Request aborted"));
|
|
736
|
+
}
|
|
737
|
+
this.#actorPendingRequests.delete(requestIdStr);
|
|
738
|
+
}
|
|
739
|
+
async #sendResponse(requestId, response) {
|
|
740
|
+
const body = response.body ? await response.arrayBuffer() : null;
|
|
741
|
+
const headers = /* @__PURE__ */ new Map();
|
|
742
|
+
response.headers.forEach((value, key) => {
|
|
743
|
+
headers.set(key, value);
|
|
744
|
+
});
|
|
745
|
+
if (body && !headers.has("content-length")) {
|
|
746
|
+
headers.set("content-length", String(body.byteLength));
|
|
747
|
+
}
|
|
748
|
+
this.#sendMessage(requestId, {
|
|
749
|
+
tag: "ToServerResponseStart",
|
|
750
|
+
val: {
|
|
751
|
+
status: response.status,
|
|
752
|
+
headers,
|
|
753
|
+
body: body || null,
|
|
754
|
+
stream: false
|
|
755
|
+
}
|
|
756
|
+
});
|
|
757
|
+
}
|
|
758
|
+
#sendResponseError(requestId, status, message) {
|
|
759
|
+
const headers = /* @__PURE__ */ new Map();
|
|
760
|
+
headers.set("content-type", "text/plain");
|
|
761
|
+
this.#sendMessage(requestId, {
|
|
762
|
+
tag: "ToServerResponseStart",
|
|
763
|
+
val: {
|
|
764
|
+
status,
|
|
765
|
+
headers,
|
|
766
|
+
body: new TextEncoder().encode(message).buffer,
|
|
767
|
+
stream: false
|
|
768
|
+
}
|
|
769
|
+
});
|
|
770
|
+
}
|
|
771
|
+
async #handleWebSocketOpen(requestId, open) {
|
|
772
|
+
var _a, _b, _c;
|
|
773
|
+
const webSocketId = bufferToString(requestId);
|
|
774
|
+
const actor = this.#runner.getActor(open.actorId);
|
|
775
|
+
if (!actor) {
|
|
776
|
+
(_a = logger()) == null ? void 0 : _a.warn({
|
|
777
|
+
msg: "ignoring websocket for unknown actor",
|
|
778
|
+
actorId: open.actorId
|
|
779
|
+
});
|
|
780
|
+
this.#sendMessage(requestId, {
|
|
781
|
+
tag: "ToServerWebSocketClose",
|
|
782
|
+
val: {
|
|
783
|
+
code: 1011,
|
|
784
|
+
reason: "Actor not found"
|
|
785
|
+
}
|
|
786
|
+
});
|
|
787
|
+
return;
|
|
788
|
+
}
|
|
789
|
+
const websocketHandler = this.#runner.config.websocket;
|
|
790
|
+
if (!websocketHandler) {
|
|
791
|
+
(_b = logger()) == null ? void 0 : _b.error({
|
|
792
|
+
msg: "no websocket handler configured for tunnel"
|
|
793
|
+
});
|
|
794
|
+
this.#sendMessage(requestId, {
|
|
795
|
+
tag: "ToServerWebSocketClose",
|
|
796
|
+
val: {
|
|
797
|
+
code: 1011,
|
|
798
|
+
reason: "Not Implemented"
|
|
799
|
+
}
|
|
800
|
+
});
|
|
801
|
+
return;
|
|
802
|
+
}
|
|
803
|
+
if (actor) {
|
|
804
|
+
actor.webSockets.add(webSocketId);
|
|
805
|
+
}
|
|
806
|
+
try {
|
|
807
|
+
const adapter = new WebSocketTunnelAdapter(
|
|
808
|
+
webSocketId,
|
|
809
|
+
(data, isBinary) => {
|
|
810
|
+
const dataBuffer = typeof data === "string" ? new TextEncoder().encode(data).buffer : data;
|
|
811
|
+
this.#sendMessage(requestId, {
|
|
812
|
+
tag: "ToServerWebSocketMessage",
|
|
813
|
+
val: {
|
|
814
|
+
data: dataBuffer,
|
|
815
|
+
binary: isBinary
|
|
816
|
+
}
|
|
817
|
+
});
|
|
818
|
+
},
|
|
819
|
+
(code, reason) => {
|
|
820
|
+
this.#sendMessage(requestId, {
|
|
821
|
+
tag: "ToServerWebSocketClose",
|
|
822
|
+
val: {
|
|
823
|
+
code: code || null,
|
|
824
|
+
reason: reason || null
|
|
825
|
+
}
|
|
826
|
+
});
|
|
827
|
+
this.#actorWebSockets.delete(webSocketId);
|
|
828
|
+
if (actor) {
|
|
829
|
+
actor.webSockets.delete(webSocketId);
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
);
|
|
833
|
+
this.#actorWebSockets.set(webSocketId, adapter);
|
|
834
|
+
this.#sendMessage(requestId, {
|
|
835
|
+
tag: "ToServerWebSocketOpen",
|
|
836
|
+
val: null
|
|
837
|
+
});
|
|
838
|
+
adapter._handleOpen();
|
|
839
|
+
const headerInit = {};
|
|
840
|
+
if (open.headers) {
|
|
841
|
+
for (const [k, v] of open.headers) {
|
|
842
|
+
headerInit[k] = v;
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
headerInit["Upgrade"] = "websocket";
|
|
846
|
+
headerInit["Connection"] = "Upgrade";
|
|
847
|
+
const request = new Request(`http://localhost${open.path}`, {
|
|
848
|
+
method: "GET",
|
|
849
|
+
headers: headerInit
|
|
850
|
+
});
|
|
851
|
+
await websocketHandler(
|
|
852
|
+
this.#runner,
|
|
853
|
+
open.actorId,
|
|
854
|
+
adapter,
|
|
855
|
+
request
|
|
856
|
+
);
|
|
857
|
+
} catch (error) {
|
|
858
|
+
(_c = logger()) == null ? void 0 : _c.error({ msg: "error handling websocket open", error });
|
|
859
|
+
this.#sendMessage(requestId, {
|
|
860
|
+
tag: "ToServerWebSocketClose",
|
|
861
|
+
val: {
|
|
862
|
+
code: 1011,
|
|
863
|
+
reason: "Server Error"
|
|
864
|
+
}
|
|
865
|
+
});
|
|
866
|
+
this.#actorWebSockets.delete(webSocketId);
|
|
867
|
+
if (actor) {
|
|
868
|
+
actor.webSockets.delete(webSocketId);
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
async #handleWebSocketMessage(requestId, msg) {
|
|
873
|
+
const webSocketId = bufferToString(requestId);
|
|
874
|
+
const adapter = this.#actorWebSockets.get(webSocketId);
|
|
875
|
+
if (adapter) {
|
|
876
|
+
const data = msg.binary ? new Uint8Array(msg.data) : new TextDecoder().decode(new Uint8Array(msg.data));
|
|
877
|
+
adapter._handleMessage(data, msg.binary);
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
async #handleWebSocketClose(requestId, close) {
|
|
881
|
+
const webSocketId = bufferToString(requestId);
|
|
882
|
+
const adapter = this.#actorWebSockets.get(webSocketId);
|
|
883
|
+
if (adapter) {
|
|
884
|
+
adapter._handleClose(
|
|
885
|
+
close.code || void 0,
|
|
886
|
+
close.reason || void 0
|
|
887
|
+
);
|
|
888
|
+
this.#actorWebSockets.delete(webSocketId);
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
};
|
|
892
|
+
function bufferToString(buffer) {
|
|
893
|
+
return Buffer.from(buffer).toString("base64");
|
|
894
|
+
}
|
|
895
|
+
function generateUuidBuffer() {
|
|
896
|
+
const buffer = new Uint8Array(16);
|
|
897
|
+
uuidv4(void 0, buffer);
|
|
898
|
+
return buffer.buffer;
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
// src/websocket.ts
|
|
902
|
+
var webSocketPromise = null;
|
|
903
|
+
async function importWebSocket() {
|
|
904
|
+
if (webSocketPromise !== null) {
|
|
905
|
+
return webSocketPromise;
|
|
906
|
+
}
|
|
907
|
+
webSocketPromise = (async () => {
|
|
908
|
+
var _a, _b, _c;
|
|
909
|
+
let _WebSocket;
|
|
910
|
+
if (typeof WebSocket !== "undefined") {
|
|
911
|
+
_WebSocket = WebSocket;
|
|
912
|
+
(_a = logger()) == null ? void 0 : _a.debug({ msg: "using native websocket" });
|
|
913
|
+
} else {
|
|
914
|
+
try {
|
|
915
|
+
const ws = await import("ws");
|
|
916
|
+
_WebSocket = ws.default;
|
|
917
|
+
(_b = logger()) == null ? void 0 : _b.debug({ msg: "using websocket from npm" });
|
|
918
|
+
} catch {
|
|
919
|
+
_WebSocket = class MockWebSocket {
|
|
920
|
+
constructor() {
|
|
921
|
+
throw new Error(
|
|
922
|
+
'WebSocket support requires installing the "ws" peer dependency.'
|
|
923
|
+
);
|
|
924
|
+
}
|
|
925
|
+
};
|
|
926
|
+
(_c = logger()) == null ? void 0 : _c.debug({ msg: "using mock websocket" });
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
return _WebSocket;
|
|
930
|
+
})();
|
|
931
|
+
return webSocketPromise;
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
// src/mod.ts
|
|
935
|
+
var KV_EXPIRE = 3e4;
|
|
936
|
+
var PROTOCOL_VERSION = 1;
|
|
937
|
+
var EVENT_BACKLOG_WARN_THRESHOLD = 1e4;
|
|
938
|
+
var SIGNAL_HANDLERS = [];
|
|
939
|
+
var Runner = class {
|
|
940
|
+
#config;
|
|
941
|
+
get config() {
|
|
942
|
+
return this.#config;
|
|
943
|
+
}
|
|
944
|
+
#actors = /* @__PURE__ */ new Map();
|
|
945
|
+
#actorWebSockets = /* @__PURE__ */ new Map();
|
|
946
|
+
// WebSocket
|
|
947
|
+
#pegboardWebSocket;
|
|
948
|
+
runnerId;
|
|
949
|
+
#lastCommandIdx = -1;
|
|
950
|
+
#pingLoop;
|
|
951
|
+
#nextEventIdx = 0n;
|
|
952
|
+
#started = false;
|
|
953
|
+
#shutdown = false;
|
|
954
|
+
#reconnectAttempt = 0;
|
|
955
|
+
#reconnectTimeout;
|
|
956
|
+
// Runner lost threshold management
|
|
957
|
+
#runnerLostThreshold;
|
|
958
|
+
#runnerLostTimeout;
|
|
959
|
+
// Event storage for resending
|
|
960
|
+
#eventHistory = [];
|
|
961
|
+
#eventBacklogWarned = false;
|
|
962
|
+
// Command acknowledgment
|
|
963
|
+
#ackInterval;
|
|
964
|
+
// KV operations
|
|
965
|
+
#nextRequestId = 0;
|
|
966
|
+
#kvRequests = /* @__PURE__ */ new Map();
|
|
967
|
+
#kvCleanupInterval;
|
|
968
|
+
// Tunnel for HTTP/WebSocket forwarding
|
|
969
|
+
#tunnel;
|
|
970
|
+
constructor(config) {
|
|
971
|
+
this.#config = config;
|
|
972
|
+
if (this.#config.logger) setLogger(this.#config.logger);
|
|
973
|
+
this.#kvCleanupInterval = setInterval(() => {
|
|
974
|
+
this.#cleanupOldKvRequests();
|
|
975
|
+
}, 15e3);
|
|
976
|
+
}
|
|
977
|
+
// MARK: Manage actors
|
|
978
|
+
sleepActor(actorId, generation) {
|
|
979
|
+
const actor = this.getActor(actorId, generation);
|
|
980
|
+
if (!actor) return;
|
|
981
|
+
this.#sendActorIntent(actorId, actor.generation, "sleep");
|
|
982
|
+
}
|
|
983
|
+
async stopActor(actorId, generation) {
|
|
984
|
+
const actor = this.getActor(actorId, generation);
|
|
985
|
+
if (!actor) return;
|
|
986
|
+
this.#sendActorIntent(actorId, actor.generation, "stop");
|
|
987
|
+
}
|
|
988
|
+
async forceStopActor(actorId, generation) {
|
|
989
|
+
var _a;
|
|
990
|
+
const actor = this.#removeActor(actorId, generation);
|
|
991
|
+
if (!actor) return;
|
|
992
|
+
(_a = this.#tunnel) == null ? void 0 : _a.unregisterActor(actor);
|
|
993
|
+
try {
|
|
994
|
+
await this.#config.onActorStop(actorId, actor.generation);
|
|
995
|
+
} catch (err) {
|
|
996
|
+
console.error(`Error in onActorStop for actor ${actorId}:`, err);
|
|
997
|
+
}
|
|
998
|
+
this.#sendActorStateUpdate(actorId, actor.generation, "stopped");
|
|
999
|
+
this.#config.onActorStop(actorId, actor.generation).catch((err) => {
|
|
1000
|
+
var _a2;
|
|
1001
|
+
(_a2 = logger()) == null ? void 0 : _a2.error({
|
|
1002
|
+
msg: "error in onactorstop for actor",
|
|
1003
|
+
runnerId: this.runnerId,
|
|
1004
|
+
actorId,
|
|
1005
|
+
err
|
|
1006
|
+
});
|
|
1007
|
+
});
|
|
1008
|
+
}
|
|
1009
|
+
#stopAllActors() {
|
|
1010
|
+
var _a;
|
|
1011
|
+
(_a = logger()) == null ? void 0 : _a.info({
|
|
1012
|
+
msg: "stopping all actors due to runner lost threshold exceeded",
|
|
1013
|
+
runnerId: this.runnerId
|
|
1014
|
+
});
|
|
1015
|
+
const actorIds = Array.from(this.#actors.keys());
|
|
1016
|
+
for (const actorId of actorIds) {
|
|
1017
|
+
this.forceStopActor(actorId);
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
getActor(actorId, generation) {
|
|
1021
|
+
var _a, _b;
|
|
1022
|
+
const actor = this.#actors.get(actorId);
|
|
1023
|
+
if (!actor) {
|
|
1024
|
+
(_a = logger()) == null ? void 0 : _a.error({
|
|
1025
|
+
msg: "actor not found",
|
|
1026
|
+
runnerId: this.runnerId,
|
|
1027
|
+
actorId
|
|
1028
|
+
});
|
|
1029
|
+
return void 0;
|
|
1030
|
+
}
|
|
1031
|
+
if (generation !== void 0 && actor.generation !== generation) {
|
|
1032
|
+
(_b = logger()) == null ? void 0 : _b.error({
|
|
1033
|
+
msg: "actor generation mismatch",
|
|
1034
|
+
runnerId: this.runnerId,
|
|
1035
|
+
actorId,
|
|
1036
|
+
generation
|
|
1037
|
+
});
|
|
1038
|
+
return void 0;
|
|
1039
|
+
}
|
|
1040
|
+
return actor;
|
|
1041
|
+
}
|
|
1042
|
+
hasActor(actorId, generation) {
|
|
1043
|
+
const actor = this.#actors.get(actorId);
|
|
1044
|
+
return !!actor && (generation === void 0 || actor.generation === generation);
|
|
1045
|
+
}
|
|
1046
|
+
#removeActor(actorId, generation) {
|
|
1047
|
+
var _a, _b, _c;
|
|
1048
|
+
const actor = this.#actors.get(actorId);
|
|
1049
|
+
if (!actor) {
|
|
1050
|
+
(_a = logger()) == null ? void 0 : _a.error({
|
|
1051
|
+
msg: "actor not found for removal",
|
|
1052
|
+
runnerId: this.runnerId,
|
|
1053
|
+
actorId
|
|
1054
|
+
});
|
|
1055
|
+
return void 0;
|
|
1056
|
+
}
|
|
1057
|
+
if (generation !== void 0 && actor.generation !== generation) {
|
|
1058
|
+
(_b = logger()) == null ? void 0 : _b.error({
|
|
1059
|
+
msg: "actor generation mismatch",
|
|
1060
|
+
runnerId: this.runnerId,
|
|
1061
|
+
actorId,
|
|
1062
|
+
generation
|
|
1063
|
+
});
|
|
1064
|
+
return void 0;
|
|
1065
|
+
}
|
|
1066
|
+
this.#actors.delete(actorId);
|
|
1067
|
+
const actorWebSockets = this.#actorWebSockets.get(actorId);
|
|
1068
|
+
if (actorWebSockets) {
|
|
1069
|
+
for (const ws of actorWebSockets) {
|
|
1070
|
+
try {
|
|
1071
|
+
ws.close(1e3, "Actor stopped");
|
|
1072
|
+
} catch (err) {
|
|
1073
|
+
(_c = logger()) == null ? void 0 : _c.error({
|
|
1074
|
+
msg: "error closing websocket for actor",
|
|
1075
|
+
runnerId: this.runnerId,
|
|
1076
|
+
actorId,
|
|
1077
|
+
err
|
|
1078
|
+
});
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
this.#actorWebSockets.delete(actorId);
|
|
1082
|
+
}
|
|
1083
|
+
return actor;
|
|
1084
|
+
}
|
|
1085
|
+
// MARK: Start
|
|
1086
|
+
async start() {
|
|
1087
|
+
var _a, _b;
|
|
1088
|
+
if (this.#started) throw new Error("Cannot call runner.start twice");
|
|
1089
|
+
this.#started = true;
|
|
1090
|
+
(_a = logger()) == null ? void 0 : _a.info({ msg: "starting runner" });
|
|
1091
|
+
this.#tunnel = new Tunnel(this);
|
|
1092
|
+
this.#tunnel.start();
|
|
1093
|
+
try {
|
|
1094
|
+
await this.#openPegboardWebSocket();
|
|
1095
|
+
} catch (error) {
|
|
1096
|
+
this.#started = false;
|
|
1097
|
+
throw error;
|
|
1098
|
+
}
|
|
1099
|
+
if (!this.#config.noAutoShutdown) {
|
|
1100
|
+
if (!SIGNAL_HANDLERS.length) {
|
|
1101
|
+
process.on("SIGTERM", () => {
|
|
1102
|
+
var _a2;
|
|
1103
|
+
(_a2 = logger()) == null ? void 0 : _a2.debug("received SIGTERM");
|
|
1104
|
+
for (const handler of SIGNAL_HANDLERS) {
|
|
1105
|
+
handler();
|
|
1106
|
+
}
|
|
1107
|
+
process.exit(0);
|
|
1108
|
+
});
|
|
1109
|
+
process.on("SIGINT", () => {
|
|
1110
|
+
var _a2;
|
|
1111
|
+
(_a2 = logger()) == null ? void 0 : _a2.debug("received SIGINT");
|
|
1112
|
+
for (const handler of SIGNAL_HANDLERS) {
|
|
1113
|
+
handler();
|
|
1114
|
+
}
|
|
1115
|
+
process.exit(0);
|
|
1116
|
+
});
|
|
1117
|
+
(_b = logger()) == null ? void 0 : _b.debug({
|
|
1118
|
+
msg: "added SIGTERM listeners"
|
|
1119
|
+
});
|
|
1120
|
+
}
|
|
1121
|
+
SIGNAL_HANDLERS.push(() => {
|
|
1122
|
+
var _a2;
|
|
1123
|
+
const weak = new WeakRef(this);
|
|
1124
|
+
(_a2 = weak.deref()) == null ? void 0 : _a2.shutdown(false, false);
|
|
1125
|
+
});
|
|
1126
|
+
}
|
|
1127
|
+
}
|
|
1128
|
+
// MARK: Shutdown
|
|
1129
|
+
async shutdown(immediate, exit = false) {
|
|
1130
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
1131
|
+
(_a = logger()) == null ? void 0 : _a.info({
|
|
1132
|
+
msg: "starting shutdown",
|
|
1133
|
+
runnerId: this.runnerId,
|
|
1134
|
+
immediate,
|
|
1135
|
+
exit
|
|
1136
|
+
});
|
|
1137
|
+
this.#shutdown = true;
|
|
1138
|
+
if (this.#reconnectTimeout) {
|
|
1139
|
+
clearTimeout(this.#reconnectTimeout);
|
|
1140
|
+
this.#reconnectTimeout = void 0;
|
|
1141
|
+
}
|
|
1142
|
+
if (this.#runnerLostTimeout) {
|
|
1143
|
+
clearTimeout(this.#runnerLostTimeout);
|
|
1144
|
+
this.#runnerLostTimeout = void 0;
|
|
1145
|
+
}
|
|
1146
|
+
if (this.#pingLoop) {
|
|
1147
|
+
clearInterval(this.#pingLoop);
|
|
1148
|
+
this.#pingLoop = void 0;
|
|
1149
|
+
}
|
|
1150
|
+
if (this.#ackInterval) {
|
|
1151
|
+
clearInterval(this.#ackInterval);
|
|
1152
|
+
this.#ackInterval = void 0;
|
|
1153
|
+
}
|
|
1154
|
+
if (this.#kvCleanupInterval) {
|
|
1155
|
+
clearInterval(this.#kvCleanupInterval);
|
|
1156
|
+
this.#kvCleanupInterval = void 0;
|
|
1157
|
+
}
|
|
1158
|
+
for (const request of this.#kvRequests.values()) {
|
|
1159
|
+
request.reject(
|
|
1160
|
+
new Error("WebSocket connection closed during shutdown")
|
|
1161
|
+
);
|
|
1162
|
+
}
|
|
1163
|
+
this.#kvRequests.clear();
|
|
1164
|
+
if (this.#pegboardWebSocket && this.#pegboardWebSocket.readyState === 1) {
|
|
1165
|
+
const pegboardWebSocket = this.#pegboardWebSocket;
|
|
1166
|
+
if (immediate) {
|
|
1167
|
+
pegboardWebSocket.close(1e3, "Stopping");
|
|
1168
|
+
} else {
|
|
1169
|
+
try {
|
|
1170
|
+
(_b = logger()) == null ? void 0 : _b.info({
|
|
1171
|
+
msg: "sending stopping message",
|
|
1172
|
+
runnerId: this.runnerId,
|
|
1173
|
+
readyState: pegboardWebSocket.readyState
|
|
1174
|
+
});
|
|
1175
|
+
const encoded = protocol.encodeToServer({
|
|
1176
|
+
tag: "ToServerStopping",
|
|
1177
|
+
val: null
|
|
1178
|
+
});
|
|
1179
|
+
if (this.#pegboardWebSocket && this.#pegboardWebSocket.readyState === 1) {
|
|
1180
|
+
this.#pegboardWebSocket.send(encoded);
|
|
1181
|
+
} else {
|
|
1182
|
+
(_c = logger()) == null ? void 0 : _c.error(
|
|
1183
|
+
"WebSocket not available or not open for sending data"
|
|
1184
|
+
);
|
|
1185
|
+
}
|
|
1186
|
+
const closePromise = new Promise((resolve) => {
|
|
1187
|
+
if (!pegboardWebSocket)
|
|
1188
|
+
throw new Error("missing pegboardWebSocket");
|
|
1189
|
+
pegboardWebSocket.addEventListener("close", (ev) => {
|
|
1190
|
+
var _a2;
|
|
1191
|
+
(_a2 = logger()) == null ? void 0 : _a2.info({
|
|
1192
|
+
msg: "connection closed",
|
|
1193
|
+
runnerId: this.runnerId,
|
|
1194
|
+
code: ev.code,
|
|
1195
|
+
reason: ev.reason.toString()
|
|
1196
|
+
});
|
|
1197
|
+
resolve();
|
|
1198
|
+
});
|
|
1199
|
+
});
|
|
1200
|
+
(_d = logger()) == null ? void 0 : _d.info({
|
|
1201
|
+
msg: "closing WebSocket",
|
|
1202
|
+
runnerId: this.runnerId
|
|
1203
|
+
});
|
|
1204
|
+
pegboardWebSocket.close(1e3, "Stopping");
|
|
1205
|
+
await closePromise;
|
|
1206
|
+
(_e = logger()) == null ? void 0 : _e.info({
|
|
1207
|
+
msg: "websocket shutdown completed",
|
|
1208
|
+
runnerId: this.runnerId
|
|
1209
|
+
});
|
|
1210
|
+
} catch (error) {
|
|
1211
|
+
(_f = logger()) == null ? void 0 : _f.error({
|
|
1212
|
+
msg: "error during websocket shutdown:",
|
|
1213
|
+
runnerId: this.runnerId,
|
|
1214
|
+
error
|
|
1215
|
+
});
|
|
1216
|
+
pegboardWebSocket.close();
|
|
1217
|
+
}
|
|
1218
|
+
}
|
|
1219
|
+
} else {
|
|
1220
|
+
(_h = logger()) == null ? void 0 : _h.warn({
|
|
1221
|
+
msg: "no runner WebSocket to shutdown or already closed",
|
|
1222
|
+
runnerId: this.runnerId,
|
|
1223
|
+
readyState: (_g = this.#pegboardWebSocket) == null ? void 0 : _g.readyState
|
|
1224
|
+
});
|
|
1225
|
+
}
|
|
1226
|
+
if (this.#tunnel) {
|
|
1227
|
+
this.#tunnel.shutdown();
|
|
1228
|
+
this.#tunnel = void 0;
|
|
1229
|
+
}
|
|
1230
|
+
if (exit) process.exit(0);
|
|
1231
|
+
this.#config.onShutdown();
|
|
1232
|
+
}
|
|
1233
|
+
// MARK: Networking
|
|
1234
|
+
get pegboardUrl() {
|
|
1235
|
+
const endpoint = this.#config.pegboardEndpoint || this.#config.endpoint;
|
|
1236
|
+
const wsEndpoint = endpoint.replace("http://", "ws://").replace("https://", "wss://");
|
|
1237
|
+
return `${wsEndpoint}?protocol_version=${PROTOCOL_VERSION}&namespace=${encodeURIComponent(this.#config.namespace)}&runner_key=${encodeURIComponent(this.#config.runnerKey)}`;
|
|
1238
|
+
}
|
|
1239
|
+
// MARK: Runner protocol
|
|
1240
|
+
async #openPegboardWebSocket() {
|
|
1241
|
+
const protocols = ["rivet", `rivet_target.runner`];
|
|
1242
|
+
if (this.config.token)
|
|
1243
|
+
protocols.push(`rivet_token.${this.config.token}`);
|
|
1244
|
+
const WS = await importWebSocket();
|
|
1245
|
+
const ws = new WS(this.pegboardUrl, protocols);
|
|
1246
|
+
this.#pegboardWebSocket = ws;
|
|
1247
|
+
ws.addEventListener("open", () => {
|
|
1248
|
+
var _a;
|
|
1249
|
+
(_a = logger()) == null ? void 0 : _a.info({ msg: "Connected" });
|
|
1250
|
+
this.#reconnectAttempt = 0;
|
|
1251
|
+
if (this.#reconnectTimeout) {
|
|
1252
|
+
clearTimeout(this.#reconnectTimeout);
|
|
1253
|
+
this.#reconnectTimeout = void 0;
|
|
1254
|
+
}
|
|
1255
|
+
if (this.#runnerLostTimeout) {
|
|
1256
|
+
clearTimeout(this.#runnerLostTimeout);
|
|
1257
|
+
this.#runnerLostTimeout = void 0;
|
|
1258
|
+
}
|
|
1259
|
+
const init = {
|
|
1260
|
+
name: this.#config.runnerName,
|
|
1261
|
+
version: this.#config.version,
|
|
1262
|
+
totalSlots: this.#config.totalSlots,
|
|
1263
|
+
lastCommandIdx: this.#lastCommandIdx >= 0 ? BigInt(this.#lastCommandIdx) : null,
|
|
1264
|
+
prepopulateActorNames: new Map(
|
|
1265
|
+
Object.entries(this.#config.prepopulateActorNames).map(
|
|
1266
|
+
([name, data]) => [
|
|
1267
|
+
name,
|
|
1268
|
+
{ metadata: JSON.stringify(data.metadata) }
|
|
1269
|
+
]
|
|
1270
|
+
)
|
|
1271
|
+
),
|
|
1272
|
+
metadata: JSON.stringify(this.#config.metadata)
|
|
1273
|
+
};
|
|
1274
|
+
this.__sendToServer({
|
|
1275
|
+
tag: "ToServerInit",
|
|
1276
|
+
val: init
|
|
1277
|
+
});
|
|
1278
|
+
this.#processUnsentKvRequests();
|
|
1279
|
+
const pingInterval = 1e3;
|
|
1280
|
+
const pingLoop = setInterval(() => {
|
|
1281
|
+
var _a2;
|
|
1282
|
+
if (ws.readyState === 1) {
|
|
1283
|
+
this.__sendToServer({
|
|
1284
|
+
tag: "ToServerPing",
|
|
1285
|
+
val: {
|
|
1286
|
+
ts: BigInt(Date.now())
|
|
1287
|
+
}
|
|
1288
|
+
});
|
|
1289
|
+
} else {
|
|
1290
|
+
clearInterval(pingLoop);
|
|
1291
|
+
(_a2 = logger()) == null ? void 0 : _a2.info({
|
|
1292
|
+
msg: "WebSocket not open, stopping ping loop",
|
|
1293
|
+
runnerId: this.runnerId
|
|
1294
|
+
});
|
|
1295
|
+
}
|
|
1296
|
+
}, pingInterval);
|
|
1297
|
+
this.#pingLoop = pingLoop;
|
|
1298
|
+
const ackInterval = 5 * 60 * 1e3;
|
|
1299
|
+
const ackLoop = setInterval(() => {
|
|
1300
|
+
var _a2;
|
|
1301
|
+
if (ws.readyState === 1) {
|
|
1302
|
+
this.#sendCommandAcknowledgment();
|
|
1303
|
+
} else {
|
|
1304
|
+
clearInterval(ackLoop);
|
|
1305
|
+
(_a2 = logger()) == null ? void 0 : _a2.info({
|
|
1306
|
+
msg: "WebSocket not open, stopping ack loop",
|
|
1307
|
+
runnerId: this.runnerId
|
|
1308
|
+
});
|
|
1309
|
+
}
|
|
1310
|
+
}, ackInterval);
|
|
1311
|
+
this.#ackInterval = ackLoop;
|
|
1312
|
+
});
|
|
1313
|
+
ws.addEventListener("message", async (ev) => {
|
|
1314
|
+
var _a, _b, _c, _d;
|
|
1315
|
+
let buf;
|
|
1316
|
+
if (ev.data instanceof Blob) {
|
|
1317
|
+
buf = new Uint8Array(await ev.data.arrayBuffer());
|
|
1318
|
+
} else if (Buffer.isBuffer(ev.data)) {
|
|
1319
|
+
buf = new Uint8Array(ev.data);
|
|
1320
|
+
} else {
|
|
1321
|
+
throw new Error(`expected binary data, got ${typeof ev.data}`);
|
|
1322
|
+
}
|
|
1323
|
+
const message = protocol.decodeToClient(buf);
|
|
1324
|
+
if (message.tag === "ToClientInit") {
|
|
1325
|
+
const init = message.val;
|
|
1326
|
+
if (this.runnerId !== init.runnerId) {
|
|
1327
|
+
this.runnerId = init.runnerId;
|
|
1328
|
+
this.#eventHistory.length = 0;
|
|
1329
|
+
}
|
|
1330
|
+
this.#runnerLostThreshold = ((_a = init.metadata) == null ? void 0 : _a.runnerLostThreshold) ? Number(init.metadata.runnerLostThreshold) : void 0;
|
|
1331
|
+
(_b = logger()) == null ? void 0 : _b.info({
|
|
1332
|
+
msg: "received init",
|
|
1333
|
+
runnerId: init.runnerId,
|
|
1334
|
+
lastEventIdx: init.lastEventIdx,
|
|
1335
|
+
runnerLostThreshold: this.#runnerLostThreshold
|
|
1336
|
+
});
|
|
1337
|
+
this.#resendUnacknowledgedEvents(init.lastEventIdx);
|
|
1338
|
+
this.#config.onConnected();
|
|
1339
|
+
} else if (message.tag === "ToClientCommands") {
|
|
1340
|
+
const commands = message.val;
|
|
1341
|
+
this.#handleCommands(commands);
|
|
1342
|
+
} else if (message.tag === "ToClientAckEvents") {
|
|
1343
|
+
this.#handleAckEvents(message.val);
|
|
1344
|
+
} else if (message.tag === "ToClientKvResponse") {
|
|
1345
|
+
const kvResponse = message.val;
|
|
1346
|
+
this.#handleKvResponse(kvResponse);
|
|
1347
|
+
} else if (message.tag === "ToClientTunnelMessage") {
|
|
1348
|
+
(_c = this.#tunnel) == null ? void 0 : _c.handleTunnelMessage(message.val);
|
|
1349
|
+
} else if (message.tag === "ToClientClose") {
|
|
1350
|
+
(_d = this.#tunnel) == null ? void 0 : _d.shutdown();
|
|
1351
|
+
ws.close(1e3, "manual closure");
|
|
1352
|
+
} else {
|
|
1353
|
+
unreachable(message);
|
|
1354
|
+
}
|
|
1355
|
+
});
|
|
1356
|
+
ws.addEventListener("error", (ev) => {
|
|
1357
|
+
var _a, _b;
|
|
1358
|
+
(_a = logger()) == null ? void 0 : _a.error({
|
|
1359
|
+
msg: `WebSocket error: ${ev.error}`,
|
|
1360
|
+
runnerId: this.runnerId
|
|
1361
|
+
});
|
|
1362
|
+
if (!this.#shutdown) {
|
|
1363
|
+
if (!this.#runnerLostTimeout && this.#runnerLostThreshold && this.#runnerLostThreshold > 0) {
|
|
1364
|
+
(_b = logger()) == null ? void 0 : _b.info({
|
|
1365
|
+
msg: "starting runner lost timeout",
|
|
1366
|
+
runnerId: this.runnerId,
|
|
1367
|
+
seconds: this.#runnerLostThreshold / 1e3
|
|
1368
|
+
});
|
|
1369
|
+
this.#runnerLostTimeout = setTimeout(() => {
|
|
1370
|
+
this.#stopAllActors();
|
|
1371
|
+
}, this.#runnerLostThreshold);
|
|
1372
|
+
}
|
|
1373
|
+
this.#scheduleReconnect();
|
|
1374
|
+
}
|
|
1375
|
+
});
|
|
1376
|
+
ws.addEventListener("close", async (ev) => {
|
|
1377
|
+
var _a, _b, _c;
|
|
1378
|
+
(_a = logger()) == null ? void 0 : _a.info({
|
|
1379
|
+
msg: "connection closed",
|
|
1380
|
+
runnerId: this.runnerId,
|
|
1381
|
+
code: ev.code,
|
|
1382
|
+
reason: ev.reason.toString()
|
|
1383
|
+
});
|
|
1384
|
+
this.#config.onDisconnected();
|
|
1385
|
+
if (ev.reason.toString().startsWith("ws.eviction")) {
|
|
1386
|
+
(_b = logger()) == null ? void 0 : _b.info({
|
|
1387
|
+
msg: "runner evicted",
|
|
1388
|
+
runnerId: this.runnerId
|
|
1389
|
+
});
|
|
1390
|
+
await this.shutdown(true);
|
|
1391
|
+
}
|
|
1392
|
+
if (this.#pingLoop) {
|
|
1393
|
+
clearInterval(this.#pingLoop);
|
|
1394
|
+
this.#pingLoop = void 0;
|
|
1395
|
+
}
|
|
1396
|
+
if (this.#ackInterval) {
|
|
1397
|
+
clearInterval(this.#ackInterval);
|
|
1398
|
+
this.#ackInterval = void 0;
|
|
1399
|
+
}
|
|
1400
|
+
if (!this.#shutdown) {
|
|
1401
|
+
if (!this.#runnerLostTimeout && this.#runnerLostThreshold && this.#runnerLostThreshold > 0) {
|
|
1402
|
+
(_c = logger()) == null ? void 0 : _c.info({
|
|
1403
|
+
msg: "starting runner lost timeout",
|
|
1404
|
+
runnerId: this.runnerId,
|
|
1405
|
+
seconds: this.#runnerLostThreshold / 1e3
|
|
1406
|
+
});
|
|
1407
|
+
this.#runnerLostTimeout = setTimeout(() => {
|
|
1408
|
+
this.#stopAllActors();
|
|
1409
|
+
}, this.#runnerLostThreshold);
|
|
1410
|
+
}
|
|
1411
|
+
this.#scheduleReconnect();
|
|
1412
|
+
}
|
|
1413
|
+
});
|
|
1414
|
+
}
|
|
1415
|
+
#handleCommands(commands) {
|
|
1416
|
+
var _a, _b;
|
|
1417
|
+
(_a = logger()) == null ? void 0 : _a.info({
|
|
1418
|
+
msg: "received commands",
|
|
1419
|
+
runnerId: this.runnerId,
|
|
1420
|
+
commandCount: commands.length
|
|
1421
|
+
});
|
|
1422
|
+
for (const commandWrapper of commands) {
|
|
1423
|
+
(_b = logger()) == null ? void 0 : _b.info({
|
|
1424
|
+
msg: "received command",
|
|
1425
|
+
runnerId: this.runnerId,
|
|
1426
|
+
commandWrapper
|
|
1427
|
+
});
|
|
1428
|
+
if (commandWrapper.inner.tag === "CommandStartActor") {
|
|
1429
|
+
this.#handleCommandStartActor(commandWrapper);
|
|
1430
|
+
} else if (commandWrapper.inner.tag === "CommandStopActor") {
|
|
1431
|
+
this.#handleCommandStopActor(commandWrapper);
|
|
1432
|
+
} else {
|
|
1433
|
+
unreachable(commandWrapper.inner);
|
|
1434
|
+
}
|
|
1435
|
+
this.#lastCommandIdx = Number(commandWrapper.index);
|
|
1436
|
+
}
|
|
1437
|
+
}
|
|
1438
|
+
#handleAckEvents(ack) {
|
|
1439
|
+
var _a;
|
|
1440
|
+
const lastAckedIdx = ack.lastEventIdx;
|
|
1441
|
+
const originalLength = this.#eventHistory.length;
|
|
1442
|
+
this.#eventHistory = this.#eventHistory.filter(
|
|
1443
|
+
(event) => event.index > lastAckedIdx
|
|
1444
|
+
);
|
|
1445
|
+
const prunedCount = originalLength - this.#eventHistory.length;
|
|
1446
|
+
if (prunedCount > 0) {
|
|
1447
|
+
(_a = logger()) == null ? void 0 : _a.info({
|
|
1448
|
+
msg: "pruned acknowledged events",
|
|
1449
|
+
runnerId: this.runnerId,
|
|
1450
|
+
lastAckedIdx: lastAckedIdx.toString(),
|
|
1451
|
+
prunedCount
|
|
1452
|
+
});
|
|
1453
|
+
}
|
|
1454
|
+
if (this.#eventHistory.length <= EVENT_BACKLOG_WARN_THRESHOLD) {
|
|
1455
|
+
this.#eventBacklogWarned = false;
|
|
1456
|
+
}
|
|
1457
|
+
}
|
|
1458
|
+
/** Track events to send to the server in case we need to resend it on disconnect. */
|
|
1459
|
+
#recordEvent(eventWrapper) {
|
|
1460
|
+
var _a;
|
|
1461
|
+
this.#eventHistory.push(eventWrapper);
|
|
1462
|
+
if (this.#eventHistory.length > EVENT_BACKLOG_WARN_THRESHOLD && !this.#eventBacklogWarned) {
|
|
1463
|
+
this.#eventBacklogWarned = true;
|
|
1464
|
+
(_a = logger()) == null ? void 0 : _a.warn({
|
|
1465
|
+
msg: "unacknowledged event backlog exceeds threshold",
|
|
1466
|
+
runnerId: this.runnerId,
|
|
1467
|
+
backlogSize: this.#eventHistory.length,
|
|
1468
|
+
threshold: EVENT_BACKLOG_WARN_THRESHOLD
|
|
1469
|
+
});
|
|
1470
|
+
}
|
|
1471
|
+
}
|
|
1472
|
+
#handleCommandStartActor(commandWrapper) {
|
|
1473
|
+
const startCommand = commandWrapper.inner.val;
|
|
1474
|
+
const actorId = startCommand.actorId;
|
|
1475
|
+
const generation = startCommand.generation;
|
|
1476
|
+
const config = startCommand.config;
|
|
1477
|
+
const actorConfig = {
|
|
1478
|
+
name: config.name,
|
|
1479
|
+
key: config.key,
|
|
1480
|
+
createTs: config.createTs,
|
|
1481
|
+
input: config.input ? new Uint8Array(config.input) : null
|
|
1482
|
+
};
|
|
1483
|
+
const instance = {
|
|
1484
|
+
actorId,
|
|
1485
|
+
generation,
|
|
1486
|
+
config: actorConfig,
|
|
1487
|
+
requests: /* @__PURE__ */ new Set(),
|
|
1488
|
+
webSockets: /* @__PURE__ */ new Set()
|
|
1489
|
+
};
|
|
1490
|
+
this.#actors.set(actorId, instance);
|
|
1491
|
+
this.#sendActorStateUpdate(actorId, generation, "running");
|
|
1492
|
+
this.#config.onActorStart(actorId, generation, actorConfig).catch((err) => {
|
|
1493
|
+
var _a;
|
|
1494
|
+
(_a = logger()) == null ? void 0 : _a.error({
|
|
1495
|
+
msg: "error in onactorstart for actor",
|
|
1496
|
+
runnerId: this.runnerId,
|
|
1497
|
+
actorId,
|
|
1498
|
+
err
|
|
1499
|
+
});
|
|
1500
|
+
this.forceStopActor(actorId, generation);
|
|
1501
|
+
});
|
|
1502
|
+
}
|
|
1503
|
+
#handleCommandStopActor(commandWrapper) {
|
|
1504
|
+
const stopCommand = commandWrapper.inner.val;
|
|
1505
|
+
const actorId = stopCommand.actorId;
|
|
1506
|
+
const generation = stopCommand.generation;
|
|
1507
|
+
this.forceStopActor(actorId, generation);
|
|
1508
|
+
}
|
|
1509
|
+
#sendActorIntent(actorId, generation, intentType) {
|
|
1510
|
+
var _a, _b;
|
|
1511
|
+
if (this.#shutdown) {
|
|
1512
|
+
(_a = logger()) == null ? void 0 : _a.warn({
|
|
1513
|
+
msg: "Runner is shut down, cannot send actor intent",
|
|
1514
|
+
runnerId: this.runnerId
|
|
1515
|
+
});
|
|
1516
|
+
return;
|
|
1517
|
+
}
|
|
1518
|
+
let actorIntent;
|
|
1519
|
+
if (intentType === "sleep") {
|
|
1520
|
+
actorIntent = { tag: "ActorIntentSleep", val: null };
|
|
1521
|
+
} else if (intentType === "stop") {
|
|
1522
|
+
actorIntent = {
|
|
1523
|
+
tag: "ActorIntentStop",
|
|
1524
|
+
val: null
|
|
1525
|
+
};
|
|
1526
|
+
} else {
|
|
1527
|
+
unreachable(intentType);
|
|
1528
|
+
}
|
|
1529
|
+
const intentEvent = {
|
|
1530
|
+
actorId,
|
|
1531
|
+
generation,
|
|
1532
|
+
intent: actorIntent
|
|
1533
|
+
};
|
|
1534
|
+
const eventIndex = this.#nextEventIdx++;
|
|
1535
|
+
const eventWrapper = {
|
|
1536
|
+
index: eventIndex,
|
|
1537
|
+
inner: {
|
|
1538
|
+
tag: "EventActorIntent",
|
|
1539
|
+
val: intentEvent
|
|
1540
|
+
}
|
|
1541
|
+
};
|
|
1542
|
+
this.#recordEvent(eventWrapper);
|
|
1543
|
+
(_b = logger()) == null ? void 0 : _b.info({
|
|
1544
|
+
msg: "sending event to server",
|
|
1545
|
+
runnerId: this.runnerId,
|
|
1546
|
+
index: eventWrapper.index,
|
|
1547
|
+
tag: eventWrapper.inner.tag,
|
|
1548
|
+
val: eventWrapper.inner.val
|
|
1549
|
+
});
|
|
1550
|
+
this.__sendToServer({
|
|
1551
|
+
tag: "ToServerEvents",
|
|
1552
|
+
val: [eventWrapper]
|
|
1553
|
+
});
|
|
1554
|
+
}
|
|
1555
|
+
#sendActorStateUpdate(actorId, generation, stateType) {
|
|
1556
|
+
var _a, _b;
|
|
1557
|
+
if (this.#shutdown) {
|
|
1558
|
+
(_a = logger()) == null ? void 0 : _a.warn({
|
|
1559
|
+
msg: "Runner is shut down, cannot send actor state update",
|
|
1560
|
+
runnerId: this.runnerId
|
|
1561
|
+
});
|
|
1562
|
+
return;
|
|
1563
|
+
}
|
|
1564
|
+
let actorState;
|
|
1565
|
+
if (stateType === "running") {
|
|
1566
|
+
actorState = { tag: "ActorStateRunning", val: null };
|
|
1567
|
+
} else if (stateType === "stopped") {
|
|
1568
|
+
actorState = {
|
|
1569
|
+
tag: "ActorStateStopped",
|
|
1570
|
+
val: {
|
|
1571
|
+
code: protocol.StopCode.Ok,
|
|
1572
|
+
message: null
|
|
1573
|
+
}
|
|
1574
|
+
};
|
|
1575
|
+
} else {
|
|
1576
|
+
unreachable(stateType);
|
|
1577
|
+
}
|
|
1578
|
+
const stateUpdateEvent = {
|
|
1579
|
+
actorId,
|
|
1580
|
+
generation,
|
|
1581
|
+
state: actorState
|
|
1582
|
+
};
|
|
1583
|
+
const eventIndex = this.#nextEventIdx++;
|
|
1584
|
+
const eventWrapper = {
|
|
1585
|
+
index: eventIndex,
|
|
1586
|
+
inner: {
|
|
1587
|
+
tag: "EventActorStateUpdate",
|
|
1588
|
+
val: stateUpdateEvent
|
|
1589
|
+
}
|
|
1590
|
+
};
|
|
1591
|
+
this.#recordEvent(eventWrapper);
|
|
1592
|
+
(_b = logger()) == null ? void 0 : _b.info({
|
|
1593
|
+
msg: "sending event to server",
|
|
1594
|
+
runnerId: this.runnerId,
|
|
1595
|
+
index: eventWrapper.index,
|
|
1596
|
+
tag: eventWrapper.inner.tag,
|
|
1597
|
+
val: eventWrapper.inner.val
|
|
1598
|
+
});
|
|
1599
|
+
this.__sendToServer({
|
|
1600
|
+
tag: "ToServerEvents",
|
|
1601
|
+
val: [eventWrapper]
|
|
1602
|
+
});
|
|
1603
|
+
}
|
|
1604
|
+
#sendCommandAcknowledgment() {
|
|
1605
|
+
var _a;
|
|
1606
|
+
if (this.#shutdown) {
|
|
1607
|
+
(_a = logger()) == null ? void 0 : _a.warn({
|
|
1608
|
+
msg: "Runner is shut down, cannot send command acknowledgment",
|
|
1609
|
+
runnerId: this.runnerId
|
|
1610
|
+
});
|
|
1611
|
+
return;
|
|
1612
|
+
}
|
|
1613
|
+
if (this.#lastCommandIdx < 0) {
|
|
1614
|
+
return;
|
|
1615
|
+
}
|
|
1616
|
+
this.__sendToServer({
|
|
1617
|
+
tag: "ToServerAckCommands",
|
|
1618
|
+
val: {
|
|
1619
|
+
lastCommandIdx: BigInt(this.#lastCommandIdx)
|
|
1620
|
+
}
|
|
1621
|
+
});
|
|
1622
|
+
}
|
|
1623
|
+
#handleKvResponse(response) {
|
|
1624
|
+
var _a;
|
|
1625
|
+
const requestId = response.requestId;
|
|
1626
|
+
const request = this.#kvRequests.get(requestId);
|
|
1627
|
+
if (!request) {
|
|
1628
|
+
const msg = "received kv response for unknown request id";
|
|
1629
|
+
(_a = logger()) == null ? void 0 : _a.error({ msg, runnerId: this.runnerId, requestId });
|
|
1630
|
+
return;
|
|
1631
|
+
}
|
|
1632
|
+
this.#kvRequests.delete(requestId);
|
|
1633
|
+
if (response.data.tag === "KvErrorResponse") {
|
|
1634
|
+
request.reject(
|
|
1635
|
+
new Error(response.data.val.message || "Unknown KV error")
|
|
1636
|
+
);
|
|
1637
|
+
} else {
|
|
1638
|
+
request.resolve(response.data.val);
|
|
1639
|
+
}
|
|
1640
|
+
}
|
|
1641
|
+
#parseGetResponseSimple(response, requestedKeys) {
|
|
1642
|
+
const responseKeys = [];
|
|
1643
|
+
const responseValues = [];
|
|
1644
|
+
for (const key of response.keys) {
|
|
1645
|
+
responseKeys.push(new Uint8Array(key));
|
|
1646
|
+
}
|
|
1647
|
+
for (const value of response.values) {
|
|
1648
|
+
responseValues.push(new Uint8Array(value));
|
|
1649
|
+
}
|
|
1650
|
+
const result = [];
|
|
1651
|
+
for (const requestedKey of requestedKeys) {
|
|
1652
|
+
let found = false;
|
|
1653
|
+
for (let i = 0; i < responseKeys.length; i++) {
|
|
1654
|
+
if (this.#keysEqual(requestedKey, responseKeys[i])) {
|
|
1655
|
+
result.push(responseValues[i]);
|
|
1656
|
+
found = true;
|
|
1657
|
+
break;
|
|
1658
|
+
}
|
|
1659
|
+
}
|
|
1660
|
+
if (!found) {
|
|
1661
|
+
result.push(null);
|
|
1662
|
+
}
|
|
1663
|
+
}
|
|
1664
|
+
return result;
|
|
1665
|
+
}
|
|
1666
|
+
#keysEqual(key1, key2) {
|
|
1667
|
+
if (key1.length !== key2.length) return false;
|
|
1668
|
+
for (let i = 0; i < key1.length; i++) {
|
|
1669
|
+
if (key1[i] !== key2[i]) return false;
|
|
1670
|
+
}
|
|
1671
|
+
return true;
|
|
1672
|
+
}
|
|
1673
|
+
//#parseGetResponse(response: protocol.KvGetResponse) {
|
|
1674
|
+
// const keys: string[] = [];
|
|
1675
|
+
// const values: Uint8Array[] = [];
|
|
1676
|
+
// const metadata: { version: Uint8Array; createTs: bigint }[] = [];
|
|
1677
|
+
//
|
|
1678
|
+
// for (const key of response.keys) {
|
|
1679
|
+
// keys.push(new TextDecoder().decode(key));
|
|
1680
|
+
// }
|
|
1681
|
+
//
|
|
1682
|
+
// for (const value of response.values) {
|
|
1683
|
+
// values.push(new Uint8Array(value));
|
|
1684
|
+
// }
|
|
1685
|
+
//
|
|
1686
|
+
// for (const meta of response.metadata) {
|
|
1687
|
+
// metadata.push({
|
|
1688
|
+
// version: new Uint8Array(meta.version),
|
|
1689
|
+
// createTs: meta.createTs,
|
|
1690
|
+
// });
|
|
1691
|
+
// }
|
|
1692
|
+
//
|
|
1693
|
+
// return { keys, values, metadata };
|
|
1694
|
+
//}
|
|
1695
|
+
#parseListResponseSimple(response) {
|
|
1696
|
+
const result = [];
|
|
1697
|
+
for (let i = 0; i < response.keys.length; i++) {
|
|
1698
|
+
const key = response.keys[i];
|
|
1699
|
+
const value = response.values[i];
|
|
1700
|
+
if (key && value) {
|
|
1701
|
+
const keyBytes = new Uint8Array(key);
|
|
1702
|
+
const valueBytes = new Uint8Array(value);
|
|
1703
|
+
result.push([keyBytes, valueBytes]);
|
|
1704
|
+
}
|
|
1705
|
+
}
|
|
1706
|
+
return result;
|
|
1707
|
+
}
|
|
1708
|
+
//#parseListResponse(response: protocol.KvListResponse) {
|
|
1709
|
+
// const keys: string[] = [];
|
|
1710
|
+
// const values: Uint8Array[] = [];
|
|
1711
|
+
// const metadata: { version: Uint8Array; createTs: bigint }[] = [];
|
|
1712
|
+
//
|
|
1713
|
+
// for (const key of response.keys) {
|
|
1714
|
+
// keys.push(new TextDecoder().decode(key));
|
|
1715
|
+
// }
|
|
1716
|
+
//
|
|
1717
|
+
// for (const value of response.values) {
|
|
1718
|
+
// values.push(new Uint8Array(value));
|
|
1719
|
+
// }
|
|
1720
|
+
//
|
|
1721
|
+
// for (const meta of response.metadata) {
|
|
1722
|
+
// metadata.push({
|
|
1723
|
+
// version: new Uint8Array(meta.version),
|
|
1724
|
+
// createTs: meta.createTs,
|
|
1725
|
+
// });
|
|
1726
|
+
// }
|
|
1727
|
+
//
|
|
1728
|
+
// return { keys, values, metadata };
|
|
1729
|
+
//}
|
|
1730
|
+
// MARK: KV Operations
|
|
1731
|
+
async kvGet(actorId, keys) {
|
|
1732
|
+
const kvKeys = keys.map(
|
|
1733
|
+
(key) => key.buffer.slice(
|
|
1734
|
+
key.byteOffset,
|
|
1735
|
+
key.byteOffset + key.byteLength
|
|
1736
|
+
)
|
|
1737
|
+
);
|
|
1738
|
+
const requestData = {
|
|
1739
|
+
tag: "KvGetRequest",
|
|
1740
|
+
val: { keys: kvKeys }
|
|
1741
|
+
};
|
|
1742
|
+
const response = await this.#sendKvRequest(actorId, requestData);
|
|
1743
|
+
return this.#parseGetResponseSimple(response, keys);
|
|
1744
|
+
}
|
|
1745
|
+
async kvListAll(actorId, options) {
|
|
1746
|
+
const requestData = {
|
|
1747
|
+
tag: "KvListRequest",
|
|
1748
|
+
val: {
|
|
1749
|
+
query: { tag: "KvListAllQuery", val: null },
|
|
1750
|
+
reverse: (options == null ? void 0 : options.reverse) || null,
|
|
1751
|
+
limit: (options == null ? void 0 : options.limit) !== void 0 ? BigInt(options.limit) : null
|
|
1752
|
+
}
|
|
1753
|
+
};
|
|
1754
|
+
const response = await this.#sendKvRequest(actorId, requestData);
|
|
1755
|
+
return this.#parseListResponseSimple(response);
|
|
1756
|
+
}
|
|
1757
|
+
async kvListRange(actorId, start, end, exclusive, options) {
|
|
1758
|
+
const startKey = start.buffer.slice(
|
|
1759
|
+
start.byteOffset,
|
|
1760
|
+
start.byteOffset + start.byteLength
|
|
1761
|
+
);
|
|
1762
|
+
const endKey = end.buffer.slice(
|
|
1763
|
+
end.byteOffset,
|
|
1764
|
+
end.byteOffset + end.byteLength
|
|
1765
|
+
);
|
|
1766
|
+
const requestData = {
|
|
1767
|
+
tag: "KvListRequest",
|
|
1768
|
+
val: {
|
|
1769
|
+
query: {
|
|
1770
|
+
tag: "KvListRangeQuery",
|
|
1771
|
+
val: {
|
|
1772
|
+
start: startKey,
|
|
1773
|
+
end: endKey,
|
|
1774
|
+
exclusive: exclusive || false
|
|
1775
|
+
}
|
|
1776
|
+
},
|
|
1777
|
+
reverse: (options == null ? void 0 : options.reverse) || null,
|
|
1778
|
+
limit: (options == null ? void 0 : options.limit) !== void 0 ? BigInt(options.limit) : null
|
|
1779
|
+
}
|
|
1780
|
+
};
|
|
1781
|
+
const response = await this.#sendKvRequest(actorId, requestData);
|
|
1782
|
+
return this.#parseListResponseSimple(response);
|
|
1783
|
+
}
|
|
1784
|
+
async kvListPrefix(actorId, prefix, options) {
|
|
1785
|
+
const prefixKey = prefix.buffer.slice(
|
|
1786
|
+
prefix.byteOffset,
|
|
1787
|
+
prefix.byteOffset + prefix.byteLength
|
|
1788
|
+
);
|
|
1789
|
+
const requestData = {
|
|
1790
|
+
tag: "KvListRequest",
|
|
1791
|
+
val: {
|
|
1792
|
+
query: {
|
|
1793
|
+
tag: "KvListPrefixQuery",
|
|
1794
|
+
val: { key: prefixKey }
|
|
1795
|
+
},
|
|
1796
|
+
reverse: (options == null ? void 0 : options.reverse) || null,
|
|
1797
|
+
limit: (options == null ? void 0 : options.limit) !== void 0 ? BigInt(options.limit) : null
|
|
1798
|
+
}
|
|
1799
|
+
};
|
|
1800
|
+
const response = await this.#sendKvRequest(actorId, requestData);
|
|
1801
|
+
return this.#parseListResponseSimple(response);
|
|
1802
|
+
}
|
|
1803
|
+
async kvPut(actorId, entries) {
|
|
1804
|
+
const keys = entries.map(
|
|
1805
|
+
([key, _value]) => key.buffer.slice(
|
|
1806
|
+
key.byteOffset,
|
|
1807
|
+
key.byteOffset + key.byteLength
|
|
1808
|
+
)
|
|
1809
|
+
);
|
|
1810
|
+
const values = entries.map(
|
|
1811
|
+
([_key, value]) => value.buffer.slice(
|
|
1812
|
+
value.byteOffset,
|
|
1813
|
+
value.byteOffset + value.byteLength
|
|
1814
|
+
)
|
|
1815
|
+
);
|
|
1816
|
+
const requestData = {
|
|
1817
|
+
tag: "KvPutRequest",
|
|
1818
|
+
val: { keys, values }
|
|
1819
|
+
};
|
|
1820
|
+
await this.#sendKvRequest(actorId, requestData);
|
|
1821
|
+
}
|
|
1822
|
+
async kvDelete(actorId, keys) {
|
|
1823
|
+
const kvKeys = keys.map(
|
|
1824
|
+
(key) => key.buffer.slice(
|
|
1825
|
+
key.byteOffset,
|
|
1826
|
+
key.byteOffset + key.byteLength
|
|
1827
|
+
)
|
|
1828
|
+
);
|
|
1829
|
+
const requestData = {
|
|
1830
|
+
tag: "KvDeleteRequest",
|
|
1831
|
+
val: { keys: kvKeys }
|
|
1832
|
+
};
|
|
1833
|
+
await this.#sendKvRequest(actorId, requestData);
|
|
1834
|
+
}
|
|
1835
|
+
async kvDrop(actorId) {
|
|
1836
|
+
const requestData = {
|
|
1837
|
+
tag: "KvDropRequest",
|
|
1838
|
+
val: null
|
|
1839
|
+
};
|
|
1840
|
+
await this.#sendKvRequest(actorId, requestData);
|
|
1841
|
+
}
|
|
1842
|
+
// MARK: Alarm Operations
|
|
1843
|
+
setAlarm(actorId, alarmTs, generation) {
|
|
1844
|
+
const actor = this.getActor(actorId, generation);
|
|
1845
|
+
if (!actor) return;
|
|
1846
|
+
if (this.#shutdown) {
|
|
1847
|
+
console.warn("Runner is shut down, cannot set alarm");
|
|
1848
|
+
return;
|
|
1849
|
+
}
|
|
1850
|
+
const alarmEvent = {
|
|
1851
|
+
actorId,
|
|
1852
|
+
generation: actor.generation,
|
|
1853
|
+
alarmTs: alarmTs !== null ? BigInt(alarmTs) : null
|
|
1854
|
+
};
|
|
1855
|
+
const eventIndex = this.#nextEventIdx++;
|
|
1856
|
+
const eventWrapper = {
|
|
1857
|
+
index: eventIndex,
|
|
1858
|
+
inner: {
|
|
1859
|
+
tag: "EventActorSetAlarm",
|
|
1860
|
+
val: alarmEvent
|
|
1861
|
+
}
|
|
1862
|
+
};
|
|
1863
|
+
this.#recordEvent(eventWrapper);
|
|
1864
|
+
this.__sendToServer({
|
|
1865
|
+
tag: "ToServerEvents",
|
|
1866
|
+
val: [eventWrapper]
|
|
1867
|
+
});
|
|
1868
|
+
}
|
|
1869
|
+
clearAlarm(actorId, generation) {
|
|
1870
|
+
this.setAlarm(actorId, null, generation);
|
|
1871
|
+
}
|
|
1872
|
+
#sendKvRequest(actorId, requestData) {
|
|
1873
|
+
return new Promise((resolve, reject) => {
|
|
1874
|
+
if (this.#shutdown) {
|
|
1875
|
+
reject(new Error("Runner is shut down"));
|
|
1876
|
+
return;
|
|
1877
|
+
}
|
|
1878
|
+
const requestId = this.#nextRequestId++;
|
|
1879
|
+
const isConnected = this.#pegboardWebSocket && this.#pegboardWebSocket.readyState === 1;
|
|
1880
|
+
const requestEntry = {
|
|
1881
|
+
actorId,
|
|
1882
|
+
data: requestData,
|
|
1883
|
+
resolve,
|
|
1884
|
+
reject,
|
|
1885
|
+
sent: false,
|
|
1886
|
+
timestamp: Date.now()
|
|
1887
|
+
};
|
|
1888
|
+
this.#kvRequests.set(requestId, requestEntry);
|
|
1889
|
+
if (isConnected) {
|
|
1890
|
+
this.#sendSingleKvRequest(requestId);
|
|
1891
|
+
}
|
|
1892
|
+
});
|
|
1893
|
+
}
|
|
1894
|
+
#sendSingleKvRequest(requestId) {
|
|
1895
|
+
const request = this.#kvRequests.get(requestId);
|
|
1896
|
+
if (!request || request.sent) return;
|
|
1897
|
+
try {
|
|
1898
|
+
const kvRequest = {
|
|
1899
|
+
actorId: request.actorId,
|
|
1900
|
+
requestId,
|
|
1901
|
+
data: request.data
|
|
1902
|
+
};
|
|
1903
|
+
this.__sendToServer({
|
|
1904
|
+
tag: "ToServerKvRequest",
|
|
1905
|
+
val: kvRequest
|
|
1906
|
+
});
|
|
1907
|
+
request.sent = true;
|
|
1908
|
+
request.timestamp = Date.now();
|
|
1909
|
+
} catch (error) {
|
|
1910
|
+
this.#kvRequests.delete(requestId);
|
|
1911
|
+
request.reject(error);
|
|
1912
|
+
}
|
|
1913
|
+
}
|
|
1914
|
+
#processUnsentKvRequests() {
|
|
1915
|
+
if (!this.#pegboardWebSocket || this.#pegboardWebSocket.readyState !== 1) {
|
|
1916
|
+
return;
|
|
1917
|
+
}
|
|
1918
|
+
let processedCount = 0;
|
|
1919
|
+
for (const [requestId, request] of this.#kvRequests.entries()) {
|
|
1920
|
+
if (!request.sent) {
|
|
1921
|
+
this.#sendSingleKvRequest(requestId);
|
|
1922
|
+
processedCount++;
|
|
1923
|
+
}
|
|
1924
|
+
}
|
|
1925
|
+
if (processedCount > 0) {
|
|
1926
|
+
}
|
|
1927
|
+
}
|
|
1928
|
+
__webSocketReady() {
|
|
1929
|
+
return this.#pegboardWebSocket ? this.#pegboardWebSocket.readyState === 1 : false;
|
|
1930
|
+
}
|
|
1931
|
+
__sendToServer(message) {
|
|
1932
|
+
var _a, _b;
|
|
1933
|
+
if (this.#shutdown) {
|
|
1934
|
+
(_a = logger()) == null ? void 0 : _a.warn({
|
|
1935
|
+
msg: "Runner is shut down, cannot send message to server",
|
|
1936
|
+
runnerId: this.runnerId
|
|
1937
|
+
});
|
|
1938
|
+
return;
|
|
1939
|
+
}
|
|
1940
|
+
const encoded = protocol.encodeToServer(message);
|
|
1941
|
+
if (this.#pegboardWebSocket && this.#pegboardWebSocket.readyState === 1) {
|
|
1942
|
+
this.#pegboardWebSocket.send(encoded);
|
|
1943
|
+
} else {
|
|
1944
|
+
(_b = logger()) == null ? void 0 : _b.error({
|
|
1945
|
+
msg: "WebSocket not available or not open for sending data",
|
|
1946
|
+
runnerId: this.runnerId
|
|
1947
|
+
});
|
|
1948
|
+
}
|
|
1949
|
+
}
|
|
1950
|
+
getServerlessInitPacket() {
|
|
1951
|
+
if (!this.runnerId) return void 0;
|
|
1952
|
+
const data = protocol.encodeToServerlessServer({
|
|
1953
|
+
tag: "ToServerlessServerInit",
|
|
1954
|
+
val: {
|
|
1955
|
+
runnerId: this.runnerId
|
|
1956
|
+
}
|
|
1957
|
+
});
|
|
1958
|
+
const buffer = Buffer.alloc(data.length + 2);
|
|
1959
|
+
buffer.writeUInt16LE(PROTOCOL_VERSION, 0);
|
|
1960
|
+
Buffer.from(data).copy(buffer, 2);
|
|
1961
|
+
return buffer.toString("base64");
|
|
1962
|
+
}
|
|
1963
|
+
#scheduleReconnect() {
|
|
1964
|
+
var _a, _b;
|
|
1965
|
+
if (this.#shutdown) {
|
|
1966
|
+
(_a = logger()) == null ? void 0 : _a.debug({
|
|
1967
|
+
msg: "Runner is shut down, not attempting reconnect",
|
|
1968
|
+
runnerId: this.runnerId
|
|
1969
|
+
});
|
|
1970
|
+
return;
|
|
1971
|
+
}
|
|
1972
|
+
const delay = calculateBackoff(this.#reconnectAttempt, {
|
|
1973
|
+
initialDelay: 1e3,
|
|
1974
|
+
maxDelay: 3e4,
|
|
1975
|
+
multiplier: 2,
|
|
1976
|
+
jitter: true
|
|
1977
|
+
});
|
|
1978
|
+
(_b = logger()) == null ? void 0 : _b.debug({
|
|
1979
|
+
msg: `Scheduling reconnect attempt ${this.#reconnectAttempt + 1} in ${delay}ms`,
|
|
1980
|
+
runnerId: this.runnerId
|
|
1981
|
+
});
|
|
1982
|
+
this.#reconnectTimeout = setTimeout(async () => {
|
|
1983
|
+
var _a2;
|
|
1984
|
+
if (!this.#shutdown) {
|
|
1985
|
+
this.#reconnectAttempt++;
|
|
1986
|
+
(_a2 = logger()) == null ? void 0 : _a2.debug({
|
|
1987
|
+
msg: `Attempting to reconnect (attempt ${this.#reconnectAttempt})...`,
|
|
1988
|
+
runnerId: this.runnerId
|
|
1989
|
+
});
|
|
1990
|
+
await this.#openPegboardWebSocket();
|
|
1991
|
+
}
|
|
1992
|
+
}, delay);
|
|
1993
|
+
}
|
|
1994
|
+
#resendUnacknowledgedEvents(lastEventIdx) {
|
|
1995
|
+
const eventsToResend = this.#eventHistory.filter(
|
|
1996
|
+
(event) => event.index > lastEventIdx
|
|
1997
|
+
);
|
|
1998
|
+
if (eventsToResend.length === 0) return;
|
|
1999
|
+
this.__sendToServer({
|
|
2000
|
+
tag: "ToServerEvents",
|
|
2001
|
+
val: eventsToResend
|
|
2002
|
+
});
|
|
2003
|
+
}
|
|
2004
|
+
#cleanupOldKvRequests() {
|
|
2005
|
+
const thirtySecondsAgo = Date.now() - KV_EXPIRE;
|
|
2006
|
+
const toDelete = [];
|
|
2007
|
+
for (const [requestId, request] of this.#kvRequests.entries()) {
|
|
2008
|
+
if (request.timestamp < thirtySecondsAgo) {
|
|
2009
|
+
request.reject(
|
|
2010
|
+
new Error(
|
|
2011
|
+
"KV request timed out waiting for WebSocket connection"
|
|
2012
|
+
)
|
|
2013
|
+
);
|
|
2014
|
+
toDelete.push(requestId);
|
|
2015
|
+
}
|
|
2016
|
+
}
|
|
2017
|
+
for (const requestId of toDelete) {
|
|
2018
|
+
this.#kvRequests.delete(requestId);
|
|
2019
|
+
}
|
|
2020
|
+
if (toDelete.length > 0) {
|
|
2021
|
+
}
|
|
2022
|
+
}
|
|
2023
|
+
};
|
|
2024
|
+
export {
|
|
2025
|
+
Runner
|
|
2026
|
+
};
|
|
2027
|
+
//# sourceMappingURL=mod.js.map
|