rivetkit 2.3.0-rc.5 → 2.3.0-rc.6
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/browser/client.d.ts +21 -18
- package/dist/browser/client.js +735 -170
- package/dist/browser/client.js.map +1 -1
- package/dist/browser/inspector/client.js +1 -1
- package/dist/browser/inspector/client.js.map +1 -1
- package/dist/tsup/actor/errors.cjs +4 -2
- package/dist/tsup/actor/errors.cjs.map +1 -1
- package/dist/tsup/actor/errors.d.cts +1 -74
- package/dist/tsup/actor/errors.d.ts +1 -74
- package/dist/tsup/actor/errors.js +3 -1
- package/dist/tsup/agent-os/index.cjs +1 -1
- package/dist/tsup/agent-os/index.cjs.map +1 -1
- package/dist/tsup/agent-os/index.d.cts +18 -7
- package/dist/tsup/agent-os/index.d.ts +18 -7
- package/dist/tsup/agent-os/index.js +1 -1
- package/dist/tsup/agent-os/index.js.map +1 -1
- package/dist/tsup/{chunk-2GANBXVP.cjs → chunk-2G64KSZQ.cjs} +10 -10
- package/dist/tsup/{chunk-2GANBXVP.cjs.map → chunk-2G64KSZQ.cjs.map} +1 -1
- package/dist/tsup/{chunk-N2DQSJIW.js → chunk-6S25NVAP.js} +13 -46
- package/dist/tsup/chunk-6S25NVAP.js.map +1 -0
- package/dist/tsup/{chunk-NATOT3ET.js → chunk-CAF6JDJE.js} +4 -4
- package/dist/tsup/{chunk-PGYEMIOE.js → chunk-DEO7MMWQ.js} +2 -2
- package/dist/tsup/{chunk-SULB574D.js → chunk-EMO6E3PJ.js} +3 -3
- package/dist/tsup/{chunk-FTZIZ3JG.cjs → chunk-ENK7C66G.cjs} +838 -236
- package/dist/tsup/chunk-ENK7C66G.cjs.map +1 -0
- package/dist/tsup/{chunk-JY73X7VU.js → chunk-FLODVLYW.js} +690 -88
- package/dist/tsup/chunk-FLODVLYW.js.map +1 -0
- package/dist/tsup/{chunk-OVJX4IFY.cjs → chunk-HTR4YLNT.cjs} +4 -4
- package/dist/tsup/{chunk-OVJX4IFY.cjs.map → chunk-HTR4YLNT.cjs.map} +1 -1
- package/dist/tsup/{chunk-LELRJK66.cjs → chunk-JALSAX7Z.cjs} +3 -3
- package/dist/tsup/{chunk-LELRJK66.cjs.map → chunk-JALSAX7Z.cjs.map} +1 -1
- package/dist/tsup/{chunk-JRCZDHXT.cjs → chunk-K5BA2LEO.cjs} +19 -52
- package/dist/tsup/chunk-K5BA2LEO.cjs.map +1 -0
- package/dist/tsup/{chunk-K34B3OVG.js → chunk-KIWH5H3K.js} +30 -9
- package/dist/tsup/chunk-KIWH5H3K.js.map +1 -0
- package/dist/tsup/{chunk-NW2J4SOL.cjs → chunk-LIXXFXVR.cjs} +5 -5
- package/dist/tsup/{chunk-NW2J4SOL.cjs.map → chunk-LIXXFXVR.cjs.map} +1 -1
- package/dist/tsup/{chunk-HR547GVH.cjs → chunk-M5C7YNI5.cjs} +8 -8
- package/dist/tsup/{chunk-HR547GVH.cjs.map → chunk-M5C7YNI5.cjs.map} +1 -1
- package/dist/tsup/{chunk-V3QNBJ7N.cjs → chunk-QAZLM4WT.cjs} +31 -10
- package/dist/tsup/chunk-QAZLM4WT.cjs.map +1 -0
- package/dist/tsup/{chunk-LDTT6WKJ.js → chunk-RTC2AZGB.js} +2 -2
- package/dist/tsup/{chunk-UXTP4EBU.js → chunk-ZI5QJMKO.js} +2 -2
- package/dist/tsup/client/mod.cjs +7 -7
- package/dist/tsup/client/mod.d.cts +3 -4
- package/dist/tsup/client/mod.d.ts +3 -4
- package/dist/tsup/client/mod.js +6 -6
- 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/{config-CvQUtDp9.d.ts → config-0Ta55UV0.d.ts} +10 -9
- package/dist/tsup/{config-C-a9vrke.d.cts → config-Ca8dN4cS.d.cts} +10 -9
- package/dist/tsup/{context-A7R0bsZL.d.ts → context-B_IWbWne.d.ts} +1 -1
- package/dist/tsup/{context-CA3r-pf2.d.cts → context-CUrQ9MHc.d.cts} +1 -1
- package/dist/tsup/inspector/mod.cjs +6 -6
- package/dist/tsup/inspector/mod.js +5 -5
- package/dist/tsup/mod.cjs +252 -207
- package/dist/tsup/mod.cjs.map +1 -1
- package/dist/tsup/mod.d.cts +4 -5
- package/dist/tsup/mod.d.ts +4 -5
- package/dist/tsup/mod.js +183 -138
- package/dist/tsup/mod.js.map +1 -1
- package/dist/tsup/test/mod.cjs +10 -10
- package/dist/tsup/test/mod.d.cts +2 -3
- package/dist/tsup/test/mod.d.ts +2 -3
- package/dist/tsup/test/mod.js +6 -6
- package/dist/tsup/utils-DVekpm4I.d.cts +103 -0
- package/dist/tsup/utils-DVekpm4I.d.ts +103 -0
- package/dist/tsup/utils.cjs +3 -3
- package/dist/tsup/utils.d.cts +1 -1
- package/dist/tsup/utils.d.ts +1 -1
- package/dist/tsup/utils.js +2 -2
- package/dist/tsup/workflow/mod.cjs +9 -9
- package/dist/tsup/workflow/mod.d.cts +4 -5
- package/dist/tsup/workflow/mod.d.ts +4 -5
- package/dist/tsup/workflow/mod.js +5 -5
- package/package.json +8 -8
- package/src/actor/errors.ts +53 -7
- package/src/client/actor-conn.ts +52 -29
- package/src/client/actor-handle.ts +57 -23
- package/src/client/errors.ts +2 -1
- package/src/client/raw-utils.ts +2 -4
- package/src/client/utils.ts +32 -3
- package/src/common/actor-router-consts.ts +4 -0
- package/src/common/bare/generated/client-protocol/v4.ts +599 -0
- package/src/common/client-protocol-versioned.ts +125 -18
- package/src/common/client-protocol-zod.ts +7 -0
- package/src/common/client-protocol.ts +1 -1
- package/src/common/database/native-database.test.ts +35 -0
- package/src/common/database/native-database.ts +8 -4
- package/src/common/router.ts +38 -8
- package/src/common/utils.ts +9 -52
- package/src/drivers/engine/actor-driver.ts +18 -17
- package/src/registry/config/index.ts +3 -5
- package/src/registry/index.ts +156 -19
- package/src/registry/napi-runtime.ts +44 -6
- package/src/registry/native.ts +5 -152
- package/src/registry/runtime.ts +16 -5
- package/src/registry/wasm-runtime.ts +22 -2
- package/dist/tsup/chunk-FTZIZ3JG.cjs.map +0 -1
- package/dist/tsup/chunk-JRCZDHXT.cjs.map +0 -1
- package/dist/tsup/chunk-JY73X7VU.js.map +0 -1
- package/dist/tsup/chunk-K34B3OVG.js.map +0 -1
- package/dist/tsup/chunk-N2DQSJIW.js.map +0 -1
- package/dist/tsup/chunk-V3QNBJ7N.cjs.map +0 -1
- package/dist/tsup/utils-fwx3o3K9.d.cts +0 -18
- package/dist/tsup/utils-fwx3o3K9.d.ts +0 -18
- /package/dist/tsup/{chunk-NATOT3ET.js.map → chunk-CAF6JDJE.js.map} +0 -0
- /package/dist/tsup/{chunk-PGYEMIOE.js.map → chunk-DEO7MMWQ.js.map} +0 -0
- /package/dist/tsup/{chunk-SULB574D.js.map → chunk-EMO6E3PJ.js.map} +0 -0
- /package/dist/tsup/{chunk-LDTT6WKJ.js.map → chunk-RTC2AZGB.js.map} +0 -0
- /package/dist/tsup/{chunk-UXTP4EBU.js.map → chunk-ZI5QJMKO.js.map} +0 -0
|
@@ -78,7 +78,7 @@ import {
|
|
|
78
78
|
import { logger } from "./log";
|
|
79
79
|
|
|
80
80
|
const ENVOY_SSE_PING_INTERVAL = 1000;
|
|
81
|
-
const
|
|
81
|
+
const FALLBACK_ENVOY_STOP_WAIT_MS = 15_000;
|
|
82
82
|
const INITIAL_SLEEP_TIMEOUT_MS = 250;
|
|
83
83
|
const REMOTE_ACK_HOOK_QUERY_PARAM = "__rivetkitAckHook";
|
|
84
84
|
|
|
@@ -847,6 +847,7 @@ export class EngineActorDriver implements ActorDriver {
|
|
|
847
847
|
return;
|
|
848
848
|
}
|
|
849
849
|
this.#isShuttingDown = true;
|
|
850
|
+
const envoyStopWaitMs = this.#envoyStopWaitMs();
|
|
850
851
|
|
|
851
852
|
logger().info({ msg: "stopping engine actor driver", immediate });
|
|
852
853
|
if (!immediate) {
|
|
@@ -861,7 +862,7 @@ export class EngineActorDriver implements ActorDriver {
|
|
|
861
862
|
this.startSleep(actorId);
|
|
862
863
|
}
|
|
863
864
|
|
|
864
|
-
const actorSleepDeadline = Date.now() +
|
|
865
|
+
const actorSleepDeadline = Date.now() + envoyStopWaitMs;
|
|
865
866
|
while (this.#actors.size > 0 && Date.now() < actorSleepDeadline) {
|
|
866
867
|
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
867
868
|
}
|
|
@@ -870,7 +871,7 @@ export class EngineActorDriver implements ActorDriver {
|
|
|
870
871
|
logger().warn({
|
|
871
872
|
msg: "timed out waiting for actors to stop before envoy drain",
|
|
872
873
|
remainingActors: this.#actors.size,
|
|
873
|
-
waitMs:
|
|
874
|
+
waitMs: envoyStopWaitMs,
|
|
874
875
|
});
|
|
875
876
|
// Snapshot so concurrent removals from `stopActor` do not
|
|
876
877
|
// invalidate the iterator.
|
|
@@ -912,13 +913,13 @@ export class EngineActorDriver implements ActorDriver {
|
|
|
912
913
|
const stopped = await Promise.race([
|
|
913
914
|
this.#envoyStopped.promise.then(() => true),
|
|
914
915
|
new Promise<false>((resolve) =>
|
|
915
|
-
setTimeout(() => resolve(false),
|
|
916
|
+
setTimeout(() => resolve(false), envoyStopWaitMs),
|
|
916
917
|
),
|
|
917
918
|
]);
|
|
918
919
|
if (!stopped) {
|
|
919
920
|
logger().warn({
|
|
920
921
|
msg: "timed out waiting for envoy shutdown",
|
|
921
|
-
waitMs:
|
|
922
|
+
waitMs: envoyStopWaitMs,
|
|
922
923
|
});
|
|
923
924
|
}
|
|
924
925
|
|
|
@@ -929,6 +930,16 @@ export class EngineActorDriver implements ActorDriver {
|
|
|
929
930
|
await this.#envoy.started();
|
|
930
931
|
}
|
|
931
932
|
|
|
933
|
+
#envoyStopWaitMs(): number {
|
|
934
|
+
const actorStopThreshold = Number(
|
|
935
|
+
this.#envoy.getProtocolMetadata()?.actorStopThreshold,
|
|
936
|
+
);
|
|
937
|
+
if (Number.isFinite(actorStopThreshold) && actorStopThreshold > 0) {
|
|
938
|
+
return actorStopThreshold;
|
|
939
|
+
}
|
|
940
|
+
return FALLBACK_ENVOY_STOP_WAIT_MS;
|
|
941
|
+
}
|
|
942
|
+
|
|
932
943
|
async #bindHibernatableConnectSocket(
|
|
933
944
|
binding: HibernatableConnectBinding,
|
|
934
945
|
isRestoringHibernatable: boolean,
|
|
@@ -2190,12 +2201,7 @@ export class EngineActorDriver implements ActorDriver {
|
|
|
2190
2201
|
isRestoringHibernatable,
|
|
2191
2202
|
);
|
|
2192
2203
|
} catch (error) {
|
|
2193
|
-
const { group, code } = deconstructError(
|
|
2194
|
-
error,
|
|
2195
|
-
logger(),
|
|
2196
|
-
{},
|
|
2197
|
-
false,
|
|
2198
|
-
);
|
|
2204
|
+
const { group, code } = deconstructError(error, false);
|
|
2199
2205
|
logger().error({
|
|
2200
2206
|
msg: "failed to bind dynamic hibernatable websocket",
|
|
2201
2207
|
actorId,
|
|
@@ -2233,12 +2239,7 @@ export class EngineActorDriver implements ActorDriver {
|
|
|
2233
2239
|
},
|
|
2234
2240
|
);
|
|
2235
2241
|
} catch (error) {
|
|
2236
|
-
const { group, code } = deconstructError(
|
|
2237
|
-
error,
|
|
2238
|
-
logger(),
|
|
2239
|
-
{},
|
|
2240
|
-
false,
|
|
2241
|
-
);
|
|
2242
|
+
const { group, code } = deconstructError(error, false);
|
|
2242
2243
|
logger().error({
|
|
2243
2244
|
msg: "failed to open dynamic websocket",
|
|
2244
2245
|
actorId,
|
|
@@ -264,8 +264,8 @@ export const RegistryConfigSchema = z
|
|
|
264
264
|
.object({
|
|
265
265
|
/**
|
|
266
266
|
* Wait this many milliseconds for the serve promise to resolve
|
|
267
|
-
* after calling `CoreRegistry::shutdown()`. Defaults to
|
|
268
|
-
*
|
|
267
|
+
* after calling `CoreRegistry::shutdown()`. Defaults to the
|
|
268
|
+
* engine-provided actor stop threshold once the envoy connects.
|
|
269
269
|
*
|
|
270
270
|
* Must be >= rivetkit-core's drain timeout (20s) + margin.
|
|
271
271
|
*/
|
|
@@ -273,8 +273,7 @@ export const RegistryConfigSchema = z
|
|
|
273
273
|
.number()
|
|
274
274
|
.int()
|
|
275
275
|
.min(1_000)
|
|
276
|
-
.optional()
|
|
277
|
-
.default(30_000),
|
|
276
|
+
.optional(),
|
|
278
277
|
/**
|
|
279
278
|
* If true, rivetkit will not install SIGINT/SIGTERM handlers.
|
|
280
279
|
* Use when the host application owns signal policy and will
|
|
@@ -284,7 +283,6 @@ export const RegistryConfigSchema = z
|
|
|
284
283
|
})
|
|
285
284
|
.optional()
|
|
286
285
|
.default(() => ({
|
|
287
|
-
gracePeriodMs: 30_000,
|
|
288
286
|
disableSignalHandlers: false,
|
|
289
287
|
})),
|
|
290
288
|
})
|
package/src/registry/index.ts
CHANGED
|
@@ -13,6 +13,22 @@ import type { RuntimeServerlessResponseHead } from "./runtime";
|
|
|
13
13
|
|
|
14
14
|
type ShutdownSignal = "SIGINT" | "SIGTERM";
|
|
15
15
|
|
|
16
|
+
function signalExitCode(signal: ShutdownSignal): number {
|
|
17
|
+
switch (signal) {
|
|
18
|
+
case "SIGINT":
|
|
19
|
+
return 130;
|
|
20
|
+
case "SIGTERM":
|
|
21
|
+
return 143;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function finishShutdownSignal(signal: ShutdownSignal): void {
|
|
26
|
+
if (process.pid === 1) {
|
|
27
|
+
process.exit(signalExitCode(signal));
|
|
28
|
+
}
|
|
29
|
+
process.kill(process.pid, signal);
|
|
30
|
+
}
|
|
31
|
+
|
|
16
32
|
export type FetchHandler = (
|
|
17
33
|
request: Request,
|
|
18
34
|
...args: any
|
|
@@ -22,13 +38,15 @@ export interface ServerlessHandler {
|
|
|
22
38
|
fetch: FetchHandler;
|
|
23
39
|
}
|
|
24
40
|
|
|
25
|
-
export interface
|
|
26
|
-
|
|
27
|
-
|
|
41
|
+
export interface RegistryRoutes {
|
|
42
|
+
health(): Promise<Response>;
|
|
43
|
+
metadata(): Promise<Response>;
|
|
44
|
+
prometheusMetrics(request?: Request): Promise<Response>;
|
|
28
45
|
}
|
|
29
46
|
|
|
30
47
|
export class Registry<A extends RegistryActors> {
|
|
31
48
|
#config: RegistryConfigInput<A>;
|
|
49
|
+
public readonly routes: RegistryRoutes;
|
|
32
50
|
|
|
33
51
|
get config(): RegistryConfigInput<A> {
|
|
34
52
|
return this.#config;
|
|
@@ -49,6 +67,12 @@ export class Registry<A extends RegistryActors> {
|
|
|
49
67
|
|
|
50
68
|
constructor(config: RegistryConfigInput<A>) {
|
|
51
69
|
this.#config = config;
|
|
70
|
+
this.routes = {
|
|
71
|
+
health: () => this.#healthRoute(),
|
|
72
|
+
metadata: () => this.#metadataRoute(),
|
|
73
|
+
prometheusMetrics: (request?: Request) =>
|
|
74
|
+
this.#prometheusMetricsRoute(request),
|
|
75
|
+
};
|
|
52
76
|
}
|
|
53
77
|
|
|
54
78
|
#ensureServerlessPoolConfigured(config: RegistryConfig): Promise<void> | undefined {
|
|
@@ -279,7 +303,92 @@ export class Registry<A extends RegistryActors> {
|
|
|
279
303
|
};
|
|
280
304
|
}
|
|
281
305
|
|
|
282
|
-
|
|
306
|
+
/**
|
|
307
|
+
* Returns a health response suitable for mounting in a user-owned router.
|
|
308
|
+
*/
|
|
309
|
+
async #healthRoute(): Promise<Response> {
|
|
310
|
+
const configured = await this.#activeConfiguredRegistry();
|
|
311
|
+
if (!configured) {
|
|
312
|
+
return jsonRouteResponse(503, {
|
|
313
|
+
status: "not_started",
|
|
314
|
+
runtime: "rivetkit",
|
|
315
|
+
version: VERSION,
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
const { runtime, registry } = configured;
|
|
320
|
+
if (!runtime.registryHealth) {
|
|
321
|
+
return jsonRouteResponse(501, {
|
|
322
|
+
status: "unsupported",
|
|
323
|
+
runtime: "rivetkit",
|
|
324
|
+
version: VERSION,
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
const response = await runtime.registryHealth(registry);
|
|
329
|
+
return new Response(new Uint8Array(response.body), {
|
|
330
|
+
status: response.status,
|
|
331
|
+
headers: response.headers,
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* Returns serverless metadata suitable for mounting in a user-owned router.
|
|
337
|
+
*/
|
|
338
|
+
async #metadataRoute(): Promise<Response> {
|
|
339
|
+
const configured = await this.#activeConfiguredRegistry();
|
|
340
|
+
if (!configured) {
|
|
341
|
+
return new Response("registry not started\n", {
|
|
342
|
+
status: 503,
|
|
343
|
+
headers: { "content-type": "text/plain; charset=utf-8" },
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
const { runtime, registry } = configured;
|
|
348
|
+
if (!runtime.registryMetadata) {
|
|
349
|
+
return new Response("metadata is not supported by this runtime\n", {
|
|
350
|
+
status: 501,
|
|
351
|
+
headers: { "content-type": "text/plain; charset=utf-8" },
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
const response = await runtime.registryMetadata(registry);
|
|
356
|
+
return new Response(new Uint8Array(response.body), {
|
|
357
|
+
status: response.status,
|
|
358
|
+
headers: response.headers,
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* Returns a Prometheus metrics response suitable for mounting in a user-owned router.
|
|
364
|
+
*/
|
|
365
|
+
async #prometheusMetricsRoute(_request?: Request): Promise<Response> {
|
|
366
|
+
const configured = await this.#activeConfiguredRegistry();
|
|
367
|
+
if (!configured) {
|
|
368
|
+
return new Response("registry not started\n", {
|
|
369
|
+
status: 503,
|
|
370
|
+
headers: { "content-type": "text/plain; charset=utf-8" },
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
const { runtime, registry } = configured;
|
|
375
|
+
if (!runtime.registryMetrics) {
|
|
376
|
+
return new Response("metrics are not supported by this runtime\n", {
|
|
377
|
+
status: 501,
|
|
378
|
+
headers: { "content-type": "text/plain; charset=utf-8" },
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
const response = await runtime.registryMetrics(registry);
|
|
383
|
+
return new Response(new Uint8Array(response.body), {
|
|
384
|
+
status: response.status,
|
|
385
|
+
headers: response.headers,
|
|
386
|
+
});
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
async #activeConfiguredRegistry(): Promise<
|
|
390
|
+
Awaited<ReturnType<typeof buildConfiguredRegistry>> | undefined
|
|
391
|
+
> {
|
|
283
392
|
const candidates = [
|
|
284
393
|
this.#runtimeServerlessPromise,
|
|
285
394
|
this.#runtimeServeConfiguredPromise,
|
|
@@ -287,13 +396,8 @@ export class Registry<A extends RegistryActors> {
|
|
|
287
396
|
candidate !== undefined
|
|
288
397
|
);
|
|
289
398
|
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
const diagnostics = await runtime.registryDiagnostics?.(registry);
|
|
293
|
-
if (diagnostics) return diagnostics;
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
return { mode: "not_started", envoyActiveActorCount: null };
|
|
399
|
+
if (candidates.length === 0) return undefined;
|
|
400
|
+
return await candidates[0]!;
|
|
297
401
|
}
|
|
298
402
|
|
|
299
403
|
/**
|
|
@@ -364,10 +468,11 @@ export class Registry<A extends RegistryActors> {
|
|
|
364
468
|
): void {
|
|
365
469
|
if (this.#shutdownInFlight !== null) {
|
|
366
470
|
// Second delivery of the same (or another) shutdown signal.
|
|
367
|
-
// Remove our handler only
|
|
368
|
-
//
|
|
471
|
+
// Remove our handler only, preserving any user-installed listeners.
|
|
472
|
+
// PID 1 must exit directly because re-raised default signals can be
|
|
473
|
+
// swallowed by the container signal path.
|
|
369
474
|
this.#removeSignalHandlers();
|
|
370
|
-
|
|
475
|
+
finishShutdownSignal(signal);
|
|
371
476
|
return;
|
|
372
477
|
}
|
|
373
478
|
this.#shutdownInFlight = this.#runShutdown(
|
|
@@ -384,11 +489,13 @@ export class Registry<A extends RegistryActors> {
|
|
|
384
489
|
config: RegistryConfig,
|
|
385
490
|
configuredRegistryPromise: ReturnType<typeof buildConfiguredRegistry>,
|
|
386
491
|
): Promise<void> {
|
|
387
|
-
const gracePeriodMs =
|
|
492
|
+
const gracePeriodMs =
|
|
493
|
+
config.shutdown?.gracePeriodMs ??
|
|
494
|
+
(await this.#actorStopThresholdMs(configuredRegistryPromise)) ??
|
|
495
|
+
30 * 60 * 1000;
|
|
388
496
|
// Race the entire drain sequence (both modes + serve promise) against
|
|
389
|
-
// a single grace ceiling.
|
|
390
|
-
//
|
|
391
|
-
// we re-raise the signal.
|
|
497
|
+
// a single grace ceiling. By default, this uses the engine-provided
|
|
498
|
+
// actor stop threshold, matching Pegboard's hard cutoff for actors.
|
|
392
499
|
const drain = async () => {
|
|
393
500
|
// Shut down every live `CoreRegistry` we know about. Mode A
|
|
394
501
|
// (`start()`) and Mode B (`handler()`) each build a separate
|
|
@@ -442,7 +549,30 @@ export class Registry<A extends RegistryActors> {
|
|
|
442
549
|
),
|
|
443
550
|
]);
|
|
444
551
|
this.#removeSignalHandlers();
|
|
445
|
-
|
|
552
|
+
finishShutdownSignal(signal);
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
async #actorStopThresholdMs(
|
|
556
|
+
configuredRegistryPromise: ReturnType<typeof buildConfiguredRegistry>,
|
|
557
|
+
): Promise<number | undefined> {
|
|
558
|
+
try {
|
|
559
|
+
const { runtime, registry } = await configuredRegistryPromise;
|
|
560
|
+
const thresholdMs =
|
|
561
|
+
await runtime.registryActorStopThresholdMs?.(registry);
|
|
562
|
+
if (
|
|
563
|
+
thresholdMs !== undefined &&
|
|
564
|
+
Number.isFinite(thresholdMs) &&
|
|
565
|
+
thresholdMs > 0
|
|
566
|
+
) {
|
|
567
|
+
return thresholdMs;
|
|
568
|
+
}
|
|
569
|
+
} catch (err) {
|
|
570
|
+
logger().warn(
|
|
571
|
+
{ err },
|
|
572
|
+
"failed to read actor stop threshold for shutdown grace",
|
|
573
|
+
);
|
|
574
|
+
}
|
|
575
|
+
return undefined;
|
|
446
576
|
}
|
|
447
577
|
|
|
448
578
|
#removeSignalHandlers(): void {
|
|
@@ -524,6 +654,13 @@ function isServerlessMetadataRequest(request: Request, basePath: string): boolea
|
|
|
524
654
|
return parsed.pathname === `${normalizedBase}/metadata`;
|
|
525
655
|
}
|
|
526
656
|
|
|
657
|
+
function jsonRouteResponse(status: number, body: unknown): Response {
|
|
658
|
+
return new Response(JSON.stringify(body), {
|
|
659
|
+
status,
|
|
660
|
+
headers: { "content-type": "application/json" },
|
|
661
|
+
});
|
|
662
|
+
}
|
|
663
|
+
|
|
527
664
|
export function setup<A extends RegistryActors>(
|
|
528
665
|
input: RegistryConfigInput<A>,
|
|
529
666
|
): Registry<A> {
|
|
@@ -24,7 +24,7 @@ import type {
|
|
|
24
24
|
RuntimeQueueTryNextBatchOptions,
|
|
25
25
|
RuntimeQueueWaitOptions,
|
|
26
26
|
RuntimeRequestSaveOpts,
|
|
27
|
-
|
|
27
|
+
RuntimeRegistryRouteResponse,
|
|
28
28
|
RuntimeServeConfig,
|
|
29
29
|
RuntimeServerlessRequest,
|
|
30
30
|
RuntimeServerlessResponseHead,
|
|
@@ -202,13 +202,45 @@ export class NapiCoreRuntime implements CoreRuntime {
|
|
|
202
202
|
await asNativeRegistry(registry).shutdown();
|
|
203
203
|
}
|
|
204
204
|
|
|
205
|
-
async
|
|
205
|
+
async registryActorStopThresholdMs(
|
|
206
206
|
registry: RegistryHandle,
|
|
207
|
-
): Promise<
|
|
208
|
-
|
|
207
|
+
): Promise<number | undefined> {
|
|
208
|
+
return (
|
|
209
|
+
(await asNativeRegistry(registry).actorStopThresholdMs()) ??
|
|
210
|
+
undefined
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
async registryHealth(
|
|
215
|
+
registry: RegistryHandle,
|
|
216
|
+
): Promise<RuntimeRegistryRouteResponse> {
|
|
217
|
+
const response = await asNativeRegistry(registry).health();
|
|
218
|
+
return {
|
|
219
|
+
status: response.status,
|
|
220
|
+
headers: response.headers,
|
|
221
|
+
body: response.body,
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
async registryMetadata(
|
|
226
|
+
registry: RegistryHandle,
|
|
227
|
+
): Promise<RuntimeRegistryRouteResponse> {
|
|
228
|
+
const response = asNativeRegistry(registry).metadata();
|
|
209
229
|
return {
|
|
210
|
-
|
|
211
|
-
|
|
230
|
+
status: response.status,
|
|
231
|
+
headers: response.headers,
|
|
232
|
+
body: response.body,
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
async registryMetrics(
|
|
237
|
+
registry: RegistryHandle,
|
|
238
|
+
): Promise<RuntimeRegistryRouteResponse> {
|
|
239
|
+
const response = asNativeRegistry(registry).metrics();
|
|
240
|
+
return {
|
|
241
|
+
status: response.status,
|
|
242
|
+
headers: response.headers,
|
|
243
|
+
body: response.body,
|
|
212
244
|
};
|
|
213
245
|
}
|
|
214
246
|
|
|
@@ -414,6 +446,12 @@ export class NapiCoreRuntime implements CoreRuntime {
|
|
|
414
446
|
asNativeActorContext(ctx).waitUntil(promise);
|
|
415
447
|
}
|
|
416
448
|
|
|
449
|
+
async actorWaitForTrackedShutdownWork(
|
|
450
|
+
ctx: ActorContextHandle,
|
|
451
|
+
): Promise<boolean> {
|
|
452
|
+
return await asNativeActorContext(ctx).waitForTrackedShutdownWork();
|
|
453
|
+
}
|
|
454
|
+
|
|
417
455
|
actorKeepAwake(ctx: ActorContextHandle, promise: Promise<unknown>): void {
|
|
418
456
|
asNativeActorContext(ctx).keepAwake(promise);
|
|
419
457
|
}
|
package/src/registry/native.ts
CHANGED
|
@@ -29,22 +29,9 @@ import {
|
|
|
29
29
|
createClientWithDriver,
|
|
30
30
|
} from "@/client/client";
|
|
31
31
|
import { convertRegistryConfigToClientConfig } from "@/client/config";
|
|
32
|
-
import {
|
|
33
|
-
HEADER_CONN_PARAMS,
|
|
34
|
-
HEADER_ENCODING,
|
|
35
|
-
} from "@/common/actor-router-consts";
|
|
36
|
-
import type * as protocol from "@/common/client-protocol";
|
|
37
|
-
import {
|
|
38
|
-
CURRENT_VERSION as CLIENT_PROTOCOL_CURRENT_VERSION,
|
|
39
|
-
HTTP_RESPONSE_ERROR_VERSIONED,
|
|
40
|
-
} from "@/common/client-protocol-versioned";
|
|
41
|
-
import {
|
|
42
|
-
type HttpResponseError as HttpResponseErrorJson,
|
|
43
|
-
HttpResponseErrorSchema,
|
|
44
|
-
} from "@/common/client-protocol-zod";
|
|
32
|
+
import { HEADER_CONN_PARAMS } from "@/common/actor-router-consts";
|
|
45
33
|
import type { AnyDatabaseProvider } from "@/common/database/config";
|
|
46
34
|
import { wrapJsNativeDatabase } from "@/common/database/native-database";
|
|
47
|
-
import type { Encoding } from "@/common/encoding";
|
|
48
35
|
import { decodeWorkflowHistoryTransport } from "@/common/inspector-transport";
|
|
49
36
|
import { deconstructError, stringifyError } from "@/common/utils";
|
|
50
37
|
import type {
|
|
@@ -61,11 +48,9 @@ import type {
|
|
|
61
48
|
SqliteBackend,
|
|
62
49
|
} from "@/registry/config";
|
|
63
50
|
import {
|
|
64
|
-
contentTypeForEncoding,
|
|
65
51
|
decodeCborCompat,
|
|
66
52
|
decodeCborJsonCompat,
|
|
67
53
|
encodeCborCompat,
|
|
68
|
-
serializeWithEncoding,
|
|
69
54
|
} from "@/serde";
|
|
70
55
|
import { getEnvUniversal, VERSION } from "@/utils";
|
|
71
56
|
import { logger } from "./log";
|
|
@@ -269,9 +254,6 @@ type NativeDatabaseClientState = {
|
|
|
269
254
|
type NativeActorRuntimeState = {
|
|
270
255
|
sql?: ReturnType<typeof wrapJsNativeDatabase>;
|
|
271
256
|
databaseClient?: NativeDatabaseClientState;
|
|
272
|
-
keepAwakeCount?: number;
|
|
273
|
-
deferSleepCleanupUntilKeepAwakeIdle?: boolean;
|
|
274
|
-
deferredSleepCleanupActorCtx?: ActorContextHandleAdapter;
|
|
275
257
|
varsInitialized?: boolean;
|
|
276
258
|
vars?: unknown;
|
|
277
259
|
destroyGate?: NativeDestroyGate;
|
|
@@ -424,26 +406,12 @@ async function cleanupNativeSleepRuntimeState(
|
|
|
424
406
|
runtime: CoreRuntime,
|
|
425
407
|
ctx: ActorContextHandle,
|
|
426
408
|
): Promise<void> {
|
|
409
|
+
await runtime.actorWaitForTrackedShutdownWork(ctx);
|
|
427
410
|
await closeNativeDatabaseClient(runtime, ctx);
|
|
428
411
|
await closeNativeSqlDatabase(runtime, ctx);
|
|
429
412
|
clearNativeRuntimeState(runtime, ctx);
|
|
430
413
|
}
|
|
431
414
|
|
|
432
|
-
async function cleanupDeferredNativeSleepRuntimeState(
|
|
433
|
-
runtime: CoreRuntime,
|
|
434
|
-
ctx: ActorContextHandle,
|
|
435
|
-
runtimeState: NativeActorRuntimeState,
|
|
436
|
-
): Promise<void> {
|
|
437
|
-
runtimeState.deferSleepCleanupUntilKeepAwakeIdle = false;
|
|
438
|
-
const actorCtx = runtimeState.deferredSleepCleanupActorCtx;
|
|
439
|
-
runtimeState.deferredSleepCleanupActorCtx = undefined;
|
|
440
|
-
try {
|
|
441
|
-
await cleanupNativeSleepRuntimeState(runtime, ctx);
|
|
442
|
-
} finally {
|
|
443
|
-
await actorCtx?.dispose();
|
|
444
|
-
}
|
|
445
|
-
}
|
|
446
|
-
|
|
447
415
|
function closeNativeSqlDatabase(
|
|
448
416
|
runtime: CoreRuntime,
|
|
449
417
|
ctx: ActorContextHandle,
|
|
@@ -670,28 +638,7 @@ function isStructuredBridgeError(
|
|
|
670
638
|
function encodeNativeCallbackError(error: unknown): Error {
|
|
671
639
|
const structuredError = isStructuredBridgeError(error)
|
|
672
640
|
? error
|
|
673
|
-
: deconstructError(error,
|
|
674
|
-
bridge: "native_callback",
|
|
675
|
-
});
|
|
676
|
-
let stack: string | undefined;
|
|
677
|
-
if (error instanceof Error) {
|
|
678
|
-
try {
|
|
679
|
-
stack = error.stack;
|
|
680
|
-
} catch {
|
|
681
|
-
stack = undefined;
|
|
682
|
-
}
|
|
683
|
-
}
|
|
684
|
-
|
|
685
|
-
logger().warn({
|
|
686
|
-
msg: "native callback error encoded for bridge",
|
|
687
|
-
group: structuredError.group,
|
|
688
|
-
code: structuredError.code,
|
|
689
|
-
message: structuredError.message,
|
|
690
|
-
metadata: structuredError.metadata,
|
|
691
|
-
originalError: stringifyError(error),
|
|
692
|
-
stack,
|
|
693
|
-
bridge: "native_callback",
|
|
694
|
-
});
|
|
641
|
+
: deconstructError(error, true);
|
|
695
642
|
|
|
696
643
|
const bridgeError = new Error(encodeBridgeRivetError(structuredError), {
|
|
697
644
|
cause: error instanceof Error ? error : undefined,
|
|
@@ -2807,11 +2754,6 @@ export class ActorContextHandleAdapter {
|
|
|
2807
2754
|
}
|
|
2808
2755
|
|
|
2809
2756
|
keepAwake<T>(promise: Promise<T>): Promise<T> {
|
|
2810
|
-
const runtimeState = getNativeRuntimeState(this.#runtime, this.#ctx);
|
|
2811
|
-
// Increment before native registration so sleep cleanup observes JS work
|
|
2812
|
-
// even if the promise settles immediately.
|
|
2813
|
-
runtimeState.keepAwakeCount = (runtimeState.keepAwakeCount ?? 0) + 1;
|
|
2814
|
-
let registered = false;
|
|
2815
2757
|
const trackedPromise = Promise.resolve(promise)
|
|
2816
2758
|
.catch((error) => {
|
|
2817
2759
|
logger().warn({
|
|
@@ -2819,36 +2761,12 @@ export class ActorContextHandleAdapter {
|
|
|
2819
2761
|
error: stringifyError(error),
|
|
2820
2762
|
});
|
|
2821
2763
|
})
|
|
2822
|
-
.finally(async () => {
|
|
2823
|
-
if (!registered) {
|
|
2824
|
-
return;
|
|
2825
|
-
}
|
|
2826
|
-
runtimeState.keepAwakeCount = Math.max(
|
|
2827
|
-
(runtimeState.keepAwakeCount ?? 1) - 1,
|
|
2828
|
-
0,
|
|
2829
|
-
);
|
|
2830
|
-
if (
|
|
2831
|
-
runtimeState.keepAwakeCount === 0 &&
|
|
2832
|
-
runtimeState.deferSleepCleanupUntilKeepAwakeIdle
|
|
2833
|
-
) {
|
|
2834
|
-
await cleanupDeferredNativeSleepRuntimeState(
|
|
2835
|
-
this.#runtime,
|
|
2836
|
-
this.#ctx,
|
|
2837
|
-
runtimeState,
|
|
2838
|
-
);
|
|
2839
|
-
}
|
|
2840
|
-
})
|
|
2841
2764
|
.then(() => null);
|
|
2842
2765
|
try {
|
|
2843
2766
|
callNativeSync(() =>
|
|
2844
2767
|
this.#runtime.actorKeepAwake(this.#ctx, trackedPromise),
|
|
2845
2768
|
);
|
|
2846
|
-
registered = true;
|
|
2847
2769
|
} catch (error) {
|
|
2848
|
-
runtimeState.keepAwakeCount = Math.max(
|
|
2849
|
-
(runtimeState.keepAwakeCount ?? 1) - 1,
|
|
2850
|
-
0,
|
|
2851
|
-
);
|
|
2852
2770
|
if (!isClosedTaskRegistrationError(error)) {
|
|
2853
2771
|
throw error;
|
|
2854
2772
|
}
|
|
@@ -3265,52 +3183,6 @@ function withConnContext(
|
|
|
3265
3183
|
);
|
|
3266
3184
|
}
|
|
3267
3185
|
|
|
3268
|
-
function buildNativeRequestErrorResponse(
|
|
3269
|
-
encoding: Encoding,
|
|
3270
|
-
path: string,
|
|
3271
|
-
error: unknown,
|
|
3272
|
-
): Response {
|
|
3273
|
-
const { statusCode, group, code, message, metadata } = deconstructError(
|
|
3274
|
-
error,
|
|
3275
|
-
logger(),
|
|
3276
|
-
{
|
|
3277
|
-
path,
|
|
3278
|
-
runtime: "native",
|
|
3279
|
-
},
|
|
3280
|
-
false,
|
|
3281
|
-
);
|
|
3282
|
-
const body = serializeWithEncoding<
|
|
3283
|
-
protocol.HttpResponseError,
|
|
3284
|
-
HttpResponseErrorJson,
|
|
3285
|
-
{ group: string; code: string; message: string; metadata?: unknown }
|
|
3286
|
-
>(
|
|
3287
|
-
encoding,
|
|
3288
|
-
{ group, code, message, metadata },
|
|
3289
|
-
HTTP_RESPONSE_ERROR_VERSIONED,
|
|
3290
|
-
CLIENT_PROTOCOL_CURRENT_VERSION,
|
|
3291
|
-
HttpResponseErrorSchema,
|
|
3292
|
-
(value) => value,
|
|
3293
|
-
(value) => ({
|
|
3294
|
-
group: value.group,
|
|
3295
|
-
code: value.code,
|
|
3296
|
-
message: value.message,
|
|
3297
|
-
metadata:
|
|
3298
|
-
value.metadata === undefined
|
|
3299
|
-
? null
|
|
3300
|
-
: runtimeBytesToArrayBuffer(
|
|
3301
|
-
encodeCborCompat(value.metadata),
|
|
3302
|
-
),
|
|
3303
|
-
}),
|
|
3304
|
-
);
|
|
3305
|
-
|
|
3306
|
-
return new Response(body, {
|
|
3307
|
-
status: statusCode,
|
|
3308
|
-
headers: {
|
|
3309
|
-
"Content-Type": contentTypeForEncoding(encoding),
|
|
3310
|
-
},
|
|
3311
|
-
});
|
|
3312
|
-
}
|
|
3313
|
-
|
|
3314
3186
|
function buildActorConfig(
|
|
3315
3187
|
definition: AnyActorDefinition,
|
|
3316
3188
|
registryConfig: RegistryConfig,
|
|
@@ -4013,12 +3885,9 @@ export function buildNativeFactory(
|
|
|
4013
3885
|
}
|
|
4014
3886
|
}
|
|
4015
3887
|
} finally {
|
|
4016
|
-
|
|
4017
|
-
if ((runtimeState.keepAwakeCount ?? 0) > 0) {
|
|
4018
|
-
runtimeState.deferSleepCleanupUntilKeepAwakeIdle = true;
|
|
4019
|
-
runtimeState.deferredSleepCleanupActorCtx = actorCtx;
|
|
4020
|
-
} else {
|
|
3888
|
+
try {
|
|
4021
3889
|
await cleanupNativeSleepRuntimeState(runtime, ctx);
|
|
3890
|
+
} finally {
|
|
4022
3891
|
await actorCtx.dispose();
|
|
4023
3892
|
}
|
|
4024
3893
|
}
|
|
@@ -4366,22 +4235,6 @@ export function buildNativeFactory(
|
|
|
4366
4235
|
);
|
|
4367
4236
|
}
|
|
4368
4237
|
return await toRuntimeHttpResponse(response);
|
|
4369
|
-
} catch (error) {
|
|
4370
|
-
const encodingHeader =
|
|
4371
|
-
jsRequest.headers.get(HEADER_ENCODING);
|
|
4372
|
-
const encoding: Encoding =
|
|
4373
|
-
encodingHeader === "cbor" ||
|
|
4374
|
-
encodingHeader === "bare"
|
|
4375
|
-
? encodingHeader
|
|
4376
|
-
: "json";
|
|
4377
|
-
const path = new URL(jsRequest.url).pathname;
|
|
4378
|
-
return await toRuntimeHttpResponse(
|
|
4379
|
-
buildNativeRequestErrorResponse(
|
|
4380
|
-
encoding,
|
|
4381
|
-
path,
|
|
4382
|
-
error,
|
|
4383
|
-
),
|
|
4384
|
-
);
|
|
4385
4238
|
} finally {
|
|
4386
4239
|
await requestCtx?.dispose();
|
|
4387
4240
|
if (conn) {
|