rivetkit 2.0.22-rc.1 → 2.0.22

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.
Files changed (124) hide show
  1. package/dist/schemas/actor-persist/v2.ts +259 -0
  2. package/dist/tsup/{chunk-LCQDY73V.cjs → chunk-2GJILCGQ.cjs} +3 -3
  3. package/dist/tsup/{chunk-LCQDY73V.cjs.map → chunk-2GJILCGQ.cjs.map} +1 -1
  4. package/dist/tsup/{chunk-JN6GPVFY.js → chunk-2K2LR56Q.js} +3 -3
  5. package/dist/tsup/{chunk-EEXX243L.js → chunk-2WVCZCJL.js} +6 -6
  6. package/dist/tsup/{chunk-FETQGZN4.js → chunk-3BJJSSTM.js} +272 -89
  7. package/dist/tsup/chunk-3BJJSSTM.js.map +1 -0
  8. package/dist/tsup/{chunk-ZZYMCYAY.cjs → chunk-3LFMVAJV.cjs} +14 -14
  9. package/dist/tsup/{chunk-ZZYMCYAY.cjs.map → chunk-3LFMVAJV.cjs.map} +1 -1
  10. package/dist/tsup/{chunk-NDLOG2JH.js → chunk-6YQKMAMV.js} +2 -2
  11. package/dist/tsup/{chunk-C2U6KGOG.cjs → chunk-AR4S2QJ7.cjs} +3 -3
  12. package/dist/tsup/{chunk-C2U6KGOG.cjs.map → chunk-AR4S2QJ7.cjs.map} +1 -1
  13. package/dist/tsup/{chunk-PELXJCJS.cjs → chunk-B4QZKOMH.cjs} +8 -8
  14. package/dist/tsup/{chunk-PELXJCJS.cjs.map → chunk-B4QZKOMH.cjs.map} +1 -1
  15. package/dist/tsup/{chunk-5EB77IQ2.cjs → chunk-CYA35VI3.cjs} +6 -6
  16. package/dist/tsup/{chunk-5EB77IQ2.cjs.map → chunk-CYA35VI3.cjs.map} +1 -1
  17. package/dist/tsup/{chunk-UBCUW7HD.js → chunk-D7AA2DK5.js} +2 -2
  18. package/dist/tsup/{chunk-I7EJWHYV.js → chunk-EBSGEDD3.js} +51 -47
  19. package/dist/tsup/chunk-EBSGEDD3.js.map +1 -0
  20. package/dist/tsup/{chunk-R6XOZKMU.cjs → chunk-HSO2H2SB.cjs} +467 -284
  21. package/dist/tsup/chunk-HSO2H2SB.cjs.map +1 -0
  22. package/dist/tsup/{chunk-VJLGVVGP.cjs → chunk-HZ4ZM3FL.cjs} +31 -12
  23. package/dist/tsup/chunk-HZ4ZM3FL.cjs.map +1 -0
  24. package/dist/tsup/{chunk-7FEMVD3D.cjs → chunk-LMZSOCYD.cjs} +12 -12
  25. package/dist/tsup/{chunk-7FEMVD3D.cjs.map → chunk-LMZSOCYD.cjs.map} +1 -1
  26. package/dist/tsup/{chunk-ZVEDMBFT.js → chunk-PBFLG45S.js} +3 -3
  27. package/dist/tsup/{chunk-GJPOIJHZ.js → chunk-ST6FGRCH.js} +26 -7
  28. package/dist/tsup/chunk-ST6FGRCH.js.map +1 -0
  29. package/dist/tsup/{chunk-BIOPK7IB.cjs → chunk-TI72NLP3.cjs} +71 -67
  30. package/dist/tsup/chunk-TI72NLP3.cjs.map +1 -0
  31. package/dist/tsup/{chunk-RPI45FGS.js → chunk-TQ4OAC2G.js} +2 -2
  32. package/dist/tsup/{chunk-4B25D5OW.js → chunk-UB4OHFDW.js} +385 -104
  33. package/dist/tsup/chunk-UB4OHFDW.js.map +1 -0
  34. package/dist/tsup/{chunk-6Z3YA6QR.cjs → chunk-V6C34TVH.cjs} +35 -15
  35. package/dist/tsup/chunk-V6C34TVH.cjs.map +1 -0
  36. package/dist/tsup/{chunk-OAB7ECAB.cjs → chunk-WVUAO2F7.cjs} +558 -277
  37. package/dist/tsup/chunk-WVUAO2F7.cjs.map +1 -0
  38. package/dist/tsup/{chunk-JKNDUKFI.js → chunk-WWAZJHTS.js} +36 -16
  39. package/dist/tsup/chunk-WWAZJHTS.js.map +1 -0
  40. package/dist/tsup/client/mod.cjs +9 -9
  41. package/dist/tsup/client/mod.d.cts +2 -2
  42. package/dist/tsup/client/mod.d.ts +2 -2
  43. package/dist/tsup/client/mod.js +8 -8
  44. package/dist/tsup/common/log.cjs +3 -3
  45. package/dist/tsup/common/log.js +2 -2
  46. package/dist/tsup/common/websocket.cjs +4 -4
  47. package/dist/tsup/common/websocket.js +3 -3
  48. package/dist/tsup/{conn-Clu655RU.d.ts → conn-BYXlxnh0.d.ts} +111 -102
  49. package/dist/tsup/{conn-lUvFLo_q.d.cts → conn-BiazosE_.d.cts} +111 -102
  50. package/dist/tsup/driver-helpers/mod.cjs +5 -5
  51. package/dist/tsup/driver-helpers/mod.d.cts +1 -1
  52. package/dist/tsup/driver-helpers/mod.d.ts +1 -1
  53. package/dist/tsup/driver-helpers/mod.js +4 -4
  54. package/dist/tsup/driver-test-suite/mod.cjs +71 -71
  55. package/dist/tsup/driver-test-suite/mod.d.cts +1 -1
  56. package/dist/tsup/driver-test-suite/mod.d.ts +1 -1
  57. package/dist/tsup/driver-test-suite/mod.js +11 -11
  58. package/dist/tsup/inspector/mod.cjs +6 -6
  59. package/dist/tsup/inspector/mod.d.cts +2 -2
  60. package/dist/tsup/inspector/mod.d.ts +2 -2
  61. package/dist/tsup/inspector/mod.js +5 -5
  62. package/dist/tsup/mod.cjs +10 -10
  63. package/dist/tsup/mod.d.cts +3 -3
  64. package/dist/tsup/mod.d.ts +3 -3
  65. package/dist/tsup/mod.js +9 -9
  66. package/dist/tsup/test/mod.cjs +11 -11
  67. package/dist/tsup/test/mod.d.cts +1 -1
  68. package/dist/tsup/test/mod.d.ts +1 -1
  69. package/dist/tsup/test/mod.js +10 -10
  70. package/dist/tsup/utils.cjs +8 -2
  71. package/dist/tsup/utils.cjs.map +1 -1
  72. package/dist/tsup/utils.d.cts +8 -1
  73. package/dist/tsup/utils.d.ts +8 -1
  74. package/dist/tsup/utils.js +7 -1
  75. package/package.json +5 -4
  76. package/src/actor/config.ts +10 -0
  77. package/src/actor/conn-drivers.ts +43 -1
  78. package/src/actor/conn-socket.ts +1 -1
  79. package/src/actor/conn.ts +22 -2
  80. package/src/actor/context.ts +1 -1
  81. package/src/actor/driver.ts +13 -2
  82. package/src/actor/instance.ts +248 -57
  83. package/src/actor/persisted.ts +7 -0
  84. package/src/actor/router-endpoints.ts +67 -45
  85. package/src/actor/router.ts +25 -17
  86. package/src/client/actor-conn.ts +9 -5
  87. package/src/common/cors.ts +57 -0
  88. package/src/common/log.ts +26 -5
  89. package/src/common/utils.ts +5 -9
  90. package/src/common/websocket-interface.ts +10 -0
  91. package/src/driver-helpers/utils.ts +1 -0
  92. package/src/drivers/engine/actor-driver.ts +261 -14
  93. package/src/drivers/engine/config.ts +2 -4
  94. package/src/drivers/file-system/actor.ts +3 -2
  95. package/src/drivers/file-system/global-state.ts +1 -1
  96. package/src/drivers/file-system/manager.ts +3 -0
  97. package/src/engine-process/mod.ts +22 -4
  98. package/src/inspector/config.ts +0 -45
  99. package/src/manager/gateway.ts +45 -32
  100. package/src/manager/hono-websocket-adapter.ts +31 -3
  101. package/src/manager/router.ts +11 -17
  102. package/src/registry/run-config.ts +2 -8
  103. package/src/remote-manager-driver/actor-http-client.ts +5 -8
  104. package/src/remote-manager-driver/actor-websocket-client.ts +2 -14
  105. package/src/remote-manager-driver/mod.ts +0 -1
  106. package/src/schemas/actor-persist/mod.ts +1 -1
  107. package/src/schemas/actor-persist/versioned.ts +22 -10
  108. package/src/utils.ts +26 -0
  109. package/dist/tsup/chunk-4B25D5OW.js.map +0 -1
  110. package/dist/tsup/chunk-6Z3YA6QR.cjs.map +0 -1
  111. package/dist/tsup/chunk-BIOPK7IB.cjs.map +0 -1
  112. package/dist/tsup/chunk-FETQGZN4.js.map +0 -1
  113. package/dist/tsup/chunk-GJPOIJHZ.js.map +0 -1
  114. package/dist/tsup/chunk-I7EJWHYV.js.map +0 -1
  115. package/dist/tsup/chunk-JKNDUKFI.js.map +0 -1
  116. package/dist/tsup/chunk-OAB7ECAB.cjs.map +0 -1
  117. package/dist/tsup/chunk-R6XOZKMU.cjs.map +0 -1
  118. package/dist/tsup/chunk-VJLGVVGP.cjs.map +0 -1
  119. /package/dist/tsup/{chunk-JN6GPVFY.js.map → chunk-2K2LR56Q.js.map} +0 -0
  120. /package/dist/tsup/{chunk-EEXX243L.js.map → chunk-2WVCZCJL.js.map} +0 -0
  121. /package/dist/tsup/{chunk-NDLOG2JH.js.map → chunk-6YQKMAMV.js.map} +0 -0
  122. /package/dist/tsup/{chunk-UBCUW7HD.js.map → chunk-D7AA2DK5.js.map} +0 -0
  123. /package/dist/tsup/{chunk-ZVEDMBFT.js.map → chunk-PBFLG45S.js.map} +0 -0
  124. /package/dist/tsup/{chunk-RPI45FGS.js.map → chunk-TQ4OAC2G.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 { UniversalWebSocket } from "@/common/websocket-interface";
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 ?? 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 ?? runConfig.namespace,
100
- totalSlots: runConfig.totalSlots ?? runConfig.totalSlots,
101
- runnerName: runConfig.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
- await handler.actor._stop();
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
- runner: Runner,
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
- runner: Runner,
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
- async sleep(actorId: string) {
624
+ startSleep(actorId: string) {
419
625
  this.#runner.sleepActor(actorId);
420
626
  }
421
627
 
422
- async shutdown(immediate: boolean): Promise<void> {
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
- this.shutdown(true);
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
- .default(
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
- sleep(actorId: string): Promise<void> {
83
- return this.#state.sleepActor(actorId);
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._stop();
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
- issues: "https://github.com/rivet-dev/rivetkit/issues",
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
- issues: "https://github.com/rivet-dev/rivetkit/issues",
232
- support: "https://rivet.dev/discord",
250
+ ...EXTRA_ERROR_LOG,
233
251
  });
234
252
  try {
235
253
  await fs.unlink(tempPath);
@@ -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>;