rivetkit 2.0.24-rc.1 → 2.0.25-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/schemas/actor-persist/v1.ts +6 -0
- package/dist/schemas/actor-persist/v2.ts +9 -3
- package/dist/schemas/actor-persist/v3.ts +280 -0
- package/dist/schemas/client-protocol/v1.ts +6 -0
- package/dist/schemas/client-protocol/v2.ts +438 -0
- package/dist/schemas/file-system-driver/v1.ts +6 -0
- package/dist/schemas/file-system-driver/v2.ts +142 -0
- package/dist/tsup/actor/errors.cjs +2 -4
- package/dist/tsup/actor/errors.cjs.map +1 -1
- package/dist/tsup/actor/errors.d.cts +7 -10
- package/dist/tsup/actor/errors.d.ts +7 -10
- package/dist/tsup/actor/errors.js +9 -11
- package/dist/tsup/{actor-router-consts-B3Lu87yJ.d.cts → actor-router-consts-DzI2szci.d.cts} +5 -9
- package/dist/tsup/{actor-router-consts-B3Lu87yJ.d.ts → actor-router-consts-DzI2szci.d.ts} +5 -9
- package/dist/tsup/{chunk-ZTH3KYFH.cjs → chunk-3FG5OJ3G.cjs} +3 -3
- package/dist/tsup/{chunk-ZTH3KYFH.cjs.map → chunk-3FG5OJ3G.cjs.map} +1 -1
- package/dist/tsup/{chunk-BLK27ES3.js → chunk-6JN6W6G3.js} +44 -56
- package/dist/tsup/chunk-6JN6W6G3.js.map +1 -0
- package/dist/tsup/chunk-7IBNNGQ2.js +514 -0
- package/dist/tsup/chunk-7IBNNGQ2.js.map +1 -0
- package/dist/tsup/{chunk-36JJ4IQB.cjs → chunk-AZATXPR4.cjs} +4 -8
- package/dist/tsup/chunk-AZATXPR4.cjs.map +1 -0
- package/dist/tsup/chunk-B7MENRD5.cjs +5694 -0
- package/dist/tsup/chunk-B7MENRD5.cjs.map +1 -0
- package/dist/tsup/{chunk-BOMZS2TJ.js → chunk-BBVFDEYD.js} +9 -9
- package/dist/tsup/chunk-BBVFDEYD.js.map +1 -0
- package/dist/tsup/{chunk-KSRXX3Z4.cjs → chunk-D6762AOA.cjs} +20 -25
- package/dist/tsup/chunk-D6762AOA.cjs.map +1 -0
- package/dist/tsup/{chunk-2JYPS5YM.cjs → chunk-E63WZNMR.cjs} +6 -6
- package/dist/tsup/chunk-E63WZNMR.cjs.map +1 -0
- package/dist/tsup/{chunk-YBG6R7LX.js → chunk-EDGN4OC7.js} +3 -7
- package/dist/tsup/chunk-EDGN4OC7.js.map +1 -0
- package/dist/tsup/{chunk-BYMKMOBS.js → chunk-FLOQ3UWM.js} +1844 -1681
- package/dist/tsup/chunk-FLOQ3UWM.js.map +1 -0
- package/dist/tsup/{chunk-7L65NNWP.cjs → chunk-H7GV5DIW.cjs} +187 -185
- package/dist/tsup/chunk-H7GV5DIW.cjs.map +1 -0
- package/dist/tsup/{chunk-227FEWMB.js → chunk-HZYZ7JSF.js} +3322 -2251
- package/dist/tsup/chunk-HZYZ7JSF.js.map +1 -0
- package/dist/tsup/{chunk-FX7TWFQR.js → chunk-IDJK7ILQ.js} +2 -6
- package/dist/tsup/chunk-IDJK7ILQ.js.map +1 -0
- package/dist/tsup/{chunk-VHGY7PU5.cjs → chunk-ILFXA4AL.cjs} +1900 -1737
- package/dist/tsup/chunk-ILFXA4AL.cjs.map +1 -0
- package/dist/tsup/chunk-MV6M3FDL.cjs +514 -0
- package/dist/tsup/chunk-MV6M3FDL.cjs.map +1 -0
- package/dist/tsup/{chunk-PLUN2NQT.js → chunk-NWBKMCWC.js} +189 -187
- package/dist/tsup/chunk-NWBKMCWC.js.map +1 -0
- package/dist/tsup/{chunk-CD33GT6Z.js → chunk-QIHBDXTO.js} +2 -2
- package/dist/tsup/{chunk-G64QUEDJ.js → chunk-W6RDS6NW.js} +23 -28
- package/dist/tsup/chunk-W6RDS6NW.js.map +1 -0
- package/dist/tsup/{chunk-INNFK746.cjs → chunk-WQU4M4ZC.cjs} +10 -14
- package/dist/tsup/chunk-WQU4M4ZC.cjs.map +1 -0
- package/dist/tsup/{chunk-SHVX2QUR.cjs → chunk-XKZA47XS.cjs} +17 -17
- package/dist/tsup/chunk-XKZA47XS.cjs.map +1 -0
- package/dist/tsup/{chunk-HHFKKVLR.cjs → chunk-YHWIOWVA.cjs} +45 -57
- package/dist/tsup/chunk-YHWIOWVA.cjs.map +1 -0
- package/dist/tsup/{chunk-YBHYXIP6.js → chunk-YVL6IRUM.js} +3 -3
- package/dist/tsup/chunk-YVL6IRUM.js.map +1 -0
- package/dist/tsup/client/mod.cjs +9 -9
- package/dist/tsup/client/mod.d.cts +5 -7
- package/dist/tsup/client/mod.d.ts +5 -7
- package/dist/tsup/client/mod.js +8 -8
- package/dist/tsup/common/log.cjs +3 -3
- package/dist/tsup/common/log.js +2 -2
- package/dist/tsup/common/websocket.cjs +4 -4
- package/dist/tsup/common/websocket.js +3 -3
- package/dist/tsup/{conn-B3Vhbgnd.d.ts → config-BRDYDraU.d.cts} +1119 -1047
- package/dist/tsup/{conn-DJWL3nGx.d.cts → config-Bo-blHpJ.d.ts} +1119 -1047
- package/dist/tsup/driver-helpers/mod.cjs +5 -13
- package/dist/tsup/driver-helpers/mod.cjs.map +1 -1
- package/dist/tsup/driver-helpers/mod.d.cts +11 -9
- package/dist/tsup/driver-helpers/mod.d.ts +11 -9
- package/dist/tsup/driver-helpers/mod.js +14 -22
- package/dist/tsup/driver-test-suite/mod.cjs +474 -303
- package/dist/tsup/driver-test-suite/mod.cjs.map +1 -1
- package/dist/tsup/driver-test-suite/mod.d.cts +6 -9
- package/dist/tsup/driver-test-suite/mod.d.ts +6 -9
- package/dist/tsup/driver-test-suite/mod.js +1085 -914
- package/dist/tsup/driver-test-suite/mod.js.map +1 -1
- package/dist/tsup/inspector/mod.cjs +6 -6
- package/dist/tsup/inspector/mod.d.cts +5 -7
- package/dist/tsup/inspector/mod.d.ts +5 -7
- package/dist/tsup/inspector/mod.js +5 -5
- package/dist/tsup/mod.cjs +10 -16
- package/dist/tsup/mod.cjs.map +1 -1
- package/dist/tsup/mod.d.cts +23 -25
- package/dist/tsup/mod.d.ts +23 -25
- package/dist/tsup/mod.js +17 -23
- package/dist/tsup/test/mod.cjs +11 -11
- package/dist/tsup/test/mod.d.cts +4 -6
- package/dist/tsup/test/mod.d.ts +4 -6
- package/dist/tsup/test/mod.js +10 -10
- package/dist/tsup/utils.cjs +3 -5
- package/dist/tsup/utils.cjs.map +1 -1
- package/dist/tsup/utils.d.cts +1 -2
- package/dist/tsup/utils.d.ts +1 -2
- package/dist/tsup/utils.js +2 -4
- package/package.json +13 -6
- package/src/actor/config.ts +56 -44
- package/src/actor/conn/driver.ts +61 -0
- package/src/actor/conn/drivers/http.ts +17 -0
- package/src/actor/conn/drivers/raw-request.ts +24 -0
- package/src/actor/conn/drivers/raw-websocket.ts +65 -0
- package/src/actor/conn/drivers/websocket.ts +129 -0
- package/src/actor/conn/mod.ts +232 -0
- package/src/actor/conn/persisted.ts +81 -0
- package/src/actor/conn/state-manager.ts +196 -0
- package/src/actor/contexts/action.ts +23 -0
- package/src/actor/{context.ts → contexts/actor.ts} +19 -8
- package/src/actor/contexts/conn-init.ts +31 -0
- package/src/actor/contexts/conn.ts +48 -0
- package/src/actor/contexts/create-conn-state.ts +13 -0
- package/src/actor/contexts/on-before-connect.ts +13 -0
- package/src/actor/contexts/on-connect.ts +22 -0
- package/src/actor/contexts/request.ts +48 -0
- package/src/actor/contexts/websocket.ts +48 -0
- package/src/actor/definition.ts +3 -3
- package/src/actor/driver.ts +36 -5
- package/src/actor/errors.ts +19 -24
- package/src/actor/instance/connection-manager.ts +465 -0
- package/src/actor/instance/event-manager.ts +292 -0
- package/src/actor/instance/kv.ts +15 -0
- package/src/actor/instance/mod.ts +1107 -0
- package/src/actor/instance/persisted.ts +67 -0
- package/src/actor/instance/schedule-manager.ts +349 -0
- package/src/actor/instance/state-manager.ts +502 -0
- package/src/actor/mod.ts +13 -16
- package/src/actor/protocol/old.ts +131 -43
- package/src/actor/protocol/serde.ts +19 -4
- package/src/actor/router-endpoints.ts +61 -586
- package/src/actor/router-websocket-endpoints.ts +408 -0
- package/src/actor/router.ts +63 -197
- package/src/actor/schedule.ts +1 -1
- package/src/client/actor-conn.ts +183 -249
- package/src/client/actor-handle.ts +29 -6
- package/src/client/client.ts +0 -4
- package/src/client/config.ts +1 -4
- package/src/client/mod.ts +0 -1
- package/src/client/raw-utils.ts +3 -3
- package/src/client/utils.ts +85 -39
- package/src/common/actor-router-consts.ts +5 -12
- package/src/common/{inline-websocket-adapter2.ts → inline-websocket-adapter.ts} +26 -48
- package/src/common/log.ts +1 -1
- package/src/common/router.ts +28 -17
- package/src/common/utils.ts +2 -0
- package/src/driver-helpers/mod.ts +7 -10
- package/src/driver-helpers/utils.ts +18 -9
- package/src/driver-test-suite/mod.ts +26 -50
- package/src/driver-test-suite/test-inline-client-driver.ts +27 -51
- package/src/driver-test-suite/tests/actor-conn-hibernation.ts +150 -0
- package/src/driver-test-suite/tests/actor-conn-state.ts +1 -4
- package/src/driver-test-suite/tests/actor-conn.ts +5 -9
- package/src/driver-test-suite/tests/actor-destroy.ts +294 -0
- package/src/driver-test-suite/tests/actor-driver.ts +0 -7
- package/src/driver-test-suite/tests/actor-handle.ts +12 -12
- package/src/driver-test-suite/tests/actor-metadata.ts +1 -1
- package/src/driver-test-suite/tests/manager-driver.ts +1 -1
- package/src/driver-test-suite/tests/raw-http-direct-registry.ts +8 -8
- package/src/driver-test-suite/tests/raw-http-request-properties.ts +6 -5
- package/src/driver-test-suite/tests/raw-http.ts +5 -5
- package/src/driver-test-suite/tests/raw-websocket-direct-registry.ts +7 -7
- package/src/driver-test-suite/tests/request-access.ts +4 -4
- package/src/driver-test-suite/utils.ts +6 -10
- package/src/drivers/engine/actor-driver.ts +614 -424
- package/src/drivers/engine/mod.ts +0 -1
- package/src/drivers/file-system/actor.ts +24 -12
- package/src/drivers/file-system/global-state.ts +427 -37
- package/src/drivers/file-system/manager.ts +71 -83
- package/src/drivers/file-system/mod.ts +3 -0
- package/src/drivers/file-system/utils.ts +18 -8
- package/src/engine-process/mod.ts +38 -38
- package/src/inspector/utils.ts +7 -5
- package/src/manager/driver.ts +11 -4
- package/src/manager/gateway.ts +4 -29
- package/src/manager/protocol/mod.ts +0 -2
- package/src/manager/protocol/query.ts +0 -4
- package/src/manager/router.ts +67 -64
- package/src/manager-api/actors.ts +13 -0
- package/src/mod.ts +1 -3
- package/src/registry/mod.ts +20 -20
- package/src/registry/serve.ts +9 -14
- package/src/remote-manager-driver/actor-websocket-client.ts +1 -16
- package/src/remote-manager-driver/api-endpoints.ts +13 -1
- package/src/remote-manager-driver/api-utils.ts +8 -0
- package/src/remote-manager-driver/metadata.ts +58 -0
- package/src/remote-manager-driver/mod.ts +47 -62
- package/src/remote-manager-driver/ws-proxy.ts +1 -1
- package/src/schemas/actor-persist/mod.ts +1 -1
- package/src/schemas/actor-persist/versioned.ts +56 -31
- package/src/schemas/client-protocol/mod.ts +1 -1
- package/src/schemas/client-protocol/versioned.ts +41 -21
- package/src/schemas/client-protocol-zod/mod.ts +103 -0
- package/src/schemas/file-system-driver/mod.ts +1 -1
- package/src/schemas/file-system-driver/versioned.ts +42 -19
- package/src/serde.ts +33 -11
- package/src/test/mod.ts +7 -3
- package/src/utils/node.ts +173 -0
- package/src/utils.ts +0 -4
- package/dist/tsup/chunk-227FEWMB.js.map +0 -1
- package/dist/tsup/chunk-2JYPS5YM.cjs.map +0 -1
- package/dist/tsup/chunk-36JJ4IQB.cjs.map +0 -1
- package/dist/tsup/chunk-7L65NNWP.cjs.map +0 -1
- package/dist/tsup/chunk-BLK27ES3.js.map +0 -1
- package/dist/tsup/chunk-BOMZS2TJ.js.map +0 -1
- package/dist/tsup/chunk-BYMKMOBS.js.map +0 -1
- package/dist/tsup/chunk-FX7TWFQR.js.map +0 -1
- package/dist/tsup/chunk-G64QUEDJ.js.map +0 -1
- package/dist/tsup/chunk-HHFKKVLR.cjs.map +0 -1
- package/dist/tsup/chunk-INNFK746.cjs.map +0 -1
- package/dist/tsup/chunk-KSRXX3Z4.cjs.map +0 -1
- package/dist/tsup/chunk-O44LFKSB.cjs +0 -4623
- package/dist/tsup/chunk-O44LFKSB.cjs.map +0 -1
- package/dist/tsup/chunk-PLUN2NQT.js.map +0 -1
- package/dist/tsup/chunk-S4UJG7ZE.js +0 -1119
- package/dist/tsup/chunk-S4UJG7ZE.js.map +0 -1
- package/dist/tsup/chunk-SHVX2QUR.cjs.map +0 -1
- package/dist/tsup/chunk-VFB23BYZ.cjs +0 -1119
- package/dist/tsup/chunk-VFB23BYZ.cjs.map +0 -1
- package/dist/tsup/chunk-VHGY7PU5.cjs.map +0 -1
- package/dist/tsup/chunk-YBG6R7LX.js.map +0 -1
- package/dist/tsup/chunk-YBHYXIP6.js.map +0 -1
- package/src/actor/action.ts +0 -178
- package/src/actor/conn-drivers.ts +0 -216
- package/src/actor/conn-socket.ts +0 -8
- package/src/actor/conn.ts +0 -272
- package/src/actor/instance.ts +0 -2336
- package/src/actor/persisted.ts +0 -49
- package/src/actor/unstable-react.ts +0 -110
- package/src/driver-test-suite/tests/actor-reconnect.ts +0 -170
- package/src/drivers/engine/kv.ts +0 -3
- package/src/manager/hono-websocket-adapter.ts +0 -393
- /package/dist/tsup/{chunk-CD33GT6Z.js.map → chunk-QIHBDXTO.js.map} +0 -0
|
@@ -1,65 +1,74 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
ActorConfig as EngineActorConfig,
|
|
3
3
|
RunnerConfig as EngineRunnerConfig,
|
|
4
|
-
|
|
4
|
+
HibernatingWebSocketMetadata,
|
|
5
5
|
} from "@rivetkit/engine-runner";
|
|
6
|
-
import { Runner } from "@rivetkit/engine-runner";
|
|
6
|
+
import { idToStr, Runner } from "@rivetkit/engine-runner";
|
|
7
7
|
import * as cbor from "cbor-x";
|
|
8
8
|
import type { Context as HonoContext } from "hono";
|
|
9
9
|
import { streamSSE } from "hono/streaming";
|
|
10
|
-
import { WSContext } from "hono/ws";
|
|
10
|
+
import { WSContext, type WSContextInit } from "hono/ws";
|
|
11
11
|
import invariant from "invariant";
|
|
12
|
+
import { type AnyConn, CONN_STATE_MANAGER_SYMBOL } from "@/actor/conn/mod";
|
|
12
13
|
import { lookupInRegistry } from "@/actor/definition";
|
|
13
|
-
import {
|
|
14
|
+
import { KEYS } from "@/actor/instance/kv";
|
|
14
15
|
import { deserializeActorKey } from "@/actor/keys";
|
|
15
|
-
import {
|
|
16
|
+
import { getValueLength } from "@/actor/protocol/old";
|
|
16
17
|
import { type ActorRouter, createActorRouter } from "@/actor/router";
|
|
17
18
|
import {
|
|
18
|
-
|
|
19
|
-
|
|
19
|
+
parseWebSocketProtocols,
|
|
20
|
+
routeWebSocket,
|
|
20
21
|
truncateRawWebSocketPathPrefix,
|
|
21
|
-
|
|
22
|
+
type UpgradeWebSocketArgs,
|
|
23
|
+
} from "@/actor/router-websocket-endpoints";
|
|
22
24
|
import type { Client } from "@/client/client";
|
|
23
25
|
import {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
WS_PROTOCOL_TOKEN,
|
|
26
|
+
PATH_CONNECT,
|
|
27
|
+
PATH_INSPECTOR_CONNECT,
|
|
28
|
+
PATH_WEBSOCKET_BASE,
|
|
29
|
+
PATH_WEBSOCKET_PREFIX,
|
|
29
30
|
} from "@/common/actor-router-consts";
|
|
30
|
-
import type { UpgradeWebSocketArgs } from "@/common/inline-websocket-adapter2";
|
|
31
31
|
import { getLogger } from "@/common/log";
|
|
32
32
|
import type {
|
|
33
|
-
RivetEvent,
|
|
34
33
|
RivetMessageEvent,
|
|
35
34
|
UniversalWebSocket,
|
|
36
35
|
} from "@/common/websocket-interface";
|
|
37
36
|
import {
|
|
38
37
|
type ActorDriver,
|
|
39
38
|
type AnyActorInstance,
|
|
39
|
+
getInitialActorKvState,
|
|
40
40
|
type ManagerDriver,
|
|
41
|
-
serializeEmptyPersistData,
|
|
42
41
|
} from "@/driver-helpers/mod";
|
|
43
42
|
import { buildActorNames, type RegistryConfig } from "@/registry/config";
|
|
44
43
|
import type { RunnerConfig } from "@/registry/run-config";
|
|
45
44
|
import { getEndpoint } from "@/remote-manager-driver/api-utils";
|
|
46
45
|
import {
|
|
47
|
-
arrayBuffersEqual,
|
|
48
|
-
idToStr,
|
|
49
46
|
type LongTimeoutHandle,
|
|
50
47
|
promiseWithResolvers,
|
|
51
48
|
setLongTimeout,
|
|
52
49
|
stringifyError,
|
|
53
50
|
} from "@/utils";
|
|
54
|
-
import { KEYS } from "./kv";
|
|
55
51
|
import { logger } from "./log";
|
|
56
52
|
|
|
57
53
|
const RUNNER_SSE_PING_INTERVAL = 1000;
|
|
58
54
|
|
|
55
|
+
// Message ack deadline is 30s on the gateway, but we will ack more frequently
|
|
56
|
+
// in order to minimize the message buffer size on the gateway and to give
|
|
57
|
+
// generous breathing room for the timeout.
|
|
58
|
+
//
|
|
59
|
+
// See engine/packages/pegboard-gateway/src/shared_state.rs
|
|
60
|
+
// (HWS_MESSAGE_ACK_TIMEOUT)
|
|
61
|
+
const CONN_MESSAGE_ACK_DEADLINE = 5_000;
|
|
62
|
+
|
|
63
|
+
// Force saveState when cumulative message size reaches this threshold (0.5 MB)
|
|
64
|
+
//
|
|
65
|
+
// See engine/packages/pegboard-gateway/src/shared_state.rs
|
|
66
|
+
// (HWS_MAX_PENDING_MSGS_SIZE_PER_REQ)
|
|
67
|
+
const CONN_BUFFERED_MESSAGE_SIZE_THRESHOLD = 500_000;
|
|
68
|
+
|
|
59
69
|
interface ActorHandler {
|
|
60
70
|
actor?: AnyActorInstance;
|
|
61
71
|
actorStartPromise?: ReturnType<typeof promiseWithResolvers<void>>;
|
|
62
|
-
persistedData?: Uint8Array;
|
|
63
72
|
}
|
|
64
73
|
|
|
65
74
|
export type DriverContext = {};
|
|
@@ -79,12 +88,32 @@ export class EngineActorDriver implements ActorDriver {
|
|
|
79
88
|
#runnerStopped: PromiseWithResolvers<undefined> = promiseWithResolvers();
|
|
80
89
|
#isRunnerStopped: boolean = false;
|
|
81
90
|
|
|
82
|
-
//
|
|
83
|
-
|
|
91
|
+
// HACK: Track actor stop intent locally since the runner protocol doesn't
|
|
92
|
+
// pass the stop reason to onActorStop. This will be fixed when the runner
|
|
93
|
+
// protocol is updated to send the intent directly (see RVT-5284)
|
|
94
|
+
#actorStopIntent: Map<string, "sleep" | "destroy"> = new Map();
|
|
95
|
+
|
|
96
|
+
// Map of conn IDs to message index waiting to be persisted before sending
|
|
97
|
+
// an ack
|
|
98
|
+
//
|
|
99
|
+
// serverMessageIndex is updated and pendingAck is flagged in needed in
|
|
100
|
+
// onBeforePersistConnect, then the HWS ack message is sent in
|
|
101
|
+
// onAfterPersistConn. This allows us to track what's about to be written
|
|
102
|
+
// to storage to prevent race conditions with the serverMessageIndex being
|
|
103
|
+
// updated while writing the existing state.
|
|
104
|
+
//
|
|
105
|
+
// bufferedMessageSize tracks the total bytes received since last persist
|
|
106
|
+
// to force a saveState when threshold is reached. This is the amount of
|
|
107
|
+
// data currently buffered on the gateway.
|
|
108
|
+
#hwsMessageIndex = new Map<
|
|
84
109
|
string,
|
|
85
|
-
{
|
|
86
|
-
|
|
87
|
-
|
|
110
|
+
{
|
|
111
|
+
serverMessageIndex: number;
|
|
112
|
+
bufferedMessageSize: number;
|
|
113
|
+
pendingAckFromMessageIndex: boolean;
|
|
114
|
+
pendingAckFromBufferSize: boolean;
|
|
115
|
+
}
|
|
116
|
+
>();
|
|
88
117
|
|
|
89
118
|
constructor(
|
|
90
119
|
registryConfig: RegistryConfig,
|
|
@@ -133,159 +162,12 @@ export class EngineActorDriver implements ActorDriver {
|
|
|
133
162
|
},
|
|
134
163
|
fetch: this.#runnerFetch.bind(this),
|
|
135
164
|
websocket: this.#runnerWebSocket.bind(this),
|
|
165
|
+
hibernatableWebSocket: {
|
|
166
|
+
canHibernate: this.#hwsCanHibernate.bind(this),
|
|
167
|
+
},
|
|
136
168
|
onActorStart: this.#runnerOnActorStart.bind(this),
|
|
137
169
|
onActorStop: this.#runnerOnActorStop.bind(this),
|
|
138
170
|
logger: getLogger("engine-runner"),
|
|
139
|
-
getActorHibernationConfig: (
|
|
140
|
-
actorId: string,
|
|
141
|
-
requestId: ArrayBuffer,
|
|
142
|
-
request: Request,
|
|
143
|
-
): HibernationConfig => {
|
|
144
|
-
const url = new URL(request.url);
|
|
145
|
-
const path = url.pathname;
|
|
146
|
-
|
|
147
|
-
// Get actor instance from runner to access actor name
|
|
148
|
-
const actorInstance = this.#runner.getActor(actorId);
|
|
149
|
-
if (!actorInstance) {
|
|
150
|
-
logger().warn({
|
|
151
|
-
msg: "actor not found in getActorHibernationConfig",
|
|
152
|
-
actorId,
|
|
153
|
-
});
|
|
154
|
-
return { enabled: false, lastMsgIndex: undefined };
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
// Load actor handler to access persisted data
|
|
158
|
-
const handler = this.#actors.get(actorId);
|
|
159
|
-
if (!handler) {
|
|
160
|
-
logger().warn({
|
|
161
|
-
msg: "actor handler not found in getActorHibernationConfig",
|
|
162
|
-
actorId,
|
|
163
|
-
});
|
|
164
|
-
return { enabled: false, lastMsgIndex: undefined };
|
|
165
|
-
}
|
|
166
|
-
if (!handler.actor) {
|
|
167
|
-
logger().warn({
|
|
168
|
-
msg: "actor not found in getActorHibernationConfig",
|
|
169
|
-
actorId,
|
|
170
|
-
});
|
|
171
|
-
return { enabled: false, lastMsgIndex: undefined };
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
// Check for existing WS
|
|
175
|
-
const hibernatableArray =
|
|
176
|
-
handler.actor[PERSIST_SYMBOL].hibernatableWebSocket;
|
|
177
|
-
logger().debug({
|
|
178
|
-
msg: "checking hibernatable websockets",
|
|
179
|
-
requestId: idToStr(requestId),
|
|
180
|
-
existingHibernatableWebSockets: hibernatableArray.length,
|
|
181
|
-
});
|
|
182
|
-
const existingWs = hibernatableArray.find((ws) =>
|
|
183
|
-
arrayBuffersEqual(ws.requestId, requestId),
|
|
184
|
-
);
|
|
185
|
-
|
|
186
|
-
// Determine configuration for new WS
|
|
187
|
-
let hibernationConfig: HibernationConfig;
|
|
188
|
-
if (existingWs) {
|
|
189
|
-
logger().debug({
|
|
190
|
-
msg: "found existing hibernatable websocket",
|
|
191
|
-
requestId: idToStr(requestId),
|
|
192
|
-
lastMsgIndex: existingWs.msgIndex,
|
|
193
|
-
});
|
|
194
|
-
hibernationConfig = {
|
|
195
|
-
enabled: true,
|
|
196
|
-
lastMsgIndex: Number(existingWs.msgIndex),
|
|
197
|
-
};
|
|
198
|
-
} else {
|
|
199
|
-
logger().debug({
|
|
200
|
-
msg: "no existing hibernatable websocket found",
|
|
201
|
-
requestId: idToStr(requestId),
|
|
202
|
-
});
|
|
203
|
-
if (path === PATH_CONNECT_WEBSOCKET) {
|
|
204
|
-
hibernationConfig = {
|
|
205
|
-
enabled: true,
|
|
206
|
-
lastMsgIndex: undefined,
|
|
207
|
-
};
|
|
208
|
-
} else if (path.startsWith(PATH_RAW_WEBSOCKET_PREFIX)) {
|
|
209
|
-
// Find actor config
|
|
210
|
-
const definition = lookupInRegistry(
|
|
211
|
-
this.#registryConfig,
|
|
212
|
-
actorInstance.config.name,
|
|
213
|
-
);
|
|
214
|
-
|
|
215
|
-
// Check if can hibernate
|
|
216
|
-
const canHibernatWebSocket =
|
|
217
|
-
definition.config.options?.canHibernatWebSocket;
|
|
218
|
-
if (canHibernatWebSocket === true) {
|
|
219
|
-
hibernationConfig = {
|
|
220
|
-
enabled: true,
|
|
221
|
-
lastMsgIndex: undefined,
|
|
222
|
-
};
|
|
223
|
-
} else if (typeof canHibernatWebSocket === "function") {
|
|
224
|
-
try {
|
|
225
|
-
// Truncate the path to match the behavior on onRawWebSocket
|
|
226
|
-
const newPath = truncateRawWebSocketPathPrefix(
|
|
227
|
-
url.pathname,
|
|
228
|
-
);
|
|
229
|
-
const truncatedRequest = new Request(
|
|
230
|
-
`http://actor${newPath}`,
|
|
231
|
-
request,
|
|
232
|
-
);
|
|
233
|
-
|
|
234
|
-
const canHibernate =
|
|
235
|
-
canHibernatWebSocket(truncatedRequest);
|
|
236
|
-
hibernationConfig = {
|
|
237
|
-
enabled: canHibernate,
|
|
238
|
-
lastMsgIndex: undefined,
|
|
239
|
-
};
|
|
240
|
-
} catch (error) {
|
|
241
|
-
logger().error({
|
|
242
|
-
msg: "error calling canHibernatWebSocket",
|
|
243
|
-
error,
|
|
244
|
-
});
|
|
245
|
-
hibernationConfig = {
|
|
246
|
-
enabled: false,
|
|
247
|
-
lastMsgIndex: undefined,
|
|
248
|
-
};
|
|
249
|
-
}
|
|
250
|
-
} else {
|
|
251
|
-
hibernationConfig = {
|
|
252
|
-
enabled: false,
|
|
253
|
-
lastMsgIndex: undefined,
|
|
254
|
-
};
|
|
255
|
-
}
|
|
256
|
-
} else {
|
|
257
|
-
logger().warn({
|
|
258
|
-
msg: "unexpected path for getActorHibernationConfig",
|
|
259
|
-
path,
|
|
260
|
-
});
|
|
261
|
-
hibernationConfig = {
|
|
262
|
-
enabled: false,
|
|
263
|
-
lastMsgIndex: undefined,
|
|
264
|
-
};
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
// Save or update hibernatable WebSocket
|
|
269
|
-
if (existingWs) {
|
|
270
|
-
logger().debug({
|
|
271
|
-
msg: "updated existing hibernatable websocket timestamp",
|
|
272
|
-
requestId: idToStr(requestId),
|
|
273
|
-
});
|
|
274
|
-
existingWs.lastSeenTimestamp = BigInt(Date.now());
|
|
275
|
-
} else {
|
|
276
|
-
logger().debug({
|
|
277
|
-
msg: "created new hibernatable websocket entry",
|
|
278
|
-
requestId: idToStr(requestId),
|
|
279
|
-
});
|
|
280
|
-
handler.actor[PERSIST_SYMBOL].hibernatableWebSocket.push({
|
|
281
|
-
requestId,
|
|
282
|
-
lastSeenTimestamp: BigInt(Date.now()),
|
|
283
|
-
msgIndex: -1n,
|
|
284
|
-
});
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
return hibernationConfig;
|
|
288
|
-
},
|
|
289
171
|
};
|
|
290
172
|
|
|
291
173
|
// Create and start runner
|
|
@@ -297,15 +179,10 @@ export class EngineActorDriver implements ActorDriver {
|
|
|
297
179
|
namespace: runConfig.namespace,
|
|
298
180
|
runnerName: runConfig.runnerName,
|
|
299
181
|
});
|
|
182
|
+
}
|
|
300
183
|
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
// Decreasing this reduces the amount of buffered messages on the
|
|
304
|
-
// gateway
|
|
305
|
-
//
|
|
306
|
-
// Gateway timeout configured to 30s
|
|
307
|
-
// https://github.com/rivet-dev/rivet/blob/222dae87e3efccaffa2b503de40ecf8afd4e31eb/engine/packages/pegboard-gateway/src/shared_state.rs#L17
|
|
308
|
-
this.#wsAckFlushInterval = setInterval(() => this.#flushWsAcks(), 1000);
|
|
184
|
+
getExtraActorLogParams(): Record<string, string> {
|
|
185
|
+
return { runnerId: this.#runner.runnerId ?? "-" };
|
|
309
186
|
}
|
|
310
187
|
|
|
311
188
|
async #loadActorHandler(actorId: string): Promise<ActorHandler> {
|
|
@@ -318,52 +195,10 @@ export class EngineActorDriver implements ActorDriver {
|
|
|
318
195
|
return handler;
|
|
319
196
|
}
|
|
320
197
|
|
|
321
|
-
async loadActor(actorId: string): Promise<AnyActorInstance> {
|
|
322
|
-
const handler = await this.#loadActorHandler(actorId);
|
|
323
|
-
if (!handler.actor) throw new Error(`Actor ${actorId} failed to load`);
|
|
324
|
-
return handler.actor;
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
#flushWsAcks(): void {
|
|
328
|
-
if (this.#wsAckQueue.size === 0) return;
|
|
329
|
-
|
|
330
|
-
for (const {
|
|
331
|
-
requestIdBuf: requestId,
|
|
332
|
-
messageIndex: index,
|
|
333
|
-
} of this.#wsAckQueue.values()) {
|
|
334
|
-
this.#runner.sendWebsocketMessageAck(requestId, index);
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
this.#wsAckQueue.clear();
|
|
338
|
-
}
|
|
339
|
-
|
|
340
198
|
getContext(actorId: string): DriverContext {
|
|
341
199
|
return {};
|
|
342
200
|
}
|
|
343
201
|
|
|
344
|
-
async readPersistedData(actorId: string): Promise<Uint8Array | undefined> {
|
|
345
|
-
const handler = this.#actors.get(actorId);
|
|
346
|
-
if (!handler) throw new Error(`Actor ${actorId} not loaded`);
|
|
347
|
-
|
|
348
|
-
// This was loaded during actor startup
|
|
349
|
-
return handler.persistedData;
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
async writePersistedData(actorId: string, data: Uint8Array): Promise<void> {
|
|
353
|
-
const handler = this.#actors.get(actorId);
|
|
354
|
-
if (!handler) throw new Error(`Actor ${actorId} not loaded`);
|
|
355
|
-
|
|
356
|
-
handler.persistedData = data;
|
|
357
|
-
|
|
358
|
-
logger().debug({
|
|
359
|
-
msg: "writing persisted data for actor",
|
|
360
|
-
actorId,
|
|
361
|
-
dataSize: data.byteLength,
|
|
362
|
-
});
|
|
363
|
-
|
|
364
|
-
await this.#runner.kvPut(actorId, [[KEYS.PERSIST_DATA, data]]);
|
|
365
|
-
}
|
|
366
|
-
|
|
367
202
|
async setAlarm(actor: AnyActorInstance, timestamp: number): Promise<void> {
|
|
368
203
|
// Clear prev timeout
|
|
369
204
|
if (this.#alarmTimeout) {
|
|
@@ -374,7 +209,7 @@ export class EngineActorDriver implements ActorDriver {
|
|
|
374
209
|
// Set alarm
|
|
375
210
|
const delay = Math.max(0, timestamp - Date.now());
|
|
376
211
|
this.#alarmTimeout = setLongTimeout(() => {
|
|
377
|
-
actor.
|
|
212
|
+
actor.onAlarm();
|
|
378
213
|
this.#alarmTimeout = undefined;
|
|
379
214
|
}, delay);
|
|
380
215
|
|
|
@@ -385,7 +220,7 @@ export class EngineActorDriver implements ActorDriver {
|
|
|
385
220
|
// Instead, it just wakes the actor on the alarm (if not
|
|
386
221
|
// already awake).
|
|
387
222
|
//
|
|
388
|
-
//
|
|
223
|
+
// onAlarm is automatically called on `ActorInstance.start` when waking
|
|
389
224
|
// again.
|
|
390
225
|
this.#runner.setAlarm(actor.id, timestamp);
|
|
391
226
|
}
|
|
@@ -394,7 +229,172 @@ export class EngineActorDriver implements ActorDriver {
|
|
|
394
229
|
return undefined;
|
|
395
230
|
}
|
|
396
231
|
|
|
397
|
-
//
|
|
232
|
+
// MARK: - Batch KV operations
|
|
233
|
+
async kvBatchPut(
|
|
234
|
+
actorId: string,
|
|
235
|
+
entries: [Uint8Array, Uint8Array][],
|
|
236
|
+
): Promise<void> {
|
|
237
|
+
await this.#runner.kvPut(actorId, entries);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
async kvBatchGet(
|
|
241
|
+
actorId: string,
|
|
242
|
+
keys: Uint8Array[],
|
|
243
|
+
): Promise<(Uint8Array | null)[]> {
|
|
244
|
+
return await this.#runner.kvGet(actorId, keys);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
async kvBatchDelete(actorId: string, keys: Uint8Array[]): Promise<void> {
|
|
248
|
+
await this.#runner.kvDelete(actorId, keys);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
async kvList(actorId: string): Promise<Uint8Array[]> {
|
|
252
|
+
const entries = await this.#runner.kvListPrefix(
|
|
253
|
+
actorId,
|
|
254
|
+
new Uint8Array(),
|
|
255
|
+
);
|
|
256
|
+
const keys = entries.map(([key]) => key);
|
|
257
|
+
logger().info({
|
|
258
|
+
msg: "kvList called",
|
|
259
|
+
actorId,
|
|
260
|
+
keysCount: keys.length,
|
|
261
|
+
keys: keys.map((k) => new TextDecoder().decode(k)),
|
|
262
|
+
});
|
|
263
|
+
return keys;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
async kvListPrefix(
|
|
267
|
+
actorId: string,
|
|
268
|
+
prefix: Uint8Array,
|
|
269
|
+
): Promise<[Uint8Array, Uint8Array][]> {
|
|
270
|
+
const result = await this.#runner.kvListPrefix(actorId, prefix);
|
|
271
|
+
logger().info({
|
|
272
|
+
msg: "kvListPrefix called",
|
|
273
|
+
actorId,
|
|
274
|
+
prefixStr: new TextDecoder().decode(prefix),
|
|
275
|
+
entriesCount: result.length,
|
|
276
|
+
keys: result.map(([key]) => new TextDecoder().decode(key)),
|
|
277
|
+
});
|
|
278
|
+
return result;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// MARK: - Actor Lifecycle
|
|
282
|
+
async loadActor(actorId: string): Promise<AnyActorInstance> {
|
|
283
|
+
const handler = await this.#loadActorHandler(actorId);
|
|
284
|
+
if (!handler.actor) throw new Error(`Actor ${actorId} failed to load`);
|
|
285
|
+
return handler.actor;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
startSleep(actorId: string) {
|
|
289
|
+
// HACK: Track intent for onActorStop (see RVT-5284)
|
|
290
|
+
this.#actorStopIntent.set(actorId, "sleep");
|
|
291
|
+
this.#runner.sleepActor(actorId);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
startDestroy(actorId: string) {
|
|
295
|
+
// HACK: Track intent for onActorStop (see RVT-5284)
|
|
296
|
+
this.#actorStopIntent.set(actorId, "destroy");
|
|
297
|
+
this.#runner.stopActor(actorId);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
async shutdownRunner(immediate: boolean): Promise<void> {
|
|
301
|
+
logger().info({ msg: "stopping engine actor driver", immediate });
|
|
302
|
+
|
|
303
|
+
// TODO: We need to update the runner to have a draining state so:
|
|
304
|
+
// 1. Send ToServerDraining
|
|
305
|
+
// - This causes Pegboard to stop allocating actors to this runner
|
|
306
|
+
// 2. Pegboard sends ToClientStopActor for all actors on this runner which handles the graceful migration of each actor independently
|
|
307
|
+
// 3. Send ToServerStopping once all actors have successfully stopped
|
|
308
|
+
//
|
|
309
|
+
// What's happening right now is:
|
|
310
|
+
// 1. All actors enter stopped state
|
|
311
|
+
// 2. Actors still respond to requests because only RivetKit knows it's
|
|
312
|
+
// stopping, this causes all requests to issue errors that the actor is
|
|
313
|
+
// stopping. (This will NOT return a 503 bc the runner has no idea the
|
|
314
|
+
// actors are stopping.)
|
|
315
|
+
// 3. Once the last actor stops, then the runner finally stops + actors
|
|
316
|
+
// reschedule
|
|
317
|
+
//
|
|
318
|
+
// This means that:
|
|
319
|
+
// - All actors on this runner are bricked until the slowest onStop finishes
|
|
320
|
+
// - Guard will not gracefully handle requests bc it's not receiving a 503
|
|
321
|
+
// - Actors can still be scheduled to this runner while the other
|
|
322
|
+
// actors are stopping, meaning that those actors will NOT get onStop
|
|
323
|
+
// and will potentiall corrupt their state
|
|
324
|
+
//
|
|
325
|
+
// HACK: Stop all actors to allow state to be saved
|
|
326
|
+
// NOTE: onStop is only supposed to be called by the runner, we're
|
|
327
|
+
// abusing it here
|
|
328
|
+
logger().debug({
|
|
329
|
+
msg: "stopping all actors before shutdown",
|
|
330
|
+
actorCount: this.#actors.size,
|
|
331
|
+
});
|
|
332
|
+
const stopPromises: Promise<void>[] = [];
|
|
333
|
+
for (const [_actorId, handler] of this.#actors.entries()) {
|
|
334
|
+
if (handler.actor) {
|
|
335
|
+
stopPromises.push(
|
|
336
|
+
handler.actor.onStop("sleep").catch((err) => {
|
|
337
|
+
handler.actor?.rLog.error({
|
|
338
|
+
msg: "onStop errored",
|
|
339
|
+
error: stringifyError(err),
|
|
340
|
+
});
|
|
341
|
+
}),
|
|
342
|
+
);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
await Promise.all(stopPromises);
|
|
346
|
+
logger().debug({ msg: "all actors stopped" });
|
|
347
|
+
|
|
348
|
+
await this.#runner.shutdown(immediate);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
async serverlessHandleStart(c: HonoContext): Promise<Response> {
|
|
352
|
+
return streamSSE(c, async (stream) => {
|
|
353
|
+
// NOTE: onAbort does not work reliably
|
|
354
|
+
stream.onAbort(() => {});
|
|
355
|
+
c.req.raw.signal.addEventListener("abort", () => {
|
|
356
|
+
logger().debug("SSE aborted, shutting down runner");
|
|
357
|
+
|
|
358
|
+
// We cannot assume that the request will always be closed gracefully by Rivet. We always proceed with a graceful shutdown in case the request was terminated for any other reason.
|
|
359
|
+
//
|
|
360
|
+
// If we did not use a graceful shutdown, the runner would
|
|
361
|
+
this.shutdownRunner(false);
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
await this.#runnerStarted.promise;
|
|
365
|
+
|
|
366
|
+
// Runner id should be set if the runner started
|
|
367
|
+
const payload = this.#runner.getServerlessInitPacket();
|
|
368
|
+
invariant(payload, "runnerId not set");
|
|
369
|
+
await stream.writeSSE({ data: payload });
|
|
370
|
+
|
|
371
|
+
// Send ping every second to keep the connection alive
|
|
372
|
+
while (true) {
|
|
373
|
+
if (this.#isRunnerStopped) {
|
|
374
|
+
logger().debug({
|
|
375
|
+
msg: "runner is stopped",
|
|
376
|
+
});
|
|
377
|
+
break;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
if (stream.closed || stream.aborted) {
|
|
381
|
+
logger().debug({
|
|
382
|
+
msg: "runner sse stream closed",
|
|
383
|
+
closed: stream.closed,
|
|
384
|
+
aborted: stream.aborted,
|
|
385
|
+
});
|
|
386
|
+
break;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
await stream.writeSSE({ event: "ping", data: "" });
|
|
390
|
+
await stream.sleep(RUNNER_SSE_PING_INTERVAL);
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
// Wait for the runner to stop if the SSE stream aborted early for any reason
|
|
394
|
+
await this.#runnerStopped.promise;
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
|
|
398
398
|
async #runnerOnActorStart(
|
|
399
399
|
actorId: string,
|
|
400
400
|
generation: number,
|
|
@@ -422,32 +422,33 @@ export class EngineActorDriver implements ActorDriver {
|
|
|
422
422
|
// create the same handler simultaneously.
|
|
423
423
|
handler = {
|
|
424
424
|
actorStartPromise: promiseWithResolvers(),
|
|
425
|
-
persistedData: undefined,
|
|
426
425
|
};
|
|
427
426
|
this.#actors.set(actorId, handler);
|
|
427
|
+
}
|
|
428
428
|
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
]);
|
|
433
|
-
|
|
434
|
-
handler.persistedData =
|
|
435
|
-
persistedValue !== null
|
|
436
|
-
? persistedValue
|
|
437
|
-
: serializeEmptyPersistData(input);
|
|
429
|
+
const name = actorConfig.name as string;
|
|
430
|
+
invariant(actorConfig.key, "actor should have a key");
|
|
431
|
+
const key = deserializeActorKey(actorConfig.key);
|
|
438
432
|
|
|
433
|
+
// Initialize storage
|
|
434
|
+
const [persistDataBuffer] = await this.#runner.kvGet(actorId, [
|
|
435
|
+
KEYS.PERSIST_DATA,
|
|
436
|
+
]);
|
|
437
|
+
if (persistDataBuffer === null) {
|
|
438
|
+
const initialKvState = getInitialActorKvState(input);
|
|
439
|
+
await this.#runner.kvPut(actorId, initialKvState);
|
|
440
|
+
logger().debug({
|
|
441
|
+
msg: "initialized persist data for new actor",
|
|
442
|
+
actorId,
|
|
443
|
+
});
|
|
444
|
+
} else {
|
|
439
445
|
logger().debug({
|
|
440
|
-
msg: "
|
|
446
|
+
msg: "found existing persist data for actor",
|
|
441
447
|
actorId,
|
|
442
|
-
dataSize:
|
|
443
|
-
wasInStorage: persistedValue !== null,
|
|
448
|
+
dataSize: persistDataBuffer.byteLength,
|
|
444
449
|
});
|
|
445
450
|
}
|
|
446
451
|
|
|
447
|
-
const name = actorConfig.name as string;
|
|
448
|
-
invariant(actorConfig.key, "actor should have a key");
|
|
449
|
-
const key = deserializeActorKey(actorConfig.key);
|
|
450
|
-
|
|
451
452
|
// Create actor instance
|
|
452
453
|
const definition = lookupInRegistry(
|
|
453
454
|
this.#registryConfig,
|
|
@@ -465,10 +466,6 @@ export class EngineActorDriver implements ActorDriver {
|
|
|
465
466
|
"unknown", // TODO: Add regions
|
|
466
467
|
);
|
|
467
468
|
|
|
468
|
-
// Resolve promise if waiting
|
|
469
|
-
handler.actorStartPromise?.resolve();
|
|
470
|
-
handler.actorStartPromise = undefined;
|
|
471
|
-
|
|
472
469
|
logger().debug({ msg: "runner actor started", actorId, name, key });
|
|
473
470
|
}
|
|
474
471
|
|
|
@@ -478,25 +475,37 @@ export class EngineActorDriver implements ActorDriver {
|
|
|
478
475
|
): Promise<void> {
|
|
479
476
|
logger().debug({ msg: "runner actor stopping", actorId, generation });
|
|
480
477
|
|
|
478
|
+
// HACK: Retrieve the stop intent we tracked locally (see RVT-5284)
|
|
479
|
+
// Default to "sleep" if no intent was recorded (e.g., if the runner
|
|
480
|
+
// initiated the stop)
|
|
481
|
+
//
|
|
482
|
+
// TODO: This will not work if the actor is destroyed from the API
|
|
483
|
+
// correctly. Currently, it will use the sleep intent, but it's
|
|
484
|
+
// actually a destroy intent.
|
|
485
|
+
const reason = this.#actorStopIntent.get(actorId) ?? "sleep";
|
|
486
|
+
this.#actorStopIntent.delete(actorId);
|
|
487
|
+
|
|
481
488
|
const handler = this.#actors.get(actorId);
|
|
482
489
|
if (handler?.actor) {
|
|
483
490
|
try {
|
|
484
|
-
await handler.actor.
|
|
491
|
+
await handler.actor.onStop(reason);
|
|
485
492
|
} catch (err) {
|
|
486
493
|
logger().error({
|
|
487
|
-
msg: "error in
|
|
494
|
+
msg: "error in onStop, proceeding with removing actor",
|
|
488
495
|
err: stringifyError(err),
|
|
489
496
|
});
|
|
490
497
|
}
|
|
491
498
|
this.#actors.delete(actorId);
|
|
492
499
|
}
|
|
493
500
|
|
|
494
|
-
logger().debug({ msg: "runner actor stopped", actorId });
|
|
501
|
+
logger().debug({ msg: "runner actor stopped", actorId, reason });
|
|
495
502
|
}
|
|
496
503
|
|
|
504
|
+
// MARK: - Runner Networking
|
|
497
505
|
async #runnerFetch(
|
|
498
506
|
_runner: Runner,
|
|
499
507
|
actorId: string,
|
|
508
|
+
_gatewayIdBuf: ArrayBuffer,
|
|
500
509
|
_requestIdBuf: ArrayBuffer,
|
|
501
510
|
request: Request,
|
|
502
511
|
): Promise<Response> {
|
|
@@ -513,243 +522,424 @@ export class EngineActorDriver implements ActorDriver {
|
|
|
513
522
|
_runner: Runner,
|
|
514
523
|
actorId: string,
|
|
515
524
|
websocketRaw: any,
|
|
525
|
+
gatewayIdBuf: ArrayBuffer,
|
|
516
526
|
requestIdBuf: ArrayBuffer,
|
|
517
527
|
request: Request,
|
|
528
|
+
requestPath: string,
|
|
529
|
+
requestHeaders: Record<string, string>,
|
|
530
|
+
isHibernatable: boolean,
|
|
531
|
+
isRestoringHibernatable: boolean,
|
|
518
532
|
): Promise<void> {
|
|
519
533
|
const websocket = websocketRaw as UniversalWebSocket;
|
|
520
|
-
const requestId = idToStr(requestIdBuf);
|
|
521
534
|
|
|
522
|
-
|
|
535
|
+
// Add a unique ID to track this WebSocket object
|
|
536
|
+
const wsUniqueId = `ws_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
537
|
+
(websocket as any).__rivet_ws_id = wsUniqueId;
|
|
523
538
|
|
|
524
|
-
|
|
539
|
+
logger().debug({
|
|
540
|
+
msg: "runner websocket",
|
|
541
|
+
actorId,
|
|
542
|
+
url: request.url,
|
|
543
|
+
isRestoringHibernatable,
|
|
544
|
+
websocketObjectId: websocketRaw
|
|
545
|
+
? Object.prototype.toString.call(websocketRaw)
|
|
546
|
+
: "null",
|
|
547
|
+
websocketType: websocketRaw?.constructor?.name,
|
|
548
|
+
wsUniqueId,
|
|
549
|
+
websocketProps: websocketRaw
|
|
550
|
+
? Object.keys(websocketRaw).join(", ")
|
|
551
|
+
: "null",
|
|
552
|
+
});
|
|
525
553
|
|
|
526
554
|
// Parse configuration from Sec-WebSocket-Protocol header (optional for path-based routing)
|
|
527
555
|
const protocols = request.headers.get("sec-websocket-protocol");
|
|
528
|
-
|
|
529
|
-
let encodingRaw: string | undefined;
|
|
530
|
-
let connParamsRaw: string | undefined;
|
|
531
|
-
|
|
532
|
-
if (protocols) {
|
|
533
|
-
const protocolList = protocols.split(",").map((p) => p.trim());
|
|
534
|
-
for (const protocol of protocolList) {
|
|
535
|
-
if (protocol.startsWith(WS_PROTOCOL_ENCODING)) {
|
|
536
|
-
encodingRaw = protocol.substring(
|
|
537
|
-
WS_PROTOCOL_ENCODING.length,
|
|
538
|
-
);
|
|
539
|
-
} else if (protocol.startsWith(WS_PROTOCOL_CONN_PARAMS)) {
|
|
540
|
-
connParamsRaw = decodeURIComponent(
|
|
541
|
-
protocol.substring(WS_PROTOCOL_CONN_PARAMS.length),
|
|
542
|
-
);
|
|
543
|
-
}
|
|
544
|
-
}
|
|
545
|
-
}
|
|
546
|
-
|
|
547
|
-
const encoding = EncodingSchema.parse(encodingRaw);
|
|
548
|
-
const connParams = connParamsRaw
|
|
549
|
-
? JSON.parse(connParamsRaw)
|
|
550
|
-
: undefined;
|
|
556
|
+
const { encoding, connParams } = parseWebSocketProtocols(protocols);
|
|
551
557
|
|
|
552
558
|
// Fetch WS handler
|
|
553
559
|
//
|
|
554
560
|
// We store the promise since we need to add WebSocket event listeners immediately that will wait for the promise to resolve
|
|
555
|
-
let
|
|
556
|
-
|
|
557
|
-
|
|
561
|
+
let wsHandler: UpgradeWebSocketArgs;
|
|
562
|
+
try {
|
|
563
|
+
wsHandler = await routeWebSocket(
|
|
558
564
|
request,
|
|
565
|
+
requestPath,
|
|
566
|
+
requestHeaders,
|
|
559
567
|
this.#runConfig,
|
|
560
568
|
this,
|
|
561
569
|
actorId,
|
|
562
570
|
encoding,
|
|
563
571
|
connParams,
|
|
564
|
-
|
|
565
|
-
requestIdBuf,
|
|
566
|
-
// Extract connId and connToken from protocols if needed
|
|
567
|
-
undefined,
|
|
568
|
-
undefined,
|
|
569
|
-
);
|
|
570
|
-
} else if (url.pathname.startsWith(PATH_RAW_WEBSOCKET_PREFIX)) {
|
|
571
|
-
wsHandlerPromise = handleRawWebSocketHandler(
|
|
572
|
-
request,
|
|
573
|
-
url.pathname + url.search,
|
|
574
|
-
this,
|
|
575
|
-
actorId,
|
|
572
|
+
gatewayIdBuf,
|
|
576
573
|
requestIdBuf,
|
|
574
|
+
isHibernatable,
|
|
575
|
+
isRestoringHibernatable,
|
|
577
576
|
);
|
|
578
|
-
}
|
|
579
|
-
|
|
577
|
+
} catch (err) {
|
|
578
|
+
logger().error({ msg: "building websocket handlers errored", err });
|
|
579
|
+
websocketRaw.close(1011, "ws.route_error");
|
|
580
|
+
return;
|
|
580
581
|
}
|
|
581
582
|
|
|
582
|
-
// TODO: Add close
|
|
583
|
-
|
|
584
583
|
// Connect the Hono WS hook to the adapter
|
|
584
|
+
//
|
|
585
|
+
// We need to assign to `raw` in order for WSContext to expose it on
|
|
586
|
+
// `ws.raw`
|
|
587
|
+
(websocket as WSContextInit).raw = websocket;
|
|
585
588
|
const wsContext = new WSContext(websocket);
|
|
586
589
|
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
+
// Get connection and actor from wsHandler (may be undefined for inspector endpoint)
|
|
591
|
+
const conn = wsHandler.conn;
|
|
592
|
+
const actor = wsHandler.actor;
|
|
593
|
+
const connStateManager = conn?.[CONN_STATE_MANAGER_SYMBOL];
|
|
594
|
+
|
|
595
|
+
// Bind event listeners to Hono WebSocket handlers
|
|
596
|
+
//
|
|
597
|
+
// We update the HWS data after calling handlers in order to ensure
|
|
598
|
+
// that the handler ran successfully. By doing this, we ensure at least
|
|
599
|
+
// once delivery of events to the event handlers.
|
|
600
|
+
|
|
601
|
+
// Log when attaching event listeners
|
|
602
|
+
logger().debug({
|
|
603
|
+
msg: "attaching websocket event listeners",
|
|
604
|
+
actorId,
|
|
605
|
+
connId: conn?.id,
|
|
606
|
+
wsUniqueId: (websocket as any).__rivet_ws_id,
|
|
607
|
+
isRestoringHibernatable,
|
|
608
|
+
websocketType: websocket?.constructor?.name,
|
|
590
609
|
});
|
|
591
610
|
|
|
592
|
-
if (
|
|
593
|
-
|
|
594
|
-
x.onOpen?.(new Event("open"), wsContext),
|
|
595
|
-
);
|
|
596
|
-
} else {
|
|
597
|
-
websocket.addEventListener("open", (event) => {
|
|
598
|
-
wsHandlerPromise.then((x) => x.onOpen?.(event, wsContext));
|
|
599
|
-
});
|
|
611
|
+
if (isRestoringHibernatable) {
|
|
612
|
+
wsHandler.onRestore?.(wsContext);
|
|
600
613
|
}
|
|
601
614
|
|
|
615
|
+
websocket.addEventListener("open", (event) => {
|
|
616
|
+
wsHandler.onOpen(event, wsContext);
|
|
617
|
+
});
|
|
618
|
+
|
|
602
619
|
websocket.addEventListener("message", (event: RivetMessageEvent) => {
|
|
603
|
-
|
|
620
|
+
logger().debug({
|
|
621
|
+
msg: "websocket message event listener triggered",
|
|
622
|
+
connId: conn?.id,
|
|
623
|
+
actorId: actor?.id,
|
|
624
|
+
messageIndex: event.rivetMessageIndex,
|
|
625
|
+
hasWsHandler: !!wsHandler,
|
|
626
|
+
hasOnMessage: !!wsHandler?.onMessage,
|
|
627
|
+
actorIsStopping: actor?.isStopping,
|
|
628
|
+
websocketType: websocket?.constructor?.name,
|
|
629
|
+
wsUniqueId: (websocket as any).__rivet_ws_id,
|
|
630
|
+
eventTargetWsId: (event.target as any)?.__rivet_ws_id,
|
|
631
|
+
});
|
|
604
632
|
|
|
605
|
-
|
|
606
|
-
|
|
633
|
+
// Check if actor is stopping - if so, don't process new messages.
|
|
634
|
+
// These messages will be reprocessed when the actor wakes up from hibernation.
|
|
635
|
+
// TODO: This will never retransmit the socket and the socket will close
|
|
636
|
+
if (actor?.isStopping) {
|
|
637
|
+
logger().debug({
|
|
638
|
+
msg: "ignoring ws message, actor is stopping",
|
|
639
|
+
connId: conn?.id,
|
|
640
|
+
actorId: actor?.id,
|
|
641
|
+
messageIndex: event.rivetMessageIndex,
|
|
642
|
+
});
|
|
643
|
+
return;
|
|
644
|
+
}
|
|
607
645
|
|
|
608
|
-
//
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
646
|
+
// Process message
|
|
647
|
+
logger().debug({
|
|
648
|
+
msg: "calling wsHandler.onMessage",
|
|
649
|
+
connId: conn?.id,
|
|
650
|
+
messageIndex: event.rivetMessageIndex,
|
|
651
|
+
});
|
|
652
|
+
wsHandler.onMessage(event, wsContext);
|
|
653
|
+
|
|
654
|
+
// Persist message index for hibernatable connections
|
|
655
|
+
const hibernate = connStateManager?.hibernatableData;
|
|
656
|
+
|
|
657
|
+
if (hibernate && conn && actor) {
|
|
658
|
+
invariant(
|
|
659
|
+
typeof event.rivetMessageIndex === "number",
|
|
660
|
+
"missing event.rivetMessageIndex",
|
|
661
|
+
);
|
|
662
|
+
|
|
663
|
+
// Persist message index
|
|
664
|
+
const previousMsgIndex = hibernate.serverMessageIndex;
|
|
665
|
+
hibernate.serverMessageIndex = event.rivetMessageIndex;
|
|
666
|
+
logger().info({
|
|
667
|
+
msg: "persisting message index",
|
|
668
|
+
connId: conn.id,
|
|
669
|
+
previousMsgIndex,
|
|
670
|
+
newMsgIndex: event.rivetMessageIndex,
|
|
671
|
+
});
|
|
672
|
+
|
|
673
|
+
// Calculate message size and track cumulative size
|
|
674
|
+
const entry = this.#hwsMessageIndex.get(conn.id);
|
|
675
|
+
if (entry) {
|
|
676
|
+
// Track message length
|
|
677
|
+
const messageLength = getValueLength(event.data);
|
|
678
|
+
entry.bufferedMessageSize += messageLength;
|
|
679
|
+
|
|
680
|
+
if (
|
|
681
|
+
entry.bufferedMessageSize >=
|
|
682
|
+
CONN_BUFFERED_MESSAGE_SIZE_THRESHOLD
|
|
683
|
+
) {
|
|
684
|
+
// Reset buffered message size immeidatley (instead
|
|
685
|
+
// of waiting for onAfterPersistConn) since we may
|
|
686
|
+
// receive more messages before onAfterPersistConn
|
|
687
|
+
// is called, which would called saveState
|
|
688
|
+
// immediate multiple times
|
|
689
|
+
entry.bufferedMessageSize = 0;
|
|
690
|
+
entry.pendingAckFromBufferSize = true;
|
|
691
|
+
|
|
692
|
+
// Save state immediately if approaching buffer threshold
|
|
693
|
+
actor.stateManager.saveState({
|
|
694
|
+
immediate: true,
|
|
695
|
+
});
|
|
696
|
+
} else {
|
|
697
|
+
// Save message index. The maxWait is set to the ack deadline
|
|
698
|
+
// since we ack the message immediately after persisting the index.
|
|
699
|
+
// If cumulative size exceeds threshold, force immediate persist.
|
|
700
|
+
//
|
|
701
|
+
// This will call EngineActorDriver.onAfterPersistConn after
|
|
702
|
+
// persist to send the ack to the gateway.
|
|
703
|
+
actor.stateManager.saveState({
|
|
704
|
+
maxWait: CONN_MESSAGE_ACK_DEADLINE,
|
|
705
|
+
});
|
|
706
|
+
}
|
|
614
707
|
} else {
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
queuedMessageIndex: currentEntry,
|
|
619
|
-
eventMessageIndex: event.rivetMessageIndex,
|
|
708
|
+
// Fallback if entry missing
|
|
709
|
+
actor.stateManager.saveState({
|
|
710
|
+
maxWait: CONN_MESSAGE_ACK_DEADLINE,
|
|
620
711
|
});
|
|
621
712
|
}
|
|
622
|
-
} else {
|
|
623
|
-
this.#wsAckQueue.set(requestId, {
|
|
624
|
-
requestIdBuf,
|
|
625
|
-
messageIndex: event.rivetMessageIndex,
|
|
626
|
-
});
|
|
627
713
|
}
|
|
628
714
|
});
|
|
629
715
|
|
|
630
716
|
websocket.addEventListener("close", (event) => {
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
717
|
+
wsHandler.onClose(event, wsContext);
|
|
718
|
+
|
|
719
|
+
// NOTE: Persisted connection is removed when `conn.disconnect`
|
|
720
|
+
// is called by the WebSocket route
|
|
634
721
|
});
|
|
635
722
|
|
|
636
723
|
websocket.addEventListener("error", (event) => {
|
|
637
|
-
|
|
724
|
+
wsHandler.onError(event, wsContext);
|
|
638
725
|
});
|
|
639
|
-
}
|
|
640
726
|
|
|
641
|
-
|
|
642
|
-
|
|
727
|
+
// Log event listener attachment for restored connections
|
|
728
|
+
if (isRestoringHibernatable) {
|
|
729
|
+
logger().info({
|
|
730
|
+
msg: "event listeners attached to restored websocket",
|
|
731
|
+
actorId,
|
|
732
|
+
connId: conn?.id,
|
|
733
|
+
gatewayId: idToStr(gatewayIdBuf),
|
|
734
|
+
requestId: idToStr(requestIdBuf),
|
|
735
|
+
websocketType: websocket?.constructor?.name,
|
|
736
|
+
hasMessageListener: !!websocket.addEventListener,
|
|
737
|
+
});
|
|
738
|
+
}
|
|
643
739
|
}
|
|
644
740
|
|
|
645
|
-
|
|
646
|
-
|
|
741
|
+
// MARK: - Hibernating WebSockets
|
|
742
|
+
#hwsCanHibernate(
|
|
743
|
+
actorId: string,
|
|
744
|
+
gatewayId: ArrayBuffer,
|
|
745
|
+
requestId: ArrayBuffer,
|
|
746
|
+
request: Request,
|
|
747
|
+
): boolean {
|
|
748
|
+
const url = new URL(request.url);
|
|
749
|
+
const path = url.pathname;
|
|
647
750
|
|
|
648
|
-
//
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
//
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
751
|
+
// Get actor instance from runner to access actor name
|
|
752
|
+
const actorInstance = this.#runner.getActor(actorId);
|
|
753
|
+
if (!actorInstance) {
|
|
754
|
+
logger().warn({
|
|
755
|
+
msg: "actor not found in #hwsCanHibernate",
|
|
756
|
+
actorId,
|
|
757
|
+
});
|
|
758
|
+
return false;
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
// Load actor handler to access persisted data
|
|
762
|
+
const handler = this.#actors.get(actorId);
|
|
763
|
+
if (!handler) {
|
|
764
|
+
logger().warn({
|
|
765
|
+
msg: "actor handler not found in #hwsCanHibernate",
|
|
766
|
+
actorId,
|
|
767
|
+
});
|
|
768
|
+
return false;
|
|
769
|
+
}
|
|
770
|
+
if (!handler.actor) {
|
|
771
|
+
logger().warn({
|
|
772
|
+
msg: "actor not found in #hwsCanHibernate",
|
|
773
|
+
actorId,
|
|
774
|
+
});
|
|
775
|
+
return false;
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
// Determine configuration for new WS
|
|
673
779
|
logger().debug({
|
|
674
|
-
msg: "
|
|
675
|
-
|
|
780
|
+
msg: "no existing hibernatable websocket found",
|
|
781
|
+
gatewayId: idToStr(gatewayId),
|
|
782
|
+
requestId: idToStr(requestId),
|
|
676
783
|
});
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
784
|
+
if (path === PATH_CONNECT) {
|
|
785
|
+
return true;
|
|
786
|
+
} else if (
|
|
787
|
+
path === PATH_WEBSOCKET_BASE ||
|
|
788
|
+
path.startsWith(PATH_WEBSOCKET_PREFIX)
|
|
789
|
+
) {
|
|
790
|
+
// Find actor config
|
|
791
|
+
const definition = lookupInRegistry(
|
|
792
|
+
this.#registryConfig,
|
|
793
|
+
actorInstance.config.name,
|
|
794
|
+
);
|
|
795
|
+
|
|
796
|
+
// Check if can hibernate
|
|
797
|
+
const canHibernateWebSocket =
|
|
798
|
+
definition.config.options?.canHibernateWebSocket;
|
|
799
|
+
if (canHibernateWebSocket === true) {
|
|
800
|
+
return true;
|
|
801
|
+
} else if (typeof canHibernateWebSocket === "function") {
|
|
802
|
+
try {
|
|
803
|
+
// Truncate the path to match the behavior on onRawWebSocket
|
|
804
|
+
const newPath = truncateRawWebSocketPathPrefix(
|
|
805
|
+
url.pathname,
|
|
806
|
+
);
|
|
807
|
+
const truncatedRequest = new Request(
|
|
808
|
+
`http://actor${newPath}`,
|
|
809
|
+
request,
|
|
810
|
+
);
|
|
811
|
+
|
|
812
|
+
const canHibernate =
|
|
813
|
+
canHibernateWebSocket(truncatedRequest);
|
|
814
|
+
return canHibernate;
|
|
815
|
+
} catch (error) {
|
|
816
|
+
logger().error({
|
|
817
|
+
msg: "error calling canHibernateWebSocket",
|
|
818
|
+
error,
|
|
819
|
+
});
|
|
820
|
+
return false;
|
|
821
|
+
}
|
|
822
|
+
} else {
|
|
823
|
+
return false;
|
|
688
824
|
}
|
|
825
|
+
} else if (path === PATH_INSPECTOR_CONNECT) {
|
|
826
|
+
return false;
|
|
827
|
+
} else {
|
|
828
|
+
logger().warn({
|
|
829
|
+
msg: "unexpected path for getActorHibernationConfig",
|
|
830
|
+
path,
|
|
831
|
+
});
|
|
832
|
+
return false;
|
|
689
833
|
}
|
|
690
|
-
|
|
691
|
-
logger().debug({ msg: "all actors stopped" });
|
|
834
|
+
}
|
|
692
835
|
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
836
|
+
async #hwsLoadAll(
|
|
837
|
+
actorId: string,
|
|
838
|
+
): Promise<HibernatingWebSocketMetadata[]> {
|
|
839
|
+
const actor = await this.loadActor(actorId);
|
|
840
|
+
return actor.conns
|
|
841
|
+
.values()
|
|
842
|
+
.map((conn) => {
|
|
843
|
+
const connStateManager = conn[CONN_STATE_MANAGER_SYMBOL];
|
|
844
|
+
const hibernatable = connStateManager.hibernatableData;
|
|
845
|
+
if (!hibernatable) return undefined;
|
|
846
|
+
return {
|
|
847
|
+
gatewayId: hibernatable.gatewayId,
|
|
848
|
+
requestId: hibernatable.requestId,
|
|
849
|
+
serverMessageIndex: hibernatable.serverMessageIndex,
|
|
850
|
+
clientMessageIndex: hibernatable.clientMessageIndex,
|
|
851
|
+
path: hibernatable.requestPath,
|
|
852
|
+
headers: hibernatable.requestHeaders,
|
|
853
|
+
} satisfies HibernatingWebSocketMetadata;
|
|
854
|
+
})
|
|
855
|
+
.filter((x) => x !== undefined)
|
|
856
|
+
.toArray();
|
|
857
|
+
}
|
|
698
858
|
|
|
699
|
-
|
|
700
|
-
|
|
859
|
+
async onBeforeActorStart(actor: AnyActorInstance): Promise<void> {
|
|
860
|
+
// Resolve promise if waiting
|
|
861
|
+
const handler = this.#actors.get(actor.id);
|
|
862
|
+
invariant(handler, "missing actor handler in onBeforeActorReady");
|
|
863
|
+
handler.actorStartPromise?.resolve();
|
|
864
|
+
handler.actorStartPromise = undefined;
|
|
701
865
|
|
|
702
|
-
|
|
866
|
+
// Restore hibernating requests
|
|
867
|
+
const metaEntries = await this.#hwsLoadAll(actor.id);
|
|
868
|
+
await this.#runner.restoreHibernatingRequests(actor.id, metaEntries);
|
|
703
869
|
}
|
|
704
870
|
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
stream.onAbort(() => {});
|
|
709
|
-
c.req.raw.signal.addEventListener("abort", () => {
|
|
710
|
-
logger().debug("SSE aborted, shutting down runner");
|
|
871
|
+
onCreateConn(conn: AnyConn) {
|
|
872
|
+
const hibernatable = conn[CONN_STATE_MANAGER_SYMBOL].hibernatableData;
|
|
873
|
+
if (!hibernatable) return;
|
|
711
874
|
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
875
|
+
this.#hwsMessageIndex.set(conn.id, {
|
|
876
|
+
serverMessageIndex: hibernatable.serverMessageIndex,
|
|
877
|
+
bufferedMessageSize: 0,
|
|
878
|
+
pendingAckFromMessageIndex: false,
|
|
879
|
+
pendingAckFromBufferSize: false,
|
|
880
|
+
});
|
|
717
881
|
|
|
718
|
-
|
|
882
|
+
logger().debug({
|
|
883
|
+
msg: "created #hwsMessageIndex entry",
|
|
884
|
+
connId: conn.id,
|
|
885
|
+
serverMessageIndex: hibernatable.serverMessageIndex,
|
|
886
|
+
});
|
|
887
|
+
}
|
|
719
888
|
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
invariant(payload, "runnerId not set");
|
|
723
|
-
await stream.writeSSE({ data: payload });
|
|
889
|
+
onDestroyConn(conn: AnyConn) {
|
|
890
|
+
this.#hwsMessageIndex.delete(conn.id);
|
|
724
891
|
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
});
|
|
731
|
-
break;
|
|
732
|
-
}
|
|
892
|
+
logger().debug({
|
|
893
|
+
msg: "removed #hwsMessageIndex entry",
|
|
894
|
+
connId: conn.id,
|
|
895
|
+
});
|
|
896
|
+
}
|
|
733
897
|
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
closed: stream.closed,
|
|
738
|
-
aborted: stream.aborted,
|
|
739
|
-
});
|
|
740
|
-
break;
|
|
741
|
-
}
|
|
898
|
+
onBeforePersistConn(conn: AnyConn) {
|
|
899
|
+
const stateManager = conn[CONN_STATE_MANAGER_SYMBOL];
|
|
900
|
+
const hibernatable = stateManager.hibernatableDataOrError();
|
|
742
901
|
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
902
|
+
const entry = this.#hwsMessageIndex.get(conn.id);
|
|
903
|
+
if (!entry) {
|
|
904
|
+
logger().warn({
|
|
905
|
+
msg: "missing EngineActorDriver.#hwsMessageIndex entry for conn",
|
|
906
|
+
connId: conn.id,
|
|
907
|
+
});
|
|
908
|
+
return;
|
|
909
|
+
}
|
|
746
910
|
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
911
|
+
// There is a newer message index
|
|
912
|
+
entry.pendingAckFromMessageIndex =
|
|
913
|
+
hibernatable.serverMessageIndex > entry.serverMessageIndex;
|
|
914
|
+
entry.serverMessageIndex = hibernatable.serverMessageIndex;
|
|
750
915
|
}
|
|
751
916
|
|
|
752
|
-
|
|
753
|
-
|
|
917
|
+
onAfterPersistConn(conn: AnyConn) {
|
|
918
|
+
const stateManager = conn[CONN_STATE_MANAGER_SYMBOL];
|
|
919
|
+
const hibernatable = stateManager.hibernatableDataOrError();
|
|
920
|
+
|
|
921
|
+
const entry = this.#hwsMessageIndex.get(conn.id);
|
|
922
|
+
if (!entry) {
|
|
923
|
+
logger().warn({
|
|
924
|
+
msg: "missing EngineActorDriver.#hwsMessageIndex entry for conn",
|
|
925
|
+
connId: conn.id,
|
|
926
|
+
});
|
|
927
|
+
return;
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
// Ack entry
|
|
931
|
+
if (
|
|
932
|
+
entry.pendingAckFromMessageIndex ||
|
|
933
|
+
entry.pendingAckFromBufferSize
|
|
934
|
+
) {
|
|
935
|
+
this.#runner.sendHibernatableWebSocketMessageAck(
|
|
936
|
+
hibernatable.gatewayId,
|
|
937
|
+
hibernatable.requestId,
|
|
938
|
+
entry.serverMessageIndex,
|
|
939
|
+
);
|
|
940
|
+
entry.pendingAckFromMessageIndex = false;
|
|
941
|
+
entry.pendingAckFromBufferSize = false;
|
|
942
|
+
entry.bufferedMessageSize = 0;
|
|
943
|
+
}
|
|
754
944
|
}
|
|
755
945
|
}
|