@rikalabs/akter 0.1.0-alpha.0 → 0.1.0-alpha.3
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 +46 -3
- package/README.md +2 -2
- package/dist/actor/definition.d.ts +13 -9
- package/dist/actor/descriptor.d.ts +2 -2
- package/dist/actor/descriptor.js +4 -4
- package/dist/client/calls.js +1 -1
- package/dist/client/index.d.ts +1 -1
- package/dist/client/index.js +1 -1
- package/dist/errors/actor.d.ts +12 -42
- package/dist/errors/actor.js +13 -42
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/members/payload.d.ts +1 -1
- package/dist/protocol/inspection.d.ts +221 -1
- package/dist/protocol/inspection.js +151 -2
- package/dist/protocol/wire.d.ts +6 -49
- package/dist/protocol/wire.js +1 -10
- package/dist/runtime/actors.d.ts +3 -0
- package/dist/runtime/adoption/backfill.js +1 -1
- package/dist/runtime/adoption/enforce.js +2 -2
- package/dist/runtime/adoption/observe.js +1 -1
- package/dist/runtime/adoption/plan.d.ts +1 -1
- package/dist/runtime/adoption/startup.js +1 -1
- package/dist/runtime/adoption/status.d.ts +1 -1
- package/dist/runtime/app.d.ts +65 -0
- package/dist/runtime/app.js +38 -0
- package/dist/runtime/connections/holder.d.ts +11 -1
- package/dist/runtime/connections/holder.js +8 -4
- package/dist/runtime/connections/streams.d.ts +4 -4
- package/dist/runtime/content/store.js +15 -11
- package/dist/runtime/database/bounded.d.ts +4 -18
- package/dist/runtime/database/bounded.js +36 -22
- package/dist/runtime/database/coordination.d.ts +12 -3
- package/dist/runtime/database/coordination.js +29 -36
- package/dist/runtime/database/migrations.d.ts +61 -29
- package/dist/runtime/database/migrations.js +375 -139
- package/dist/runtime/database/replica.d.ts +4 -1
- package/dist/runtime/database/replica.js +4 -1
- package/dist/runtime/database/schema.d.ts +13 -0
- package/dist/runtime/database/schema.js +13 -0
- package/dist/runtime/database/shards.d.ts +31 -13
- package/dist/runtime/database/shards.js +38 -26
- package/dist/runtime/entity/register.d.ts +8 -1
- package/dist/runtime/entity/register.js +58 -7
- package/dist/runtime/fleet/checks.js +4 -4
- package/dist/runtime/fleet/maintainer.d.ts +1 -1
- package/dist/runtime/fleet/maintainer.js +1 -1
- package/dist/runtime/index.d.ts +2 -0
- package/dist/runtime/index.js +1 -0
- package/dist/runtime/inspector/layer.d.ts +20 -0
- package/dist/runtime/inspector/layer.js +224 -5
- package/dist/runtime/inspector/queries.d.ts +54 -8
- package/dist/runtime/inspector/queries.js +84 -15
- package/dist/runtime/layer.d.ts +48 -39
- package/dist/runtime/layer.js +124 -56
- package/dist/runtime/operators/audit.d.ts +3 -0
- package/dist/runtime/operators/audit.js +6 -2
- package/dist/runtime/operators/repair.js +30 -10
- package/dist/runtime/operators/seed.js +4 -2
- package/dist/runtime/payloads/versions.d.ts +4 -4
- package/dist/runtime/payloads/versions.js +24 -15
- package/dist/runtime/queries.js +4 -2
- package/dist/runtime/registration.d.ts +5 -2
- package/dist/runtime/registration.js +3 -3
- package/dist/runtime/runner.d.ts +12 -0
- package/dist/runtime/runner.js +12 -0
- package/dist/runtime/storage/codec.d.ts +19 -7
- package/dist/runtime/storage/codec.js +23 -7
- package/dist/runtime/storage/placements.d.ts +6 -1
- package/dist/runtime/storage/placements.js +98 -6
- package/dist/runtime/storage/retention.d.ts +6 -0
- package/dist/runtime/storage/retention.js +25 -13
- package/dist/runtime/subscriptions/relay.js +47 -44
- package/dist/runtime/subscriptions/storage.d.ts +3 -2
- package/dist/runtime/subscriptions/storage.js +9 -6
- package/dist/runtime/telemetry/live.d.ts +255 -0
- package/dist/runtime/telemetry/live.js +540 -0
- package/dist/runtime/telemetry/metrics.d.ts +2 -0
- package/dist/runtime/telemetry/metrics.js +4 -1
- package/dist/runtime/telemetry/sampler.js +33 -13
- package/dist/runtime/telemetry/spans.d.ts +1 -1
- package/dist/runtime/telemetry/spans.js +1 -1
- package/dist/runtime/topology/locks.d.ts +6 -3
- package/dist/runtime/topology/locks.js +27 -4
- package/dist/runtime/turn/execute.d.ts +2 -0
- package/dist/runtime/turn/execute.js +5 -2
- package/dist/runtime/turn/pipeline.d.ts +5 -12
- package/dist/runtime/turn/pipeline.js +11 -11
- package/dist/runtime/turn/relay.d.ts +13 -3
- package/dist/runtime/turn/relay.js +27 -14
- package/dist/runtime/turn/rows.js +4 -4
- package/dist/runtime/workflows/compatibility.d.ts +1 -1
- package/dist/runtime/workflows/compatibility.js +85 -31
- package/dist/runtime/workflows/engine.js +20 -17
- package/dist/serve/layer.js +3 -2
- package/dist/tables/owned.d.ts +1 -1
- package/dist/tables/owned.js +1 -1
- package/dist/testing/actor-test.d.ts +2 -24
- package/dist/testing/actor-test.js +1 -27
- package/dist/testing/index.d.ts +0 -12
- package/dist/testing/index.js +0 -6
- package/package.json +1 -1
- package/dist/runtime/database/neki/migrations.d.ts +0 -31
- package/dist/runtime/database/neki/migrations.js +0 -276
- package/dist/runtime/database/neki/session.d.ts +0 -23
- package/dist/runtime/database/neki/session.js +0 -31
- package/dist/testing/cluster.d.ts +0 -93
- package/dist/testing/cluster.js +0 -296
- package/dist/testing/conformance/access.d.ts +0 -5
- package/dist/testing/conformance/access.js +0 -531
- package/dist/testing/conformance/admission.d.ts +0 -11
- package/dist/testing/conformance/admission.js +0 -182
- package/dist/testing/conformance/adoption.d.ts +0 -2
- package/dist/testing/conformance/adoption.js +0 -1019
- package/dist/testing/conformance/assertions.d.ts +0 -202
- package/dist/testing/conformance/assertions.js +0 -849
- package/dist/testing/conformance/batches.d.ts +0 -41
- package/dist/testing/conformance/batches.js +0 -388
- package/dist/testing/conformance/blobs.d.ts +0 -14
- package/dist/testing/conformance/blobs.js +0 -405
- package/dist/testing/conformance/capacity.d.ts +0 -8
- package/dist/testing/conformance/capacity.js +0 -433
- package/dist/testing/conformance/client.d.ts +0 -5
- package/dist/testing/conformance/client.js +0 -719
- package/dist/testing/conformance/cold-serve.d.ts +0 -16
- package/dist/testing/conformance/cold-serve.js +0 -254
- package/dist/testing/conformance/connections/actors.d.ts +0 -1143
- package/dist/testing/conformance/connections/actors.js +0 -180
- package/dist/testing/conformance/connections/harness.d.ts +0 -1754
- package/dist/testing/conformance/connections/harness.js +0 -142
- package/dist/testing/conformance/connections/holders.d.ts +0 -4
- package/dist/testing/conformance/connections/holders.js +0 -165
- package/dist/testing/conformance/connections/lifecycle.d.ts +0 -4
- package/dist/testing/conformance/connections/lifecycle.js +0 -113
- package/dist/testing/conformance/connections/limits.d.ts +0 -4
- package/dist/testing/conformance/connections/limits.js +0 -127
- package/dist/testing/conformance/connections/reauthorization.d.ts +0 -4
- package/dist/testing/conformance/connections/reauthorization.js +0 -170
- package/dist/testing/conformance/connections/resync.d.ts +0 -4
- package/dist/testing/conformance/connections/resync.js +0 -360
- package/dist/testing/conformance/connections/takeover.d.ts +0 -4
- package/dist/testing/conformance/connections/takeover.js +0 -287
- package/dist/testing/conformance/connections.d.ts +0 -4
- package/dist/testing/conformance/connections.js +0 -15
- package/dist/testing/conformance/content-blobs.d.ts +0 -16
- package/dist/testing/conformance/content-blobs.js +0 -534
- package/dist/testing/conformance/counter.d.ts +0 -16
- package/dist/testing/conformance/counter.js +0 -813
- package/dist/testing/conformance/cron.d.ts +0 -5
- package/dist/testing/conformance/cron.js +0 -665
- package/dist/testing/conformance/drain.d.ts +0 -26
- package/dist/testing/conformance/drain.js +0 -540
- package/dist/testing/conformance/effect-control.d.ts +0 -38
- package/dist/testing/conformance/effect-control.js +0 -1059
- package/dist/testing/conformance/effects.d.ts +0 -23
- package/dist/testing/conformance/effects.js +0 -432
- package/dist/testing/conformance/events.d.ts +0 -28
- package/dist/testing/conformance/events.js +0 -335
- package/dist/testing/conformance/export.d.ts +0 -2
- package/dist/testing/conformance/export.js +0 -368
- package/dist/testing/conformance/fleet.d.ts +0 -444
- package/dist/testing/conformance/fleet.js +0 -1009
- package/dist/testing/conformance/groups.d.ts +0 -7
- package/dist/testing/conformance/groups.js +0 -353
- package/dist/testing/conformance/heap.d.ts +0 -3
- package/dist/testing/conformance/heap.js +0 -149
- package/dist/testing/conformance/http.d.ts +0 -2809
- package/dist/testing/conformance/http.js +0 -1234
- package/dist/testing/conformance/inspection-views.d.ts +0 -8
- package/dist/testing/conformance/inspection-views.js +0 -272
- package/dist/testing/conformance/inspector.d.ts +0 -7
- package/dist/testing/conformance/inspector.js +0 -650
- package/dist/testing/conformance/mint.d.ts +0 -58
- package/dist/testing/conformance/mint.js +0 -692
- package/dist/testing/conformance/multi-runner.d.ts +0 -8
- package/dist/testing/conformance/multi-runner.js +0 -403
- package/dist/testing/conformance/neki/plan.d.ts +0 -19
- package/dist/testing/conformance/neki/plan.js +0 -30
- package/dist/testing/conformance/observability.d.ts +0 -3
- package/dist/testing/conformance/observability.js +0 -343
- package/dist/testing/conformance/offline.d.ts +0 -4
- package/dist/testing/conformance/offline.js +0 -474
- package/dist/testing/conformance/operator-harness.d.ts +0 -57
- package/dist/testing/conformance/operator-harness.js +0 -78
- package/dist/testing/conformance/operator.d.ts +0 -3
- package/dist/testing/conformance/operator.js +0 -379
- package/dist/testing/conformance/outbox-cross-shard.d.ts +0 -14
- package/dist/testing/conformance/outbox-cross-shard.js +0 -160
- package/dist/testing/conformance/outbox.d.ts +0 -36
- package/dist/testing/conformance/outbox.js +0 -437
- package/dist/testing/conformance/payload-migrations.d.ts +0 -3
- package/dist/testing/conformance/payload-migrations.js +0 -612
- package/dist/testing/conformance/pipeline.d.ts +0 -3
- package/dist/testing/conformance/pipeline.js +0 -1040
- package/dist/testing/conformance/placement.d.ts +0 -22
- package/dist/testing/conformance/placement.js +0 -539
- package/dist/testing/conformance/platform.d.ts +0 -40
- package/dist/testing/conformance/platform.js +0 -101
- package/dist/testing/conformance/progress.d.ts +0 -39
- package/dist/testing/conformance/progress.js +0 -666
- package/dist/testing/conformance/properties.d.ts +0 -6
- package/dist/testing/conformance/properties.js +0 -295
- package/dist/testing/conformance/protocols.d.ts +0 -4
- package/dist/testing/conformance/protocols.js +0 -618
- package/dist/testing/conformance/provider/advertise.d.ts +0 -21
- package/dist/testing/conformance/provider/advertise.js +0 -44
- package/dist/testing/conformance/read-your-writes.d.ts +0 -20
- package/dist/testing/conformance/read-your-writes.js +0 -192
- package/dist/testing/conformance/reducers.d.ts +0 -8
- package/dist/testing/conformance/reducers.js +0 -258
- package/dist/testing/conformance/relay.d.ts +0 -42
- package/dist/testing/conformance/relay.js +0 -1115
- package/dist/testing/conformance/restore.d.ts +0 -43
- package/dist/testing/conformance/restore.js +0 -341
- package/dist/testing/conformance/retention.d.ts +0 -14
- package/dist/testing/conformance/retention.js +0 -463
- package/dist/testing/conformance/rls.d.ts +0 -24
- package/dist/testing/conformance/rls.js +0 -576
- package/dist/testing/conformance/simulation.d.ts +0 -31
- package/dist/testing/conformance/simulation.js +0 -167
- package/dist/testing/conformance/single-shard.d.ts +0 -2
- package/dist/testing/conformance/single-shard.js +0 -125
- package/dist/testing/conformance/singleton.d.ts +0 -3
- package/dist/testing/conformance/singleton.js +0 -293
- package/dist/testing/conformance/statements.d.ts +0 -60
- package/dist/testing/conformance/statements.js +0 -108
- package/dist/testing/conformance/streams.d.ts +0 -7
- package/dist/testing/conformance/streams.js +0 -280
- package/dist/testing/conformance/subscriptions/access.d.ts +0 -4
- package/dist/testing/conformance/subscriptions/access.js +0 -98
- package/dist/testing/conformance/subscriptions/actors.d.ts +0 -4910
- package/dist/testing/conformance/subscriptions/actors.js +0 -372
- package/dist/testing/conformance/subscriptions/cluster.d.ts +0 -4
- package/dist/testing/conformance/subscriptions/cluster.js +0 -89
- package/dist/testing/conformance/subscriptions/delivery.d.ts +0 -4
- package/dist/testing/conformance/subscriptions/delivery.js +0 -253
- package/dist/testing/conformance/subscriptions/deploys.d.ts +0 -4
- package/dist/testing/conformance/subscriptions/deploys.js +0 -162
- package/dist/testing/conformance/subscriptions/epochs.d.ts +0 -4
- package/dist/testing/conformance/subscriptions/epochs.js +0 -169
- package/dist/testing/conformance/subscriptions/failures.d.ts +0 -4
- package/dist/testing/conformance/subscriptions/failures.js +0 -108
- package/dist/testing/conformance/subscriptions/feeds.d.ts +0 -4
- package/dist/testing/conformance/subscriptions/feeds.js +0 -218
- package/dist/testing/conformance/subscriptions/harness.d.ts +0 -124
- package/dist/testing/conformance/subscriptions/harness.js +0 -143
- package/dist/testing/conformance/subscriptions/operator.d.ts +0 -4
- package/dist/testing/conformance/subscriptions/operator.js +0 -128
- package/dist/testing/conformance/subscriptions/retention.d.ts +0 -4
- package/dist/testing/conformance/subscriptions/retention.js +0 -288
- package/dist/testing/conformance/subscriptions/routing.d.ts +0 -4
- package/dist/testing/conformance/subscriptions/routing.js +0 -179
- package/dist/testing/conformance/subscriptions.d.ts +0 -4
- package/dist/testing/conformance/subscriptions.js +0 -19
- package/dist/testing/conformance/tables.d.ts +0 -125
- package/dist/testing/conformance/tables.js +0 -664
- package/dist/testing/conformance/transports/actors.d.ts +0 -1907
- package/dist/testing/conformance/transports/actors.js +0 -163
- package/dist/testing/conformance/transports/client.d.ts +0 -3
- package/dist/testing/conformance/transports/client.js +0 -237
- package/dist/testing/conformance/transports/feeds.d.ts +0 -3
- package/dist/testing/conformance/transports/feeds.js +0 -232
- package/dist/testing/conformance/transports/harness.d.ts +0 -93
- package/dist/testing/conformance/transports/harness.js +0 -84
- package/dist/testing/conformance/transports/streams.d.ts +0 -3
- package/dist/testing/conformance/transports/streams.js +0 -96
- package/dist/testing/conformance/transports/websocket.d.ts +0 -3
- package/dist/testing/conformance/transports/websocket.js +0 -384
- package/dist/testing/conformance/transports/wire.d.ts +0 -141
- package/dist/testing/conformance/transports/wire.js +0 -204
- package/dist/testing/conformance/transports.d.ts +0 -3
- package/dist/testing/conformance/transports.js +0 -11
- package/dist/testing/conformance/watch.d.ts +0 -59
- package/dist/testing/conformance/watch.js +0 -879
- package/dist/testing/conformance/workflow-engine.d.ts +0 -97
- package/dist/testing/conformance/workflow-engine.js +0 -253
- package/dist/testing/conformance/workflow-versions.d.ts +0 -3
- package/dist/testing/conformance/workflow-versions.js +0 -545
- package/dist/testing/conformance/workflows/access.d.ts +0 -4
- package/dist/testing/conformance/workflows/access.js +0 -147
- package/dist/testing/conformance/workflows/actors.d.ts +0 -2317
- package/dist/testing/conformance/workflows/actors.js +0 -305
- package/dist/testing/conformance/workflows/basics.d.ts +0 -4
- package/dist/testing/conformance/workflows/basics.js +0 -144
- package/dist/testing/conformance/workflows/engine.d.ts +0 -4
- package/dist/testing/conformance/workflows/engine.js +0 -80
- package/dist/testing/conformance/workflows/guards.d.ts +0 -4
- package/dist/testing/conformance/workflows/guards.js +0 -172
- package/dist/testing/conformance/workflows/harness.d.ts +0 -14
- package/dist/testing/conformance/workflows/harness.js +0 -44
- package/dist/testing/conformance/workflows/recovery.d.ts +0 -4
- package/dist/testing/conformance/workflows/recovery.js +0 -181
- package/dist/testing/conformance/workflows/starts.d.ts +0 -4
- package/dist/testing/conformance/workflows/starts.js +0 -57
- package/dist/testing/conformance/workflows/suspension.d.ts +0 -4
- package/dist/testing/conformance/workflows/suspension.js +0 -106
- package/dist/testing/conformance/workflows/waits.d.ts +0 -4
- package/dist/testing/conformance/workflows/waits.js +0 -284
- package/dist/testing/conformance/workflows.d.ts +0 -4
- package/dist/testing/conformance/workflows.js +0 -19
- package/dist/testing/conformance.d.ts +0 -330
- package/dist/testing/conformance.js +0 -364
- package/dist/testing/foundation.d.ts +0 -41
- package/dist/testing/foundation.js +0 -517
- package/dist/testing/simulate-cluster.d.ts +0 -112
- package/dist/testing/simulate-cluster.js +0 -257
- package/dist/testing/simulate.d.ts +0 -90
- package/dist/testing/simulate.js +0 -127
package/dist/runtime/layer.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { PgClient, PgTypes } from "@effect/sql-pg";
|
|
2
2
|
import { PgliteClient } from "@effect/sql-pglite";
|
|
3
|
-
import { Cause, Clock, Semaphore, Context, Crypto, Duration, Deferred, Effect, Fiber, Layer, Option, Result, Schedule, Schema, Stream, } from "effect";
|
|
3
|
+
import { Cause, Clock, Semaphore, Context, Crypto, Duration, Deferred, Effect, Exit, Fiber, Layer, Option, Predicate, Result, Schedule, Schema, Stream, } from "effect";
|
|
4
4
|
import { ClusterError, Entity, EntityId, RunnerHealth, Runners, RunnerStorage, Sharding, ShardingConfig, } from "effect/cluster";
|
|
5
5
|
import { SqlClient, SqlError } from "effect/sql";
|
|
6
6
|
import { ActorError, ActorUnavailable, InvalidInput, Unauthorized, Timeout, MailboxFull, RunnerAtCapacity, } from "../errors/actor.js";
|
|
@@ -11,7 +11,7 @@ import { InternalActors } from "./actors.js";
|
|
|
11
11
|
import { System } from "../identity/caller.js";
|
|
12
12
|
import { deriveMintId } from "../identity/mint.js";
|
|
13
13
|
import { migrate } from "./database/migrations.js";
|
|
14
|
-
import {
|
|
14
|
+
import { schemaChange } from "./database/schema.js";
|
|
15
15
|
import { queryPoolLayer, ReadReplica, replicaLayer } from "./database/replica.js";
|
|
16
16
|
import { Coordination, coordinationLayer } from "./database/coordination.js";
|
|
17
17
|
import { withKeepalives } from "./database/keepalive.js";
|
|
@@ -30,6 +30,7 @@ import { recordedPlacement } from "./storage/placements.js";
|
|
|
30
30
|
import { TurnHooks } from "./turn/hooks.js";
|
|
31
31
|
import { requestAttributes, SpanNames } from "./telemetry/spans.js";
|
|
32
32
|
import { DefectLog, boundedDefectLog } from "./telemetry/defects.js";
|
|
33
|
+
import { liveRecorder, LiveRuntime, } from "./telemetry/live.js";
|
|
33
34
|
import { OperatorRuntime, operatorRuntime } from "./operators/repair.js";
|
|
34
35
|
import { seedRuntime } from "./operators/seed.js";
|
|
35
36
|
import { TelemetrySampler } from "./telemetry/sampler.js";
|
|
@@ -56,7 +57,7 @@ import { RuntimeControl, runtimeControl, turnGate } from "./drain.js";
|
|
|
56
57
|
import { eventFeeds } from "./feeds.js";
|
|
57
58
|
import { committedReads } from "./queries.js";
|
|
58
59
|
import { servingReadiness } from "./readiness.js";
|
|
59
|
-
import { acquiredShards, checkRunnerConfiguration, RunnerReadiness, LocalRequestSerialization, RunnerWiring, } from "./runner.js";
|
|
60
|
+
import { acquiredShards, checkRunnerConfiguration, RunnerPeers, runnerPeers, RunnerReadiness, LocalRequestSerialization, RunnerWiring, } from "./runner.js";
|
|
60
61
|
import { actorRegistration } from "./registration.js";
|
|
61
62
|
import { startSweeps } from "./sweeps.js";
|
|
62
63
|
import { checkFleet } from "./fleet/checks.js";
|
|
@@ -74,6 +75,14 @@ const CLAIM_MARGIN_MS = 5000;
|
|
|
74
75
|
const DEFAULT_CLAIM_LEASE_MS = 37_000;
|
|
75
76
|
/** How long a progress send may take before it is given up as a lost frame. */
|
|
76
77
|
const PROGRESS_SEND_TIMEOUT = "5 seconds";
|
|
78
|
+
/**
|
|
79
|
+
* The reply to a command the closing runtime scope cut off or never started.
|
|
80
|
+
* The caller cannot interrupt an admitted command, so an interrupt there comes
|
|
81
|
+
* from shutdown, and the caller can retry on a runtime that is still serving.
|
|
82
|
+
*/
|
|
83
|
+
const shuttingDown = Exit.fail(ActorError.make({
|
|
84
|
+
reason: ActorUnavailable.make({ cause: new Error("Runtime is shutting down") }),
|
|
85
|
+
}));
|
|
77
86
|
/**
|
|
78
87
|
* An entity type with no messages, registered before any actor. The cluster's
|
|
79
88
|
* idle sweep takes its interval from the first entity type registered and
|
|
@@ -92,7 +101,10 @@ const IdleSweep = Entity.make("akter/IdleSweep", []);
|
|
|
92
101
|
* Constraints the wiring keeps:
|
|
93
102
|
* - Turns run through the drain gate, so a drain refuses new turns and
|
|
94
103
|
* interrupts the rest. The runtime scope, not the caller, owns an in-flight
|
|
95
|
-
* turn: interrupting a waiter never cancels it.
|
|
104
|
+
* turn: interrupting a waiter never cancels it. Closing that scope does, and
|
|
105
|
+
* a command it cuts off, or one sent after it starts closing, fails with a
|
|
106
|
+
* retryable `ActorUnavailable` instead of an interrupt or a wait that never
|
|
107
|
+
* ends; delivery stops retrying once the scope is closed.
|
|
96
108
|
* - Commands are direct, so Cluster keeps no messages and the receipt is the
|
|
97
109
|
* only admission record; a restarted activation or lost runner drops the
|
|
98
110
|
* uncommitted attempt, the handle retries with the same command id, and the
|
|
@@ -210,6 +222,14 @@ export const layer = (options = {}) => {
|
|
|
210
222
|
let refreshedAt;
|
|
211
223
|
const services = yield* Effect.context();
|
|
212
224
|
const defectLog = boundedDefectLog(defectCapacity);
|
|
225
|
+
let recorder;
|
|
226
|
+
const currentRecorder = () => recorder;
|
|
227
|
+
const enableRecorder = Effect.map(Clock.currentTimeMillis, (startedAtMs) => {
|
|
228
|
+
recorder ??= liveRecorder({ startedAtMs, epoch: runtimeId.slice(0, 8) });
|
|
229
|
+
return recorder;
|
|
230
|
+
});
|
|
231
|
+
const servedStreams = new Map();
|
|
232
|
+
const peers = yield* RunnerPeers;
|
|
213
233
|
const database = yield* rowsDatabase;
|
|
214
234
|
const clockOffset = yield* FrameworkClock;
|
|
215
235
|
const content = options.content === undefined
|
|
@@ -341,7 +361,7 @@ export const layer = (options = {}) => {
|
|
|
341
361
|
}).pipe(refreshing.withPermits(1), Effect.provideContext(services), Effect.provideService(FrameworkClock, frameworkClock));
|
|
342
362
|
/**
|
|
343
363
|
* A runtime that could not refresh its writer rows within the window
|
|
344
|
-
* may already count as gone to `
|
|
364
|
+
* may already count as gone to `akter payloads clear`, so it starts
|
|
345
365
|
* no turn until a refresh succeeds. The check is local.
|
|
346
366
|
*/
|
|
347
367
|
const writable = Effect.gen(function* () {
|
|
@@ -466,8 +486,9 @@ export const layer = (options = {}) => {
|
|
|
466
486
|
reason: ActorUnavailable.make({ cause: Cause.squash(cause) }),
|
|
467
487
|
}));
|
|
468
488
|
}));
|
|
469
|
-
const retrying = (attempt) => deliver.pipe(Effect.catchIf((error) =>
|
|
470
|
-
Schema.is(
|
|
489
|
+
const retrying = (attempt) => deliver.pipe(Effect.catchIf((error) => !Predicate.isTagged(scope.state, "Closed") &&
|
|
490
|
+
((Schema.is(ActorUnavailable)(error.reason) && !isOverloaded(error)) ||
|
|
491
|
+
Schema.is(RunnerAtCapacity)(error.reason)), (error) => Effect.sleep(retryDelay(attempt)(error)).pipe(Effect.andThen(Effect.suspend(() => retrying(attempt + 1))))));
|
|
471
492
|
const executed = yield* retrying(0);
|
|
472
493
|
if (external)
|
|
473
494
|
yield* authorize(request, executed.endedAtMs);
|
|
@@ -613,6 +634,51 @@ export const layer = (options = {}) => {
|
|
|
613
634
|
wake: relay.wake,
|
|
614
635
|
tenantScope,
|
|
615
636
|
});
|
|
637
|
+
const live = LiveRuntime.of({
|
|
638
|
+
recorder: currentRecorder,
|
|
639
|
+
enable: enableRecorder,
|
|
640
|
+
resident: (tenant) => [...diagnostics].flatMap(([actorType, { resident }]) => resident().flatMap((activation) => activation.tenant === tenant
|
|
641
|
+
? [{ actorType, actorId: activation.id, mailbox: activation.mailbox }]
|
|
642
|
+
: [])),
|
|
643
|
+
connections: (tenant) => [
|
|
644
|
+
...(holder?.census(tenant) ?? []).map(({ ref, member, feed }) => ({
|
|
645
|
+
actorType: ref.actor,
|
|
646
|
+
actorId: ref.id,
|
|
647
|
+
kind: member === FEED_MEMBER ? "feed" : isWatchMember(member) ? "watch" : "socket",
|
|
648
|
+
events: member === FEED_MEMBER ? feed : [],
|
|
649
|
+
})),
|
|
650
|
+
...[...servedStreams.values()].flatMap((served) => served.tenant === tenant
|
|
651
|
+
? Array.from({ length: served.count }, () => ({
|
|
652
|
+
actorType: served.actorType,
|
|
653
|
+
actorId: served.actorId,
|
|
654
|
+
kind: "stream",
|
|
655
|
+
events: [],
|
|
656
|
+
}))
|
|
657
|
+
: []),
|
|
658
|
+
],
|
|
659
|
+
schedules: () => [...registrations.values()].flatMap((registration) => registration.cron.map((entry) => ({
|
|
660
|
+
actorType: registration.name,
|
|
661
|
+
key: entry.key,
|
|
662
|
+
command: entry.command,
|
|
663
|
+
}))),
|
|
664
|
+
peers: peers.peers,
|
|
665
|
+
streamOpened: (ref) => {
|
|
666
|
+
const key = JSON.stringify([ref.tenant, ref.actor, ref.id]);
|
|
667
|
+
const served = servedStreams.get(key) ?? {
|
|
668
|
+
tenant: ref.tenant,
|
|
669
|
+
actorType: ref.actor,
|
|
670
|
+
actorId: ref.id,
|
|
671
|
+
count: 0,
|
|
672
|
+
};
|
|
673
|
+
served.count += 1;
|
|
674
|
+
servedStreams.set(key, served);
|
|
675
|
+
return () => {
|
|
676
|
+
served.count -= 1;
|
|
677
|
+
if (served.count === 0)
|
|
678
|
+
servedStreams.delete(key);
|
|
679
|
+
};
|
|
680
|
+
},
|
|
681
|
+
});
|
|
616
682
|
const internalActors = InternalActors.of({
|
|
617
683
|
fleet: (request) => fleetSubscribe === undefined
|
|
618
684
|
? ActorError.make({ reason: InvalidInput.make({ code: "unknown_route" }) })
|
|
@@ -661,6 +727,7 @@ export const layer = (options = {}) => {
|
|
|
661
727
|
writable,
|
|
662
728
|
defectLog,
|
|
663
729
|
outbox,
|
|
730
|
+
recorder: currentRecorder,
|
|
664
731
|
}),
|
|
665
732
|
...committedReads({
|
|
666
733
|
registrations,
|
|
@@ -683,15 +750,18 @@ export const layer = (options = {}) => {
|
|
|
683
750
|
deliverProgress: (message) => deliverProgress(message).pipe(Effect.ignoreCause, Effect.provideContext(services)),
|
|
684
751
|
transport,
|
|
685
752
|
holder,
|
|
753
|
+
live,
|
|
686
754
|
hibernate: (ref) => Effect.flatMap(entityId(ref), (id) => owners.get(ref.actor)?.hibernate(id) ?? Effect.void).pipe(Effect.provideContext(services)),
|
|
687
755
|
execute: (request) => Effect.gen(function* () {
|
|
688
756
|
const reply = yield* Deferred.make();
|
|
689
757
|
const pending = new Set();
|
|
690
|
-
|
|
758
|
+
const settle = (exit) => Deferred.done(reply, Exit.isFailure(exit) && Cause.hasInterruptsOnly(exit.cause) ? shuttingDown : exit);
|
|
759
|
+
const admitted = yield* admission
|
|
691
760
|
.admit(dispatch(request, true, pending).pipe(Effect.tap(observe), Effect.onExit((exit) => [...pending].some((fiber) => fiber.pollUnsafe() === undefined)
|
|
692
|
-
?
|
|
761
|
+
? settle(exit)
|
|
693
762
|
: Effect.void), Effect.ensuring(Effect.suspend(() => Effect.forEach(pending, Fiber.await, { discard: true })))))
|
|
694
|
-
.pipe(Effect.onExit(
|
|
763
|
+
.pipe(Effect.onExit(settle), Effect.ignoreCause, Effect.forkIn(scope));
|
|
764
|
+
admitted.addObserver(() => Deferred.doneUnsafe(reply, shuttingDown));
|
|
695
765
|
return yield* Deferred.await(reply);
|
|
696
766
|
}),
|
|
697
767
|
overloaded: admission.full,
|
|
@@ -739,9 +809,10 @@ export const layer = (options = {}) => {
|
|
|
739
809
|
if (!registration.blobs.some((declared) => declared.name === blob && isContent(declared)))
|
|
740
810
|
return Option.none();
|
|
741
811
|
const sql = yield* SqlClient.SqlClient;
|
|
812
|
+
const key = routingKey({ ref, placement: registration.placement });
|
|
742
813
|
const [found] = yield* sql `
|
|
743
814
|
SELECT hash, size::float8 AS size FROM actor_content_refs
|
|
744
|
-
WHERE routing_key = ${
|
|
815
|
+
WHERE routing_key = ${key}
|
|
745
816
|
AND tenant_id = ${ref.tenant} AND actor_type = ${ref.actor} AND actor_id = ${ref.id}
|
|
746
817
|
AND blob = ${blob} AND name = ${name}`;
|
|
747
818
|
const timeoutMs = "policy" in registration ? registration.policy.executionMs : registration.timeoutMs;
|
|
@@ -794,7 +865,7 @@ export const layer = (options = {}) => {
|
|
|
794
865
|
yield* checkRunnerConfiguration(wiring);
|
|
795
866
|
const sharding = (wiring?.sharding ?? Sharding.layer.pipe(Layer.provide(Runners.layerNoop))).pipe(Layer.merge(Layer.effect(RunnerReadiness, wiring?.production === true
|
|
796
867
|
? acquiredShards
|
|
797
|
-
: Effect.succeed({ acquired: () => Effect.succeed(true) }))), Layer.provideMerge(directMessages), Layer.provideMerge(mailboxRefusals), Layer.provide([
|
|
868
|
+
: Effect.succeed({ acquired: () => Effect.succeed(true) })).pipe(Layer.merge(Layer.effect(RunnerPeers, runnerPeers)))), Layer.provideMerge(directMessages), Layer.provideMerge(mailboxRefusals), Layer.provide([
|
|
798
869
|
runnerStorage === "memory"
|
|
799
870
|
? Layer.effect(RunnerStorage.RunnerStorage, Effect.map(RunnerStorage.makeMemory, keepAcquiredShards))
|
|
800
871
|
: Layer.effect(RunnerStorage.RunnerStorage, coordinatedRunnerStorage.pipe(Effect.map(wiring?.storage ?? ((storage) => storage)), Effect.map(keepAcquiredShards))).pipe(Layer.orDie),
|
|
@@ -826,51 +897,48 @@ export const layer = (options = {}) => {
|
|
|
826
897
|
})));
|
|
827
898
|
}));
|
|
828
899
|
};
|
|
900
|
+
/**
|
|
901
|
+
* A runner holds three primary pools, plus optional replica and coordination
|
|
902
|
+
* pools. Turns lease sessions from the turn pool, `maxConnections` (default
|
|
903
|
+
* 50): a command holds one session until its transaction ends, so a pool
|
|
904
|
+
* smaller than the commands in flight queues callers behind
|
|
905
|
+
* it. Queries read from the query pool, `queryConnections` (default 10),
|
|
906
|
+
* except those of types with owned tables or blobs. Command admission,
|
|
907
|
+
* receipt replays, the relay, migrations, and cluster storage use the
|
|
908
|
+
* off-turn pool, `offTurnConnections` (default 10), unless `coordination`
|
|
909
|
+
* moves Cluster storage and deployment locks to an independent unsharded
|
|
910
|
+
* primary. All runners must designate the same authority. Every pool opens
|
|
911
|
+
* connections only as load needs them, refuses checkouts past its
|
|
912
|
+
* connections plus a bounded waiter allowance, and hands its connections out
|
|
913
|
+
* first come, first served. Keep the sum across runners below the server's
|
|
914
|
+
* `max_connections`.
|
|
915
|
+
*
|
|
916
|
+
* `replica` is this runner's nearest streaming replica of the same primary.
|
|
917
|
+
* Queries read there once it has replayed the commit version their caller
|
|
918
|
+
* last saw, and read the primary when it is behind or fails. Its pool
|
|
919
|
+
* (`maxConnections` default 10) opens connections only as queries need them.
|
|
920
|
+
* Every pool requests server TCP keepalives at 5 seconds idle, 2 seconds
|
|
921
|
+
* between probes, and 3 probes. `startupParameters` overrides these defaults
|
|
922
|
+
* independently on the primary and replica configurations.
|
|
923
|
+
*
|
|
924
|
+
* Registers a `regclass` codec because the pinned driver lacks one and the
|
|
925
|
+
* migrator needs it on restart; remove once Effect #8309 lands.
|
|
926
|
+
*/
|
|
927
|
+
function postgres(options) {
|
|
928
|
+
const types = PgTypes.makeRegistry();
|
|
929
|
+
types.register(2205, {
|
|
930
|
+
encode: (value) => PgTypes.encode(value, PgTypes.OID.oid),
|
|
931
|
+
decode: (bytes) => bytes.length === 4
|
|
932
|
+
? Result.succeed(new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength).getUint32(0))
|
|
933
|
+
: Result.fail(new PgTypes.CodecError({ message: "Invalid regclass value" })),
|
|
934
|
+
});
|
|
935
|
+
const { offTurnConnections, queryConnections, replica, coordination, ...configured } = options;
|
|
936
|
+
const pool = withKeepalives(configured);
|
|
937
|
+
return Layer.mergeAll(boundedLayer({ ...pool, maxConnections: offTurnConnections ?? 10, types }), turnGroups.pipe(Layer.provideMerge(turnConnections({ ...pool, maxConnections: pool.maxConnections ?? 50, types }))), queryPoolLayer({ ...pool, maxConnections: queryConnections ?? 10, types }), replicaLayer(replica === undefined ? undefined : { ...withKeepalives(replica), types }), coordinationLayer(coordination === undefined ? undefined : { ...withKeepalives(coordination), types }));
|
|
938
|
+
}
|
|
829
939
|
/** Database layers for `Actors.layer`: `postgres` for real deployments, `pglite` for embedded and test use. */
|
|
830
940
|
export const Database = {
|
|
831
|
-
|
|
832
|
-
* A runner holds three primary pools, plus optional replica and coordination
|
|
833
|
-
* pools. Turns lease sessions from the turn pool, `maxConnections` (default
|
|
834
|
-
* 50): a command holds one session until its transaction ends, so a pool
|
|
835
|
-
* smaller than the commands in flight queues callers behind
|
|
836
|
-
* it. Queries read from the query pool, `queryConnections` (default 10),
|
|
837
|
-
* except those of types with owned tables or blobs. Command admission,
|
|
838
|
-
* receipt replays, the relay, migrations, and cluster storage use the
|
|
839
|
-
* off-turn pool, `offTurnConnections` (default 10), unless `coordination`
|
|
840
|
-
* moves Cluster storage and deployment locks to an independent unsharded
|
|
841
|
-
* primary. All runners must designate the same authority. Every pool opens
|
|
842
|
-
* connections only as load needs them, refuses checkouts past its
|
|
843
|
-
* connections plus a bounded waiter allowance, and hands its connections out
|
|
844
|
-
* first come, first served. Keep the sum across runners below the server's
|
|
845
|
-
* `max_connections`.
|
|
846
|
-
*
|
|
847
|
-
* `replica` is this runner's nearest streaming replica of the same primary.
|
|
848
|
-
* Queries read there once it has replayed the commit version their caller
|
|
849
|
-
* last saw, and read the primary when it is behind or fails. Its pool
|
|
850
|
-
* (`maxConnections` default 10) opens connections only as queries need them.
|
|
851
|
-
* Every pool requests server TCP keepalives at 5 seconds idle, 2 seconds
|
|
852
|
-
* between probes, and 3 probes. `startupParameters` overrides these defaults
|
|
853
|
-
* independently on the primary and replica configurations.
|
|
854
|
-
*
|
|
855
|
-
* Registers a `regclass` codec because the pinned driver lacks one and the
|
|
856
|
-
* migrator needs it on restart; remove once Effect #8309 lands.
|
|
857
|
-
*/
|
|
858
|
-
postgres: (options) => {
|
|
859
|
-
const types = PgTypes.makeRegistry();
|
|
860
|
-
types.register(2205, {
|
|
861
|
-
encode: (value) => PgTypes.encode(value, PgTypes.OID.oid),
|
|
862
|
-
decode: (bytes) => bytes.length === 4
|
|
863
|
-
? Result.succeed(new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength).getUint32(0))
|
|
864
|
-
: Result.fail(new PgTypes.CodecError({ message: "Invalid regclass value" })),
|
|
865
|
-
});
|
|
866
|
-
const { offTurnConnections, queryConnections, replica, neki, coordination, ...configured } = options;
|
|
867
|
-
const pool = withKeepalives(configured);
|
|
868
|
-
const database = Layer.mergeAll(boundedLayer({ ...pool, maxConnections: offTurnConnections ?? 10, types }), neki === true
|
|
869
|
-
? turnConnections({ ...pool, maxConnections: pool.maxConnections ?? 50, types })
|
|
870
|
-
: turnGroups.pipe(Layer.provideMerge(turnConnections({ ...pool, maxConnections: pool.maxConnections ?? 50, types }))), queryPoolLayer({ ...pool, maxConnections: queryConnections ?? 10, types }), replicaLayer(replica === undefined ? undefined : { ...withKeepalives(replica), types }), coordinationLayer(coordination === undefined ? undefined : { ...withKeepalives(coordination), types }));
|
|
871
|
-
return neki === undefined
|
|
872
|
-
? database
|
|
873
|
-
: database.pipe(Layer.provideMerge(Layer.succeed(NekiTurnSessions, neki)));
|
|
874
|
-
},
|
|
941
|
+
postgres,
|
|
875
942
|
pglite,
|
|
943
|
+
schemaChange,
|
|
876
944
|
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Crypto, Effect, Option, Schema } from "effect";
|
|
2
2
|
import { SqlClient } from "effect/sql";
|
|
3
3
|
import { type Placement } from "../storage/codec.ts";
|
|
4
|
+
import { type BucketRange } from "../database/shards.ts";
|
|
4
5
|
import { Capability, type OperatorAction } from "./grants.ts";
|
|
5
6
|
/** One operator action as the audit log records it. */
|
|
6
7
|
export interface AuditEntry {
|
|
@@ -49,6 +50,8 @@ export type AuditRecord = typeof AuditRecord.Type;
|
|
|
49
50
|
export declare const listAudit: (args_0: {
|
|
50
51
|
readonly tenant: string;
|
|
51
52
|
readonly limit: number;
|
|
53
|
+
/** The buckets read, when each shard is read on its own. */
|
|
54
|
+
readonly range?: BucketRange;
|
|
52
55
|
}) => Effect.Effect<readonly {
|
|
53
56
|
readonly auditId: string;
|
|
54
57
|
readonly atMs: number;
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { Crypto, Effect, Option, Schema } from "effect";
|
|
2
2
|
import { SqlClient } from "effect/sql";
|
|
3
3
|
import { routingKey } from "../storage/codec.js";
|
|
4
|
+
import { withinRange } from "../database/shards.js";
|
|
5
|
+
import { BUCKETS } from "../turn/outbox.js";
|
|
4
6
|
import { databaseTime } from "../turn/admission.js";
|
|
5
7
|
import { Capability } from "./grants.js";
|
|
6
8
|
const encodeJson = Schema.encodeEffect(Schema.fromJsonString(Schema.Json));
|
|
@@ -45,9 +47,11 @@ export const AuditRecord = Schema.Struct({
|
|
|
45
47
|
outcome: Schema.String,
|
|
46
48
|
});
|
|
47
49
|
/** A tenant's newest audit rows, or every tenant's for `"*"`, newest first. */
|
|
48
|
-
export const listAudit = Effect.fnUntraced(function* ({ tenant, limit, }) {
|
|
50
|
+
export const listAudit = Effect.fnUntraced(function* ({ tenant, limit, range = BUCKETS, }) {
|
|
49
51
|
const sql = yield* SqlClient.SqlClient;
|
|
50
|
-
const filter = tenant === "*"
|
|
52
|
+
const filter = tenant === "*"
|
|
53
|
+
? withinRange({ sql, range, column: "routing_key", keyword: "WHERE" })
|
|
54
|
+
: sql `WHERE tenant_id = ${tenant} ${withinRange({ sql, range, column: "routing_key" })}`;
|
|
51
55
|
return yield* sql `SELECT audit_id AS "auditId", at_ms::float8 AS "atMs", operator,
|
|
52
56
|
action, tenant_id AS tenant, actor_type AS "actorType", actor_id AS "actorId", target,
|
|
53
57
|
capability, reason, outcome
|
|
@@ -5,6 +5,7 @@ import { ActorRef, System } from "../../identity/caller.js";
|
|
|
5
5
|
import * as Queries from "../inspector/queries.js";
|
|
6
6
|
import { TenantScope, withTenant } from "../database/tenancy.js";
|
|
7
7
|
import { routingKey } from "../storage/codec.js";
|
|
8
|
+
import { forEachRange, withinRange } from "../database/shards.js";
|
|
8
9
|
import { recordedPlacement } from "../storage/placements.js";
|
|
9
10
|
import { databaseTime, FrameworkClock } from "../turn/admission.js";
|
|
10
11
|
import { OutboxRuntime, outboxStatements } from "../turn/outbox.js";
|
|
@@ -31,6 +32,16 @@ export class JobNotServed extends Schema.TaggedError()("JobNotServed", {
|
|
|
31
32
|
*/
|
|
32
33
|
export class OperatorRuntime extends Context.Service()("@rikalabs/akter/runtime/operators/repair/OperatorRuntime") {
|
|
33
34
|
}
|
|
35
|
+
const encoder = new TextEncoder();
|
|
36
|
+
/** Compares two strings by their UTF-8 bytes, as Postgres orders them under `COLLATE "C"`. */
|
|
37
|
+
const byteOrder = (left, right) => {
|
|
38
|
+
const a = encoder.encode(left);
|
|
39
|
+
const b = encoder.encode(right);
|
|
40
|
+
for (let index = 0; index < Math.min(a.length, b.length); index++)
|
|
41
|
+
if (a[index] !== b[index])
|
|
42
|
+
return a[index] - b[index];
|
|
43
|
+
return a.length - b.length;
|
|
44
|
+
};
|
|
34
45
|
/**
|
|
35
46
|
* Builds the service over the runtime's SQL, crypto, clock, and outbox, and
|
|
36
47
|
* its job registrations, which say whether a retried job is capped.
|
|
@@ -98,7 +109,7 @@ export const operatorRuntime = (deps) => {
|
|
|
98
109
|
expiresAtMs: found.expires_at_ms,
|
|
99
110
|
}));
|
|
100
111
|
}).pipe(Effect.catchTag("OperatorNotFound", () => Effect.succeedNone), provided),
|
|
101
|
-
exportSeed: (target) =>
|
|
112
|
+
exportSeed: (target) => keyOf(target).pipe(Effect.andThen(exportActor(target)), Effect.catchTag("OperatorNotFound", () => Effect.succeedNone), Effect.catchIf(SqlError.isSqlError, Effect.die), provided),
|
|
102
113
|
retry: ({ target, jobId, providerChecked, audit }) => Effect.gen(function* () {
|
|
103
114
|
const ref = ActorRef.make({
|
|
104
115
|
tenant: target.tenant,
|
|
@@ -192,7 +203,7 @@ export const operatorRuntime = (deps) => {
|
|
|
192
203
|
yield* deps.wake;
|
|
193
204
|
return { through };
|
|
194
205
|
}).pipe(provided),
|
|
195
|
-
lagging: ({ tenant, minAttempts, limit }) => Queries.readOnly(tenant)(Effect.gen(function* () {
|
|
206
|
+
lagging: ({ tenant, minAttempts, limit }) => forEachRange((range) => Queries.readOnly(tenant)(Effect.gen(function* () {
|
|
196
207
|
const sql = yield* SqlClient.SqlClient;
|
|
197
208
|
return yield* sql `
|
|
198
209
|
SELECT s.source_type AS "sourceType", s.source_id AS "sourceId",
|
|
@@ -206,21 +217,30 @@ export const operatorRuntime = (deps) => {
|
|
|
206
217
|
AND g.tenant_id = s.tenant_id AND g.actor_type = s.source_type
|
|
207
218
|
AND g.actor_id = s.source_id
|
|
208
219
|
WHERE s.tenant_id = ${tenant} AND s.active AND s.last_error IS NOT NULL
|
|
209
|
-
AND s.attempts >= ${minAttempts}
|
|
220
|
+
AND s.attempts >= ${minAttempts} ${withinRange({ sql, range, column: "s.routing_key" })}
|
|
210
221
|
ORDER BY s.attempts DESC, g.event_sequence - s.delivered DESC,
|
|
211
222
|
s.source_type COLLATE "C", s.source_id COLLATE "C",
|
|
212
223
|
s.subscriber_type COLLATE "C", s.subscription COLLATE "C",
|
|
213
224
|
s.subscriber_id COLLATE "C"
|
|
214
225
|
LIMIT ${limit}`;
|
|
215
|
-
})).pipe(
|
|
216
|
-
|
|
226
|
+
}))).pipe(Effect.map((shards) => shards
|
|
227
|
+
.flat()
|
|
228
|
+
.sort((a, b) => b.attempts - a.attempts ||
|
|
229
|
+
Number(BigInt(b.lag) - BigInt(a.lag)) ||
|
|
230
|
+
byteOrder(a.sourceType, b.sourceType) ||
|
|
231
|
+
byteOrder(a.sourceId, b.sourceId) ||
|
|
232
|
+
byteOrder(a.subscriberType, b.subscriberType) ||
|
|
233
|
+
byteOrder(a.subscription, b.subscription) ||
|
|
234
|
+
byteOrder(a.subscriberId, b.subscriberId))
|
|
235
|
+
.slice(0, limit)), provided, Effect.orDie),
|
|
236
|
+
audit: (page) => forEachRange((range) => Queries.readOnly(page.tenant)(listAudit({ ...page, range }))).pipe(Effect.map((shards) => shards
|
|
237
|
+
.flat()
|
|
238
|
+
.sort((a, b) => b.atMs - a.atMs || byteOrder(a.auditId, b.auditId))
|
|
239
|
+
.slice(0, page.limit)), provided, Effect.orDie),
|
|
217
240
|
record: (entry, outcome) => Effect.gen(function* () {
|
|
218
241
|
const found = entry.actorType === undefined ? Option.none() : yield* placement(entry.actorType);
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
key: auditRoutingKey({ entry, placement: Option.getOrUndefined(found) }),
|
|
222
|
-
outcome,
|
|
223
|
-
});
|
|
242
|
+
const key = auditRoutingKey({ entry, placement: Option.getOrUndefined(found) });
|
|
243
|
+
yield* writeAudit({ entry, key, outcome });
|
|
224
244
|
}).pipe(provided, Effect.orDie),
|
|
225
245
|
});
|
|
226
246
|
};
|
|
@@ -78,7 +78,8 @@ export const seedRuntime = (deps) => Effect.fnUntraced(function* ({ ref, caller,
|
|
|
78
78
|
if (placement === undefined)
|
|
79
79
|
return yield* Effect.die(new Error(`Actor ${ref.actor} is not registered`));
|
|
80
80
|
const key = routingKey({ ref, placement });
|
|
81
|
-
yield*
|
|
81
|
+
yield* sql
|
|
82
|
+
.withTransaction(Effect.gen(function* () {
|
|
82
83
|
const [inserted] = yield* sql `
|
|
83
84
|
INSERT INTO actor_generations (routing_key, tenant_id, actor_type, actor_id, created)
|
|
84
85
|
VALUES (${key}, ${ref.tenant}, ${ref.actor}, ${ref.id}, ${seed.created})
|
|
@@ -118,6 +119,7 @@ export const seedRuntime = (deps) => Effect.fnUntraced(function* ({ ref, caller,
|
|
|
118
119
|
}, databaseTime);
|
|
119
120
|
for (const statement of staged.statements)
|
|
120
121
|
yield* statement;
|
|
121
|
-
}))
|
|
122
|
+
}))
|
|
123
|
+
.pipe(withTenant(ref.tenant));
|
|
122
124
|
yield* deps.wake;
|
|
123
125
|
}, (effect) => effect.pipe(Effect.catchIf(SqlError.isSqlError, Effect.die), Effect.provideService(FrameworkClock, deps.clock), Effect.provideService(OutboxRuntime, deps.outbox), Effect.provideService(TenantScope, deps.tenantScope), Effect.provideContext(deps.services)));
|
|
@@ -17,7 +17,7 @@ export declare const formatPayloadProblem: (problem: PayloadProblem) => string;
|
|
|
17
17
|
* Compares declared chains with the versions the database may hold. A
|
|
18
18
|
* version recorded above a chain's current version is a rollback past a
|
|
19
19
|
* schema change. A chain that starts above version `v` can't read values of
|
|
20
|
-
* `v`: an event version counts as stored until `
|
|
20
|
+
* `v`: an event version counts as stored until `akter payloads clear` marks
|
|
21
21
|
* it cleared. Every stored event has a recorded version, because a runtime
|
|
22
22
|
* records what it writes before it takes a shard and the migration refused
|
|
23
23
|
* databases that held rows before it. Job versions are read from the
|
|
@@ -39,9 +39,9 @@ export declare const recordPayloadVersions: (declarations: readonly PayloadDecla
|
|
|
39
39
|
export declare const refreshWriters: (runtimeId: string, windowMs: number, declarations: readonly PayloadDeclaration[]) => Effect.Effect<void, import("effect/sql/SqlError").SqlError, SqlClient.SqlClient>;
|
|
40
40
|
/** Drops a stopping runtime's writer rows, so a clear need not wait out its window. */
|
|
41
41
|
export declare const dropWriters: (runtimeId: string) => Effect.Effect<void, import("effect/sql/SqlError").SqlError, SqlClient.SqlClient>;
|
|
42
|
-
/** `
|
|
42
|
+
/** `akter payloads check`: the startup check for `actors`, read-only. */
|
|
43
43
|
export declare const checkPayloads: (actors: ReadonlyArray<object>) => Effect.Effect<PayloadProblem[], import("effect/sql/SqlError").SqlError, SqlClient.SqlClient>;
|
|
44
|
-
/** What `
|
|
44
|
+
/** What `akter payloads clear` found for one superseded event version. */
|
|
45
45
|
export interface ClearResult {
|
|
46
46
|
readonly actorType: string;
|
|
47
47
|
readonly tag: string;
|
|
@@ -54,7 +54,7 @@ export interface ClearResult {
|
|
|
54
54
|
readonly outcome: "cleared" | "writer" | "stored";
|
|
55
55
|
}
|
|
56
56
|
/**
|
|
57
|
-
* `
|
|
57
|
+
* `akter payloads clear`: marks each superseded event version of `actors`
|
|
58
58
|
* cleared once its retention horizon has passed, no runtime can still write
|
|
59
59
|
* it, and no event of it remains. The version row is locked first, the
|
|
60
60
|
* writer rows are read again after the scan, and each statement of the
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { Effect } from "effect";
|
|
2
2
|
import { SqlClient } from "effect/sql";
|
|
3
|
+
import { registry } from "../database/coordination.js";
|
|
4
|
+
import { forEachRange, withinRange } from "../database/shards.js";
|
|
3
5
|
import { descriptorOf } from "../../actor/descriptor.js";
|
|
4
6
|
import {} from "../../members/payload.js";
|
|
5
7
|
import { inReadOnlySnapshot } from "../database/snapshot.js";
|
|
@@ -14,7 +16,7 @@ const keyOf = (declared) => `${declared.actorType}\u0000${declared.kind}\u0000${
|
|
|
14
16
|
* Compares declared chains with the versions the database may hold. A
|
|
15
17
|
* version recorded above a chain's current version is a rollback past a
|
|
16
18
|
* schema change. A chain that starts above version `v` can't read values of
|
|
17
|
-
* `v`: an event version counts as stored until `
|
|
19
|
+
* `v`: an event version counts as stored until `akter payloads clear` marks
|
|
18
20
|
* it cleared. Every stored event has a recorded version, because a runtime
|
|
19
21
|
* records what it writes before it takes a shard and the migration refused
|
|
20
22
|
* databases that held rows before it. Job versions are read from the
|
|
@@ -60,19 +62,24 @@ export const findPayloadProblems = Effect.fnUntraced(function* (declarations, wr
|
|
|
60
62
|
if (declared.kind === "event") {
|
|
61
63
|
for (const row of versions)
|
|
62
64
|
if (row.version < first && !row.cleared)
|
|
63
|
-
problem(`version ${row.version} may still be stored below this chain's first version ${first}; run
|
|
65
|
+
problem(`version ${row.version} may still be stored below this chain's first version ${first}; run akter payloads clear once its events are gone`);
|
|
64
66
|
}
|
|
65
67
|
else {
|
|
66
|
-
const
|
|
68
|
+
const shards = (yield* forEachRange((range) => sql `
|
|
67
69
|
SELECT min(v)::int AS version, count(*)::int AS rows FROM (
|
|
68
70
|
SELECT payload_version AS v FROM actor_outbox
|
|
69
71
|
WHERE kind = 'job' AND actor_type = ${declared.actorType} AND command = ${declared.tag}
|
|
70
|
-
AND payload_version < ${first}
|
|
72
|
+
AND payload_version < ${first} ${withinRange({ sql, range, column: "routing_key" })}
|
|
71
73
|
UNION ALL
|
|
72
74
|
SELECT payload_version FROM actor_dead_letters
|
|
73
75
|
WHERE actor_type = ${declared.actorType} AND job = ${declared.tag}
|
|
74
|
-
AND payload_version < ${first}) stored
|
|
75
|
-
|
|
76
|
+
AND payload_version < ${first} ${withinRange({ sql, range, column: "routing_key" })}) stored`)).flat();
|
|
77
|
+
const versions = shards.flatMap((row) => (row.version === null ? [] : [row.version]));
|
|
78
|
+
const stored = {
|
|
79
|
+
version: versions.length === 0 ? null : Math.min(...versions),
|
|
80
|
+
rows: shards.reduce((sum, row) => sum + row.rows, 0),
|
|
81
|
+
};
|
|
82
|
+
if (stored.version !== null)
|
|
76
83
|
problem(`version ${stored.version} stored in ${stored.rows} pending job or dead letter row${stored.rows === 1 ? "" : "s"} below this chain's first version ${first}`);
|
|
77
84
|
}
|
|
78
85
|
}
|
|
@@ -85,14 +92,15 @@ export const findPayloadProblems = Effect.fnUntraced(function* (declarations, wr
|
|
|
85
92
|
.map((row) => row.tag)),
|
|
86
93
|
];
|
|
87
94
|
for (const tag of removed) {
|
|
88
|
-
const [pending] = yield* sql `
|
|
95
|
+
const [pending] = (yield* forEachRange((range) => sql `
|
|
89
96
|
SELECT s.subscriber_type, s.subscription FROM actor_subscriptions s
|
|
90
97
|
WHERE s.source_type = ${writer.actorType} AND ${tag} = ANY(s.events)
|
|
91
98
|
AND EXISTS (SELECT 1 FROM actor_events e
|
|
92
99
|
WHERE e.routing_key = s.routing_key AND e.tenant_id = s.tenant_id
|
|
93
100
|
AND e.actor_type = s.source_type AND e.actor_id = s.source_id
|
|
94
101
|
AND e.event = ${tag} AND e.sequence > s.delivered)
|
|
95
|
-
|
|
102
|
+
${withinRange({ sql, range, column: "s.routing_key" })}
|
|
103
|
+
LIMIT 1`)).flat();
|
|
96
104
|
if (pending !== undefined)
|
|
97
105
|
problems.push({
|
|
98
106
|
actorType: writer.actorType,
|
|
@@ -159,7 +167,7 @@ const definitionsOf = (actors) => actors.flatMap((actor) => {
|
|
|
159
167
|
const payloads = descriptorOf(actor)?.definitionPayloads;
|
|
160
168
|
return payloads === undefined ? [] : [payloads];
|
|
161
169
|
});
|
|
162
|
-
/** `
|
|
170
|
+
/** `akter payloads check`: the startup check for `actors`, read-only. */
|
|
163
171
|
export const checkPayloads = (actors) => Effect.suspend(() => {
|
|
164
172
|
const definitions = definitionsOf(actors);
|
|
165
173
|
return inReadOnlySnapshot(findPayloadProblems(definitions.flatMap((d) => d.declarations), definitions.flatMap((d) => {
|
|
@@ -177,7 +185,7 @@ export const checkPayloads = (actors) => Effect.suspend(() => {
|
|
|
177
185
|
})));
|
|
178
186
|
});
|
|
179
187
|
/**
|
|
180
|
-
* `
|
|
188
|
+
* `akter payloads clear`: marks each superseded event version of `actors`
|
|
181
189
|
* cleared once its retention horizon has passed, no runtime can still write
|
|
182
190
|
* it, and no event of it remains. The version row is locked first, the
|
|
183
191
|
* writer rows are read again after the scan, and each statement of the
|
|
@@ -186,7 +194,8 @@ export const checkPayloads = (actors) => Effect.suspend(() => {
|
|
|
186
194
|
* its window and then the longest command timeout have passed.
|
|
187
195
|
*/
|
|
188
196
|
export const clearPayloads = (actors) => Effect.gen(function* () {
|
|
189
|
-
const
|
|
197
|
+
const data = yield* SqlClient.SqlClient;
|
|
198
|
+
const sql = yield* registry;
|
|
190
199
|
const definitions = definitionsOf(actors);
|
|
191
200
|
const commandTimeoutMs = Math.max(0, ...definitions.map((d) => d.commandTimeoutMs));
|
|
192
201
|
const results = [];
|
|
@@ -219,11 +228,11 @@ export const clearPayloads = (actors) => Effect.gen(function* () {
|
|
|
219
228
|
return undefined;
|
|
220
229
|
if ((yield* writing).length > 0)
|
|
221
230
|
return "writer";
|
|
222
|
-
const stored = yield*
|
|
231
|
+
const stored = yield* forEachRange((range) => data `SELECT 1 FROM actor_events
|
|
223
232
|
WHERE actor_type = ${declared.actorType} AND event = ${declared.tag}
|
|
224
|
-
AND payload_version = ${version}
|
|
225
|
-
LIMIT 1
|
|
226
|
-
if (stored.length > 0)
|
|
233
|
+
AND payload_version = ${version} ${withinRange({ sql: data, range, column: "routing_key" })}
|
|
234
|
+
LIMIT 1`);
|
|
235
|
+
if (stored.some((rows) => rows.length > 0))
|
|
227
236
|
return "stored";
|
|
228
237
|
if ((yield* writing).length > 0)
|
|
229
238
|
return "writer";
|
package/dist/runtime/queries.js
CHANGED
|
@@ -33,9 +33,10 @@ export const committedReads = ({ registrations, queryRegistrations, services, al
|
|
|
33
33
|
reason: ActorUnavailable.make({ cause: new Error("Actor not registered") }),
|
|
34
34
|
});
|
|
35
35
|
const sql = yield* SqlClient.SqlClient;
|
|
36
|
+
const key = routingKey({ ref, placement: registration.placement });
|
|
36
37
|
const rows = yield* sql `
|
|
37
38
|
SELECT 1 FROM actor_generations
|
|
38
|
-
WHERE routing_key = ${
|
|
39
|
+
WHERE routing_key = ${key}
|
|
39
40
|
AND tenant_id = ${ref.tenant} AND actor_type = ${ref.actor} AND actor_id = ${ref.id}`.pipe(withTenant(ref.tenant));
|
|
40
41
|
return rows.length > 0;
|
|
41
42
|
}, Effect.provideContext(services), Effect.catchIf(SqlError.isSqlError, (cause) => Effect.fail(ActorError.make({ reason: ActorUnavailable.make({ cause }) }))));
|
|
@@ -151,9 +152,10 @@ export const committedReads = ({ registrations, queryRegistrations, services, al
|
|
|
151
152
|
});
|
|
152
153
|
yield* allow(request);
|
|
153
154
|
const sql = yield* SqlClient.SqlClient;
|
|
155
|
+
const key = routingKey({ ref: request.ref, placement: registration.placement });
|
|
154
156
|
const [row] = yield* sql `
|
|
155
157
|
SELECT status, result FROM actor_workflow_executions
|
|
156
|
-
WHERE routing_key = ${
|
|
158
|
+
WHERE routing_key = ${key}
|
|
157
159
|
AND execution_id = ${request.payload} AND tenant_id = ${request.ref.tenant}
|
|
158
160
|
AND actor_type = ${request.ref.actor} AND actor_id = ${request.ref.id}
|
|
159
161
|
AND workflow = ${request.command}`.pipe(withTenant(request.ref.tenant));
|
|
@@ -17,6 +17,7 @@ import type { SubscriptionRelay } from "./subscriptions/relay.ts";
|
|
|
17
17
|
import { DefectLog } from "./telemetry/defects.ts";
|
|
18
18
|
import { FrameworkClock } from "./turn/admission.ts";
|
|
19
19
|
import { OutboxRuntime } from "./turn/outbox.ts";
|
|
20
|
+
import type { LiveRecorder } from "./telemetry/live.ts";
|
|
20
21
|
/**
|
|
21
22
|
* Registers actor, query, and job layers with the runtime. Each
|
|
22
23
|
* registration is refused unless its placement, tables, content, workflows,
|
|
@@ -25,13 +26,13 @@ import { OutboxRuntime } from "./turn/outbox.ts";
|
|
|
25
26
|
* connection holder, and the sweeps, and forgotten when its layer's scope
|
|
26
27
|
* closes.
|
|
27
28
|
*/
|
|
28
|
-
export declare const actorRegistration: ({ registrations, queryRegistrations, jobRegistrations, residency, diagnostics, owners, heldTypes, heldType, sweepsWorkflows, checked, services, frameworkClock, content, retryWindowMs, tableRole, adoptionRole, enforcedTypes, writerDeclarations, refreshPayloadWriters, subscriptions, transport, authorize, gate, writable, defectLog, outbox, }: {
|
|
29
|
+
export declare const actorRegistration: ({ registrations, queryRegistrations, jobRegistrations, residency, diagnostics, owners, heldTypes, heldType, sweepsWorkflows, checked, services, frameworkClock, content, retryWindowMs, tableRole, adoptionRole, enforcedTypes, writerDeclarations, refreshPayloadWriters, subscriptions, transport, authorize, gate, writable, defectLog, outbox, recorder, }: {
|
|
29
30
|
readonly registrations: Map<string, Registration>;
|
|
30
31
|
readonly queryRegistrations: Map<string, QueryRegistration>;
|
|
31
32
|
readonly jobRegistrations: Map<string, JobRegistration>;
|
|
32
33
|
readonly residency: Map<string, (entityId: string) => boolean>;
|
|
33
34
|
/** Each actor type's view of its activations, for a delivery that timed out. */
|
|
34
|
-
readonly diagnostics: Map<string, Pick<Effect.Success<ReturnType<typeof registerActor>>, "diagnose" | "restarting">>;
|
|
35
|
+
readonly diagnostics: Map<string, Pick<Effect.Success<ReturnType<typeof registerActor>>, "diagnose" | "restarting" | "resident">>;
|
|
35
36
|
readonly owners: Map<string, Owner>;
|
|
36
37
|
readonly heldTypes: Map<string, HeldActorType>;
|
|
37
38
|
readonly heldType: (registration: Registration) => HeldActorType;
|
|
@@ -55,4 +56,6 @@ export declare const actorRegistration: ({ registrations, queryRegistrations, jo
|
|
|
55
56
|
readonly writable: Effect.Effect<void, ActorError>;
|
|
56
57
|
readonly defectLog: DefectLog["Service"];
|
|
57
58
|
readonly outbox: (typeof OutboxRuntime)["Service"];
|
|
59
|
+
/** The live recorder, once the inspector enabled one. */
|
|
60
|
+
readonly recorder: () => LiveRecorder | undefined;
|
|
58
61
|
}) => Pick<InternalActors["Service"], "register" | "registerQueries" | "registerJobs">;
|