@nylorun/runtime 0.22.0-beta → 0.24.0-beta
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/CHANGELOG.md +161 -0
- package/README.md +95 -30
- package/dist/adapters/execution/restate.d.ts +15 -3
- package/dist/adapters/execution/restate.js +78 -84
- package/dist/adapters/replication/pgoutput.d.ts +2 -0
- package/dist/adapters/replication/pgoutput.js +6 -3
- package/dist/adapters/sandbox/local.d.ts +9 -1
- package/dist/adapters/sandbox/local.js +16 -4
- package/dist/adapters/sandbox/virtual.js +4 -1
- package/dist/adapters/streams/s2.js +1 -13
- package/dist/api/a2a/endpoint.js +5 -1
- package/dist/api/a2a/routes.d.ts +2 -0
- package/dist/api/a2a/routes.js +2 -2
- package/dist/api/a2a/tasks.js +2 -1
- package/dist/api/ag-ui/endpoint.js +4 -2
- package/dist/api/ag-ui/history.js +4 -1
- package/dist/api/ag-ui/routes.d.ts +4 -1
- package/dist/api/ag-ui/routes.js +11 -10
- package/dist/api/ag-ui/translate.js +3 -2
- package/dist/api/components.d.ts +4953 -1204
- package/dist/api/components.js +25 -16
- package/dist/api/gate/keys.js +7 -1
- package/dist/api/gate/routes.d.ts +6 -0
- package/dist/api/gate/routes.js +39 -3
- package/dist/api/http/app.js +4 -4
- package/dist/api/http/define.d.ts +6 -0
- package/dist/api/http/define.js +13 -1
- package/dist/api/http/respond.js +1 -1
- package/dist/api/http/routes/artifacts.js +35 -8
- package/dist/api/http/routes/coverage.d.ts +9 -0
- package/dist/api/http/routes/coverage.js +34 -0
- package/dist/api/http/routes/environments.d.ts +11 -0
- package/dist/api/http/routes/environments.js +283 -0
- package/dist/api/http/routes/mcp.d.ts +1 -1
- package/dist/api/http/routes/mcp.js +2 -2
- package/dist/api/http/routes/me.js +1 -1
- package/dist/api/http/routes/reads.d.ts +45 -9
- package/dist/api/http/routes/reads.js +54 -5
- package/dist/api/http/routes/sessions.js +11 -6
- package/dist/api/http/routes/tenant.js +32 -10
- package/dist/api/openapi.js +31 -26
- package/dist/artifacts/disk.d.ts +59 -0
- package/dist/artifacts/disk.js +563 -0
- package/dist/artifacts/export.d.ts +1 -1
- package/dist/artifacts/export.js +2 -3
- package/dist/artifacts/folders.d.ts +6 -3
- package/dist/artifacts/folders.js +17 -12
- package/dist/artifacts/service.d.ts +2 -1
- package/dist/artifacts/service.js +6 -1
- package/dist/artifacts/workspace.d.ts +1 -1
- package/dist/artifacts/workspace.js +1 -1
- package/dist/core/flow-host.d.ts +3 -22
- package/dist/core/flow-host.js +11 -37
- package/dist/core/provider.d.ts +3 -2
- package/dist/core/provider.js +5 -37
- package/dist/core/sandbox-routes.d.ts +2 -21
- package/dist/core/sandbox-routes.js +10 -43
- package/dist/execution/types.d.ts +11 -9
- package/dist/execution/types.js +5 -7
- package/dist/gates/contract.d.ts +3 -24
- package/dist/gates/contract.js +1 -36
- package/dist/gates/egress-credentials.d.ts +9 -0
- package/dist/gates/egress-credentials.js +11 -4
- package/dist/gates/egress.d.ts +28 -1
- package/dist/gates/egress.js +115 -10
- package/dist/gates/fixture-model.d.ts +28 -0
- package/dist/gates/fixture-model.js +92 -0
- package/dist/gates/handler.js +4 -4
- package/dist/gates/http-tool.d.ts +6 -2
- package/dist/gates/http-tool.js +3 -5
- package/dist/gates/in-process.d.ts +5 -0
- package/dist/gates/in-process.js +12 -1
- package/dist/gates/journal.d.ts +30 -0
- package/dist/gates/journal.js +52 -0
- package/dist/gates/mcp-handler.d.ts +4 -0
- package/dist/gates/mcp-handler.js +5 -1
- package/dist/gates/meter.d.ts +10 -2
- package/dist/gates/meter.js +20 -5
- package/dist/gates/policy.d.ts +49 -0
- package/dist/gates/policy.js +134 -0
- package/dist/gates/tenant-vaults.d.ts +7 -7
- package/dist/gates/tenant-vaults.js +5 -5
- package/dist/gates/tool-calls.d.ts +10 -5
- package/dist/gates/tool-calls.js +28 -10
- package/dist/gates/tool-client.d.ts +8 -0
- package/dist/gates/tool-client.js +27 -11
- package/dist/gates/tool-contract.d.ts +10 -68
- package/dist/gates/tool-contract.js +1 -85
- package/dist/gates/tool-gate.d.ts +1 -1
- package/dist/harness/calls.d.ts +12 -5
- package/dist/harness/calls.js +21 -12
- package/dist/harness/executors.js +12 -9
- package/dist/harness/main.js +18 -5
- package/dist/harness/pod.d.ts +34 -0
- package/dist/harness/pod.js +41 -0
- package/dist/harness/service.d.ts +0 -1
- package/dist/harness/service.js +13 -5
- package/dist/harness/setup.d.ts +27 -0
- package/dist/harness/setup.js +150 -0
- package/dist/harness/ws-client.js +3 -2
- package/dist/harness/ws-port.d.ts +1 -5
- package/dist/harness/ws-port.js +1 -5
- package/dist/harness-api/in-process.js +3 -3
- package/dist/harness-api/record.d.ts +15 -1
- package/dist/harness-api/record.js +174 -44
- package/dist/harness-api/redact.d.ts +3 -0
- package/dist/harness-api/redact.js +45 -0
- package/dist/harness-api/server.d.ts +8 -0
- package/dist/harness-api/server.js +93 -13
- package/dist/harness-api/start.d.ts +10 -3
- package/dist/harness-api/start.js +40 -10
- package/dist/harness-api/tool-journal.d.ts +96 -0
- package/dist/harness-api/tool-journal.js +102 -0
- package/dist/harness-api/tool-status.d.ts +13 -0
- package/dist/harness-api/tool-status.js +26 -0
- package/dist/harness-api/workspace.d.ts +6 -3
- package/dist/harness-api/workspace.js +27 -11
- package/dist/harness-api/ws-server.d.ts +11 -3
- package/dist/harness-api/ws-server.js +94 -15
- package/dist/host/execution.d.ts +8 -6
- package/dist/host/gates.d.ts +2 -1
- package/dist/host/gates.js +10 -2
- package/dist/host/main.js +64 -21
- package/dist/host/operate.js +67 -5
- package/dist/host/stack-config.d.ts +70 -0
- package/dist/host/stack-config.js +67 -1
- package/dist/infra/execution.d.ts +18 -1
- package/dist/infra/execution.js +28 -4
- package/dist/infra/index.d.ts +1 -1
- package/dist/keys/client.js +4 -1
- package/dist/keys/keys.d.ts +5 -5
- package/dist/keys/keys.js +5 -7
- package/dist/management-openapi.json +999 -2211
- package/dist/mcp/connect.d.ts +25 -20
- package/dist/mcp/connect.js +29 -30
- package/dist/mcp/pool.d.ts +2 -2
- package/dist/mcp/pool.js +1 -1
- package/dist/mcp/preview.d.ts +3 -3
- package/dist/mcp/preview.js +10 -20
- package/dist/mcp/snapshot.d.ts +5 -11
- package/dist/mcp/snapshot.js +6 -7
- package/dist/model/pi-model.js +8 -0
- package/dist/openapi.json +6955 -2526
- package/dist/reads/types.d.ts +16 -3
- package/dist/record/append.d.ts +3 -1
- package/dist/record/append.js +1 -0
- package/dist/record/envelope.d.ts +5 -1
- package/dist/record/envelope.js +7 -3
- package/dist/record/environment.d.ts +25 -0
- package/dist/record/environment.js +44 -0
- package/dist/record/index.d.ts +1 -1
- package/dist/record/index.js +1 -1
- package/dist/retry.d.ts +21 -0
- package/dist/retry.js +34 -0
- package/dist/sandbox/disk-fs.d.ts +95 -0
- package/dist/sandbox/disk-fs.js +206 -0
- package/dist/sandbox/join.d.ts +17 -18
- package/dist/sandbox/join.js +46 -20
- package/dist/sandbox/manager.d.ts +16 -8
- package/dist/sandbox/manager.js +33 -17
- package/dist/sandbox/placement.d.ts +13 -12
- package/dist/sandbox/placement.js +29 -12
- package/dist/sandbox/pods/client.d.ts +6 -1
- package/dist/sandbox/pods/lifecycle.d.ts +28 -27
- package/dist/sandbox/pods/lifecycle.js +14 -26
- package/dist/sandbox/pods/reconcile.js +21 -15
- package/dist/sandbox/pods/spec.d.ts +15 -13
- package/dist/sandbox/pods/spec.js +16 -20
- package/dist/sandbox/records.d.ts +2 -2
- package/dist/sandbox/records.js +3 -3
- package/dist/sandbox/resolve.d.ts +35 -27
- package/dist/sandbox/resolve.js +66 -41
- package/dist/sandbox/session-sandbox.js +1 -1
- package/dist/sandbox/setup.d.ts +61 -0
- package/dist/sandbox/setup.js +123 -0
- package/dist/sandbox/share.d.ts +11 -21
- package/dist/sandbox/share.js +12 -41
- package/dist/sandbox/tenant-config.d.ts +12 -14
- package/dist/sandbox/tenant-config.js +12 -17
- package/dist/sandbox/types.d.ts +6 -0
- package/dist/store/postgres/control.js +8 -1
- package/dist/store/postgres/drizzle/0018_wake_outbox_retries.sql +3 -0
- package/dist/store/postgres/drizzle/0019_sandbox_signal_outbox.sql +18 -0
- package/dist/store/postgres/drizzle/0020_environments.sql +63 -0
- package/dist/store/postgres/drizzle/0021_disk_versions.sql +7 -0
- package/dist/store/postgres/drizzle/0022_model_usage_timing.sql +5 -0
- package/dist/store/postgres/drizzle/0023_artifact_pages.sql +1 -0
- package/dist/store/postgres/drizzle/meta/_journal.json +42 -0
- package/dist/store/postgres/reads.js +117 -17
- package/dist/store/postgres/record-writer.d.ts +4 -4
- package/dist/store/postgres/record-writer.js +9 -9
- package/dist/store/postgres/record.d.ts +7 -0
- package/dist/store/postgres/record.js +23 -2
- package/dist/store/postgres/schema.d.ts +1213 -779
- package/dist/store/postgres/schema.js +136 -76
- package/dist/store/postgres/store.js +283 -117
- package/dist/store/types.d.ts +206 -88
- package/dist/streams/relay/core.js +1 -13
- package/dist/tenant/advance.d.ts +7 -6
- package/dist/tenant/advance.js +121 -49
- package/dist/tenant/artifact-tool.js +3 -4
- package/dist/tenant/commands.d.ts +15 -2
- package/dist/tenant/commands.js +94 -14
- package/dist/tenant/context.d.ts +67 -33
- package/dist/tenant/context.js +4 -15
- package/dist/tenant/control.d.ts +8 -1
- package/dist/tenant/control.js +2 -0
- package/dist/tenant/effects.d.ts +1 -1
- package/dist/tenant/effects.js +37 -20
- package/dist/tenant/environments.d.ts +167 -0
- package/dist/tenant/environments.js +823 -0
- package/dist/tenant/harness-tokens.d.ts +21 -0
- package/dist/tenant/harness-tokens.js +61 -0
- package/dist/tenant/host-token.d.ts +1 -1
- package/dist/tenant/host-token.js +2 -4
- package/dist/tenant/identity-file.d.ts +4 -4
- package/dist/tenant/identity-file.js +17 -8
- package/dist/tenant/issuers.js +3 -3
- package/dist/tenant/principals.js +1 -1
- package/dist/tenant/reset.js +3 -3
- package/dist/tenant/runtime.d.ts +6 -1
- package/dist/tenant/runtime.js +30 -4
- package/dist/tenant/scheduler.d.ts +7 -0
- package/dist/tenant/scheduler.js +38 -7
- package/dist/tenant/session-streams.d.ts +0 -1
- package/dist/tenant/session-streams.js +1 -13
- package/dist/tenant/session.d.ts +3 -3
- package/dist/tenant/session.js +0 -14
- package/dist/tenant/sessions.d.ts +13 -13
- package/dist/tenant/sessions.js +61 -138
- package/dist/tenant/signing-keys.d.ts +5 -1
- package/dist/tenant/status.js +3 -3
- package/dist/tenant/streams.js +14 -29
- package/dist/tenant/sweep.d.ts +39 -12
- package/dist/tenant/sweep.js +168 -63
- package/dist/tenant/transcript.d.ts +210 -20
- package/dist/tenant/transcript.js +62 -11
- package/dist/tenant/turn-timing.d.ts +43 -0
- package/dist/tenant/turn-timing.js +39 -0
- package/dist/tenant/types.d.ts +2 -0
- package/dist/traces/ids.d.ts +42 -0
- package/dist/traces/ids.js +85 -0
- package/dist/traces/key.d.ts +6 -0
- package/dist/traces/key.js +37 -0
- package/dist/traces/map.d.ts +113 -0
- package/dist/traces/map.js +317 -0
- package/dist/traces/options.d.ts +7 -0
- package/dist/traces/options.js +1 -0
- package/dist/traces/otlp.d.ts +42 -0
- package/dist/traces/otlp.js +84 -0
- package/dist/traces/projector.d.ts +58 -0
- package/dist/traces/projector.js +200 -0
- package/dist/traces/read.d.ts +23 -0
- package/dist/traces/read.js +16 -0
- package/dist/vault/coverage.d.ts +26 -0
- package/dist/vault/coverage.js +64 -0
- package/dist/vault/error.d.ts +11 -1
- package/dist/vault/error.js +5 -1
- package/dist/vault/service.d.ts +47 -48
- package/dist/vault/service.js +321 -382
- package/dist/vault/sources.d.ts +5 -16
- package/dist/vault/sources.js +6 -8
- package/dist/version.d.ts +1 -1
- package/dist/version.js +2 -2
- package/package.json +14 -13
- package/dist/api/http/routes/sandboxes.d.ts +0 -9
- package/dist/api/http/routes/sandboxes.js +0 -153
- package/dist/api/http/routes/vaults.d.ts +0 -10
- package/dist/api/http/routes/vaults.js +0 -139
- package/dist/record/sandbox.d.ts +0 -25
- package/dist/record/sandbox.js +0 -43
- package/dist/tenant/sandboxes.d.ts +0 -122
- package/dist/tenant/sandboxes.js +0 -562
package/dist/tenant/sweep.js
CHANGED
|
@@ -4,26 +4,36 @@
|
|
|
4
4
|
* and startup scan the Tenant runtime used to keep. It reconciles the Session Store with what
|
|
5
5
|
* Durable Session Execution was asked to do; it never decides when a session advances:
|
|
6
6
|
*
|
|
7
|
-
* 1. **
|
|
8
|
-
* after commit that failed, or that a crash between the commit and the send cut
|
|
9
|
-
* first pass on this process takes every row, so a previous process's are not
|
|
10
|
-
* waiting; later passes take
|
|
11
|
-
*
|
|
12
|
-
*
|
|
7
|
+
* 1. **Outbox.** Outbox rows (`Tx.wake`, `Tx.signalSandbox`) are delivered again and deleted:
|
|
8
|
+
* a delivery after commit that failed, or that a crash between the commit and the send cut
|
|
9
|
+
* off. The first pass on this process takes every row, so a previous process's are not
|
|
10
|
+
* left waiting; later passes take those whose delivery after commit failed and those older
|
|
11
|
+
* than `OUTBOX_GRACE_MS` (the database's clock), batch after batch within
|
|
12
|
+
* `DRAIN_BUDGET_MS`. Each row carries the idempotency key it was first sent with, so a
|
|
13
|
+
* request the execution did accept before the crash, or one still being sent after its
|
|
14
|
+
* commit, runs once. Rows are delivered one by one: a row that fails is retried with
|
|
15
|
+
* backoff and parked after `MAX_OUTBOX_ATTEMPTS` (`deliverOutbox`), and no delivery fails
|
|
16
|
+
* the step.
|
|
13
17
|
* 2. **Linked agents.** Pending workflow `agent` effects whose linked turn already settled
|
|
14
|
-
* are completed and the workflow is woken
|
|
15
|
-
*
|
|
18
|
+
* are completed and the workflow is woken. A linked turn's settle does both in its own
|
|
19
|
+
* transaction; this is the backstop for one that settle left pending, such as an effect
|
|
20
|
+
* an older Runtime wrote.
|
|
16
21
|
* 3. **Orphaned sessions.** On this process's first pass for the Tenant, then at most every
|
|
17
22
|
* `ORPHAN_SCAN_MS`: `running` or `runnable` sessions with no owner, or an owner whose lease
|
|
18
|
-
* expired, are woken with reason `recover
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
+
* expired, are woken with reason `recover`, page by page within `DRAIN_BUDGET_MS`; a scan
|
|
24
|
+
* the budget cut short goes on at the next pass. Neither a lost wake (step 1) nor a Worker
|
|
25
|
+
* that died mid-advance (the execution retries that advance, which takes over, §11.4)
|
|
26
|
+
* needs it: it is the backstop for what the execution itself lost or ended without an
|
|
27
|
+
* advance, such as an in-process execution's queue when its process stopped, an advance
|
|
28
|
+
* for a Tenant not open on its Worker, an invocation an operator killed, Restate's state
|
|
29
|
+
* wiped (§17.11), or a parked outbox row.
|
|
23
30
|
* 4. **Sandboxes.** Idle sandboxes are stopped, records of compute no longer held are marked
|
|
24
|
-
* stopped, and sandboxes whose session or
|
|
31
|
+
* stopped, and sandboxes whose session or Environment is gone are removed, by the
|
|
25
32
|
* workspace capability (`harness-api/workspace.ts`: here, or in the harness that serves
|
|
26
|
-
* workspaces). Idle MCP connections of the in-process harness are closed.
|
|
33
|
+
* workspaces). Idle MCP connections of the in-process harness are closed. As often as the
|
|
34
|
+
* orphan scan, pod Environments whose change stalled get a reconcile (`reconcileStalePods`).
|
|
35
|
+
* Environments unused for their `lifecycle.deleteAfter` are deleted
|
|
36
|
+
* (`sweepUnusedEnvironments`).
|
|
27
37
|
* 5. **Control signals.** Signals on the control bus older than `SIGNAL_RETENTION_MS` are
|
|
28
38
|
* deleted: followers read back only the last two minutes (`store/postgres/control.ts`).
|
|
29
39
|
* 6. **Hooks.** Callbacks registered with `ctx.onSweep`.
|
|
@@ -33,19 +43,35 @@
|
|
|
33
43
|
* step does not stop the others; the first error is rethrown at the end so the execution
|
|
34
44
|
* reports it.
|
|
35
45
|
*/
|
|
46
|
+
import { sweepUnusedEnvironments } from "./environments.js";
|
|
47
|
+
import { sweepPendingSeeds } from "../artifacts/disk.js";
|
|
36
48
|
import { pendingAgentEffects, reconcilePendingAgentEffect, } from "../core/flow-host.js";
|
|
37
49
|
const BATCH = 100;
|
|
38
50
|
/** How long a control signal is kept after it was written. */
|
|
39
51
|
const SIGNAL_RETENTION_MS = 60 * 60 * 1000;
|
|
40
52
|
/**
|
|
41
|
-
* How old
|
|
42
|
-
*
|
|
43
|
-
*
|
|
53
|
+
* How old an outbox row is before the sweep delivers it, unless its delivery after commit
|
|
54
|
+
* already failed (then it is due at once): longer than that delivery can take with its
|
|
55
|
+
* retries, so the sweep does not send again, and double the load on, an execution that is
|
|
56
|
+
* only slow. Sending one twice is harmless either way (the idempotency key).
|
|
57
|
+
*/
|
|
58
|
+
export const OUTBOX_GRACE_MS = 30_000;
|
|
59
|
+
/**
|
|
60
|
+
* How often the orphan scan, and the stale pod sandbox check, run after the first pass of a
|
|
61
|
+
* Tenant opened on this process.
|
|
44
62
|
*/
|
|
45
|
-
export const WAKE_GRACE_MS = 2000;
|
|
46
|
-
/** How often the orphan scan runs after the first pass of a Tenant opened on this process. */
|
|
47
63
|
export const ORPHAN_SCAN_MS = 60_000;
|
|
48
|
-
/**
|
|
64
|
+
/**
|
|
65
|
+
* How long one pass may spend draining the outbox, and paging through orphaned sessions.
|
|
66
|
+
* A backlog is worked off over several passes rather than holding one (and with it the
|
|
67
|
+
* Tenant's sweep object in the execution) for long.
|
|
68
|
+
*/
|
|
69
|
+
export const DRAIN_BUDGET_MS = 2000;
|
|
70
|
+
/**
|
|
71
|
+
* Each open Tenant's sweep state on this process: when its last orphan scan finished, the
|
|
72
|
+
* last session id of one that ran out of budget, which the next pass goes on from, and when
|
|
73
|
+
* the stale pod sandbox check last ran.
|
|
74
|
+
*/
|
|
49
75
|
const passes = new WeakMap();
|
|
50
76
|
export async function sweep(ctx) {
|
|
51
77
|
if (ctx.closing || ctx.closed)
|
|
@@ -54,20 +80,22 @@ export async function sweep(ctx) {
|
|
|
54
80
|
const first = !passes.has(ctx);
|
|
55
81
|
const pass = passes.get(ctx) ?? {};
|
|
56
82
|
passes.set(ctx, pass);
|
|
57
|
-
const scanOrphans = pass.orphansAt === undefined ||
|
|
83
|
+
const scanOrphans = pass.orphansAt === undefined ||
|
|
84
|
+
pass.orphansAfter !== undefined ||
|
|
85
|
+
now.getTime() - pass.orphansAt >= ORPHAN_SCAN_MS;
|
|
86
|
+
const checkPods = pass.podsAt === undefined || now.getTime() - pass.podsAt >= ORPHAN_SCAN_MS;
|
|
58
87
|
const steps = [
|
|
59
|
-
[
|
|
60
|
-
"wakes",
|
|
61
|
-
() => deliverPendingWakes(ctx, first ? now : new Date(now.getTime() - WAKE_GRACE_MS)),
|
|
62
|
-
],
|
|
88
|
+
["outbox", () => deliverOutbox(ctx, first ? 0 : OUTBOX_GRACE_MS)],
|
|
63
89
|
["linked", () => reconcileLinkedAgents(ctx)],
|
|
64
90
|
...(scanOrphans
|
|
65
91
|
? [
|
|
66
92
|
[
|
|
67
93
|
"orphans",
|
|
68
94
|
async () => {
|
|
69
|
-
await wakeOrphanedSessions(ctx, now);
|
|
70
|
-
pass.
|
|
95
|
+
const scan = await wakeOrphanedSessions(ctx, now, pass.orphansAfter);
|
|
96
|
+
pass.orphansAfter = scan.next;
|
|
97
|
+
if (scan.next === undefined)
|
|
98
|
+
pass.orphansAt = now.getTime();
|
|
71
99
|
},
|
|
72
100
|
],
|
|
73
101
|
]
|
|
@@ -77,10 +105,24 @@ export async function sweep(ctx) {
|
|
|
77
105
|
() => ctx.sandbox.sweep({
|
|
78
106
|
now: now.getTime(),
|
|
79
107
|
sessionExists: async (id) => !!(await ctx.store.tx((t) => t.get("sessions", id))),
|
|
80
|
-
sandboxExists: async (id) => (await ctx.store.tx((t) => t.
|
|
108
|
+
sandboxExists: async (id) => (await ctx.store.tx((t) => t.environment(id))) !== undefined,
|
|
81
109
|
}),
|
|
82
110
|
],
|
|
83
|
-
|
|
111
|
+
...(checkPods
|
|
112
|
+
? [
|
|
113
|
+
[
|
|
114
|
+
"pods",
|
|
115
|
+
async () => {
|
|
116
|
+
await reconcileStalePods(ctx, now);
|
|
117
|
+
pass.podsAt = now.getTime();
|
|
118
|
+
},
|
|
119
|
+
],
|
|
120
|
+
]
|
|
121
|
+
: []),
|
|
122
|
+
// Environments unused for their `lifecycle.deleteAfter`: one query when none is due.
|
|
123
|
+
["environments", () => sweepUnusedEnvironments(ctx, now.getTime())],
|
|
124
|
+
// Seeds of new volumes whose engine is connected and no process copies (R2d step 3).
|
|
125
|
+
["seeds", () => sweepPendingSeeds(ctx)],
|
|
84
126
|
// A harness elsewhere closes its own idle MCP connections.
|
|
85
127
|
["mcp", async () => ctx.mcp?.sweep()],
|
|
86
128
|
[
|
|
@@ -107,67 +149,130 @@ export async function sweep(ctx) {
|
|
|
107
149
|
if (failure)
|
|
108
150
|
throw failure.error;
|
|
109
151
|
}
|
|
152
|
+
/** Failed deliveries after which the sweep parks an outbox row (`Tx.failOutbox`). */
|
|
153
|
+
export const MAX_OUTBOX_ATTEMPTS = 10;
|
|
154
|
+
/** How long after its first failed delivery the sweep tries a row again; doubled each time. */
|
|
155
|
+
const RETRY_MS = 5000;
|
|
156
|
+
const RETRY_MAX_MS = 10 * 60_000;
|
|
110
157
|
/**
|
|
111
|
-
* Deliver the
|
|
112
|
-
*
|
|
113
|
-
*
|
|
158
|
+
* Deliver the outbox rows that are due (`Tx.pendingOutbox`: older than `graceMs`, or whose
|
|
159
|
+
* delivery failed), oldest first, batch after batch until none is left or `budgetMs` is
|
|
160
|
+
* spent, and delete the delivered ones. Each row is delivered on its own: one that fails
|
|
161
|
+
* (Restate refuses it, or a Runtime that does not know a wake's reason) is tried again later,
|
|
162
|
+
* with backoff, so it never holds back the rows behind it, and after `MAX_OUTBOX_ATTEMPTS`
|
|
163
|
+
* failures it is parked: logged once, at error, and kept in the outbox for an operator. A
|
|
164
|
+
* parked wake's session is still re-woken by the orphan scan while it is runnable, and a
|
|
165
|
+
* parked sandbox signal's pod by the stale pod check. A row the Tenant declines (it is
|
|
166
|
+
* closing) stays as it is, and ends the drain. Never throws for a delivery; returns the rows
|
|
167
|
+
* delivered.
|
|
114
168
|
*/
|
|
115
|
-
export async function
|
|
116
|
-
const
|
|
169
|
+
export async function deliverOutbox(ctx, graceMs = 0, budgetMs = DRAIN_BUDGET_MS) {
|
|
170
|
+
const deadline = Date.now() + budgetMs;
|
|
117
171
|
const delivered = [];
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
172
|
+
for (;;) {
|
|
173
|
+
const pending = await ctx.store.tx((t) => t.pendingOutbox(graceMs, BATCH));
|
|
174
|
+
const batch = [];
|
|
175
|
+
let done = pending.length < BATCH;
|
|
176
|
+
try {
|
|
177
|
+
for (const row of pending) {
|
|
178
|
+
if (Date.now() >= deadline) {
|
|
179
|
+
done = true;
|
|
180
|
+
break;
|
|
181
|
+
}
|
|
182
|
+
let accepted;
|
|
183
|
+
try {
|
|
184
|
+
accepted = await ctx.send(row.request);
|
|
185
|
+
}
|
|
186
|
+
catch (error) {
|
|
187
|
+
await failed(ctx, row, error);
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
if (accepted)
|
|
191
|
+
batch.push(row);
|
|
192
|
+
else
|
|
193
|
+
done = true;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
finally {
|
|
197
|
+
// Delivered ones go even when a later step fails, so they are not sent again.
|
|
198
|
+
if (batch.length > 0)
|
|
199
|
+
await ctx.store.tx((t) => t.deleteOutbox(batch));
|
|
200
|
+
}
|
|
201
|
+
delivered.push(...batch);
|
|
202
|
+
if (done)
|
|
203
|
+
return delivered;
|
|
127
204
|
}
|
|
128
|
-
|
|
205
|
+
}
|
|
206
|
+
/** Records a failed delivery of `row`: retried with backoff, parked after the last attempt. */
|
|
207
|
+
async function failed(ctx, row, error) {
|
|
208
|
+
const attempts = row.attempts + 1;
|
|
209
|
+
const park = attempts >= MAX_OUTBOX_ATTEMPTS;
|
|
210
|
+
const retryInMs = Math.min(RETRY_MS * 2 ** (attempts - 1), RETRY_MAX_MS);
|
|
211
|
+
await ctx.store.tx((t) => t.failOutbox(row, { retryInMs, park }));
|
|
212
|
+
const { request } = row;
|
|
213
|
+
const fields = {
|
|
214
|
+
rowId: row.id,
|
|
215
|
+
...(request.kind === "wake"
|
|
216
|
+
? { sessionId: request.sessionId, reason: request.wake.reason }
|
|
217
|
+
: { sandboxId: request.sandboxId, signal: request.signal.kind }),
|
|
218
|
+
attempts,
|
|
219
|
+
message: error instanceof Error ? error.message : String(error),
|
|
220
|
+
};
|
|
221
|
+
if (park)
|
|
222
|
+
ctx.config.logger.error("outbox row parked: its delivery keeps failing", fields);
|
|
223
|
+
else
|
|
224
|
+
ctx.config.logger.warn("outbox delivery failed; the sweep tries it again", fields);
|
|
129
225
|
}
|
|
130
226
|
/** Settle pending workflow `agent` effects whose linked turn already finished. */
|
|
131
227
|
export async function reconcileLinkedAgents(ctx) {
|
|
132
228
|
const { store } = ctx;
|
|
133
229
|
const effects = await store.tx((t) => pendingAgentEffects(t));
|
|
134
230
|
for (const effect of effects)
|
|
135
|
-
await store.tx((t) => reconcilePendingAgentEffect({
|
|
136
|
-
t,
|
|
137
|
-
effectId: effect.request.effectId,
|
|
138
|
-
schedule: ctx.wake,
|
|
139
|
-
}));
|
|
231
|
+
await store.tx((t) => reconcilePendingAgentEffect({ t, effectId: effect.request.effectId }));
|
|
140
232
|
}
|
|
141
|
-
/**
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
233
|
+
/**
|
|
234
|
+
* Wake `running`/`runnable` sessions without a live owner, page after page from the first id
|
|
235
|
+
* after `after`, until none is left or `budgetMs` is spent. Returns the ids woken, and the last
|
|
236
|
+
* id when the budget ran out first (`next`, where the next scan goes on).
|
|
237
|
+
*/
|
|
238
|
+
export async function wakeOrphanedSessions(ctx, now = new Date(), after, budgetMs = DRAIN_BUDGET_MS) {
|
|
239
|
+
const deadline = Date.now() + budgetMs;
|
|
240
|
+
const woken = [];
|
|
241
|
+
let cursor = after;
|
|
242
|
+
for (;;) {
|
|
243
|
+
const page = await ctx.store.tx((t) => t.orphanedSessions(now, BATCH, cursor));
|
|
244
|
+
for (const session of page) {
|
|
245
|
+
await ctx.wake(session.id, { reason: "recover" });
|
|
246
|
+
woken.push(session.id);
|
|
247
|
+
cursor = session.id;
|
|
248
|
+
}
|
|
249
|
+
if (page.length < BATCH)
|
|
250
|
+
return { woken };
|
|
251
|
+
if (Date.now() >= deadline)
|
|
252
|
+
return { woken, next: cursor };
|
|
253
|
+
}
|
|
148
254
|
}
|
|
149
255
|
/** A pod sandbox in a change this long without a reconcile gets one from the sweep. */
|
|
150
256
|
const STALE_POD_MS = 60_000;
|
|
151
257
|
/**
|
|
152
258
|
* Pod sandboxes (F7.2) whose change has not finished (creating, deleting, an old volume still
|
|
153
259
|
* to delete, an expiry not yet recorded), or that should run but whose engine is not connected,
|
|
154
|
-
* and whose row has not moved for a minute
|
|
155
|
-
*
|
|
156
|
-
*
|
|
260
|
+
* and whose row has not moved for a minute, get a reconcile: a pod or volume deleted outside
|
|
261
|
+
* the Runtime is found, and a change goes on whose reconcile the execution itself lost (its
|
|
262
|
+
* retry timer, an invocation an operator killed, Restate's state wiped) or whose outbox row
|
|
263
|
+
* was parked. A reconcile a commit asked for is never lost: it is in the outbox.
|
|
157
264
|
*/
|
|
158
265
|
async function reconcileStalePods(ctx, now) {
|
|
159
266
|
if (!ctx.pods)
|
|
160
267
|
return;
|
|
161
|
-
const stale = await ctx.store.tx(async (t) => (await t.
|
|
268
|
+
const stale = await ctx.store.tx(async (t) => (await t.listEnvironments()).filter((row) => {
|
|
162
269
|
const pod = row.pod;
|
|
163
270
|
if (!pod || now.getTime() - Date.parse(row.updatedAt) < STALE_POD_MS)
|
|
164
271
|
return false;
|
|
165
|
-
const expiring = pod.expiresAt !== undefined && Date.parse(pod.expiresAt) <= now.getTime() && pod.observed !== "expired";
|
|
166
272
|
return (pod.desired === "deleted" ||
|
|
167
273
|
pod.retiring !== undefined ||
|
|
168
274
|
pod.observed === "creating" ||
|
|
169
275
|
pod.observed === "deleting" ||
|
|
170
|
-
expiring ||
|
|
171
276
|
(pod.desired === "suspended" && pod.observed !== "suspended") ||
|
|
172
277
|
// Running, but its engine is not connected: the pod may be gone, or its volume.
|
|
173
278
|
(pod.desired === "running" && pod.observed === "running" && !ctx.harness.hosting(row.id)));
|