@rivetkit/engine-runner 2.0.27 → 2.0.29-rc.1
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/dist/mod.cjs +248 -143
- package/dist/mod.cjs.map +1 -1
- package/dist/mod.d.cts +9 -1
- package/dist/mod.d.ts +9 -1
- package/dist/mod.js +239 -134
- package/dist/mod.js.map +1 -1
- package/package.json +7 -2
- package/src/actor.ts +18 -4
- package/src/mod.ts +193 -105
- package/src/stringify.ts +31 -32
- package/src/tunnel.ts +4 -13
- package/src/utils.ts +16 -0
- package/.turbo/turbo-build.log +0 -22
- package/benches/actor-lifecycle.bench.ts +0 -190
- package/benches/utils.ts +0 -143
- package/tests/lifecycle.test.ts +0 -596
- package/tests/utils.test.ts +0 -194
- package/tsconfig.json +0 -11
- package/tsup.config.ts +0 -4
- package/turbo.json +0 -4
- package/vitest.config.ts +0 -16
package/package.json
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rivetkit/engine-runner",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.29-rc.1",
|
|
4
4
|
"type": "module",
|
|
5
|
+
"files": [
|
|
6
|
+
"dist",
|
|
7
|
+
"src",
|
|
8
|
+
"package.json"
|
|
9
|
+
],
|
|
5
10
|
"exports": {
|
|
6
11
|
"import": {
|
|
7
12
|
"types": "./dist/mod.d.ts",
|
|
@@ -16,7 +21,7 @@
|
|
|
16
21
|
"uuid": "^12.0.0",
|
|
17
22
|
"pino": "^9.9.5",
|
|
18
23
|
"ws": "^8.18.3",
|
|
19
|
-
"@rivetkit/engine-runner-protocol": "2.0.
|
|
24
|
+
"@rivetkit/engine-runner-protocol": "2.0.29-rc.1"
|
|
20
25
|
},
|
|
21
26
|
"devDependencies": {
|
|
22
27
|
"@types/node": "^22.18.1",
|
package/src/actor.ts
CHANGED
|
@@ -27,6 +27,10 @@ export class RunnerActor {
|
|
|
27
27
|
}> = [];
|
|
28
28
|
actorStartPromise: ReturnType<typeof promiseWithResolvers<void>>;
|
|
29
29
|
|
|
30
|
+
lastCommandIdx: bigint = -1n;
|
|
31
|
+
nextEventIdx: bigint = 0n;
|
|
32
|
+
eventHistory: protocol.EventWrapper[] = [];
|
|
33
|
+
|
|
30
34
|
/**
|
|
31
35
|
* If restoreHibernatingRequests has been called. This is used to assert
|
|
32
36
|
* that the caller is implemented correctly.
|
|
@@ -81,8 +85,8 @@ export class RunnerActor {
|
|
|
81
85
|
gatewayId,
|
|
82
86
|
requestId,
|
|
83
87
|
request: {
|
|
84
|
-
resolve: () => {},
|
|
85
|
-
reject: () => {},
|
|
88
|
+
resolve: () => { },
|
|
89
|
+
reject: () => { },
|
|
86
90
|
actorId: this.actorId,
|
|
87
91
|
gatewayId: gatewayId,
|
|
88
92
|
requestId: requestId,
|
|
@@ -118,8 +122,8 @@ export class RunnerActor {
|
|
|
118
122
|
gatewayId,
|
|
119
123
|
requestId,
|
|
120
124
|
request: {
|
|
121
|
-
resolve: () => {},
|
|
122
|
-
reject: () => {},
|
|
125
|
+
resolve: () => { },
|
|
126
|
+
reject: () => { },
|
|
123
127
|
actorId: this.actorId,
|
|
124
128
|
gatewayId: gatewayId,
|
|
125
129
|
requestId: requestId,
|
|
@@ -193,4 +197,14 @@ export class RunnerActor {
|
|
|
193
197
|
this.webSockets.splice(index, 1);
|
|
194
198
|
}
|
|
195
199
|
}
|
|
200
|
+
|
|
201
|
+
handleAckEvents(lastEventIdx: bigint) {
|
|
202
|
+
this.eventHistory = this.eventHistory.filter(
|
|
203
|
+
(event) => event.checkpoint.index > lastEventIdx,
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
recordEvent(eventWrapper: protocol.EventWrapper) {
|
|
208
|
+
this.eventHistory.push(eventWrapper);
|
|
209
|
+
}
|
|
196
210
|
}
|
package/src/mod.ts
CHANGED
|
@@ -8,6 +8,7 @@ import { type HibernatingWebSocketMetadata, Tunnel } from "./tunnel";
|
|
|
8
8
|
import {
|
|
9
9
|
calculateBackoff,
|
|
10
10
|
parseWebSocketCloseReason,
|
|
11
|
+
stringifyError,
|
|
11
12
|
unreachable,
|
|
12
13
|
} from "./utils";
|
|
13
14
|
import { importWebSocket } from "./websocket.js";
|
|
@@ -17,13 +18,18 @@ export { RunnerActor, type ActorConfig };
|
|
|
17
18
|
export { idToStr } from "./utils";
|
|
18
19
|
|
|
19
20
|
const KV_EXPIRE: number = 30_000;
|
|
20
|
-
const PROTOCOL_VERSION: number =
|
|
21
|
-
const RUNNER_PING_INTERVAL = 3_000;
|
|
21
|
+
const PROTOCOL_VERSION: number = 5;
|
|
22
22
|
|
|
23
23
|
/** Warn once the backlog significantly exceeds the server's ack batch size. */
|
|
24
24
|
const EVENT_BACKLOG_WARN_THRESHOLD = 10_000;
|
|
25
25
|
const SIGNAL_HANDLERS: (() => void | Promise<void>)[] = [];
|
|
26
26
|
|
|
27
|
+
export class RunnerShutdownError extends Error {
|
|
28
|
+
constructor() {
|
|
29
|
+
super("Runner shut down");
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
27
33
|
export interface RunnerConfig {
|
|
28
34
|
logger?: Logger;
|
|
29
35
|
version: number;
|
|
@@ -197,9 +203,6 @@ export class Runner {
|
|
|
197
203
|
// WebSocket
|
|
198
204
|
__pegboardWebSocket?: WebSocket;
|
|
199
205
|
runnerId?: string;
|
|
200
|
-
#lastCommandIdx: number = -1;
|
|
201
|
-
#pingLoop?: NodeJS.Timeout;
|
|
202
|
-
#nextEventIdx: bigint = 0n;
|
|
203
206
|
#started: boolean = false;
|
|
204
207
|
#shutdown: boolean = false;
|
|
205
208
|
#shuttingDown: boolean = false;
|
|
@@ -211,7 +214,6 @@ export class Runner {
|
|
|
211
214
|
#runnerLostTimeout?: NodeJS.Timeout;
|
|
212
215
|
|
|
213
216
|
// Event storage for resending
|
|
214
|
-
#eventHistory: protocol.EventWrapper[] = [];
|
|
215
217
|
#eventBacklogWarned: boolean = false;
|
|
216
218
|
|
|
217
219
|
// Command acknowledgment
|
|
@@ -255,7 +257,14 @@ export class Runner {
|
|
|
255
257
|
|
|
256
258
|
// Start cleaning up old unsent KV requests every 15 seconds
|
|
257
259
|
this.#kvCleanupInterval = setInterval(() => {
|
|
258
|
-
|
|
260
|
+
try {
|
|
261
|
+
this.#cleanupOldKvRequests();
|
|
262
|
+
} catch (err) {
|
|
263
|
+
this.log?.error({
|
|
264
|
+
msg: "error cleaning up kv requests",
|
|
265
|
+
error: stringifyError(err),
|
|
266
|
+
});
|
|
267
|
+
}
|
|
259
268
|
}, 15000); // Run every 15 seconds
|
|
260
269
|
}
|
|
261
270
|
|
|
@@ -282,6 +291,11 @@ export class Runner {
|
|
|
282
291
|
}
|
|
283
292
|
|
|
284
293
|
async forceStopActor(actorId: string, generation?: number) {
|
|
294
|
+
this.log?.debug({
|
|
295
|
+
msg: "force stopping actor",
|
|
296
|
+
actorId,
|
|
297
|
+
});
|
|
298
|
+
|
|
285
299
|
const actor = this.getActor(actorId, generation);
|
|
286
300
|
if (!actor) return;
|
|
287
301
|
|
|
@@ -299,22 +313,38 @@ export class Runner {
|
|
|
299
313
|
// Close requests after onActorStop so you can send messages over the tunnel
|
|
300
314
|
this.#tunnel?.closeActiveRequests(actor);
|
|
301
315
|
|
|
316
|
+
this.#sendActorStateUpdate(actorId, actor.generation, "stopped");
|
|
317
|
+
|
|
302
318
|
// Remove actor after stopping in order to ensure that we can still
|
|
303
|
-
// call actions on the runner
|
|
304
|
-
// order to ensure we don't have duplicate actors.
|
|
319
|
+
// call actions on the runner
|
|
305
320
|
this.#removeActor(actorId, generation);
|
|
306
|
-
|
|
307
|
-
this.#sendActorStateUpdate(actorId, actor.generation, "stopped");
|
|
308
321
|
}
|
|
309
322
|
|
|
310
|
-
#
|
|
323
|
+
#handleLost() {
|
|
311
324
|
this.log?.info({
|
|
312
|
-
msg: "stopping all actors due to runner lost threshold
|
|
325
|
+
msg: "stopping all actors due to runner lost threshold",
|
|
313
326
|
});
|
|
314
327
|
|
|
328
|
+
// Remove all remaining kv requests
|
|
329
|
+
for (const [_, request] of this.#kvRequests.entries()) {
|
|
330
|
+
request.reject(new RunnerShutdownError());
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
this.#kvRequests.clear();
|
|
334
|
+
|
|
335
|
+
this.#stopAllActors();
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
#stopAllActors() {
|
|
315
339
|
const actorIds = Array.from(this.#actors.keys());
|
|
316
340
|
for (const actorId of actorIds) {
|
|
317
|
-
this.forceStopActor(actorId)
|
|
341
|
+
this.forceStopActor(actorId).catch((err) => {
|
|
342
|
+
this.log?.error({
|
|
343
|
+
msg: "error stopping actor",
|
|
344
|
+
actorId,
|
|
345
|
+
error: stringifyError(err),
|
|
346
|
+
});
|
|
347
|
+
});
|
|
318
348
|
}
|
|
319
349
|
}
|
|
320
350
|
|
|
@@ -477,12 +507,6 @@ export class Runner {
|
|
|
477
507
|
this.#runnerLostTimeout = undefined;
|
|
478
508
|
}
|
|
479
509
|
|
|
480
|
-
// Clear ping loop
|
|
481
|
-
if (this.#pingLoop) {
|
|
482
|
-
clearInterval(this.#pingLoop);
|
|
483
|
-
this.#pingLoop = undefined;
|
|
484
|
-
}
|
|
485
|
-
|
|
486
510
|
// Clear ack interval
|
|
487
511
|
if (this.#ackInterval) {
|
|
488
512
|
clearInterval(this.#ackInterval);
|
|
@@ -738,10 +762,6 @@ export class Runner {
|
|
|
738
762
|
name: this.#config.runnerName,
|
|
739
763
|
version: this.#config.version,
|
|
740
764
|
totalSlots: this.#config.totalSlots,
|
|
741
|
-
lastCommandIdx:
|
|
742
|
-
this.#lastCommandIdx >= 0
|
|
743
|
-
? BigInt(this.#lastCommandIdx)
|
|
744
|
-
: null,
|
|
745
765
|
prepopulateActorNames: new Map(
|
|
746
766
|
Object.entries(this.#config.prepopulateActorNames).map(
|
|
747
767
|
([name, data]) => [
|
|
@@ -758,33 +778,22 @@ export class Runner {
|
|
|
758
778
|
val: init,
|
|
759
779
|
});
|
|
760
780
|
|
|
761
|
-
// Start ping interval
|
|
762
|
-
const pingLoop = setInterval(() => {
|
|
763
|
-
if (ws.readyState === 1) {
|
|
764
|
-
this.__sendToServer({
|
|
765
|
-
tag: "ToServerPing",
|
|
766
|
-
val: {
|
|
767
|
-
ts: BigInt(Date.now()),
|
|
768
|
-
},
|
|
769
|
-
});
|
|
770
|
-
} else {
|
|
771
|
-
clearInterval(pingLoop);
|
|
772
|
-
this.log?.info({
|
|
773
|
-
msg: "WebSocket not open, stopping ping loop",
|
|
774
|
-
});
|
|
775
|
-
}
|
|
776
|
-
}, RUNNER_PING_INTERVAL);
|
|
777
|
-
this.#pingLoop = pingLoop;
|
|
778
|
-
|
|
779
781
|
// Start command acknowledgment interval (5 minutes)
|
|
780
782
|
const ackInterval = 5 * 60 * 1000; // 5 minutes in milliseconds
|
|
781
783
|
const ackLoop = setInterval(() => {
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
784
|
+
try {
|
|
785
|
+
if (ws.readyState === 1) {
|
|
786
|
+
this.#sendCommandAcknowledgment();
|
|
787
|
+
} else {
|
|
788
|
+
clearInterval(ackLoop);
|
|
789
|
+
this.log?.info({
|
|
790
|
+
msg: "WebSocket not open, stopping ack loop",
|
|
791
|
+
});
|
|
792
|
+
}
|
|
793
|
+
} catch (err) {
|
|
794
|
+
this.log?.error({
|
|
795
|
+
msg: "error in command acknowledgment loop",
|
|
796
|
+
error: stringifyError(err),
|
|
788
797
|
});
|
|
789
798
|
}
|
|
790
799
|
}, ackInterval);
|
|
@@ -815,8 +824,8 @@ export class Runner {
|
|
|
815
824
|
if (this.runnerId !== init.runnerId) {
|
|
816
825
|
this.runnerId = init.runnerId;
|
|
817
826
|
|
|
818
|
-
// Clear
|
|
819
|
-
this.#
|
|
827
|
+
// Clear actors if runner id changed
|
|
828
|
+
this.#stopAllActors();
|
|
820
829
|
}
|
|
821
830
|
|
|
822
831
|
// Store the runner lost threshold from metadata
|
|
@@ -826,13 +835,12 @@ export class Runner {
|
|
|
826
835
|
|
|
827
836
|
this.log?.info({
|
|
828
837
|
msg: "received init",
|
|
829
|
-
lastEventIdx: init.lastEventIdx,
|
|
830
838
|
runnerLostThreshold: this.#runnerLostThreshold,
|
|
831
839
|
});
|
|
832
840
|
|
|
833
841
|
// Resend pending events
|
|
834
842
|
this.#processUnsentKvRequests();
|
|
835
|
-
this.#resendUnacknowledgedEvents(
|
|
843
|
+
this.#resendUnacknowledgedEvents();
|
|
836
844
|
this.#tunnel?.resendBufferedEvents();
|
|
837
845
|
|
|
838
846
|
this.#config.onConnected();
|
|
@@ -845,10 +853,19 @@ export class Runner {
|
|
|
845
853
|
const kvResponse = message.val;
|
|
846
854
|
this.#handleKvResponse(kvResponse);
|
|
847
855
|
} else if (message.tag === "ToClientTunnelMessage") {
|
|
848
|
-
this.#tunnel?.handleTunnelMessage(message.val)
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
856
|
+
this.#tunnel?.handleTunnelMessage(message.val).catch((err) => {
|
|
857
|
+
this.log?.error({
|
|
858
|
+
msg: "error handling tunnel message",
|
|
859
|
+
error: stringifyError(err),
|
|
860
|
+
});
|
|
861
|
+
});
|
|
862
|
+
} else if (message.tag === "ToClientPing") {
|
|
863
|
+
this.__sendToServer({
|
|
864
|
+
tag: "ToServerPong",
|
|
865
|
+
val: {
|
|
866
|
+
ts: message.val.ts,
|
|
867
|
+
},
|
|
868
|
+
});
|
|
852
869
|
} else {
|
|
853
870
|
unreachable(message);
|
|
854
871
|
}
|
|
@@ -871,7 +888,14 @@ export class Runner {
|
|
|
871
888
|
seconds: this.#runnerLostThreshold / 1000,
|
|
872
889
|
});
|
|
873
890
|
this.#runnerLostTimeout = setTimeout(() => {
|
|
874
|
-
|
|
891
|
+
try {
|
|
892
|
+
this.#handleLost();
|
|
893
|
+
} catch (err) {
|
|
894
|
+
this.log?.error({
|
|
895
|
+
msg: "error handling runner lost",
|
|
896
|
+
error: stringifyError(err),
|
|
897
|
+
});
|
|
898
|
+
}
|
|
875
899
|
}, this.#runnerLostThreshold);
|
|
876
900
|
}
|
|
877
901
|
|
|
@@ -909,12 +933,6 @@ export class Runner {
|
|
|
909
933
|
this.#config.onDisconnected(ev.code, ev.reason);
|
|
910
934
|
}
|
|
911
935
|
|
|
912
|
-
// Clear ping loop on close
|
|
913
|
-
if (this.#pingLoop) {
|
|
914
|
-
clearInterval(this.#pingLoop);
|
|
915
|
-
this.#pingLoop = undefined;
|
|
916
|
-
}
|
|
917
|
-
|
|
918
936
|
// Clear ack interval on close
|
|
919
937
|
if (this.#ackInterval) {
|
|
920
938
|
clearInterval(this.#ackInterval);
|
|
@@ -933,7 +951,14 @@ export class Runner {
|
|
|
933
951
|
seconds: this.#runnerLostThreshold / 1000,
|
|
934
952
|
});
|
|
935
953
|
this.#runnerLostTimeout = setTimeout(() => {
|
|
936
|
-
|
|
954
|
+
try {
|
|
955
|
+
this.#handleLost();
|
|
956
|
+
} catch (err) {
|
|
957
|
+
this.log?.error({
|
|
958
|
+
msg: "error handling runner lost",
|
|
959
|
+
error: stringifyError(err),
|
|
960
|
+
});
|
|
961
|
+
}
|
|
937
962
|
}, this.#runnerLostThreshold);
|
|
938
963
|
}
|
|
939
964
|
|
|
@@ -952,52 +977,88 @@ export class Runner {
|
|
|
952
977
|
for (const commandWrapper of commands) {
|
|
953
978
|
if (commandWrapper.inner.tag === "CommandStartActor") {
|
|
954
979
|
// Spawn background promise
|
|
955
|
-
this.#handleCommandStartActor(commandWrapper)
|
|
980
|
+
this.#handleCommandStartActor(commandWrapper).catch((err) => {
|
|
981
|
+
this.log?.error({
|
|
982
|
+
msg: "error handling start actor command",
|
|
983
|
+
actorId: commandWrapper.checkpoint.actorId,
|
|
984
|
+
error: stringifyError(err),
|
|
985
|
+
});
|
|
986
|
+
});
|
|
987
|
+
|
|
988
|
+
// NOTE: We don't do this for CommandStopActor because the actor will be removed by that call
|
|
989
|
+
// so we cant update the checkpoint
|
|
990
|
+
const actor = this.getActor(
|
|
991
|
+
commandWrapper.checkpoint.actorId,
|
|
992
|
+
commandWrapper.checkpoint.generation,
|
|
993
|
+
);
|
|
994
|
+
if (actor) actor.lastCommandIdx = commandWrapper.checkpoint.index;
|
|
956
995
|
} else if (commandWrapper.inner.tag === "CommandStopActor") {
|
|
957
996
|
// Spawn background promise
|
|
958
|
-
this.#handleCommandStopActor(commandWrapper)
|
|
997
|
+
this.#handleCommandStopActor(commandWrapper).catch((err) => {
|
|
998
|
+
this.log?.error({
|
|
999
|
+
msg: "error handling stop actor command",
|
|
1000
|
+
actorId: commandWrapper.checkpoint.actorId,
|
|
1001
|
+
error: stringifyError(err),
|
|
1002
|
+
});
|
|
1003
|
+
});
|
|
959
1004
|
} else {
|
|
960
1005
|
unreachable(commandWrapper.inner);
|
|
961
1006
|
}
|
|
962
|
-
|
|
963
|
-
this.#lastCommandIdx = Number(commandWrapper.index);
|
|
964
1007
|
}
|
|
965
1008
|
}
|
|
966
1009
|
|
|
967
1010
|
#handleAckEvents(ack: protocol.ToClientAckEvents) {
|
|
968
|
-
|
|
1011
|
+
let originalTotalEvents = Array.from(this.#actors).reduce(
|
|
1012
|
+
(s, [_, actor]) => s + actor.eventHistory.length,
|
|
1013
|
+
0,
|
|
1014
|
+
);
|
|
1015
|
+
|
|
1016
|
+
for (const [_, actor] of this.#actors) {
|
|
1017
|
+
let checkpoint = ack.lastEventCheckpoints.find(
|
|
1018
|
+
(x) => x.actorId == actor.actorId,
|
|
1019
|
+
);
|
|
969
1020
|
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
1021
|
+
if (checkpoint) actor.handleAckEvents(checkpoint.index);
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
const totalEvents = Array.from(this.#actors).reduce(
|
|
1025
|
+
(s, [_, actor]) => s + actor.eventHistory.length,
|
|
1026
|
+
0,
|
|
973
1027
|
);
|
|
1028
|
+
const prunedCount = originalTotalEvents - totalEvents;
|
|
974
1029
|
|
|
975
|
-
const prunedCount = originalLength - this.#eventHistory.length;
|
|
976
1030
|
if (prunedCount > 0) {
|
|
977
1031
|
this.log?.info({
|
|
978
1032
|
msg: "pruned acknowledged events",
|
|
979
|
-
lastAckedIdx: lastAckedIdx.toString(),
|
|
980
1033
|
prunedCount,
|
|
981
1034
|
});
|
|
982
1035
|
}
|
|
983
1036
|
|
|
984
|
-
if (
|
|
1037
|
+
if (totalEvents <= EVENT_BACKLOG_WARN_THRESHOLD) {
|
|
985
1038
|
this.#eventBacklogWarned = false;
|
|
986
1039
|
}
|
|
987
1040
|
}
|
|
988
1041
|
|
|
989
1042
|
/** Track events to send to the server in case we need to resend it on disconnect. */
|
|
990
1043
|
#recordEvent(eventWrapper: protocol.EventWrapper) {
|
|
991
|
-
this
|
|
1044
|
+
const actor = this.getActor(eventWrapper.checkpoint.actorId);
|
|
1045
|
+
if (!actor) return;
|
|
1046
|
+
|
|
1047
|
+
actor.recordEvent(eventWrapper);
|
|
1048
|
+
|
|
1049
|
+
let totalEvents = Array.from(this.#actors).reduce(
|
|
1050
|
+
(s, [_, actor]) => s + actor.eventHistory.length,
|
|
1051
|
+
0,
|
|
1052
|
+
);
|
|
992
1053
|
|
|
993
1054
|
if (
|
|
994
|
-
|
|
1055
|
+
totalEvents > EVENT_BACKLOG_WARN_THRESHOLD &&
|
|
995
1056
|
!this.#eventBacklogWarned
|
|
996
1057
|
) {
|
|
997
1058
|
this.#eventBacklogWarned = true;
|
|
998
1059
|
this.log?.warn({
|
|
999
1060
|
msg: "unacknowledged event backlog exceeds threshold",
|
|
1000
|
-
backlogSize:
|
|
1061
|
+
backlogSize: totalEvents,
|
|
1001
1062
|
threshold: EVENT_BACKLOG_WARN_THRESHOLD,
|
|
1002
1063
|
});
|
|
1003
1064
|
}
|
|
@@ -1013,8 +1074,8 @@ export class Runner {
|
|
|
1013
1074
|
const startCommand = commandWrapper.inner
|
|
1014
1075
|
.val as protocol.CommandStartActor;
|
|
1015
1076
|
|
|
1016
|
-
const actorId =
|
|
1017
|
-
const generation =
|
|
1077
|
+
const actorId = commandWrapper.checkpoint.actorId;
|
|
1078
|
+
const generation = commandWrapper.checkpoint.generation;
|
|
1018
1079
|
const config = startCommand.config;
|
|
1019
1080
|
|
|
1020
1081
|
const actorConfig: ActorConfig = {
|
|
@@ -1094,8 +1155,8 @@ export class Runner {
|
|
|
1094
1155
|
const stopCommand = commandWrapper.inner
|
|
1095
1156
|
.val as protocol.CommandStopActor;
|
|
1096
1157
|
|
|
1097
|
-
const actorId =
|
|
1098
|
-
const generation =
|
|
1158
|
+
const actorId = commandWrapper.checkpoint.actorId;
|
|
1159
|
+
const generation = commandWrapper.checkpoint.generation;
|
|
1099
1160
|
|
|
1100
1161
|
await this.forceStopActor(actorId, generation);
|
|
1101
1162
|
}
|
|
@@ -1105,6 +1166,9 @@ export class Runner {
|
|
|
1105
1166
|
generation: number,
|
|
1106
1167
|
intentType: "sleep" | "stop",
|
|
1107
1168
|
) {
|
|
1169
|
+
const actor = this.getActor(actorId, generation);
|
|
1170
|
+
if (!actor) return;
|
|
1171
|
+
|
|
1108
1172
|
let actorIntent: protocol.ActorIntent;
|
|
1109
1173
|
|
|
1110
1174
|
if (intentType === "sleep") {
|
|
@@ -1119,14 +1183,15 @@ export class Runner {
|
|
|
1119
1183
|
}
|
|
1120
1184
|
|
|
1121
1185
|
const intentEvent: protocol.EventActorIntent = {
|
|
1122
|
-
actorId,
|
|
1123
|
-
generation,
|
|
1124
1186
|
intent: actorIntent,
|
|
1125
1187
|
};
|
|
1126
1188
|
|
|
1127
|
-
const eventIndex = this.#nextEventIdx++;
|
|
1128
1189
|
const eventWrapper: protocol.EventWrapper = {
|
|
1129
|
-
|
|
1190
|
+
checkpoint: {
|
|
1191
|
+
actorId,
|
|
1192
|
+
generation,
|
|
1193
|
+
index: actor.nextEventIdx++,
|
|
1194
|
+
},
|
|
1130
1195
|
inner: {
|
|
1131
1196
|
tag: "EventActorIntent",
|
|
1132
1197
|
val: intentEvent,
|
|
@@ -1146,6 +1211,9 @@ export class Runner {
|
|
|
1146
1211
|
generation: number,
|
|
1147
1212
|
stateType: "running" | "stopped",
|
|
1148
1213
|
) {
|
|
1214
|
+
const actor = this.getActor(actorId, generation);
|
|
1215
|
+
if (!actor) return;
|
|
1216
|
+
|
|
1149
1217
|
let actorState: protocol.ActorState;
|
|
1150
1218
|
|
|
1151
1219
|
if (stateType === "running") {
|
|
@@ -1163,14 +1231,15 @@ export class Runner {
|
|
|
1163
1231
|
}
|
|
1164
1232
|
|
|
1165
1233
|
const stateUpdateEvent: protocol.EventActorStateUpdate = {
|
|
1166
|
-
actorId,
|
|
1167
|
-
generation,
|
|
1168
1234
|
state: actorState,
|
|
1169
1235
|
};
|
|
1170
1236
|
|
|
1171
|
-
const eventIndex = this.#nextEventIdx++;
|
|
1172
1237
|
const eventWrapper: protocol.EventWrapper = {
|
|
1173
|
-
|
|
1238
|
+
checkpoint: {
|
|
1239
|
+
actorId,
|
|
1240
|
+
generation,
|
|
1241
|
+
index: actor.nextEventIdx++,
|
|
1242
|
+
},
|
|
1174
1243
|
inner: {
|
|
1175
1244
|
tag: "EventActorStateUpdate",
|
|
1176
1245
|
val: stateUpdateEvent,
|
|
@@ -1186,9 +1255,19 @@ export class Runner {
|
|
|
1186
1255
|
}
|
|
1187
1256
|
|
|
1188
1257
|
#sendCommandAcknowledgment() {
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1258
|
+
const lastCommandCheckpoints = [];
|
|
1259
|
+
|
|
1260
|
+
for (const [_, actor] of this.#actors) {
|
|
1261
|
+
if (actor.lastCommandIdx < 0) {
|
|
1262
|
+
// No commands received yet, nothing to acknowledge
|
|
1263
|
+
continue;
|
|
1264
|
+
}
|
|
1265
|
+
|
|
1266
|
+
lastCommandCheckpoints.push({
|
|
1267
|
+
actorId: actor.actorId,
|
|
1268
|
+
generation: actor.generation,
|
|
1269
|
+
index: actor.lastCommandIdx,
|
|
1270
|
+
});
|
|
1192
1271
|
}
|
|
1193
1272
|
|
|
1194
1273
|
//this.#log?.log("Sending command acknowledgment", this.#lastCommandIdx);
|
|
@@ -1196,7 +1275,7 @@ export class Runner {
|
|
|
1196
1275
|
this.__sendToServer({
|
|
1197
1276
|
tag: "ToServerAckCommands",
|
|
1198
1277
|
val: {
|
|
1199
|
-
|
|
1278
|
+
lastCommandCheckpoints,
|
|
1200
1279
|
},
|
|
1201
1280
|
});
|
|
1202
1281
|
}
|
|
@@ -1495,14 +1574,15 @@ export class Runner {
|
|
|
1495
1574
|
if (!actor) return;
|
|
1496
1575
|
|
|
1497
1576
|
const alarmEvent: protocol.EventActorSetAlarm = {
|
|
1498
|
-
actorId,
|
|
1499
|
-
generation: actor.generation,
|
|
1500
1577
|
alarmTs: alarmTs !== null ? BigInt(alarmTs) : null,
|
|
1501
1578
|
};
|
|
1502
1579
|
|
|
1503
|
-
const eventIndex = this.#nextEventIdx++;
|
|
1504
1580
|
const eventWrapper: protocol.EventWrapper = {
|
|
1505
|
-
|
|
1581
|
+
checkpoint: {
|
|
1582
|
+
actorId,
|
|
1583
|
+
generation: actor.generation,
|
|
1584
|
+
index: actor.nextEventIdx++,
|
|
1585
|
+
},
|
|
1506
1586
|
inner: {
|
|
1507
1587
|
tag: "EventActorSetAlarm",
|
|
1508
1588
|
val: alarmEvent,
|
|
@@ -1669,6 +1749,7 @@ export class Runner {
|
|
|
1669
1749
|
tag: "ToServerlessServerInit",
|
|
1670
1750
|
val: {
|
|
1671
1751
|
runnerId: this.runnerId,
|
|
1752
|
+
runnerProtocolVersion: PROTOCOL_VERSION,
|
|
1672
1753
|
},
|
|
1673
1754
|
});
|
|
1674
1755
|
|
|
@@ -1699,27 +1780,34 @@ export class Runner {
|
|
|
1699
1780
|
msg: `Scheduling reconnect attempt ${this.#reconnectAttempt + 1} in ${delay}ms`,
|
|
1700
1781
|
});
|
|
1701
1782
|
|
|
1702
|
-
this.#reconnectTimeout = setTimeout(
|
|
1783
|
+
this.#reconnectTimeout = setTimeout(() => {
|
|
1703
1784
|
if (!this.#shutdown) {
|
|
1704
1785
|
this.#reconnectAttempt++;
|
|
1705
1786
|
this.log?.debug({
|
|
1706
1787
|
msg: `Attempting to reconnect (attempt ${this.#reconnectAttempt})...`,
|
|
1707
1788
|
});
|
|
1708
|
-
|
|
1789
|
+
this.#openPegboardWebSocket().catch((err) => {
|
|
1790
|
+
this.log?.error({
|
|
1791
|
+
msg: "error during websocket reconnection",
|
|
1792
|
+
error: stringifyError(err),
|
|
1793
|
+
});
|
|
1794
|
+
});
|
|
1709
1795
|
}
|
|
1710
1796
|
}, delay);
|
|
1711
1797
|
}
|
|
1712
1798
|
|
|
1713
|
-
#resendUnacknowledgedEvents(
|
|
1714
|
-
const eventsToResend =
|
|
1715
|
-
|
|
1716
|
-
)
|
|
1799
|
+
#resendUnacknowledgedEvents() {
|
|
1800
|
+
const eventsToResend = [];
|
|
1801
|
+
|
|
1802
|
+
for (const [_, actor] of this.#actors) {
|
|
1803
|
+
eventsToResend.push(...actor.eventHistory);
|
|
1804
|
+
}
|
|
1717
1805
|
|
|
1718
1806
|
if (eventsToResend.length === 0) return;
|
|
1719
1807
|
|
|
1720
1808
|
this.log?.info({
|
|
1721
1809
|
msg: "resending unacknowledged events",
|
|
1722
|
-
|
|
1810
|
+
count: eventsToResend.length,
|
|
1723
1811
|
});
|
|
1724
1812
|
|
|
1725
1813
|
// Resend events in batches
|