rivetkit 2.0.21 → 2.0.22-rc.2
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/v2.ts +259 -0
- package/dist/tsup/actor/errors.cjs.map +1 -1
- package/dist/tsup/{chunk-HN7UXCYQ.cjs → chunk-5N6F5PXD.cjs} +31 -12
- package/dist/tsup/chunk-5N6F5PXD.cjs.map +1 -0
- package/dist/tsup/{chunk-FDJ3AVNB.cjs → chunk-5TRXLS6X.cjs} +71 -67
- package/dist/tsup/chunk-5TRXLS6X.cjs.map +1 -0
- package/dist/tsup/{chunk-HUGSRAGL.js → chunk-7RUROQAZ.js} +36 -16
- package/dist/tsup/chunk-7RUROQAZ.js.map +1 -0
- package/dist/tsup/{chunk-Y2QONT7B.js → chunk-AMK3AACS.js} +272 -89
- package/dist/tsup/chunk-AMK3AACS.js.map +1 -0
- package/dist/tsup/{chunk-RZZDFDB6.js → chunk-BHLQTKOD.js} +51 -47
- package/dist/tsup/chunk-BHLQTKOD.js.map +1 -0
- package/dist/tsup/{chunk-DYA34FHW.js → chunk-C4FPCW7T.js} +2 -2
- package/dist/tsup/{chunk-D2LS4X6E.js → chunk-CVLO2OOK.js} +3 -3
- package/dist/tsup/{chunk-6G76WIWL.js → chunk-EJXZYQ3N.js} +2 -2
- package/dist/tsup/{chunk-VLR3TDHT.js → chunk-F7WVJXPB.js} +2 -2
- package/dist/tsup/{chunk-4OINFQBR.js → chunk-FLVL7RGH.js} +3 -3
- package/dist/tsup/{chunk-JKOUXDK6.cjs → chunk-GXIO5YOT.cjs} +8 -8
- package/dist/tsup/chunk-GXIO5YOT.cjs.map +1 -0
- package/dist/tsup/{chunk-2POQCWMA.js → chunk-HLZT5C6A.js} +385 -104
- package/dist/tsup/chunk-HLZT5C6A.js.map +1 -0
- package/dist/tsup/chunk-KSRXX3Z4.cjs.map +1 -1
- package/dist/tsup/{chunk-JTIBPF7N.cjs → chunk-LFP446KS.cjs} +14 -14
- package/dist/tsup/chunk-LFP446KS.cjs.map +1 -0
- package/dist/tsup/{chunk-3UIGKLZW.js → chunk-MQDXPGNE.js} +6 -6
- package/dist/tsup/{chunk-O4GUKGK4.cjs → chunk-NDOG6IQ5.cjs} +6 -6
- package/dist/tsup/chunk-NDOG6IQ5.cjs.map +1 -0
- package/dist/tsup/{chunk-LMJHBF26.cjs → chunk-Q5CAVEKC.cjs} +467 -284
- package/dist/tsup/chunk-Q5CAVEKC.cjs.map +1 -0
- package/dist/tsup/{chunk-65SAIRRY.cjs → chunk-UBMUBNS2.cjs} +12 -12
- package/dist/tsup/chunk-UBMUBNS2.cjs.map +1 -0
- package/dist/tsup/{chunk-FUX6U6TL.js → chunk-VMFBKBJL.js} +26 -7
- package/dist/tsup/chunk-VMFBKBJL.js.map +1 -0
- package/dist/tsup/{chunk-M5BHNJHB.cjs → chunk-YLWF6RFL.cjs} +558 -277
- package/dist/tsup/chunk-YLWF6RFL.cjs.map +1 -0
- package/dist/tsup/{chunk-ELDFBXDV.cjs → chunk-YUBR6XCJ.cjs} +35 -15
- package/dist/tsup/chunk-YUBR6XCJ.cjs.map +1 -0
- package/dist/tsup/{chunk-ZNWE3XBT.cjs → chunk-ZL6NSKF2.cjs} +3 -3
- package/dist/tsup/chunk-ZL6NSKF2.cjs.map +1 -0
- package/dist/tsup/{chunk-LWGCMELP.cjs → chunk-ZY4DKLMT.cjs} +3 -3
- package/dist/tsup/chunk-ZY4DKLMT.cjs.map +1 -0
- package/dist/tsup/client/mod.cjs +9 -9
- package/dist/tsup/client/mod.cjs.map +1 -1
- package/dist/tsup/client/mod.d.cts +2 -2
- package/dist/tsup/client/mod.d.ts +2 -2
- package/dist/tsup/client/mod.js +8 -8
- package/dist/tsup/common/log.cjs +3 -3
- package/dist/tsup/common/log.cjs.map +1 -1
- package/dist/tsup/common/log.js +2 -2
- package/dist/tsup/common/websocket.cjs +4 -4
- package/dist/tsup/common/websocket.cjs.map +1 -1
- package/dist/tsup/common/websocket.js +3 -3
- package/dist/tsup/{conn-Clu655RU.d.ts → conn-BYXlxnh0.d.ts} +111 -102
- package/dist/tsup/{conn-lUvFLo_q.d.cts → conn-BiazosE_.d.cts} +111 -102
- package/dist/tsup/driver-helpers/mod.cjs +5 -5
- package/dist/tsup/driver-helpers/mod.cjs.map +1 -1
- package/dist/tsup/driver-helpers/mod.d.cts +1 -1
- package/dist/tsup/driver-helpers/mod.d.ts +1 -1
- package/dist/tsup/driver-helpers/mod.js +4 -4
- package/dist/tsup/driver-test-suite/mod.cjs +71 -71
- package/dist/tsup/driver-test-suite/mod.cjs.map +1 -1
- package/dist/tsup/driver-test-suite/mod.d.cts +1 -1
- package/dist/tsup/driver-test-suite/mod.d.ts +1 -1
- package/dist/tsup/driver-test-suite/mod.js +11 -11
- package/dist/tsup/inspector/mod.cjs +6 -6
- package/dist/tsup/inspector/mod.cjs.map +1 -1
- package/dist/tsup/inspector/mod.d.cts +2 -2
- package/dist/tsup/inspector/mod.d.ts +2 -2
- package/dist/tsup/inspector/mod.js +5 -5
- package/dist/tsup/mod.cjs +10 -10
- package/dist/tsup/mod.cjs.map +1 -1
- package/dist/tsup/mod.d.cts +3 -3
- package/dist/tsup/mod.d.ts +3 -3
- package/dist/tsup/mod.js +9 -9
- package/dist/tsup/test/mod.cjs +11 -11
- package/dist/tsup/test/mod.cjs.map +1 -1
- package/dist/tsup/test/mod.d.cts +1 -1
- package/dist/tsup/test/mod.d.ts +1 -1
- package/dist/tsup/test/mod.js +10 -10
- package/dist/tsup/utils.cjs +8 -2
- package/dist/tsup/utils.cjs.map +1 -1
- package/dist/tsup/utils.d.cts +8 -1
- package/dist/tsup/utils.d.ts +8 -1
- package/dist/tsup/utils.js +7 -1
- package/package.json +5 -4
- package/src/actor/config.ts +10 -0
- package/src/actor/conn-drivers.ts +43 -1
- package/src/actor/conn-socket.ts +1 -1
- package/src/actor/conn.ts +22 -2
- package/src/actor/context.ts +1 -1
- package/src/actor/driver.ts +13 -2
- package/src/actor/instance.ts +248 -57
- package/src/actor/persisted.ts +7 -0
- package/src/actor/router-endpoints.ts +67 -45
- package/src/actor/router.ts +25 -17
- package/src/client/actor-conn.ts +9 -5
- package/src/common/cors.ts +57 -0
- package/src/common/log.ts +26 -5
- package/src/common/utils.ts +5 -9
- package/src/common/websocket-interface.ts +10 -0
- package/src/driver-helpers/utils.ts +1 -0
- package/src/drivers/engine/actor-driver.ts +261 -14
- package/src/drivers/engine/config.ts +2 -4
- package/src/drivers/file-system/actor.ts +3 -2
- package/src/drivers/file-system/global-state.ts +1 -1
- package/src/drivers/file-system/manager.ts +3 -0
- package/src/engine-process/mod.ts +22 -4
- package/src/inspector/config.ts +0 -45
- package/src/manager/gateway.ts +45 -32
- package/src/manager/hono-websocket-adapter.ts +31 -3
- package/src/manager/router.ts +11 -17
- package/src/registry/run-config.ts +2 -8
- package/src/remote-manager-driver/actor-http-client.ts +5 -8
- package/src/remote-manager-driver/actor-websocket-client.ts +2 -14
- package/src/remote-manager-driver/mod.ts +0 -1
- package/src/schemas/actor-persist/mod.ts +1 -1
- package/src/schemas/actor-persist/versioned.ts +22 -10
- package/src/utils.ts +26 -0
- package/dist/tsup/chunk-2POQCWMA.js.map +0 -1
- package/dist/tsup/chunk-65SAIRRY.cjs.map +0 -1
- package/dist/tsup/chunk-ELDFBXDV.cjs.map +0 -1
- package/dist/tsup/chunk-FDJ3AVNB.cjs.map +0 -1
- package/dist/tsup/chunk-FUX6U6TL.js.map +0 -1
- package/dist/tsup/chunk-HN7UXCYQ.cjs.map +0 -1
- package/dist/tsup/chunk-HUGSRAGL.js.map +0 -1
- package/dist/tsup/chunk-JKOUXDK6.cjs.map +0 -1
- package/dist/tsup/chunk-JTIBPF7N.cjs.map +0 -1
- package/dist/tsup/chunk-LMJHBF26.cjs.map +0 -1
- package/dist/tsup/chunk-LWGCMELP.cjs.map +0 -1
- package/dist/tsup/chunk-M5BHNJHB.cjs.map +0 -1
- package/dist/tsup/chunk-O4GUKGK4.cjs.map +0 -1
- package/dist/tsup/chunk-RZZDFDB6.js.map +0 -1
- package/dist/tsup/chunk-Y2QONT7B.js.map +0 -1
- package/dist/tsup/chunk-ZNWE3XBT.cjs.map +0 -1
- /package/dist/tsup/{chunk-DYA34FHW.js.map → chunk-C4FPCW7T.js.map} +0 -0
- /package/dist/tsup/{chunk-D2LS4X6E.js.map → chunk-CVLO2OOK.js.map} +0 -0
- /package/dist/tsup/{chunk-6G76WIWL.js.map → chunk-EJXZYQ3N.js.map} +0 -0
- /package/dist/tsup/{chunk-VLR3TDHT.js.map → chunk-F7WVJXPB.js.map} +0 -0
- /package/dist/tsup/{chunk-4OINFQBR.js.map → chunk-FLVL7RGH.js.map} +0 -0
- /package/dist/tsup/{chunk-3UIGKLZW.js.map → chunk-MQDXPGNE.js.map} +0 -0
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
ActorConfig as EngineActorConfig,
|
|
3
3
|
RunnerConfig as EngineRunnerConfig,
|
|
4
|
+
HibernationConfig,
|
|
4
5
|
} from "@rivetkit/engine-runner";
|
|
5
6
|
import { Runner } from "@rivetkit/engine-runner";
|
|
6
7
|
import * as cbor from "cbor-x";
|
|
@@ -9,12 +10,14 @@ import { streamSSE } from "hono/streaming";
|
|
|
9
10
|
import { WSContext } from "hono/ws";
|
|
10
11
|
import invariant from "invariant";
|
|
11
12
|
import { lookupInRegistry } from "@/actor/definition";
|
|
13
|
+
import { PERSIST_SYMBOL } from "@/actor/instance";
|
|
12
14
|
import { deserializeActorKey } from "@/actor/keys";
|
|
13
15
|
import { EncodingSchema } from "@/actor/protocol/serde";
|
|
14
16
|
import { type ActorRouter, createActorRouter } from "@/actor/router";
|
|
15
17
|
import {
|
|
16
18
|
handleRawWebSocketHandler,
|
|
17
19
|
handleWebSocketConnect,
|
|
20
|
+
truncateRawWebSocketPathPrefix,
|
|
18
21
|
} from "@/actor/router-endpoints";
|
|
19
22
|
import type { Client } from "@/client/client";
|
|
20
23
|
import {
|
|
@@ -26,7 +29,11 @@ import {
|
|
|
26
29
|
} from "@/common/actor-router-consts";
|
|
27
30
|
import type { UpgradeWebSocketArgs } from "@/common/inline-websocket-adapter2";
|
|
28
31
|
import { getLogger } from "@/common/log";
|
|
29
|
-
import type {
|
|
32
|
+
import type {
|
|
33
|
+
RivetEvent,
|
|
34
|
+
RivetMessageEvent,
|
|
35
|
+
UniversalWebSocket,
|
|
36
|
+
} from "@/common/websocket-interface";
|
|
30
37
|
import {
|
|
31
38
|
type ActorDriver,
|
|
32
39
|
type AnyActorInstance,
|
|
@@ -37,13 +44,18 @@ import { buildActorNames, type RegistryConfig } from "@/registry/config";
|
|
|
37
44
|
import type { RunnerConfig } from "@/registry/run-config";
|
|
38
45
|
import { getEndpoint } from "@/remote-manager-driver/api-utils";
|
|
39
46
|
import {
|
|
47
|
+
arrayBuffersEqual,
|
|
48
|
+
idToStr,
|
|
40
49
|
type LongTimeoutHandle,
|
|
41
50
|
promiseWithResolvers,
|
|
42
51
|
setLongTimeout,
|
|
52
|
+
stringifyError,
|
|
43
53
|
} from "@/utils";
|
|
44
54
|
import { KEYS } from "./kv";
|
|
45
55
|
import { logger } from "./log";
|
|
46
56
|
|
|
57
|
+
const RUNNER_SSE_PING_INTERVAL = 1000;
|
|
58
|
+
|
|
47
59
|
interface ActorHandler {
|
|
48
60
|
actor?: AnyActorInstance;
|
|
49
61
|
actorStartPromise?: ReturnType<typeof promiseWithResolvers<void>>;
|
|
@@ -65,6 +77,14 @@ export class EngineActorDriver implements ActorDriver {
|
|
|
65
77
|
|
|
66
78
|
#runnerStarted: PromiseWithResolvers<undefined> = promiseWithResolvers();
|
|
67
79
|
#runnerStopped: PromiseWithResolvers<undefined> = promiseWithResolvers();
|
|
80
|
+
#isRunnerStopped: boolean = false;
|
|
81
|
+
|
|
82
|
+
// WebSocket message acknowledgment debouncing
|
|
83
|
+
#wsAckQueue: Map<
|
|
84
|
+
string,
|
|
85
|
+
{ requestIdBuf: ArrayBuffer; messageIndex: number }
|
|
86
|
+
> = new Map();
|
|
87
|
+
#wsAckFlushInterval?: NodeJS.Timeout;
|
|
68
88
|
|
|
69
89
|
constructor(
|
|
70
90
|
registryConfig: RegistryConfig,
|
|
@@ -79,7 +99,7 @@ export class EngineActorDriver implements ActorDriver {
|
|
|
79
99
|
|
|
80
100
|
// HACK: Override inspector token (which are likely to be
|
|
81
101
|
// removed later on) with token from x-rivet-token header
|
|
82
|
-
const token = runConfig.token
|
|
102
|
+
const token = runConfig.token;
|
|
83
103
|
if (token && runConfig.inspector && runConfig.inspector.enabled) {
|
|
84
104
|
runConfig.inspector.token = () => token;
|
|
85
105
|
}
|
|
@@ -96,10 +116,10 @@ export class EngineActorDriver implements ActorDriver {
|
|
|
96
116
|
version: this.#version,
|
|
97
117
|
endpoint: getEndpoint(runConfig),
|
|
98
118
|
token,
|
|
99
|
-
namespace: runConfig.namespace
|
|
100
|
-
totalSlots: runConfig.totalSlots
|
|
101
|
-
runnerName: runConfig.runnerName
|
|
102
|
-
runnerKey: runConfig.runnerKey,
|
|
119
|
+
namespace: runConfig.namespace,
|
|
120
|
+
totalSlots: runConfig.totalSlots,
|
|
121
|
+
runnerName: runConfig.runnerName,
|
|
122
|
+
runnerKey: runConfig.runnerKey ?? crypto.randomUUID(),
|
|
103
123
|
metadata: {
|
|
104
124
|
inspectorToken: this.#runConfig.inspector.token(),
|
|
105
125
|
},
|
|
@@ -121,22 +141,149 @@ export class EngineActorDriver implements ActorDriver {
|
|
|
121
141
|
|
|
122
142
|
this.#runnerStarted.resolve(undefined);
|
|
123
143
|
},
|
|
124
|
-
onDisconnected: () => {
|
|
144
|
+
onDisconnected: (code, reason) => {
|
|
125
145
|
logger().warn({
|
|
126
146
|
msg: "runner disconnected",
|
|
127
147
|
namespace: this.#runConfig.namespace,
|
|
128
148
|
runnerName: this.#runConfig.runnerName,
|
|
149
|
+
code,
|
|
150
|
+
reason,
|
|
129
151
|
});
|
|
130
152
|
hasDisconnected = true;
|
|
131
153
|
},
|
|
132
154
|
onShutdown: () => {
|
|
133
155
|
this.#runnerStopped.resolve(undefined);
|
|
156
|
+
this.#isRunnerStopped = true;
|
|
134
157
|
},
|
|
135
158
|
fetch: this.#runnerFetch.bind(this),
|
|
136
159
|
websocket: this.#runnerWebSocket.bind(this),
|
|
137
160
|
onActorStart: this.#runnerOnActorStart.bind(this),
|
|
138
161
|
onActorStop: this.#runnerOnActorStop.bind(this),
|
|
139
162
|
logger: getLogger("engine-runner"),
|
|
163
|
+
getActorHibernationConfig: (
|
|
164
|
+
actorId: string,
|
|
165
|
+
requestId: ArrayBuffer,
|
|
166
|
+
request: Request,
|
|
167
|
+
): HibernationConfig => {
|
|
168
|
+
const url = new URL(request.url);
|
|
169
|
+
const path = url.pathname;
|
|
170
|
+
|
|
171
|
+
// Get actor instance from runner to access actor name
|
|
172
|
+
const actorInstance = this.#runner.getActor(actorId);
|
|
173
|
+
if (!actorInstance) {
|
|
174
|
+
logger().warn({
|
|
175
|
+
msg: "actor not found in getActorHibernationConfig",
|
|
176
|
+
actorId,
|
|
177
|
+
});
|
|
178
|
+
return { enabled: false, lastMsgIndex: undefined };
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Load actor handler to access persisted data
|
|
182
|
+
const handler = this.#actors.get(actorId);
|
|
183
|
+
if (!handler) {
|
|
184
|
+
logger().warn({
|
|
185
|
+
msg: "actor handler not found in getActorHibernationConfig",
|
|
186
|
+
actorId,
|
|
187
|
+
});
|
|
188
|
+
return { enabled: false, lastMsgIndex: undefined };
|
|
189
|
+
}
|
|
190
|
+
if (!handler.actor) {
|
|
191
|
+
logger().warn({
|
|
192
|
+
msg: "actor not found in getActorHibernationConfig",
|
|
193
|
+
actorId,
|
|
194
|
+
});
|
|
195
|
+
return { enabled: false, lastMsgIndex: undefined };
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Check for existing WS
|
|
199
|
+
const existingWs = handler.actor[
|
|
200
|
+
PERSIST_SYMBOL
|
|
201
|
+
].hibernatableWebSocket.find((ws) =>
|
|
202
|
+
arrayBuffersEqual(ws.requestId, requestId),
|
|
203
|
+
);
|
|
204
|
+
|
|
205
|
+
// Determine configuration for new WS
|
|
206
|
+
let hibernationConfig: HibernationConfig;
|
|
207
|
+
if (existingWs) {
|
|
208
|
+
hibernationConfig = {
|
|
209
|
+
enabled: true,
|
|
210
|
+
lastMsgIndex: Number(existingWs.msgIndex),
|
|
211
|
+
};
|
|
212
|
+
} else {
|
|
213
|
+
if (path === PATH_CONNECT_WEBSOCKET) {
|
|
214
|
+
hibernationConfig = {
|
|
215
|
+
enabled: true,
|
|
216
|
+
lastMsgIndex: undefined,
|
|
217
|
+
};
|
|
218
|
+
} else if (path.startsWith(PATH_RAW_WEBSOCKET_PREFIX)) {
|
|
219
|
+
// Find actor config
|
|
220
|
+
const definition = lookupInRegistry(
|
|
221
|
+
this.#registryConfig,
|
|
222
|
+
actorInstance.config.name,
|
|
223
|
+
);
|
|
224
|
+
|
|
225
|
+
// Check if can hibernate
|
|
226
|
+
const canHibernatWebSocket =
|
|
227
|
+
definition.config.options?.canHibernatWebSocket;
|
|
228
|
+
if (canHibernatWebSocket === true) {
|
|
229
|
+
hibernationConfig = {
|
|
230
|
+
enabled: true,
|
|
231
|
+
lastMsgIndex: undefined,
|
|
232
|
+
};
|
|
233
|
+
} else if (typeof canHibernatWebSocket === "function") {
|
|
234
|
+
try {
|
|
235
|
+
// Truncate the path to match the behavior on onRawWebSocket
|
|
236
|
+
const newPath = truncateRawWebSocketPathPrefix(
|
|
237
|
+
url.pathname,
|
|
238
|
+
);
|
|
239
|
+
const truncatedRequest = new Request(
|
|
240
|
+
`http://actor${newPath}`,
|
|
241
|
+
request,
|
|
242
|
+
);
|
|
243
|
+
|
|
244
|
+
const canHibernate =
|
|
245
|
+
canHibernatWebSocket(truncatedRequest);
|
|
246
|
+
hibernationConfig = {
|
|
247
|
+
enabled: canHibernate,
|
|
248
|
+
lastMsgIndex: undefined,
|
|
249
|
+
};
|
|
250
|
+
} catch (error) {
|
|
251
|
+
logger().error({
|
|
252
|
+
msg: "error calling canHibernatWebSocket",
|
|
253
|
+
error,
|
|
254
|
+
});
|
|
255
|
+
hibernationConfig = {
|
|
256
|
+
enabled: false,
|
|
257
|
+
lastMsgIndex: undefined,
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
} else {
|
|
261
|
+
hibernationConfig = {
|
|
262
|
+
enabled: false,
|
|
263
|
+
lastMsgIndex: undefined,
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
} else {
|
|
267
|
+
logger().warn({
|
|
268
|
+
msg: "unexpected path for getActorHibernationConfig",
|
|
269
|
+
path,
|
|
270
|
+
});
|
|
271
|
+
hibernationConfig = {
|
|
272
|
+
enabled: false,
|
|
273
|
+
lastMsgIndex: undefined,
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Save hibernatable WebSocket
|
|
279
|
+
handler.actor[PERSIST_SYMBOL].hibernatableWebSocket.push({
|
|
280
|
+
requestId,
|
|
281
|
+
lastSeenTimestamp: BigInt(Date.now()),
|
|
282
|
+
msgIndex: -1n,
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
return hibernationConfig;
|
|
286
|
+
},
|
|
140
287
|
};
|
|
141
288
|
|
|
142
289
|
// Create and start runner
|
|
@@ -148,6 +295,15 @@ export class EngineActorDriver implements ActorDriver {
|
|
|
148
295
|
namespace: runConfig.namespace,
|
|
149
296
|
runnerName: runConfig.runnerName,
|
|
150
297
|
});
|
|
298
|
+
|
|
299
|
+
// Start WebSocket ack flush interval
|
|
300
|
+
//
|
|
301
|
+
// Decreasing this reduces the amount of buffered messages on the
|
|
302
|
+
// gateway
|
|
303
|
+
//
|
|
304
|
+
// Gateway timeout configured to 30s
|
|
305
|
+
// https://github.com/rivet-dev/rivet/blob/222dae87e3efccaffa2b503de40ecf8afd4e31eb/engine/packages/pegboard-gateway/src/shared_state.rs#L17
|
|
306
|
+
this.#wsAckFlushInterval = setInterval(() => this.#flushWsAcks(), 1000);
|
|
151
307
|
}
|
|
152
308
|
|
|
153
309
|
async #loadActorHandler(actorId: string): Promise<ActorHandler> {
|
|
@@ -166,6 +322,19 @@ export class EngineActorDriver implements ActorDriver {
|
|
|
166
322
|
return handler.actor;
|
|
167
323
|
}
|
|
168
324
|
|
|
325
|
+
#flushWsAcks(): void {
|
|
326
|
+
if (this.#wsAckQueue.size === 0) return;
|
|
327
|
+
|
|
328
|
+
for (const {
|
|
329
|
+
requestIdBuf: requestId,
|
|
330
|
+
messageIndex: index,
|
|
331
|
+
} of this.#wsAckQueue.values()) {
|
|
332
|
+
this.#runner.sendWebsocketMessageAck(requestId, index);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
this.#wsAckQueue.clear();
|
|
336
|
+
}
|
|
337
|
+
|
|
169
338
|
getContext(actorId: string): DriverContext {
|
|
170
339
|
return {};
|
|
171
340
|
}
|
|
@@ -296,7 +465,14 @@ export class EngineActorDriver implements ActorDriver {
|
|
|
296
465
|
|
|
297
466
|
const handler = this.#actors.get(actorId);
|
|
298
467
|
if (handler?.actor) {
|
|
299
|
-
|
|
468
|
+
try {
|
|
469
|
+
await handler.actor._onStop();
|
|
470
|
+
} catch (err) {
|
|
471
|
+
logger().error({
|
|
472
|
+
msg: "error in _onStop, proceeding with removing actor",
|
|
473
|
+
err: stringifyError(err),
|
|
474
|
+
});
|
|
475
|
+
}
|
|
300
476
|
this.#actors.delete(actorId);
|
|
301
477
|
}
|
|
302
478
|
|
|
@@ -304,8 +480,9 @@ export class EngineActorDriver implements ActorDriver {
|
|
|
304
480
|
}
|
|
305
481
|
|
|
306
482
|
async #runnerFetch(
|
|
307
|
-
|
|
483
|
+
_runner: Runner,
|
|
308
484
|
actorId: string,
|
|
485
|
+
_requestIdBuf: ArrayBuffer,
|
|
309
486
|
request: Request,
|
|
310
487
|
): Promise<Response> {
|
|
311
488
|
logger().debug({
|
|
@@ -318,12 +495,14 @@ export class EngineActorDriver implements ActorDriver {
|
|
|
318
495
|
}
|
|
319
496
|
|
|
320
497
|
async #runnerWebSocket(
|
|
321
|
-
|
|
498
|
+
_runner: Runner,
|
|
322
499
|
actorId: string,
|
|
323
500
|
websocketRaw: any,
|
|
501
|
+
requestIdBuf: ArrayBuffer,
|
|
324
502
|
request: Request,
|
|
325
503
|
): Promise<void> {
|
|
326
504
|
const websocket = websocketRaw as UniversalWebSocket;
|
|
505
|
+
const requestId = idToStr(requestIdBuf);
|
|
327
506
|
|
|
328
507
|
logger().debug({ msg: "runner websocket", actorId, url: request.url });
|
|
329
508
|
|
|
@@ -367,6 +546,7 @@ export class EngineActorDriver implements ActorDriver {
|
|
|
367
546
|
actorId,
|
|
368
547
|
encoding,
|
|
369
548
|
connParams,
|
|
549
|
+
requestId,
|
|
370
550
|
// Extract connId and connToken from protocols if needed
|
|
371
551
|
undefined,
|
|
372
552
|
undefined,
|
|
@@ -402,11 +582,37 @@ export class EngineActorDriver implements ActorDriver {
|
|
|
402
582
|
});
|
|
403
583
|
}
|
|
404
584
|
|
|
405
|
-
websocket.addEventListener("message", (event) => {
|
|
585
|
+
websocket.addEventListener("message", (event: RivetMessageEvent) => {
|
|
406
586
|
wsHandlerPromise.then((x) => x.onMessage?.(event, wsContext));
|
|
587
|
+
|
|
588
|
+
invariant(event.rivetRequestId, "missing rivetRequestId");
|
|
589
|
+
invariant(event.rivetMessageIndex, "missing rivetMessageIndex");
|
|
590
|
+
|
|
591
|
+
// Track only the highest seen message index per request
|
|
592
|
+
// Convert ArrayBuffer to string for Map key
|
|
593
|
+
const currentEntry = this.#wsAckQueue.get(requestId);
|
|
594
|
+
if (currentEntry) {
|
|
595
|
+
if (event.rivetMessageIndex > currentEntry.messageIndex) {
|
|
596
|
+
currentEntry.messageIndex = event.rivetMessageIndex;
|
|
597
|
+
} else {
|
|
598
|
+
logger().warn({
|
|
599
|
+
msg: "received lower index than ack queue for message",
|
|
600
|
+
requestId,
|
|
601
|
+
queuedMessageIndex: currentEntry,
|
|
602
|
+
eventMessageIndex: event.rivetMessageIndex,
|
|
603
|
+
});
|
|
604
|
+
}
|
|
605
|
+
} else {
|
|
606
|
+
this.#wsAckQueue.set(requestId, {
|
|
607
|
+
requestIdBuf,
|
|
608
|
+
messageIndex: event.rivetMessageIndex,
|
|
609
|
+
});
|
|
610
|
+
}
|
|
407
611
|
});
|
|
408
612
|
|
|
409
613
|
websocket.addEventListener("close", (event) => {
|
|
614
|
+
// Flush any pending acks before closing
|
|
615
|
+
this.#flushWsAcks();
|
|
410
616
|
wsHandlerPromise.then((x) => x.onClose?.(event, wsContext));
|
|
411
617
|
});
|
|
412
618
|
|
|
@@ -415,12 +621,22 @@ export class EngineActorDriver implements ActorDriver {
|
|
|
415
621
|
});
|
|
416
622
|
}
|
|
417
623
|
|
|
418
|
-
|
|
624
|
+
startSleep(actorId: string) {
|
|
419
625
|
this.#runner.sleepActor(actorId);
|
|
420
626
|
}
|
|
421
627
|
|
|
422
|
-
async
|
|
628
|
+
async shutdownRunner(immediate: boolean): Promise<void> {
|
|
423
629
|
logger().info({ msg: "stopping engine actor driver" });
|
|
630
|
+
|
|
631
|
+
// Clear the ack flush interval
|
|
632
|
+
if (this.#wsAckFlushInterval) {
|
|
633
|
+
clearInterval(this.#wsAckFlushInterval);
|
|
634
|
+
this.#wsAckFlushInterval = undefined;
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
// Flush any remaining acks
|
|
638
|
+
this.#flushWsAcks();
|
|
639
|
+
|
|
424
640
|
await this.#runner.shutdown(immediate);
|
|
425
641
|
}
|
|
426
642
|
|
|
@@ -430,7 +646,11 @@ export class EngineActorDriver implements ActorDriver {
|
|
|
430
646
|
stream.onAbort(() => {});
|
|
431
647
|
c.req.raw.signal.addEventListener("abort", () => {
|
|
432
648
|
logger().debug("SSE aborted, shutting down runner");
|
|
433
|
-
|
|
649
|
+
|
|
650
|
+
// 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.
|
|
651
|
+
//
|
|
652
|
+
// If we did not use a graceful shutdown, the runner would
|
|
653
|
+
this.shutdownRunner(false);
|
|
434
654
|
});
|
|
435
655
|
|
|
436
656
|
await this.#runnerStarted.promise;
|
|
@@ -440,7 +660,34 @@ export class EngineActorDriver implements ActorDriver {
|
|
|
440
660
|
invariant(payload, "runnerId not set");
|
|
441
661
|
await stream.writeSSE({ data: payload });
|
|
442
662
|
|
|
663
|
+
// Send ping every second to keep the connection alive
|
|
664
|
+
while (true) {
|
|
665
|
+
if (this.#isRunnerStopped) {
|
|
666
|
+
logger().debug({
|
|
667
|
+
msg: "runner is stopped",
|
|
668
|
+
});
|
|
669
|
+
break;
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
if (stream.closed || stream.aborted) {
|
|
673
|
+
logger().debug({
|
|
674
|
+
msg: "runner sse stream closed",
|
|
675
|
+
closed: stream.closed,
|
|
676
|
+
aborted: stream.aborted,
|
|
677
|
+
});
|
|
678
|
+
break;
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
await stream.writeSSE({ event: "ping", data: "" });
|
|
682
|
+
await stream.sleep(RUNNER_SSE_PING_INTERVAL);
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
// Wait for the runner to stop if the SSE stream aborted early for any reason
|
|
443
686
|
await this.#runnerStopped.promise;
|
|
444
687
|
});
|
|
445
688
|
}
|
|
689
|
+
|
|
690
|
+
getExtraActorLogParams(): Record<string, string> {
|
|
691
|
+
return { runnerId: this.#runner.runnerId ?? "-" };
|
|
692
|
+
}
|
|
446
693
|
}
|
|
@@ -7,10 +7,8 @@ export const EngingConfigSchema = z
|
|
|
7
7
|
/** Unique key for this runner. Runners connecting a given key will replace any other runner connected with the same key. */
|
|
8
8
|
runnerKey: z
|
|
9
9
|
.string()
|
|
10
|
-
.
|
|
11
|
-
|
|
12
|
-
getEnvUniversal("RIVET_RUNNER_KEY") ?? crypto.randomUUID(),
|
|
13
|
-
),
|
|
10
|
+
.optional()
|
|
11
|
+
.transform((x) => x ?? getEnvUniversal("RIVET_RUNNER_KEY")),
|
|
14
12
|
|
|
15
13
|
/** How many actors this runner can run. */
|
|
16
14
|
totalSlots: z.number().default(100_000),
|
|
@@ -79,7 +79,8 @@ export class FileSystemActorDriver implements ActorDriver {
|
|
|
79
79
|
return this.#state.createDatabase(actorId);
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
-
|
|
83
|
-
|
|
82
|
+
startSleep(actorId: string): void {
|
|
83
|
+
// Spawns the sleepActor promise
|
|
84
|
+
this.#state.sleepActor(actorId);
|
|
84
85
|
}
|
|
85
86
|
}
|
|
@@ -325,7 +325,7 @@ export class FileSystemGlobalState {
|
|
|
325
325
|
|
|
326
326
|
// Stop actor
|
|
327
327
|
invariant(actor.actor, "actor should be loaded");
|
|
328
|
-
await actor.actor.
|
|
328
|
+
await actor.actor._onStop();
|
|
329
329
|
|
|
330
330
|
// Remove from map after stop is complete
|
|
331
331
|
this.#actors.delete(actorId);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Context as HonoContext } from "hono";
|
|
2
2
|
import invariant from "invariant";
|
|
3
|
+
import { generateConnRequestId } from "@/actor/conn";
|
|
3
4
|
import { type ActorRouter, createActorRouter } from "@/actor/router";
|
|
4
5
|
import {
|
|
5
6
|
handleRawWebSocketHandler,
|
|
@@ -173,6 +174,7 @@ export class FileSystemManagerDriver implements ManagerDriver {
|
|
|
173
174
|
actorId,
|
|
174
175
|
encoding,
|
|
175
176
|
params,
|
|
177
|
+
generateConnRequestId(),
|
|
176
178
|
connId,
|
|
177
179
|
connToken,
|
|
178
180
|
);
|
|
@@ -231,6 +233,7 @@ export class FileSystemManagerDriver implements ManagerDriver {
|
|
|
231
233
|
actorId,
|
|
232
234
|
encoding,
|
|
233
235
|
connParams,
|
|
236
|
+
generateConnRequestId(),
|
|
234
237
|
connId,
|
|
235
238
|
connToken,
|
|
236
239
|
);
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
ensureDirectoryExists,
|
|
9
9
|
getStoragePath,
|
|
10
10
|
} from "@/drivers/file-system/utils";
|
|
11
|
+
import { EXTRA_ERROR_LOG } from "@/utils";
|
|
11
12
|
import { logger } from "./log";
|
|
12
13
|
|
|
13
14
|
export const ENGINE_PORT = 6420;
|
|
@@ -84,6 +85,25 @@ export async function ensureEngineProcess(
|
|
|
84
85
|
stdio: ["inherit", "pipe", "pipe"],
|
|
85
86
|
env: {
|
|
86
87
|
...process.env,
|
|
88
|
+
// In development, runners can be terminated without a graceful
|
|
89
|
+
// shutdown (i.e. SIGKILL instead of SIGTERM). This is treated as a
|
|
90
|
+
// crash by Rivet Engine in production and implements a backoff for
|
|
91
|
+
// rescheduling actors in case of a crash loop.
|
|
92
|
+
//
|
|
93
|
+
// This is problematic in development since this will cause actors
|
|
94
|
+
// to become unresponsive if frequently killing your dev server.
|
|
95
|
+
//
|
|
96
|
+
// We reduce the timeouts for resetting a runner as healthy in
|
|
97
|
+
// order to account for this.
|
|
98
|
+
RIVET__PEGBOARD__RETRY_RESET_DURATION: "100",
|
|
99
|
+
RIVET__PEGBOARD__BASE_RETRY_TIMEOUT: "100",
|
|
100
|
+
// Set max exponent to 1 to have a maximum of base_retry_timeout
|
|
101
|
+
RIVET__PEGBOARD__RESCHEDULE_BACKOFF_MAX_EXPONENT: "1",
|
|
102
|
+
// Reduce thresholds for faster development iteration
|
|
103
|
+
//
|
|
104
|
+
// Default ping interval is 3s, this gives a 2s & 4s grace
|
|
105
|
+
RIVET__PEGBOARD__RUNNER_ELIGIBLE_THRESHOLD: "5000",
|
|
106
|
+
RIVET__PEGBOARD__RUNNER_LOST_THRESHOLD: "7000",
|
|
87
107
|
},
|
|
88
108
|
});
|
|
89
109
|
|
|
@@ -110,8 +130,7 @@ export async function ensureEngineProcess(
|
|
|
110
130
|
msg: "engine process exited, please report this error",
|
|
111
131
|
code,
|
|
112
132
|
signal,
|
|
113
|
-
|
|
114
|
-
support: "https://rivet.dev/discord",
|
|
133
|
+
...EXTRA_ERROR_LOG,
|
|
115
134
|
});
|
|
116
135
|
// Clean up log streams
|
|
117
136
|
stdoutStream.end();
|
|
@@ -228,8 +247,7 @@ async function downloadEngineBinaryIfNeeded(
|
|
|
228
247
|
msg: "engine download failed, please report this error",
|
|
229
248
|
tempPath,
|
|
230
249
|
error,
|
|
231
|
-
|
|
232
|
-
support: "https://rivet.dev/discord",
|
|
250
|
+
...EXTRA_ERROR_LOG,
|
|
233
251
|
});
|
|
234
252
|
try {
|
|
235
253
|
await fs.unlink(tempPath);
|
package/src/inspector/config.ts
CHANGED
|
@@ -1,10 +1,6 @@
|
|
|
1
|
-
import type { cors } from "hono/cors";
|
|
2
1
|
import { z } from "zod";
|
|
3
|
-
import { HEADER_ACTOR_QUERY } from "@/driver-helpers/mod";
|
|
4
2
|
import { getEnvUniversal } from "@/utils";
|
|
5
3
|
|
|
6
|
-
type CorsOptions = NonNullable<Parameters<typeof cors>[0]>;
|
|
7
|
-
|
|
8
4
|
const defaultTokenFn = () => {
|
|
9
5
|
const envToken = getEnvUniversal("RIVETKIT_INSPECTOR_TOKEN");
|
|
10
6
|
|
|
@@ -22,41 +18,6 @@ const defaultEnabled = () => {
|
|
|
22
18
|
);
|
|
23
19
|
};
|
|
24
20
|
|
|
25
|
-
const defaultInspectorOrigins = [
|
|
26
|
-
"http://localhost:43708",
|
|
27
|
-
"http://localhost:43709",
|
|
28
|
-
"https://studio.rivet.gg",
|
|
29
|
-
"https://inspect.rivet.dev",
|
|
30
|
-
"https://dashboard.rivet.dev",
|
|
31
|
-
"https://dashboard.staging.rivet.dev",
|
|
32
|
-
];
|
|
33
|
-
|
|
34
|
-
const defaultCors: CorsOptions = {
|
|
35
|
-
origin: (origin) => {
|
|
36
|
-
if (
|
|
37
|
-
defaultInspectorOrigins.includes(origin) ||
|
|
38
|
-
(origin.startsWith("https://") &&
|
|
39
|
-
origin.endsWith("rivet-dev.vercel.app"))
|
|
40
|
-
) {
|
|
41
|
-
return origin;
|
|
42
|
-
} else {
|
|
43
|
-
return null;
|
|
44
|
-
}
|
|
45
|
-
},
|
|
46
|
-
allowMethods: ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"],
|
|
47
|
-
allowHeaders: [
|
|
48
|
-
"Authorization",
|
|
49
|
-
"Content-Type",
|
|
50
|
-
"User-Agent",
|
|
51
|
-
"baggage",
|
|
52
|
-
"sentry-trace",
|
|
53
|
-
"x-rivet-actor",
|
|
54
|
-
"x-rivet-target",
|
|
55
|
-
],
|
|
56
|
-
maxAge: 3600,
|
|
57
|
-
credentials: true,
|
|
58
|
-
};
|
|
59
|
-
|
|
60
21
|
export const InspectorConfigSchema = z
|
|
61
22
|
.object({
|
|
62
23
|
enabled: z
|
|
@@ -69,11 +30,6 @@ export const InspectorConfigSchema = z
|
|
|
69
30
|
)
|
|
70
31
|
.optional()
|
|
71
32
|
.default(defaultEnabled),
|
|
72
|
-
/** CORS configuration for the router. Uses Hono's CORS middleware options. */
|
|
73
|
-
cors: z
|
|
74
|
-
.custom<CorsOptions>()
|
|
75
|
-
.optional()
|
|
76
|
-
.default(() => defaultCors),
|
|
77
33
|
|
|
78
34
|
/**
|
|
79
35
|
* Token used to access the Inspector.
|
|
@@ -95,6 +51,5 @@ export const InspectorConfigSchema = z
|
|
|
95
51
|
.default(() => ({
|
|
96
52
|
enabled: defaultEnabled(),
|
|
97
53
|
token: defaultTokenFn,
|
|
98
|
-
cors: defaultCors,
|
|
99
54
|
}));
|
|
100
55
|
export type InspectorConfig = z.infer<typeof InspectorConfigSchema>;
|