rivetkit 2.3.23 → 2.3.24-rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/browser/client.d.ts +2 -0
- package/dist/browser/client.js +1 -1
- 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/agent-os/index.cjs +1 -1
- package/dist/tsup/agent-os/index.cjs.map +1 -1
- package/dist/tsup/agent-os/index.d.cts +2 -0
- package/dist/tsup/agent-os/index.d.ts +2 -0
- package/dist/tsup/agent-os/index.js +1 -1
- package/dist/tsup/agent-os/index.js.map +1 -1
- package/dist/tsup/{chunk-UV5AN6YV.js → chunk-64CRWLOX.js} +3 -3
- package/dist/tsup/{chunk-CIWSGFQG.js → chunk-6HW3TU7Q.js} +2 -2
- package/dist/tsup/{chunk-LKNGMRIT.js → chunk-B6WTPGOR.js} +3 -3
- package/dist/tsup/{chunk-JLM6TML2.cjs → chunk-CE6NBGOL.cjs} +3 -3
- package/dist/tsup/{chunk-JLM6TML2.cjs.map → chunk-CE6NBGOL.cjs.map} +1 -1
- package/dist/tsup/{chunk-FVVRY42Y.cjs → chunk-DAC73W53.cjs} +7 -7
- package/dist/tsup/{chunk-FVVRY42Y.cjs.map → chunk-DAC73W53.cjs.map} +1 -1
- package/dist/tsup/{chunk-VJNX5RCS.cjs → chunk-ERBNWARB.cjs} +4 -4
- package/dist/tsup/{chunk-VJNX5RCS.cjs.map → chunk-ERBNWARB.cjs.map} +1 -1
- package/dist/tsup/{chunk-674WU4L6.js → chunk-JJCUSUWO.js} +2 -2
- package/dist/tsup/chunk-JJCUSUWO.js.map +1 -0
- package/dist/tsup/{chunk-7ZDC4XEA.js → chunk-JKDAOE4T.js} +4 -4
- package/dist/tsup/{chunk-CRZJ74SA.cjs → chunk-JMO6AVUC.cjs} +3 -3
- package/dist/tsup/{chunk-CRZJ74SA.cjs.map → chunk-JMO6AVUC.cjs.map} +1 -1
- package/dist/tsup/{chunk-CSOHAAZK.js → chunk-LTGBHN5Z.js} +1 -1
- package/dist/tsup/chunk-LTGBHN5Z.js.map +1 -0
- package/dist/tsup/{chunk-Z3Z5EOSP.js → chunk-M5J3PXNU.js} +2 -2
- package/dist/tsup/{chunk-NWCAIWHL.cjs → chunk-NGJWQMUU.cjs} +1 -1
- package/dist/tsup/chunk-NGJWQMUU.cjs.map +1 -0
- package/dist/tsup/{chunk-KC3T3MMB.js → chunk-OB5JLDBM.js} +2 -2
- package/dist/tsup/{chunk-Z7QK4HTR.cjs → chunk-PFHGTLF2.cjs} +10 -10
- package/dist/tsup/{chunk-Z7QK4HTR.cjs.map → chunk-PFHGTLF2.cjs.map} +1 -1
- package/dist/tsup/{chunk-HO6UA2RJ.js → chunk-QQ454DVR.js} +2 -2
- package/dist/tsup/{chunk-GZKR7ORE.cjs → chunk-R3UI7MO3.cjs} +140 -140
- package/dist/tsup/{chunk-GZKR7ORE.cjs.map → chunk-R3UI7MO3.cjs.map} +1 -1
- package/dist/tsup/{chunk-XUCVB6BD.cjs → chunk-SWQCSORE.cjs} +3 -3
- package/dist/tsup/{chunk-XUCVB6BD.cjs.map → chunk-SWQCSORE.cjs.map} +1 -1
- package/dist/tsup/{chunk-3Y7MKDLZ.cjs → chunk-YVVFOKEP.cjs} +2 -2
- package/dist/tsup/chunk-YVVFOKEP.cjs.map +1 -0
- package/dist/tsup/client/mod.cjs +5 -5
- package/dist/tsup/client/mod.d.cts +2 -2
- package/dist/tsup/client/mod.d.ts +2 -2
- package/dist/tsup/client/mod.js +4 -4
- package/dist/tsup/common/log.cjs +2 -2
- package/dist/tsup/common/log.js +1 -1
- package/dist/tsup/common/websocket.cjs +3 -3
- package/dist/tsup/common/websocket.js +2 -2
- package/dist/tsup/{config-osdJqQwX.d.cts → config-CuKzfH58.d.cts} +4 -0
- package/dist/tsup/{config-Cv6pjCt-.d.ts → config-RV6gIa2L.d.ts} +4 -0
- package/dist/tsup/{context-DpjrM7n7.d.cts → context-DPCxyjax.d.cts} +1 -1
- package/dist/tsup/{context-CoVbKBuk.d.ts → context-DoMKZBQA.d.ts} +1 -1
- package/dist/tsup/db/drizzle.cjs +2 -2
- package/dist/tsup/db/drizzle.js +1 -1
- package/dist/tsup/db/mod.cjs +3 -3
- package/dist/tsup/db/mod.js +2 -2
- package/dist/tsup/dynamic/mod.cjs +2 -2
- package/dist/tsup/dynamic/mod.d.cts +1 -1
- package/dist/tsup/dynamic/mod.d.ts +1 -1
- package/dist/tsup/dynamic/mod.js +1 -1
- package/dist/tsup/inspector/mod.cjs +5 -5
- package/dist/tsup/inspector/mod.js +4 -4
- package/dist/tsup/inspector/workflow.cjs +4 -4
- package/dist/tsup/inspector/workflow.js +3 -3
- package/dist/tsup/inspector-tab/mod.cjs +2 -2
- package/dist/tsup/inspector-tab/mod.d.cts +2 -2
- package/dist/tsup/inspector-tab/mod.d.ts +2 -2
- package/dist/tsup/inspector-tab/mod.js +1 -1
- package/dist/tsup/mod.cjs +416 -188
- 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 +318 -90
- package/dist/tsup/mod.js.map +1 -1
- package/dist/tsup/test/mod.cjs +6 -6
- package/dist/tsup/test/mod.d.cts +1 -1
- package/dist/tsup/test/mod.d.ts +1 -1
- package/dist/tsup/test/mod.js +4 -4
- package/dist/tsup/utils.cjs +2 -2
- package/dist/tsup/utils.js +1 -1
- package/dist/tsup/workflow/mod.cjs +69 -21
- package/dist/tsup/workflow/mod.cjs.map +1 -1
- package/dist/tsup/workflow/mod.d.cts +3 -3
- package/dist/tsup/workflow/mod.d.ts +3 -3
- package/dist/tsup/workflow/mod.js +64 -16
- package/dist/tsup/workflow/mod.js.map +1 -1
- package/package.json +9 -9
- package/src/actor/config.ts +2 -0
- package/src/registry/lost-callbacks.test.ts +64 -0
- package/src/registry/napi-runtime.ts +8 -0
- package/src/registry/native.ts +332 -65
- package/src/registry/run-handler-coordinator.test.ts +102 -0
- package/src/registry/run-handler-coordinator.ts +84 -17
- package/src/registry/runtime.ts +2 -0
- package/src/registry/wasm-runtime.ts +8 -0
- package/src/workflow/context.ts +20 -0
- package/src/workflow/driver.ts +25 -1
- package/src/workflow/mod.ts +46 -12
- package/dist/tsup/chunk-3Y7MKDLZ.cjs.map +0 -1
- package/dist/tsup/chunk-674WU4L6.js.map +0 -1
- package/dist/tsup/chunk-CSOHAAZK.js.map +0 -1
- package/dist/tsup/chunk-NWCAIWHL.cjs.map +0 -1
- /package/dist/tsup/{chunk-UV5AN6YV.js.map → chunk-64CRWLOX.js.map} +0 -0
- /package/dist/tsup/{chunk-CIWSGFQG.js.map → chunk-6HW3TU7Q.js.map} +0 -0
- /package/dist/tsup/{chunk-LKNGMRIT.js.map → chunk-B6WTPGOR.js.map} +0 -0
- /package/dist/tsup/{chunk-7ZDC4XEA.js.map → chunk-JKDAOE4T.js.map} +0 -0
- /package/dist/tsup/{chunk-Z3Z5EOSP.js.map → chunk-M5J3PXNU.js.map} +0 -0
- /package/dist/tsup/{chunk-KC3T3MMB.js.map → chunk-OB5JLDBM.js.map} +0 -0
- /package/dist/tsup/{chunk-HO6UA2RJ.js.map → chunk-QQ454DVR.js.map} +0 -0
|
@@ -600,6 +600,14 @@ export class NapiCoreRuntime implements CoreRuntime {
|
|
|
600
600
|
return asNativeActorContext(ctx).actorId();
|
|
601
601
|
}
|
|
602
602
|
|
|
603
|
+
actorGeneration(ctx: ActorContextHandle): number | undefined {
|
|
604
|
+
return asNativeActorContext(ctx).generation() ?? undefined;
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
actorIsLost(ctx: ActorContextHandle): boolean {
|
|
608
|
+
return asNativeActorContext(ctx).isLost();
|
|
609
|
+
}
|
|
610
|
+
|
|
603
611
|
runWithActorInvocationContext<T>(ctx: ActorContextHandle, run: () => T): T {
|
|
604
612
|
return this.#runAs(asNativeActorContext(ctx), run);
|
|
605
613
|
}
|
package/src/registry/native.ts
CHANGED
|
@@ -541,8 +541,11 @@ async function cleanupNativeSleepRuntimeState(
|
|
|
541
541
|
void runtime
|
|
542
542
|
.actorWaitForTrackedShutdownWorkUnbounded(ctx)
|
|
543
543
|
.then(async () => {
|
|
544
|
-
|
|
545
|
-
|
|
544
|
+
try {
|
|
545
|
+
await afterTrackedWorkDrained?.();
|
|
546
|
+
} finally {
|
|
547
|
+
clearNativeRuntimeState(runtime, ctx);
|
|
548
|
+
}
|
|
546
549
|
})
|
|
547
550
|
.catch((error) => {
|
|
548
551
|
logger().warn({
|
|
@@ -553,10 +556,31 @@ async function cleanupNativeSleepRuntimeState(
|
|
|
553
556
|
return;
|
|
554
557
|
}
|
|
555
558
|
|
|
556
|
-
|
|
559
|
+
try {
|
|
560
|
+
await afterTrackedWorkDrained?.();
|
|
561
|
+
} finally {
|
|
562
|
+
try {
|
|
563
|
+
await closeNativeDatabaseClient(runtime, ctx);
|
|
564
|
+
await closeNativeSqlDatabase(runtime, ctx);
|
|
565
|
+
} finally {
|
|
566
|
+
clearNativeRuntimeState(runtime, ctx);
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
async function cleanupNativeLostRuntimeState(
|
|
572
|
+
runtime: CoreRuntime,
|
|
573
|
+
ctx: ActorContextHandle,
|
|
574
|
+
): Promise<void> {
|
|
575
|
+
// Core has already abandoned a lost generation's user work, so this does not wait for it.
|
|
576
|
+
// A custom database client's `close` is user code, so a lost generation drops the client
|
|
577
|
+
// without calling it. The native database underneath is closed by the runtime.
|
|
557
578
|
await closeNativeDatabaseClient(runtime, ctx);
|
|
558
|
-
|
|
559
|
-
|
|
579
|
+
try {
|
|
580
|
+
await closeNativeSqlDatabase(runtime, ctx);
|
|
581
|
+
} finally {
|
|
582
|
+
clearNativeRuntimeState(runtime, ctx);
|
|
583
|
+
}
|
|
560
584
|
}
|
|
561
585
|
|
|
562
586
|
function closeNativeSqlDatabase(
|
|
@@ -585,6 +609,11 @@ async function closeNativeDatabaseClient(
|
|
|
585
609
|
|
|
586
610
|
runtimeState.databaseClient = undefined;
|
|
587
611
|
|
|
612
|
+
// `close` is user code from a database provider. Cleanup paths await before reaching this,
|
|
613
|
+
// so check here: a lost generation drops the client without calling it.
|
|
614
|
+
if (callNativeSync(() => runtime.actorIsLost(ctx))) {
|
|
615
|
+
return;
|
|
616
|
+
}
|
|
588
617
|
if (
|
|
589
618
|
entry.client &&
|
|
590
619
|
typeof entry.client === "object" &&
|
|
@@ -1301,6 +1330,76 @@ function wrapNativeCallback<Args extends Array<unknown>, Result>(
|
|
|
1301
1330
|
};
|
|
1302
1331
|
}
|
|
1303
1332
|
|
|
1333
|
+
/**
|
|
1334
|
+
* Callbacks that still run for a lost generation. They release the generation's runtime
|
|
1335
|
+
* resources and skip the user hook themselves.
|
|
1336
|
+
*/
|
|
1337
|
+
const LOST_GENERATION_CLEANUP_CALLBACKS = new Set(["onSleep", "onDestroy"]);
|
|
1338
|
+
|
|
1339
|
+
/**
|
|
1340
|
+
* Refuses to start a user callback for a generation that was declared lost. Call it right
|
|
1341
|
+
* before the callback, after any other user code such as schema validation.
|
|
1342
|
+
*/
|
|
1343
|
+
function throwIfActorLost(runtime: CoreRuntime, ctx: ActorContextHandle): void {
|
|
1344
|
+
if (callNativeSync(() => runtime.actorIsLost(ctx))) {
|
|
1345
|
+
throw actorGenerationLostError();
|
|
1346
|
+
}
|
|
1347
|
+
}
|
|
1348
|
+
|
|
1349
|
+
function actorGenerationLostError(): RivetError {
|
|
1350
|
+
return new RivetError(
|
|
1351
|
+
"actor",
|
|
1352
|
+
"stopping",
|
|
1353
|
+
"Actor generation was declared lost; it starts no new user callbacks.",
|
|
1354
|
+
);
|
|
1355
|
+
}
|
|
1356
|
+
|
|
1357
|
+
/**
|
|
1358
|
+
* Refuses every runtime callback for a generation that has been declared lost. Runtime
|
|
1359
|
+
* callbacks can be queued or spawned before the loss and only start afterward, so the check
|
|
1360
|
+
* runs when JS actually enters the callback.
|
|
1361
|
+
*/
|
|
1362
|
+
export function refuseCallbacksAfterLost<T extends Record<string, unknown>>(
|
|
1363
|
+
runtime: CoreRuntime,
|
|
1364
|
+
callbacks: T,
|
|
1365
|
+
): T {
|
|
1366
|
+
const guard = (callback: (...args: unknown[]) => unknown) => {
|
|
1367
|
+
return async (...args: unknown[]) => {
|
|
1368
|
+
const ctx = (args[1] as { ctx?: ActorContextHandle } | undefined)
|
|
1369
|
+
?.ctx;
|
|
1370
|
+
if (
|
|
1371
|
+
ctx !== undefined &&
|
|
1372
|
+
callNativeSync(() => runtime.actorIsLost(ctx))
|
|
1373
|
+
) {
|
|
1374
|
+
throw encodeNativeCallbackError(actorGenerationLostError());
|
|
1375
|
+
}
|
|
1376
|
+
return await callback(...args);
|
|
1377
|
+
};
|
|
1378
|
+
};
|
|
1379
|
+
const guarded: Record<string, unknown> = {};
|
|
1380
|
+
for (const [name, value] of Object.entries(callbacks)) {
|
|
1381
|
+
if (LOST_GENERATION_CLEANUP_CALLBACKS.has(name)) {
|
|
1382
|
+
guarded[name] = value;
|
|
1383
|
+
} else if (typeof value === "function") {
|
|
1384
|
+
guarded[name] = guard(value as (...args: unknown[]) => unknown);
|
|
1385
|
+
} else if (
|
|
1386
|
+
name === "actions" &&
|
|
1387
|
+
value !== null &&
|
|
1388
|
+
typeof value === "object"
|
|
1389
|
+
) {
|
|
1390
|
+
guarded[name] = Object.fromEntries(
|
|
1391
|
+
Object.entries(value).map(([actionName, handler]) => [
|
|
1392
|
+
actionName,
|
|
1393
|
+
guard(handler as (...args: unknown[]) => unknown),
|
|
1394
|
+
]),
|
|
1395
|
+
);
|
|
1396
|
+
} else {
|
|
1397
|
+
guarded[name] = value;
|
|
1398
|
+
}
|
|
1399
|
+
}
|
|
1400
|
+
return guarded as T;
|
|
1401
|
+
}
|
|
1402
|
+
|
|
1304
1403
|
function decodeArgs(value?: RuntimeBytes | null): unknown[] {
|
|
1305
1404
|
const decoded = decodeValue<unknown>(value);
|
|
1306
1405
|
return normalizeArgs(decoded);
|
|
@@ -1880,9 +1979,26 @@ class NativeQueueAdapter {
|
|
|
1880
1979
|
this.#schemas = schemas;
|
|
1881
1980
|
}
|
|
1882
1981
|
|
|
1982
|
+
// Queue results arrive after awaits, and their schema validation is user code, so each
|
|
1983
|
+
// validation checks that the generation was not lost first.
|
|
1984
|
+
#validateBody(name: string, body: unknown): unknown {
|
|
1985
|
+
throwIfActorLost(this.#runtime, this.#ctx);
|
|
1986
|
+
return validateQueueBody(this.#schemas, name, body);
|
|
1987
|
+
}
|
|
1988
|
+
|
|
1989
|
+
#validateComplete(name: string, response: unknown): unknown {
|
|
1990
|
+
throwIfActorLost(this.#runtime, this.#ctx);
|
|
1991
|
+
return validateQueueComplete(this.#schemas, name, response);
|
|
1992
|
+
}
|
|
1993
|
+
|
|
1994
|
+
#wrap(message: Parameters<typeof wrapQueueMessage>[0]) {
|
|
1995
|
+
throwIfActorLost(this.#runtime, this.#ctx);
|
|
1996
|
+
return wrapQueueMessage(message, this.#schemas);
|
|
1997
|
+
}
|
|
1998
|
+
|
|
1883
1999
|
async send(name: string, body: unknown) {
|
|
1884
|
-
const validatedBody =
|
|
1885
|
-
return
|
|
2000
|
+
const validatedBody = this.#validateBody(name, body);
|
|
2001
|
+
return this.#wrap(
|
|
1886
2002
|
await callNative(() =>
|
|
1887
2003
|
this.#runtime.actorQueueSend(
|
|
1888
2004
|
this.#ctx,
|
|
@@ -1890,7 +2006,6 @@ class NativeQueueAdapter {
|
|
|
1890
2006
|
encodeValue(validatedBody),
|
|
1891
2007
|
),
|
|
1892
2008
|
),
|
|
1893
|
-
this.#schemas,
|
|
1894
2009
|
);
|
|
1895
2010
|
}
|
|
1896
2011
|
|
|
@@ -1948,9 +2063,7 @@ class NativeQueueAdapter {
|
|
|
1948
2063
|
token,
|
|
1949
2064
|
),
|
|
1950
2065
|
);
|
|
1951
|
-
const wrapped = messages.map((message) =>
|
|
1952
|
-
wrapQueueMessage(message, this.#schemas),
|
|
1953
|
-
);
|
|
2066
|
+
const wrapped = messages.map((message) => this.#wrap(message));
|
|
1954
2067
|
return completable
|
|
1955
2068
|
? wrapped.map((message) =>
|
|
1956
2069
|
this.#makeCompletableMessage(message),
|
|
@@ -1975,7 +2088,7 @@ class NativeQueueAdapter {
|
|
|
1975
2088
|
);
|
|
1976
2089
|
|
|
1977
2090
|
try {
|
|
1978
|
-
return
|
|
2091
|
+
return this.#wrap(
|
|
1979
2092
|
await callNative(() =>
|
|
1980
2093
|
this.#runtime.actorQueueWaitForNames(
|
|
1981
2094
|
this.#ctx,
|
|
@@ -1987,7 +2100,6 @@ class NativeQueueAdapter {
|
|
|
1987
2100
|
token,
|
|
1988
2101
|
),
|
|
1989
2102
|
),
|
|
1990
|
-
this.#schemas,
|
|
1991
2103
|
);
|
|
1992
2104
|
} finally {
|
|
1993
2105
|
cleanup?.();
|
|
@@ -2032,8 +2144,7 @@ class NativeQueueAdapter {
|
|
|
2032
2144
|
message: { id: number | bigint; name: string },
|
|
2033
2145
|
response?: unknown,
|
|
2034
2146
|
): Promise<void> {
|
|
2035
|
-
const validatedResponse =
|
|
2036
|
-
this.#schemas,
|
|
2147
|
+
const validatedResponse = this.#validateComplete(
|
|
2037
2148
|
message.name,
|
|
2038
2149
|
response,
|
|
2039
2150
|
);
|
|
@@ -2056,7 +2167,7 @@ class NativeQueueAdapter {
|
|
|
2056
2167
|
signal?: AbortSignal;
|
|
2057
2168
|
},
|
|
2058
2169
|
) {
|
|
2059
|
-
const validatedBody =
|
|
2170
|
+
const validatedBody = this.#validateBody(name, body);
|
|
2060
2171
|
const { token, cleanup } = await createCancellationTokenHandle(
|
|
2061
2172
|
this.#runtime,
|
|
2062
2173
|
options?.signal,
|
|
@@ -2076,11 +2187,7 @@ class NativeQueueAdapter {
|
|
|
2076
2187
|
);
|
|
2077
2188
|
return response === undefined || response === null
|
|
2078
2189
|
? undefined
|
|
2079
|
-
:
|
|
2080
|
-
this.#schemas,
|
|
2081
|
-
name,
|
|
2082
|
-
decodeValue(response),
|
|
2083
|
-
);
|
|
2190
|
+
: this.#validateComplete(name, decodeValue(response));
|
|
2084
2191
|
} finally {
|
|
2085
2192
|
cleanup?.();
|
|
2086
2193
|
}
|
|
@@ -2583,6 +2690,12 @@ class TrackedWebSocketHandleAdapter implements UniversalWebSocket {
|
|
|
2583
2690
|
handler: TrackedWebSocketListener,
|
|
2584
2691
|
event: any,
|
|
2585
2692
|
): void {
|
|
2693
|
+
// Messages and opens start new user work, so a lost generation drops them. Each listener
|
|
2694
|
+
// is checked because an earlier one can run while the generation is lost. Close and
|
|
2695
|
+
// error still reach listeners, like the abort signal, so user code can wind down.
|
|
2696
|
+
if ((type === "message" || type === "open") && this.#ctx.isLost) {
|
|
2697
|
+
return;
|
|
2698
|
+
}
|
|
2586
2699
|
try {
|
|
2587
2700
|
const result = handler(event);
|
|
2588
2701
|
if (!this.#isPromiseLike(result)) {
|
|
@@ -2963,6 +3076,15 @@ export class ActorContextHandleAdapter {
|
|
|
2963
3076
|
return callNativeSync(() => this.#runtime.actorId(this.#ctx));
|
|
2964
3077
|
}
|
|
2965
3078
|
|
|
3079
|
+
get actorGeneration(): number | undefined {
|
|
3080
|
+
return callNativeSync(() => this.#runtime.actorGeneration(this.#ctx));
|
|
3081
|
+
}
|
|
3082
|
+
|
|
3083
|
+
/** Whether this generation was declared lost. A lost generation starts no new user code. */
|
|
3084
|
+
get isLost(): boolean {
|
|
3085
|
+
return callNativeSync(() => this.#runtime.actorIsLost(this.#ctx));
|
|
3086
|
+
}
|
|
3087
|
+
|
|
2966
3088
|
get name(): string {
|
|
2967
3089
|
return callNativeSync(() => this.#runtime.actorName(this.#ctx));
|
|
2968
3090
|
}
|
|
@@ -3096,6 +3218,10 @@ export class ActorContextHandleAdapter {
|
|
|
3096
3218
|
}
|
|
3097
3219
|
|
|
3098
3220
|
const actorId = this.actorId;
|
|
3221
|
+
// `createClient` is user code from a database provider.
|
|
3222
|
+
if (this.isLost) {
|
|
3223
|
+
throw actorGenerationLostError();
|
|
3224
|
+
}
|
|
3099
3225
|
const createdClient = await this.#databaseProvider.createClient({
|
|
3100
3226
|
actorId,
|
|
3101
3227
|
kv: {
|
|
@@ -3273,9 +3399,12 @@ export class ActorContextHandleAdapter {
|
|
|
3273
3399
|
return;
|
|
3274
3400
|
}
|
|
3275
3401
|
|
|
3276
|
-
await this
|
|
3277
|
-
|
|
3278
|
-
)
|
|
3402
|
+
const client = await this.ensureDatabaseClient();
|
|
3403
|
+
// Creating the client awaits, and the generation can be lost meanwhile.
|
|
3404
|
+
if (this.isLost) {
|
|
3405
|
+
throw actorGenerationLostError();
|
|
3406
|
+
}
|
|
3407
|
+
await this.#databaseProvider.onMigrate(client as never);
|
|
3279
3408
|
}
|
|
3280
3409
|
|
|
3281
3410
|
async closeDatabase(): Promise<void> {
|
|
@@ -3610,7 +3739,8 @@ export class ActorContextHandleAdapter {
|
|
|
3610
3739
|
this.#runtime.actorRequestSave(this.#ctx, { immediate: false }),
|
|
3611
3740
|
);
|
|
3612
3741
|
|
|
3613
|
-
|
|
3742
|
+
// State changes are flushed asynchronously and can land after the generation was lost.
|
|
3743
|
+
if (!this.#onStateChange || this.isLost) {
|
|
3614
3744
|
return;
|
|
3615
3745
|
}
|
|
3616
3746
|
|
|
@@ -3659,6 +3789,13 @@ class NativeWorkflowRuntimeAdapter {
|
|
|
3659
3789
|
#completions = new Map<string, (response?: unknown) => Promise<void>>();
|
|
3660
3790
|
|
|
3661
3791
|
readonly id: string;
|
|
3792
|
+
/** Actor generation, used to keep workflow registrations of different generations apart. */
|
|
3793
|
+
readonly generation: number | undefined;
|
|
3794
|
+
|
|
3795
|
+
/** Whether this generation was declared lost. Workflow code checks it before each user callback. */
|
|
3796
|
+
get isLost(): boolean {
|
|
3797
|
+
return this.#ctx.isLost;
|
|
3798
|
+
}
|
|
3662
3799
|
readonly driver: {
|
|
3663
3800
|
kvBatchGet: (
|
|
3664
3801
|
actorId: string,
|
|
@@ -3730,6 +3867,7 @@ class NativeWorkflowRuntimeAdapter {
|
|
|
3730
3867
|
this.#ctx = ctx;
|
|
3731
3868
|
this.#onMessagesReceived = onMessagesReceived;
|
|
3732
3869
|
this.id = ctx.actorId;
|
|
3870
|
+
this.generation = ctx.actorGeneration;
|
|
3733
3871
|
this.driver = {
|
|
3734
3872
|
kvBatchGet: async (actorId, keys) => {
|
|
3735
3873
|
this.#assertActorId(actorId);
|
|
@@ -4101,14 +4239,29 @@ export function buildNativeFactory(
|
|
|
4101
4239
|
? new RunHandlerCoordinator(config.run)
|
|
4102
4240
|
: undefined;
|
|
4103
4241
|
const getNativeWorkflowInspector = (ctx: ActorContextHandle) => {
|
|
4242
|
+
// Inspector workflow calls run user code (history, state, replay). A lost generation
|
|
4243
|
+
// exposes no workflow inspector.
|
|
4244
|
+
if (callNativeSync(() => runtime.actorIsLost(ctx))) {
|
|
4245
|
+
return undefined;
|
|
4246
|
+
}
|
|
4104
4247
|
const actorId = callNativeSync(() => runtime.actorId(ctx));
|
|
4105
4248
|
const restart = () =>
|
|
4106
4249
|
callNativeSync(() => runtime.actorRestartRunHandler(ctx));
|
|
4107
|
-
|
|
4108
|
-
|
|
4109
|
-
|
|
4250
|
+
const actorGeneration = callNativeSync(() =>
|
|
4251
|
+
runtime.actorGeneration(ctx),
|
|
4252
|
+
);
|
|
4253
|
+
const inspector = (runHandlerCoordinator?.getInspector(
|
|
4254
|
+
actorId,
|
|
4255
|
+
restart,
|
|
4256
|
+
actorGeneration,
|
|
4257
|
+
)?.workflow ?? getRunInspectorConfig(config.run, actorId)?.workflow) as
|
|
4110
4258
|
| NativeWorkflowInspectorConfig
|
|
4111
4259
|
| undefined;
|
|
4260
|
+
// Building the inspector can run a user factory, and the generation can be lost meanwhile.
|
|
4261
|
+
if (callNativeSync(() => runtime.actorIsLost(ctx))) {
|
|
4262
|
+
return undefined;
|
|
4263
|
+
}
|
|
4264
|
+
return inspector;
|
|
4112
4265
|
};
|
|
4113
4266
|
const onStateChange =
|
|
4114
4267
|
typeof config.onStateChange === "function"
|
|
@@ -4228,6 +4381,11 @@ export function buildNativeFactory(
|
|
|
4228
4381
|
}
|
|
4229
4382
|
}
|
|
4230
4383
|
|
|
4384
|
+
// Reading connection params runs the user schema, and authentication awaited before this.
|
|
4385
|
+
const inspectorConnParams = (conn: { params: unknown }) => {
|
|
4386
|
+
throwIfActorLost(runtime, ctx);
|
|
4387
|
+
return conn.params;
|
|
4388
|
+
};
|
|
4231
4389
|
const workflowHistory = () =>
|
|
4232
4390
|
serializeWorkflowHistoryForJson(
|
|
4233
4391
|
getNativeWorkflowInspector(ctx)?.getHistory() ?? null,
|
|
@@ -4266,7 +4424,7 @@ export function buildNativeFactory(
|
|
|
4266
4424
|
id: conn.id,
|
|
4267
4425
|
details: {
|
|
4268
4426
|
type: null,
|
|
4269
|
-
params: conn
|
|
4427
|
+
params: inspectorConnParams(conn),
|
|
4270
4428
|
stateEnabled: true,
|
|
4271
4429
|
state: conn.state,
|
|
4272
4430
|
subscriptions: 0,
|
|
@@ -4526,7 +4684,7 @@ export function buildNativeFactory(
|
|
|
4526
4684
|
id: conn.id,
|
|
4527
4685
|
details: {
|
|
4528
4686
|
type: null,
|
|
4529
|
-
params: conn
|
|
4687
|
+
params: inspectorConnParams(conn),
|
|
4530
4688
|
stateEnabled: true,
|
|
4531
4689
|
state: conn.state,
|
|
4532
4690
|
subscriptions: 0,
|
|
@@ -4588,15 +4746,22 @@ export function buildNativeFactory(
|
|
|
4588
4746
|
body.properties !== undefined
|
|
4589
4747
|
? [body.properties]
|
|
4590
4748
|
: normalizeArgs(body.args);
|
|
4749
|
+
// Authentication and body parsing may finish after the generation was lost.
|
|
4750
|
+
if (callNativeSync(() => runtime.actorIsLost(ctx))) {
|
|
4751
|
+
return jsonResponse(
|
|
4752
|
+
{ error: "actor generation was declared lost" },
|
|
4753
|
+
{ status: 503 },
|
|
4754
|
+
);
|
|
4755
|
+
}
|
|
4591
4756
|
try {
|
|
4592
|
-
const
|
|
4593
|
-
|
|
4594
|
-
|
|
4595
|
-
|
|
4596
|
-
actionName,
|
|
4597
|
-
args,
|
|
4598
|
-
),
|
|
4757
|
+
const validatedArgs = validateActionArgs(
|
|
4758
|
+
schemaConfig.actionInputSchemas,
|
|
4759
|
+
actionName,
|
|
4760
|
+
args,
|
|
4599
4761
|
);
|
|
4762
|
+
// Schema validation is user code and can run while the generation is lost.
|
|
4763
|
+
throwIfActorLost(runtime, ctx);
|
|
4764
|
+
const output = await action(actorCtx, ...validatedArgs);
|
|
4600
4765
|
return jsonResponse({ output });
|
|
4601
4766
|
} catch (error) {
|
|
4602
4767
|
logger().error({
|
|
@@ -4738,7 +4903,14 @@ export function buildNativeFactory(
|
|
|
4738
4903
|
await actorCtx.closeDatabase();
|
|
4739
4904
|
}
|
|
4740
4905
|
await actorCtx.runDatabaseMigrations();
|
|
4741
|
-
|
|
4906
|
+
// Migrations may finish after the generation was lost, and a lost
|
|
4907
|
+
// generation starts no new user hooks.
|
|
4908
|
+
if (
|
|
4909
|
+
typeof config.onMigrate === "function" &&
|
|
4910
|
+
!callNativeSync(() =>
|
|
4911
|
+
runtime.actorIsLost(ctx),
|
|
4912
|
+
)
|
|
4913
|
+
) {
|
|
4742
4914
|
await config.onMigrate(actorCtx, isNew);
|
|
4743
4915
|
}
|
|
4744
4916
|
} catch (error) {
|
|
@@ -4794,10 +4966,38 @@ export function buildNativeFactory(
|
|
|
4794
4966
|
const { ctx } = unwrapTsfnPayload(error, payload);
|
|
4795
4967
|
const actorCtx = makeActorCtx(ctx);
|
|
4796
4968
|
const actorId = callNativeSync(() => runtime.actorId(ctx));
|
|
4969
|
+
const actorGeneration = callNativeSync(() =>
|
|
4970
|
+
runtime.actorGeneration(ctx),
|
|
4971
|
+
);
|
|
4972
|
+
// The engine can declare this generation lost before or during `onSleep`.
|
|
4973
|
+
// A lost generation releases its own runtime resources only: no user hook,
|
|
4974
|
+
// no state save, and nothing keyed by actor id alone.
|
|
4975
|
+
const isLost = () =>
|
|
4976
|
+
callNativeSync(() => runtime.actorIsLost(ctx));
|
|
4977
|
+
if (isLost()) {
|
|
4978
|
+
try {
|
|
4979
|
+
await cleanupNativeLostRuntimeState(runtime, ctx);
|
|
4980
|
+
} finally {
|
|
4981
|
+
runHandlerCoordinator?.destroy(
|
|
4982
|
+
actorId,
|
|
4983
|
+
actorGeneration,
|
|
4984
|
+
{
|
|
4985
|
+
disposeInspector: false,
|
|
4986
|
+
},
|
|
4987
|
+
);
|
|
4988
|
+
await actorCtx.dispose();
|
|
4989
|
+
}
|
|
4990
|
+
return;
|
|
4991
|
+
}
|
|
4797
4992
|
// TODO: Move this save hook into cleanupNativeSleepRuntimeState
|
|
4798
4993
|
// so immediate and deferred sleep cleanup share one save-state
|
|
4799
4994
|
// path instead of passing a callback through cleanup.
|
|
4800
4995
|
const saveActorState = async () => {
|
|
4996
|
+
// Cleanup waits on tracked work first, and the generation can be lost
|
|
4997
|
+
// during that wait.
|
|
4998
|
+
if (isLost()) {
|
|
4999
|
+
return;
|
|
5000
|
+
}
|
|
4801
5001
|
if (runtime.kind === "wasm") {
|
|
4802
5002
|
// Wasm cannot use the native context save helper here because
|
|
4803
5003
|
// the runtime owns the serialized state handoff.
|
|
@@ -4818,14 +5018,26 @@ export function buildNativeFactory(
|
|
|
4818
5018
|
await saveActorState();
|
|
4819
5019
|
} finally {
|
|
4820
5020
|
try {
|
|
4821
|
-
|
|
4822
|
-
runtime,
|
|
4823
|
-
|
|
4824
|
-
|
|
4825
|
-
|
|
5021
|
+
if (isLost()) {
|
|
5022
|
+
await cleanupNativeLostRuntimeState(runtime, ctx);
|
|
5023
|
+
} else {
|
|
5024
|
+
await cleanupNativeSleepRuntimeState(
|
|
5025
|
+
runtime,
|
|
5026
|
+
ctx,
|
|
5027
|
+
saveActorState,
|
|
5028
|
+
);
|
|
5029
|
+
}
|
|
4826
5030
|
} finally {
|
|
4827
|
-
runHandlerCoordinator?.destroy(
|
|
4828
|
-
|
|
5031
|
+
runHandlerCoordinator?.destroy(
|
|
5032
|
+
actorId,
|
|
5033
|
+
actorGeneration,
|
|
5034
|
+
{
|
|
5035
|
+
disposeInspector: !isLost(),
|
|
5036
|
+
},
|
|
5037
|
+
);
|
|
5038
|
+
if (!isLost()) {
|
|
5039
|
+
disposeRunInspector(config.run, actorId);
|
|
5040
|
+
}
|
|
4829
5041
|
await actorCtx.dispose();
|
|
4830
5042
|
}
|
|
4831
5043
|
}
|
|
@@ -4836,12 +5048,22 @@ export function buildNativeFactory(
|
|
|
4836
5048
|
const { ctx } = unwrapTsfnPayload(error, payload);
|
|
4837
5049
|
const actorCtx = makeActorCtx(ctx);
|
|
4838
5050
|
const actorId = callNativeSync(() => runtime.actorId(ctx));
|
|
5051
|
+
// A lost generation still releases its runtime state but starts no user hook. Loss
|
|
5052
|
+
// is rechecked before each user callback because it can happen while one runs.
|
|
5053
|
+
const isLost = () =>
|
|
5054
|
+
callNativeSync(() => runtime.actorIsLost(ctx));
|
|
4839
5055
|
// Close run control before user cleanup so replay cannot race actor
|
|
4840
5056
|
// destruction. Recreating this actor id receives a fresh controller.
|
|
4841
|
-
runHandlerCoordinator?.destroy(
|
|
4842
|
-
|
|
5057
|
+
runHandlerCoordinator?.destroy(
|
|
5058
|
+
actorId,
|
|
5059
|
+
callNativeSync(() => runtime.actorGeneration(ctx)),
|
|
5060
|
+
{ disposeInspector: !isLost() },
|
|
5061
|
+
);
|
|
5062
|
+
if (!isLost()) {
|
|
5063
|
+
disposeRunInspector(config.run, actorId);
|
|
5064
|
+
}
|
|
4843
5065
|
try {
|
|
4844
|
-
if (typeof config.onDestroy === "function") {
|
|
5066
|
+
if (typeof config.onDestroy === "function" && !isLost()) {
|
|
4845
5067
|
await config.onDestroy(actorCtx);
|
|
4846
5068
|
}
|
|
4847
5069
|
} finally {
|
|
@@ -4879,12 +5101,16 @@ export function buildNativeFactory(
|
|
|
4879
5101
|
: undefined,
|
|
4880
5102
|
);
|
|
4881
5103
|
try {
|
|
5104
|
+
const validatedParams = validateConnParams(
|
|
5105
|
+
schemaConfig.connParamsSchema,
|
|
5106
|
+
decodeValue(params),
|
|
5107
|
+
);
|
|
5108
|
+
// Schema validation is user code and can run while the
|
|
5109
|
+
// generation is lost.
|
|
5110
|
+
throwIfActorLost(runtime, ctx);
|
|
4882
5111
|
await config.onBeforeConnect(
|
|
4883
5112
|
actorCtx,
|
|
4884
|
-
|
|
4885
|
-
schemaConfig.connParamsSchema,
|
|
4886
|
-
decodeValue(params),
|
|
4887
|
-
),
|
|
5113
|
+
validatedParams,
|
|
4888
5114
|
);
|
|
4889
5115
|
} finally {
|
|
4890
5116
|
await actorCtx.dispose();
|
|
@@ -4932,15 +5158,25 @@ export function buildNativeFactory(
|
|
|
4932
5158
|
() => actorCtx.assertCanMutateState(),
|
|
4933
5159
|
);
|
|
4934
5160
|
try {
|
|
4935
|
-
|
|
4936
|
-
|
|
4937
|
-
|
|
5161
|
+
let nextConnState: unknown;
|
|
5162
|
+
if (hasStaticConnState) {
|
|
5163
|
+
nextConnState = structuredClone(
|
|
5164
|
+
config.connState,
|
|
5165
|
+
);
|
|
5166
|
+
} else {
|
|
5167
|
+
const validatedParams = validateConnParams(
|
|
5168
|
+
schemaConfig.connParamsSchema,
|
|
5169
|
+
decodeValue(params),
|
|
5170
|
+
);
|
|
5171
|
+
// Schema validation is user code and can run while the
|
|
5172
|
+
// generation is lost.
|
|
5173
|
+
throwIfActorLost(runtime, ctx);
|
|
5174
|
+
nextConnState =
|
|
5175
|
+
await config.createConnState(
|
|
4938
5176
|
actorCtx,
|
|
4939
|
-
|
|
4940
|
-
schemaConfig.connParamsSchema,
|
|
4941
|
-
decodeValue(params),
|
|
4942
|
-
),
|
|
5177
|
+
validatedParams,
|
|
4943
5178
|
);
|
|
5179
|
+
}
|
|
4944
5180
|
connAdapter.initializeState(nextConnState);
|
|
4945
5181
|
return encodeValue(nextConnState);
|
|
4946
5182
|
} finally {
|
|
@@ -5099,6 +5335,13 @@ export function buildNativeFactory(
|
|
|
5099
5335
|
) => {
|
|
5100
5336
|
const { ctx, name, args, output } =
|
|
5101
5337
|
unwrapTsfnPayload(error, payload);
|
|
5338
|
+
// The action may finish after its generation was lost, and a lost
|
|
5339
|
+
// generation starts no new user callbacks.
|
|
5340
|
+
if (
|
|
5341
|
+
callNativeSync(() => runtime.actorIsLost(ctx))
|
|
5342
|
+
) {
|
|
5343
|
+
return output;
|
|
5344
|
+
}
|
|
5102
5345
|
const actorCtx = makeActorCtx(ctx);
|
|
5103
5346
|
try {
|
|
5104
5347
|
return encodeValue(
|
|
@@ -5196,6 +5439,9 @@ export function buildNativeFactory(
|
|
|
5196
5439
|
}
|
|
5197
5440
|
};
|
|
5198
5441
|
try {
|
|
5442
|
+
// Inspector routing awaits, and the generation can be lost
|
|
5443
|
+
// meanwhile. Schema validation is user code.
|
|
5444
|
+
throwIfActorLost(runtime, ctx);
|
|
5199
5445
|
const connParams = validateConnParams(
|
|
5200
5446
|
schemaConfig.connParamsSchema,
|
|
5201
5447
|
rawConnParams
|
|
@@ -5234,6 +5480,14 @@ export function buildNativeFactory(
|
|
|
5234
5480
|
abortRequest,
|
|
5235
5481
|
);
|
|
5236
5482
|
}
|
|
5483
|
+
// Connection setup awaits, and the generation can be lost meanwhile.
|
|
5484
|
+
if (
|
|
5485
|
+
callNativeSync(() =>
|
|
5486
|
+
runtime.actorIsLost(ctx),
|
|
5487
|
+
)
|
|
5488
|
+
) {
|
|
5489
|
+
throw actorGenerationLostError();
|
|
5490
|
+
}
|
|
5237
5491
|
const response = await config.onRequest(
|
|
5238
5492
|
requestCtx,
|
|
5239
5493
|
handlerRequest,
|
|
@@ -5334,6 +5588,11 @@ export function buildNativeFactory(
|
|
|
5334
5588
|
const { ctx } = unwrapTsfnPayload(error, payload);
|
|
5335
5589
|
const actorId = callNativeSync(() => runtime.actorId(ctx));
|
|
5336
5590
|
const executeRun = async () => {
|
|
5591
|
+
// The coordinator can hold `run` behind other run work until after the
|
|
5592
|
+
// generation was lost.
|
|
5593
|
+
if (callNativeSync(() => runtime.actorIsLost(ctx))) {
|
|
5594
|
+
return;
|
|
5595
|
+
}
|
|
5337
5596
|
const actorCtx = makeActorCtx(ctx);
|
|
5338
5597
|
try {
|
|
5339
5598
|
await run(actorCtx);
|
|
@@ -5349,6 +5608,7 @@ export function buildNativeFactory(
|
|
|
5349
5608
|
runtime.actorRestartRunHandler(ctx),
|
|
5350
5609
|
),
|
|
5351
5610
|
executeRun,
|
|
5611
|
+
callNativeSync(() => runtime.actorGeneration(ctx)),
|
|
5352
5612
|
);
|
|
5353
5613
|
// Only a start suppressed behind a failed replay must preserve a
|
|
5354
5614
|
// consumed durable wake. Closed actors must remain closed.
|
|
@@ -5425,14 +5685,18 @@ export function buildNativeFactory(
|
|
|
5425
5685
|
: makeActorCtx(ctx, undefined, cancelToken);
|
|
5426
5686
|
const runAction = async () => {
|
|
5427
5687
|
try {
|
|
5688
|
+
const validatedArgs = validateActionArgs(
|
|
5689
|
+
schemaConfig.actionInputSchemas,
|
|
5690
|
+
name,
|
|
5691
|
+
decodeArgs(args),
|
|
5692
|
+
);
|
|
5693
|
+
// Schema validation is user code and can run while the
|
|
5694
|
+
// generation is lost.
|
|
5695
|
+
throwIfActorLost(runtime, ctx);
|
|
5428
5696
|
return encodeValue(
|
|
5429
5697
|
await handler(
|
|
5430
5698
|
actorCtx,
|
|
5431
|
-
...
|
|
5432
|
-
schemaConfig.actionInputSchemas,
|
|
5433
|
-
name,
|
|
5434
|
-
decodeArgs(args),
|
|
5435
|
-
),
|
|
5699
|
+
...validatedArgs,
|
|
5436
5700
|
...(scheduledFire
|
|
5437
5701
|
? [scheduledFire]
|
|
5438
5702
|
: []),
|
|
@@ -5511,6 +5775,9 @@ export function buildNativeFactory(
|
|
|
5511
5775
|
if (canPublish && !(await canPublish(actorCtx))) {
|
|
5512
5776
|
throw forbiddenError();
|
|
5513
5777
|
}
|
|
5778
|
+
// `canPublish` can finish after the generation was lost, and
|
|
5779
|
+
// sending validates the body with user schema code.
|
|
5780
|
+
throwIfActorLost(runtime, ctx);
|
|
5514
5781
|
|
|
5515
5782
|
const decodedBody = decodeValue(body);
|
|
5516
5783
|
if (wait) {
|
|
@@ -5584,7 +5851,7 @@ export function buildNativeFactory(
|
|
|
5584
5851
|
};
|
|
5585
5852
|
|
|
5586
5853
|
return runtime.createActorFactory(
|
|
5587
|
-
callbacks,
|
|
5854
|
+
refuseCallbacksAfterLost(runtime, callbacks),
|
|
5588
5855
|
buildActorConfig(definition, registryConfig, runtime.kind),
|
|
5589
5856
|
);
|
|
5590
5857
|
}
|