@spine-event-engine/server 2.0.0-snapshot.24 → 2.0.0-snapshot.25
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/README.md +38 -11
- package/REFERENCE.md +201 -130
- package/dist/agent/agent-acceptance-receipts.d.ts +69 -0
- package/dist/agent/agent-acceptance-receipts.d.ts.map +1 -0
- package/dist/agent/agent-acceptance-receipts.js +198 -0
- package/dist/agent/agent-acceptance-receipts.js.map +1 -0
- package/dist/agent/agent-execution-capacity.d.ts +37 -9
- package/dist/agent/agent-execution-capacity.d.ts.map +1 -1
- package/dist/agent/agent-execution-capacity.js +73 -15
- package/dist/agent/agent-execution-capacity.js.map +1 -1
- package/dist/agent/agent-history.d.ts +15 -10
- package/dist/agent/agent-history.d.ts.map +1 -1
- package/dist/agent/agent-history.js +26 -14
- package/dist/agent/agent-history.js.map +1 -1
- package/dist/agent/agent-live-ai-audit.d.ts +95 -0
- package/dist/agent/agent-live-ai-audit.d.ts.map +1 -0
- package/dist/agent/agent-live-ai-audit.js +290 -0
- package/dist/agent/agent-live-ai-audit.js.map +1 -0
- package/dist/agent/agent-live-ai-runtime.d.ts +300 -0
- package/dist/agent/agent-live-ai-runtime.d.ts.map +1 -0
- package/dist/agent/agent-live-ai-runtime.js +611 -0
- package/dist/agent/agent-live-ai-runtime.js.map +1 -0
- package/dist/agent/agent-live-audit.d.ts +67 -0
- package/dist/agent/agent-live-audit.d.ts.map +1 -0
- package/dist/agent/agent-live-audit.js +121 -0
- package/dist/agent/agent-live-audit.js.map +1 -0
- package/dist/agent/agent-live-budget.d.ts +84 -0
- package/dist/agent/agent-live-budget.d.ts.map +1 -0
- package/dist/agent/agent-live-budget.js +161 -0
- package/dist/agent/agent-live-budget.js.map +1 -0
- package/dist/agent/agent-live-fault.d.ts +18 -0
- package/dist/agent/agent-live-fault.d.ts.map +1 -0
- package/dist/agent/{agent-execution-fault.js → agent-live-fault.js} +7 -7
- package/dist/agent/agent-live-fault.js.map +1 -0
- package/dist/agent/agent-live-inputs.d.ts +74 -0
- package/dist/agent/agent-live-inputs.d.ts.map +1 -0
- package/dist/{bus/saved-dispatcher-binding.js → agent/agent-live-inputs.js} +2 -14
- package/dist/agent/agent-live-inputs.js.map +1 -0
- package/dist/agent/agent-live-interaction-events.d.ts +46 -0
- package/dist/agent/agent-live-interaction-events.d.ts.map +1 -0
- package/dist/agent/agent-live-interaction-events.js +104 -0
- package/dist/agent/agent-live-interaction-events.js.map +1 -0
- package/dist/agent/agent-live-mcp-controls.d.ts +129 -0
- package/dist/agent/agent-live-mcp-controls.d.ts.map +1 -0
- package/dist/agent/agent-live-mcp-controls.js +228 -0
- package/dist/agent/agent-live-mcp-controls.js.map +1 -0
- package/dist/agent/agent-live-mcp-host.d.ts +88 -0
- package/dist/agent/agent-live-mcp-host.d.ts.map +1 -0
- package/dist/agent/{agent-mcp-host.js → agent-live-mcp-host.js} +1 -1
- package/dist/agent/agent-live-mcp-host.js.map +1 -0
- package/dist/agent/{agent-mcp-runtime.d.ts → agent-live-mcp-runtime.d.ts} +6 -6
- package/dist/agent/agent-live-mcp-runtime.d.ts.map +1 -0
- package/dist/agent/{agent-mcp-runtime.js → agent-live-mcp-runtime.js} +13 -42
- package/dist/agent/agent-live-mcp-runtime.js.map +1 -0
- package/dist/agent/agent-live-read-runtime.d.ts +61 -0
- package/dist/agent/agent-live-read-runtime.d.ts.map +1 -0
- package/dist/agent/agent-live-read-runtime.js +121 -0
- package/dist/agent/agent-live-read-runtime.js.map +1 -0
- package/dist/agent/agent-model-selection.d.ts +21 -34
- package/dist/agent/agent-model-selection.d.ts.map +1 -1
- package/dist/agent/agent-model-selection.js +61 -78
- package/dist/agent/agent-model-selection.js.map +1 -1
- package/dist/bus/command-bus.d.ts +14 -18
- package/dist/bus/command-bus.d.ts.map +1 -1
- package/dist/bus/command-bus.js +31 -48
- package/dist/bus/command-bus.js.map +1 -1
- package/dist/bus/event-bus.d.ts +17 -4
- package/dist/bus/event-bus.d.ts.map +1 -1
- package/dist/bus/event-bus.js +42 -12
- package/dist/bus/event-bus.js.map +1 -1
- package/dist/context/bounded-context.d.ts +140 -87
- package/dist/context/bounded-context.d.ts.map +1 -1
- package/dist/context/bounded-context.js +702 -329
- package/dist/context/bounded-context.js.map +1 -1
- package/dist/context/entity-inbox.d.ts +7 -2
- package/dist/context/entity-inbox.d.ts.map +1 -1
- package/dist/context/entity-inbox.js +22 -13
- package/dist/context/entity-inbox.js.map +1 -1
- package/dist/context/local-inbox-handoff.d.ts +34 -34
- package/dist/context/local-inbox-handoff.d.ts.map +1 -1
- package/dist/context/local-inbox-handoff.js +108 -150
- package/dist/context/local-inbox-handoff.js.map +1 -1
- package/dist/context/projection-handoff.d.ts +2 -2
- package/dist/context/projection-handoff.d.ts.map +1 -1
- package/dist/context/projection-handoff.js +10 -8
- package/dist/context/projection-handoff.js.map +1 -1
- package/dist/context/tenant-index.d.ts +36 -31
- package/dist/context/tenant-index.d.ts.map +1 -1
- package/dist/context/tenant-index.js +123 -125
- package/dist/context/tenant-index.js.map +1 -1
- package/dist/context/tenant-unknown-error.d.ts +33 -0
- package/dist/context/tenant-unknown-error.d.ts.map +1 -0
- package/dist/context/tenant-unknown-error.js +75 -0
- package/dist/context/tenant-unknown-error.js.map +1 -0
- package/dist/delivery/agent-delivery-scope.d.ts +66 -0
- package/dist/delivery/agent-delivery-scope.d.ts.map +1 -0
- package/dist/delivery/agent-delivery-scope.js +44 -0
- package/dist/delivery/agent-delivery-scope.js.map +1 -0
- package/dist/delivery/delivery-builder.d.ts +25 -1
- package/dist/delivery/delivery-builder.d.ts.map +1 -1
- package/dist/delivery/delivery-builder.js +89 -0
- package/dist/delivery/delivery-builder.js.map +1 -1
- package/dist/delivery/delivery-monitor.d.ts +1 -1
- package/dist/delivery/delivery-monitor.js +1 -1
- package/dist/delivery/delivery-run-control.d.ts +6 -0
- package/dist/delivery/delivery-run-control.d.ts.map +1 -1
- package/dist/delivery/delivery-run-control.js +10 -0
- package/dist/delivery/delivery-run-control.js.map +1 -1
- package/dist/delivery/delivery-supervisor.d.ts.map +1 -1
- package/dist/delivery/delivery-supervisor.js +28 -4
- package/dist/delivery/delivery-supervisor.js.map +1 -1
- package/dist/delivery/delivery-tenant.d.ts +17 -0
- package/dist/delivery/delivery-tenant.d.ts.map +1 -0
- package/dist/delivery/delivery-tenant.js +50 -0
- package/dist/delivery/delivery-tenant.js.map +1 -0
- package/dist/delivery/delivery.d.ts +96 -2
- package/dist/delivery/delivery.d.ts.map +1 -1
- package/dist/delivery/delivery.js +693 -53
- package/dist/delivery/delivery.js.map +1 -1
- package/dist/delivery/inbox-records.d.ts.map +1 -1
- package/dist/delivery/inbox-records.js +9 -16
- package/dist/delivery/inbox-records.js.map +1 -1
- package/dist/delivery/inbox-storage.d.ts +6 -0
- package/dist/delivery/inbox-storage.d.ts.map +1 -1
- package/dist/delivery/inbox-storage.js +43 -6
- package/dist/delivery/inbox-storage.js.map +1 -1
- package/dist/delivery/inbox.d.ts.map +1 -1
- package/dist/delivery/inbox.js +3 -2
- package/dist/delivery/inbox.js.map +1 -1
- package/dist/delivery/reception-observation.d.ts +12 -4
- package/dist/delivery/reception-observation.d.ts.map +1 -1
- package/dist/delivery/reception-observation.js +14 -3
- package/dist/delivery/reception-observation.js.map +1 -1
- package/dist/delivery/sharded-work-registry.d.ts +6 -0
- package/dist/delivery/sharded-work-registry.d.ts.map +1 -1
- package/dist/delivery/sharded-work-registry.js +41 -4
- package/dist/delivery/sharded-work-registry.js.map +1 -1
- package/dist/entity/entity-storage-descriptor.d.ts.map +1 -1
- package/dist/entity/entity-storage-descriptor.js +7 -4
- package/dist/entity/entity-storage-descriptor.js.map +1 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/integration/external-messages.d.ts.map +1 -1
- package/dist/integration/external-messages.js +9 -15
- package/dist/integration/external-messages.js.map +1 -1
- package/dist/integration/integration-broker.d.ts.map +1 -1
- package/dist/integration/integration-broker.js +112 -6
- package/dist/integration/integration-broker.js.map +1 -1
- package/dist/integration/third-party-context.d.ts.map +1 -1
- package/dist/integration/third-party-context.js +7 -10
- package/dist/integration/third-party-context.js.map +1 -1
- package/dist/repository/primitive-id.js +26 -11
- package/dist/repository/primitive-id.js.map +1 -1
- package/dist/repository/repository.d.ts +32 -60
- package/dist/repository/repository.d.ts.map +1 -1
- package/dist/repository/repository.js +1446 -1787
- package/dist/repository/repository.js.map +1 -1
- package/dist/runtime/runtime.d.ts +13 -0
- package/dist/runtime/runtime.d.ts.map +1 -1
- package/dist/runtime/runtime.js +18 -0
- package/dist/runtime/runtime.js.map +1 -1
- package/dist/runtime/signal-metadata.d.ts.map +1 -1
- package/dist/runtime/signal-metadata.js +6 -6
- package/dist/runtime/signal-metadata.js.map +1 -1
- package/dist/runtime/signal-publisher.d.ts +0 -6
- package/dist/runtime/signal-publisher.d.ts.map +1 -1
- package/dist/runtime/signal-publisher.js +1 -9
- package/dist/runtime/signal-publisher.js.map +1 -1
- package/dist/runtime/standalone-handler-runtime.d.ts +0 -7
- package/dist/runtime/standalone-handler-runtime.d.ts.map +1 -1
- package/dist/runtime/standalone-handler-runtime.js +13 -99
- package/dist/runtime/standalone-handler-runtime.js.map +1 -1
- package/dist/server/managed-delivery-gate.d.ts +34 -0
- package/dist/server/managed-delivery-gate.d.ts.map +1 -0
- package/dist/server/managed-delivery-gate.js +70 -0
- package/dist/server/managed-delivery-gate.js.map +1 -0
- package/dist/server/managed-server-application.d.ts.map +1 -1
- package/dist/server/managed-server-application.js +7 -4
- package/dist/server/managed-server-application.js.map +1 -1
- package/dist/server/server-environment.d.ts +203 -25
- package/dist/server/server-environment.d.ts.map +1 -1
- package/dist/server/server-environment.js +661 -133
- package/dist/server/server-environment.js.map +1 -1
- package/dist/server/server-log.d.ts +7 -0
- package/dist/server/server-log.d.ts.map +1 -1
- package/dist/server/server-log.js +14 -1
- package/dist/server/server-log.js.map +1 -1
- package/dist/server/server.d.ts +18 -32
- package/dist/server/server.d.ts.map +1 -1
- package/dist/server/server.js +50 -204
- package/dist/server/server.js.map +1 -1
- package/dist/services/spine-services.d.ts +1 -1
- package/dist/services/spine-services.d.ts.map +1 -1
- package/dist/services/spine-services.js +236 -335
- package/dist/services/spine-services.js.map +1 -1
- package/dist/stand/event-origin-filter.js +3 -3
- package/dist/stand/event-origin-filter.js.map +1 -1
- package/dist/stand/stand.d.ts +14 -0
- package/dist/stand/stand.d.ts.map +1 -1
- package/dist/stand/stand.js +53 -9
- package/dist/stand/stand.js.map +1 -1
- package/dist/stand/subscription-observer.d.ts.map +1 -1
- package/dist/stand/subscription-observer.js +54 -40
- package/dist/stand/subscription-observer.js.map +1 -1
- package/dist/stand/subscription-registry.d.ts +6 -2
- package/dist/stand/subscription-registry.d.ts.map +1 -1
- package/dist/stand/subscription-registry.js +15 -11
- package/dist/stand/subscription-registry.js.map +1 -1
- package/dist/stand/subscription-runtime.d.ts +5 -3
- package/dist/stand/subscription-runtime.d.ts.map +1 -1
- package/dist/stand/subscription-runtime.js +52 -9
- package/dist/stand/subscription-runtime.js.map +1 -1
- package/dist/testing/index.d.ts.map +1 -1
- package/dist/testing/index.js +4 -3
- package/dist/testing/index.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +14 -14
- package/dist/agent/agent-admission.d.ts +0 -80
- package/dist/agent/agent-admission.d.ts.map +0 -1
- package/dist/agent/agent-admission.js +0 -127
- package/dist/agent/agent-admission.js.map +0 -1
- package/dist/agent/agent-ai-runtime.d.ts +0 -76
- package/dist/agent/agent-ai-runtime.d.ts.map +0 -1
- package/dist/agent/agent-ai-runtime.js +0 -1269
- package/dist/agent/agent-ai-runtime.js.map +0 -1
- package/dist/agent/agent-execution-fault.d.ts +0 -18
- package/dist/agent/agent-execution-fault.d.ts.map +0 -1
- package/dist/agent/agent-execution-fault.js.map +0 -1
- package/dist/agent/agent-execution-session.d.ts +0 -103
- package/dist/agent/agent-execution-session.d.ts.map +0 -1
- package/dist/agent/agent-execution-session.js +0 -238
- package/dist/agent/agent-execution-session.js.map +0 -1
- package/dist/agent/agent-interaction-audit.d.ts +0 -58
- package/dist/agent/agent-interaction-audit.d.ts.map +0 -1
- package/dist/agent/agent-interaction-audit.js +0 -114
- package/dist/agent/agent-interaction-audit.js.map +0 -1
- package/dist/agent/agent-interaction-events.d.ts +0 -40
- package/dist/agent/agent-interaction-events.d.ts.map +0 -1
- package/dist/agent/agent-interaction-events.js +0 -129
- package/dist/agent/agent-interaction-events.js.map +0 -1
- package/dist/agent/agent-mcp-budget.d.ts +0 -125
- package/dist/agent/agent-mcp-budget.d.ts.map +0 -1
- package/dist/agent/agent-mcp-budget.js +0 -566
- package/dist/agent/agent-mcp-budget.js.map +0 -1
- package/dist/agent/agent-mcp-host.d.ts +0 -125
- package/dist/agent/agent-mcp-host.d.ts.map +0 -1
- package/dist/agent/agent-mcp-host.js.map +0 -1
- package/dist/agent/agent-mcp-runtime.d.ts.map +0 -1
- package/dist/agent/agent-mcp-runtime.js.map +0 -1
- package/dist/agent/agent-read-runtime.d.ts +0 -70
- package/dist/agent/agent-read-runtime.d.ts.map +0 -1
- package/dist/agent/agent-read-runtime.js +0 -320
- package/dist/agent/agent-read-runtime.js.map +0 -1
- package/dist/agent/agent-revisions.d.ts +0 -35
- package/dist/agent/agent-revisions.d.ts.map +0 -1
- package/dist/agent/agent-revisions.js +0 -133
- package/dist/agent/agent-revisions.js.map +0 -1
- package/dist/agent/agent-scheduler.d.ts +0 -99
- package/dist/agent/agent-scheduler.d.ts.map +0 -1
- package/dist/agent/agent-scheduler.js +0 -398
- package/dist/agent/agent-scheduler.js.map +0 -1
- package/dist/bus/saved-dispatcher-binding.d.ts +0 -53
- package/dist/bus/saved-dispatcher-binding.d.ts.map +0 -1
- package/dist/bus/saved-dispatcher-binding.js.map +0 -1
- package/dist/delivery/delivery-run-coordinator.d.ts +0 -218
- package/dist/delivery/delivery-run-coordinator.d.ts.map +0 -1
- package/dist/delivery/delivery-run-coordinator.js +0 -609
- package/dist/delivery/delivery-run-coordinator.js.map +0 -1
- package/dist/delivery/parked-delivery-obligations.d.ts +0 -182
- package/dist/delivery/parked-delivery-obligations.d.ts.map +0 -1
- package/dist/delivery/parked-delivery-obligations.js +0 -390
- package/dist/delivery/parked-delivery-obligations.js.map +0 -1
- package/dist/server/environment-attachment.d.ts +0 -356
- package/dist/server/environment-attachment.d.ts.map +0 -1
- package/dist/server/environment-attachment.js +0 -2062
- package/dist/server/environment-attachment.js.map +0 -1
- package/dist/server/environment-delivery-records.d.ts +0 -77
- package/dist/server/environment-delivery-records.d.ts.map +0 -1
- package/dist/server/environment-delivery-records.js +0 -282
- package/dist/server/environment-delivery-records.js.map +0 -1
- package/dist/server/environment-delivery-worker.d.ts +0 -161
- package/dist/server/environment-delivery-worker.d.ts.map +0 -1
- package/dist/server/environment-delivery-worker.js +0 -973
- package/dist/server/environment-delivery-worker.js.map +0 -1
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
* or implied. See the License for the specific language governing permissions and limitations under
|
|
12
12
|
* the License.
|
|
13
13
|
*/
|
|
14
|
+
var _a;
|
|
14
15
|
import { Time } from "@spine-event-engine/core/time";
|
|
15
16
|
import { clone, create } from "@bufbuild/protobuf";
|
|
16
17
|
import { AnySchema, TimestampSchema } from "@bufbuild/protobuf/wkt";
|
|
@@ -22,12 +23,16 @@ import { Inbox, InboxTargets } from "./inbox.js";
|
|
|
22
23
|
import { InboxStorage, InboxStorageClock } from "./inbox-storage.js";
|
|
23
24
|
import { ShardIndex } from "./shard-index.js";
|
|
24
25
|
import { ShardedWorkRegistry } from "./sharded-work-registry.js";
|
|
26
|
+
import { deliveryTenantKey } from "./delivery-tenant.js";
|
|
25
27
|
import { withDeliveryCommitFence } from "../repository/commit-fence.js";
|
|
26
28
|
import { receptionObservation } from "./reception-observation.js";
|
|
29
|
+
import { AgentDeliveryScope } from "./agent-delivery-scope.js";
|
|
30
|
+
import { emitServerError } from "../server/server-log.js";
|
|
27
31
|
const deliveryFailureLimit = 100;
|
|
28
32
|
const controlledMessageSettlers = new WeakMap();
|
|
33
|
+
const dispatchNotAdmitted = Symbol("Delivery recipient closed before dispatch");
|
|
29
34
|
/**
|
|
30
|
-
*
|
|
35
|
+
* Runs one retained direct-delivery worker and its durable Inbox.
|
|
31
36
|
*/
|
|
32
37
|
export class Delivery {
|
|
33
38
|
// prettier-ignore
|
|
@@ -64,18 +69,146 @@ export class Delivery {
|
|
|
64
69
|
*/
|
|
65
70
|
shards;
|
|
66
71
|
#monitor;
|
|
72
|
+
#directPending = new Map();
|
|
73
|
+
#logger;
|
|
74
|
+
#backgroundObserved = new WeakSet();
|
|
75
|
+
#callerObserved = new WeakSet();
|
|
76
|
+
#activeDirect = new Map();
|
|
77
|
+
#activeRuns = new Set();
|
|
78
|
+
#directDrains = new Map();
|
|
79
|
+
#pendingDirectWake = new Set();
|
|
80
|
+
#replay;
|
|
81
|
+
#acceptReplay;
|
|
82
|
+
#closing = false;
|
|
83
|
+
#startupDraining = false;
|
|
84
|
+
#closeAttempt;
|
|
85
|
+
#closingRuns;
|
|
86
|
+
/**
|
|
87
|
+
* Sets the complete direct fallback dispatcher before receiving work.
|
|
88
|
+
*
|
|
89
|
+
* @internal
|
|
90
|
+
* @param onReplay Callback that replays a ready Inbox route.
|
|
91
|
+
* @param onAccept Reports whether a live recipient currently serves the row.
|
|
92
|
+
*/
|
|
93
|
+
setReplay(onReplay, onAccept) {
|
|
94
|
+
this.#replay = onReplay;
|
|
95
|
+
this.#acceptReplay = onAccept;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Waits for every retained direct shard run to release its session.
|
|
99
|
+
*
|
|
100
|
+
* @internal
|
|
101
|
+
* @returns Completion after retained shard sessions release.
|
|
102
|
+
*/
|
|
103
|
+
async whenIdle() {
|
|
104
|
+
const failures = [];
|
|
105
|
+
while (this.#activeRuns.size > 0) {
|
|
106
|
+
const active = [...this.#activeRuns];
|
|
107
|
+
const settled = await Promise.allSettled(active.map(({ completed }) => completed));
|
|
108
|
+
settled.forEach((outcome, index) => {
|
|
109
|
+
const failure = active[index]?.releaseFailure;
|
|
110
|
+
if (failure !== undefined)
|
|
111
|
+
failures.push(failure);
|
|
112
|
+
if (outcome.status === "rejected")
|
|
113
|
+
failures.push(outcome.reason);
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
if (failures.length === 1)
|
|
117
|
+
throw failures[0];
|
|
118
|
+
if (failures.length > 1)
|
|
119
|
+
throw new AggregateError(failures, "Delivery idle wait failed.");
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Stops direct admission and drains all entered shard work.
|
|
123
|
+
*
|
|
124
|
+
* @internal
|
|
125
|
+
* @returns Completion after admitted work and retained cleanup settle.
|
|
126
|
+
*/
|
|
127
|
+
close() {
|
|
128
|
+
if (this.#closeAttempt !== undefined)
|
|
129
|
+
return this.#closeAttempt;
|
|
130
|
+
this.beginClose();
|
|
131
|
+
const active = this.#closingRuns ?? [];
|
|
132
|
+
this.#closeAttempt = Promise.allSettled(active.map(({ completed }) => completed)).then(async (settled) => {
|
|
133
|
+
const failures = [];
|
|
134
|
+
settled.forEach((result, index) => {
|
|
135
|
+
const releaseFailure = active[index]?.releaseFailure;
|
|
136
|
+
if (releaseFailure !== undefined)
|
|
137
|
+
failures.push(releaseFailure);
|
|
138
|
+
if (result.status === "rejected")
|
|
139
|
+
failures.push(result.reason);
|
|
140
|
+
});
|
|
141
|
+
const storage = await Promise.allSettled([
|
|
142
|
+
this.inbox instanceof Inbox ? this.inbox.storage.close() : Promise.resolve(),
|
|
143
|
+
this.shards instanceof ShardedWorkRegistry ? this.shards.close() : Promise.resolve(),
|
|
144
|
+
]);
|
|
145
|
+
for (const result of storage)
|
|
146
|
+
if (result.status === "rejected")
|
|
147
|
+
failures.push(result.reason);
|
|
148
|
+
if (failures.length === 1)
|
|
149
|
+
throw failures[0];
|
|
150
|
+
if (failures.length)
|
|
151
|
+
throw new AggregateError(failures, "Delivery close failed.");
|
|
152
|
+
});
|
|
153
|
+
return this.#closeAttempt;
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Closes admission synchronously and retains admitted release outcomes.
|
|
157
|
+
*
|
|
158
|
+
* @internal
|
|
159
|
+
*/
|
|
160
|
+
beginClose() {
|
|
161
|
+
if (this.#closing)
|
|
162
|
+
return;
|
|
163
|
+
this.#closing = true;
|
|
164
|
+
this.#closingRuns ??= [...this.#activeRuns];
|
|
165
|
+
for (const active of this.#closingRuns)
|
|
166
|
+
active.drain?.stop(new Error("Delivery closed."));
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Marks public runs as unavailable while preserving exact admitted local handoffs.
|
|
170
|
+
*
|
|
171
|
+
* @internal
|
|
172
|
+
*/
|
|
173
|
+
beginStartupDrain() {
|
|
174
|
+
if (this.#startupDraining || this.#closing)
|
|
175
|
+
return;
|
|
176
|
+
this.#startupDraining = true;
|
|
177
|
+
this.#closingRuns = [...this.#activeRuns];
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Notifies an admitted shard run to scan newly eligible durable rows.
|
|
181
|
+
*
|
|
182
|
+
* @internal
|
|
183
|
+
* @param shard Delivery shard identifier.
|
|
184
|
+
*/
|
|
185
|
+
wake(shard) {
|
|
186
|
+
if (this.#closing || (this.#startupDraining && !this.#hasPending(shard.key())))
|
|
187
|
+
return;
|
|
188
|
+
const drain = this.#directDrains.get(shard.key());
|
|
189
|
+
if (drain !== undefined) {
|
|
190
|
+
if (!drain.wake())
|
|
191
|
+
this.#pendingDirectWake.add(shard.key());
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
if (this.#replay !== undefined)
|
|
195
|
+
this.#observeBackground(this.#startDirect(shard), shard);
|
|
196
|
+
}
|
|
67
197
|
/**
|
|
68
198
|
* Creates direct delivery from immutable options.
|
|
69
199
|
*
|
|
70
200
|
* @param options The delivery configuration.
|
|
71
201
|
*/
|
|
72
202
|
constructor(options) {
|
|
203
|
+
if (options.context.multitenant)
|
|
204
|
+
throw new TypeError("Delivery requires a single-tenant storage context.");
|
|
73
205
|
if (options.worker !== undefined &&
|
|
74
206
|
options.node !== undefined &&
|
|
75
207
|
options.worker.nodeId?.value !== options.node) {
|
|
76
208
|
throw new Error("Delivery worker node must match the configured delivery node.");
|
|
77
209
|
}
|
|
78
|
-
this.context =
|
|
210
|
+
this.context = _a.#contextSnapshot(options.context);
|
|
211
|
+
this.#logger = options.logger;
|
|
79
212
|
this.storageFactory = options.storageFactory;
|
|
80
213
|
this.strategy = options.strategy ?? { shardCount: 1, shardFor: () => ShardIndex.single() };
|
|
81
214
|
this.worker =
|
|
@@ -120,14 +253,13 @@ export class Delivery {
|
|
|
120
253
|
*/
|
|
121
254
|
runControlled(options) {
|
|
122
255
|
const shard = options.shard;
|
|
123
|
-
const drainOptions =
|
|
124
|
-
onMessage: options.onMessage,
|
|
125
|
-
...(options.acceptMessage === undefined ? {} : { acceptMessage: options.acceptMessage }),
|
|
126
|
-
operation: { signal: options.signal },
|
|
127
|
-
};
|
|
256
|
+
const drainOptions = this.#controlledOptions(options);
|
|
128
257
|
if (options.onMessageSettled !== undefined)
|
|
129
258
|
controlledMessageSettlers.set(drainOptions, options.onMessageSettled);
|
|
130
|
-
|
|
259
|
+
const completed = this.#replay === undefined
|
|
260
|
+
? this.drain(shard, drainOptions)
|
|
261
|
+
: this.#startShared(shard, drainOptions);
|
|
262
|
+
return completed
|
|
131
263
|
.then((run) => {
|
|
132
264
|
options.onDrainResult?.(run);
|
|
133
265
|
return Object.freeze({
|
|
@@ -144,6 +276,20 @@ export class Delivery {
|
|
|
144
276
|
controlledMessageSettlers.delete(drainOptions);
|
|
145
277
|
});
|
|
146
278
|
}
|
|
279
|
+
/**
|
|
280
|
+
* Builds the exact controlled options for an endpoint or retained replay run.
|
|
281
|
+
* @param options Controlled run input.
|
|
282
|
+
* @returns The options passed to one physical shard drain.
|
|
283
|
+
*/
|
|
284
|
+
#controlledOptions(options) {
|
|
285
|
+
return this.#replay === undefined
|
|
286
|
+
? {
|
|
287
|
+
onMessage: options.onMessage,
|
|
288
|
+
...(options.acceptMessage === undefined ? {} : { acceptMessage: options.acceptMessage }),
|
|
289
|
+
operation: { signal: options.signal },
|
|
290
|
+
}
|
|
291
|
+
: { ...this.#directOptions(), operation: { signal: options.signal } };
|
|
292
|
+
}
|
|
147
293
|
/**
|
|
148
294
|
* Delivers the selected message's shard for local handoff integration.
|
|
149
295
|
*
|
|
@@ -167,22 +313,148 @@ export class Delivery {
|
|
|
167
313
|
: { name: context.name, multitenant: false });
|
|
168
314
|
}
|
|
169
315
|
async #drainDirect(message, input) {
|
|
170
|
-
|
|
171
|
-
const
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
316
|
+
const key = _a.#messageKey(message);
|
|
317
|
+
const pending = this.#directPending.get(key);
|
|
318
|
+
if (pending !== undefined)
|
|
319
|
+
return pending.completed;
|
|
320
|
+
const selected = Promise.withResolvers();
|
|
321
|
+
const settle = (outcome) => {
|
|
322
|
+
if (this.#directPending.get(key)?.settle !== settle)
|
|
323
|
+
return false;
|
|
324
|
+
this.#directPending.delete(key);
|
|
325
|
+
selected.resolve(Object.freeze({ acknowledged: outcome.kind === "acknowledged", outcome }));
|
|
326
|
+
return true;
|
|
327
|
+
};
|
|
328
|
+
this.#directPending.set(key, {
|
|
329
|
+
shardKey: message.shard.key(),
|
|
330
|
+
input,
|
|
331
|
+
settle,
|
|
332
|
+
completed: selected.promise,
|
|
333
|
+
});
|
|
334
|
+
const completed = this.#startDirect(message.shard);
|
|
335
|
+
this.#observeSelectedRun(message.shard, key, completed, settle);
|
|
336
|
+
return selected.promise;
|
|
337
|
+
}
|
|
338
|
+
/**
|
|
339
|
+
* Transfers an unresolved exact-row observer to a successor shard run.
|
|
340
|
+
*/
|
|
341
|
+
#observeSelectedRun(shard, key, completed, settle) {
|
|
342
|
+
void completed.then((run) => {
|
|
343
|
+
if (!this.#directPending.has(key))
|
|
344
|
+
return;
|
|
345
|
+
const successor = this.#activeDirect.get(shard.key())?.completed;
|
|
346
|
+
if (successor !== undefined && successor !== completed) {
|
|
347
|
+
this.#observeSelectedRun(shard, key, successor, settle);
|
|
348
|
+
}
|
|
349
|
+
else
|
|
350
|
+
settle({ kind: "unresolved", run });
|
|
351
|
+
}, (error) => {
|
|
352
|
+
if (settle({ kind: "failed", error }))
|
|
353
|
+
this.#callerObserved.add(completed);
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
static #messageKey(message) {
|
|
357
|
+
return JSON.stringify([
|
|
358
|
+
deliveryTenantKey(message),
|
|
359
|
+
message.id.value,
|
|
360
|
+
message.id.shard.index,
|
|
361
|
+
message.id.shard.ofTotal,
|
|
362
|
+
]);
|
|
363
|
+
}
|
|
364
|
+
/**
|
|
365
|
+
* Starts or wakes the retained direct run for one shard.
|
|
366
|
+
*/
|
|
367
|
+
#startDirect(shard) {
|
|
368
|
+
if (this.#closing || (this.#startupDraining && !this.#hasPending(shard.key())))
|
|
369
|
+
return Promise.resolve(result("STOPPED"));
|
|
370
|
+
return this.#startShared(shard, this.#directOptions());
|
|
371
|
+
}
|
|
372
|
+
/**
|
|
373
|
+
* Checks whether an exact accepted handoff needs this shard.
|
|
374
|
+
* @param shardKey Canonical shard key.
|
|
375
|
+
* @returns Whether an exact pending observer remains.
|
|
376
|
+
*/
|
|
377
|
+
#hasPending(shardKey) {
|
|
378
|
+
return [...this.#directPending.values()].some((pending) => pending.shardKey === shardKey);
|
|
379
|
+
}
|
|
380
|
+
/**
|
|
381
|
+
* Starts or joins the one retained shard run used by local posts and supervision.
|
|
382
|
+
* @param shard Shard selected by the accepted message or supervisor.
|
|
383
|
+
* @param options Complete drain callbacks for a newly admitted run.
|
|
384
|
+
* @returns The terminal run shared by both entry paths.
|
|
385
|
+
*/
|
|
386
|
+
#startShared(shard, options) {
|
|
387
|
+
const key = shard.key();
|
|
388
|
+
if (this.#startupDraining && !this.#hasPending(key))
|
|
389
|
+
return Promise.resolve(result("STOPPED"));
|
|
390
|
+
const active = this.#activeDirect.get(key);
|
|
391
|
+
if (active !== undefined) {
|
|
392
|
+
active.wake();
|
|
393
|
+
return active.completed;
|
|
394
|
+
}
|
|
395
|
+
const completed = this.#trackDrain(shard, options, true);
|
|
396
|
+
this.#activeDirect.set(key, {
|
|
397
|
+
completed,
|
|
398
|
+
wake: () => {
|
|
399
|
+
const drain = this.#directDrains.get(key);
|
|
400
|
+
if (drain === undefined)
|
|
401
|
+
this.#pendingDirectWake.add(key);
|
|
402
|
+
else if (!drain.wake())
|
|
403
|
+
this.#pendingDirectWake.add(key);
|
|
183
404
|
},
|
|
184
405
|
});
|
|
185
|
-
|
|
406
|
+
void completed
|
|
407
|
+
.finally(() => {
|
|
408
|
+
this.#activeDirect.delete(key);
|
|
409
|
+
this.#directDrains.delete(key);
|
|
410
|
+
if (this.#pendingDirectWake.delete(key) && !this.#closing)
|
|
411
|
+
this.#observeBackground(this.#startDirect(shard), shard);
|
|
412
|
+
})
|
|
413
|
+
.catch(() => undefined);
|
|
414
|
+
return completed;
|
|
415
|
+
}
|
|
416
|
+
/**
|
|
417
|
+
* Reports a detached local shard failure without changing its result.
|
|
418
|
+
* @param completed Detached shard run.
|
|
419
|
+
* @param shard Failed shard.
|
|
420
|
+
*/
|
|
421
|
+
#observeBackground(completed, shard) {
|
|
422
|
+
if (this.#backgroundObserved.has(completed))
|
|
423
|
+
return;
|
|
424
|
+
this.#backgroundObserved.add(completed);
|
|
425
|
+
void completed.catch(async (error) => {
|
|
426
|
+
// Give exact-row observers of this rejection a chance to settle first.
|
|
427
|
+
await Promise.resolve();
|
|
428
|
+
if (this.#closing || this.#logger === undefined || this.#callerObserved.has(completed))
|
|
429
|
+
return;
|
|
430
|
+
emitServerError(this.#logger, "Background Delivery failed.", {
|
|
431
|
+
shardId: shard.key(),
|
|
432
|
+
errorName: error instanceof Error ? error.name : "UnknownError",
|
|
433
|
+
});
|
|
434
|
+
});
|
|
435
|
+
}
|
|
436
|
+
/**
|
|
437
|
+
* Dispatches each selected direct row through its registered endpoint.
|
|
438
|
+
*/
|
|
439
|
+
#directOptions() {
|
|
440
|
+
const request = (message) => this.#directPending.get(_a.#messageKey(message));
|
|
441
|
+
return {
|
|
442
|
+
onMessage: (message) => {
|
|
443
|
+
const selected = request(message);
|
|
444
|
+
return selected === undefined ? this.#replay?.(message) : selected.input.onMessage(message);
|
|
445
|
+
},
|
|
446
|
+
acceptMessage: (message) => {
|
|
447
|
+
const selected = request(message);
|
|
448
|
+
return selected === undefined
|
|
449
|
+
? !this.#startupDraining &&
|
|
450
|
+
this.#replay !== undefined &&
|
|
451
|
+
(this.#acceptReplay?.(message) ?? true)
|
|
452
|
+
: (selected.input.acceptMessage?.(message) ?? true);
|
|
453
|
+
},
|
|
454
|
+
onDelivered: (message) => request(message)?.input.onDelivered?.(message),
|
|
455
|
+
onDuplicateRemoved: (message) => request(message)?.input.onDuplicateRemoved?.(message),
|
|
456
|
+
onMessageOutcome: (message, outcome) => request(message)?.settle(outcome),
|
|
457
|
+
};
|
|
186
458
|
}
|
|
187
459
|
/**
|
|
188
460
|
* Executes one owned shard drain while containing monitor and endpoint failures.
|
|
@@ -191,8 +463,37 @@ export class Delivery {
|
|
|
191
463
|
* @param options The endpoint and optional operation signal.
|
|
192
464
|
* @returns The terminal direct-delivery result.
|
|
193
465
|
*/
|
|
194
|
-
|
|
195
|
-
|
|
466
|
+
drain(shard, options) {
|
|
467
|
+
return this.#trackDrain(shard, options, false);
|
|
468
|
+
}
|
|
469
|
+
/**
|
|
470
|
+
* Tracks one complete physical drain before opening its shard session.
|
|
471
|
+
* @param shard Selected shard.
|
|
472
|
+
* @param options Complete drain callbacks.
|
|
473
|
+
* @param shared Whether the run serves the shared replay route.
|
|
474
|
+
* @returns Completion after shard release.
|
|
475
|
+
*/
|
|
476
|
+
#trackDrain(shard, options, shared) {
|
|
477
|
+
if (this.#closing || (this.#startupDraining && (!shared || !this.#hasPending(shard.key()))))
|
|
478
|
+
return Promise.resolve(result("STOPPED"));
|
|
479
|
+
const completion = Promise.withResolvers();
|
|
480
|
+
const active = { completed: completion.promise, shared };
|
|
481
|
+
this.#activeRuns.add(active);
|
|
482
|
+
if (this.#startupDraining)
|
|
483
|
+
this.#closingRuns?.push(active);
|
|
484
|
+
void this.#drainAdmitted(shard, options, active).then(completion.resolve, completion.reject);
|
|
485
|
+
void active.completed.finally(() => this.#activeRuns.delete(active)).catch(() => undefined);
|
|
486
|
+
return active.completed;
|
|
487
|
+
}
|
|
488
|
+
/**
|
|
489
|
+
* Completes shard acquisition, callbacks, and release for an admitted run.
|
|
490
|
+
* @param shard Selected shard.
|
|
491
|
+
* @param options Complete drain callbacks.
|
|
492
|
+
* @param active Admitted run record.
|
|
493
|
+
* @returns Terminal result after release.
|
|
494
|
+
*/
|
|
495
|
+
async #drainAdmitted(shard, options, active) {
|
|
496
|
+
if (this.#shouldStop(options))
|
|
196
497
|
return result("STOPPED");
|
|
197
498
|
let session;
|
|
198
499
|
try {
|
|
@@ -206,11 +507,41 @@ export class Delivery {
|
|
|
206
507
|
await safely(async () => (await this.#monitor.onShardAlreadyPicked(new AlreadyPickedUp(shard))).execute());
|
|
207
508
|
return result("SKIPPED");
|
|
208
509
|
}
|
|
209
|
-
if (options
|
|
210
|
-
|
|
510
|
+
if (this.#shouldStop(options))
|
|
511
|
+
return this.#releaseStoppedSession(session, active);
|
|
512
|
+
return this.#runSession(shard, options, session, active);
|
|
513
|
+
}
|
|
514
|
+
/**
|
|
515
|
+
* Releases a session acquired after direct admission closed.
|
|
516
|
+
*
|
|
517
|
+
* @param session The acquired session to release.
|
|
518
|
+
* @param active The admitted run retaining release failure.
|
|
519
|
+
* @returns The stopped or failed direct-delivery result.
|
|
520
|
+
*/
|
|
521
|
+
async #releaseStoppedSession(session, active) {
|
|
522
|
+
try {
|
|
523
|
+
const released = await this.shards.release(session);
|
|
524
|
+
if (!released)
|
|
525
|
+
active.releaseFailure = new Error("Shard release was not confirmed.");
|
|
211
526
|
return result(released ? "STOPPED" : "FAILED");
|
|
212
527
|
}
|
|
213
|
-
|
|
528
|
+
catch (error) {
|
|
529
|
+
active.releaseFailure =
|
|
530
|
+
error instanceof Error ? error : new Error("Shard release failed.", { cause: error });
|
|
531
|
+
return result("FAILED");
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
/**
|
|
535
|
+
* Reads current admission fences after asynchronous shard acquisition.
|
|
536
|
+
*/
|
|
537
|
+
#shouldStop(options) {
|
|
538
|
+
return this.#closing || options.operation?.signal?.aborted === true;
|
|
539
|
+
}
|
|
540
|
+
/**
|
|
541
|
+
* Tracks an admitted direct session through its complete drain.
|
|
542
|
+
*/
|
|
543
|
+
#runSession(shard, options, session, active) {
|
|
544
|
+
const drain = new DeliveryDrain({
|
|
214
545
|
inbox: this.inbox,
|
|
215
546
|
monitor: this.#monitor,
|
|
216
547
|
options,
|
|
@@ -218,22 +549,58 @@ export class Delivery {
|
|
|
218
549
|
session,
|
|
219
550
|
shards: this.shards,
|
|
220
551
|
shard,
|
|
221
|
-
})
|
|
552
|
+
});
|
|
553
|
+
active.drain = drain;
|
|
554
|
+
if (active.shared) {
|
|
555
|
+
this.#directDrains.set(shard.key(), drain);
|
|
556
|
+
if (this.#pendingDirectWake.delete(shard.key()))
|
|
557
|
+
drain.wake();
|
|
558
|
+
}
|
|
559
|
+
const completed = drain.run().finally(() => {
|
|
560
|
+
active.releaseFailure = drain.releaseFailure;
|
|
561
|
+
});
|
|
562
|
+
void completed
|
|
563
|
+
.finally(() => {
|
|
564
|
+
if (active.shared && this.#directDrains.get(shard.key()) === drain)
|
|
565
|
+
this.#directDrains.delete(shard.key());
|
|
566
|
+
})
|
|
567
|
+
.catch(() => undefined);
|
|
568
|
+
return completed;
|
|
222
569
|
}
|
|
223
570
|
}
|
|
571
|
+
_a = Delivery;
|
|
224
572
|
/**
|
|
225
573
|
* Processes inbox pages while retaining one shard session.
|
|
226
574
|
*/
|
|
227
575
|
class DeliveryDrain {
|
|
228
576
|
input;
|
|
229
577
|
#statistics = counts();
|
|
578
|
+
#agentController = new AbortController();
|
|
579
|
+
#liveAgents = new Set();
|
|
230
580
|
#failures = [];
|
|
231
581
|
#deduplication;
|
|
232
582
|
#blockedTargets = new Set();
|
|
583
|
+
#capacityBlockedTargets = new Set();
|
|
233
584
|
#removedInPage = new Set();
|
|
585
|
+
#messageOutcomes = new WeakMap();
|
|
586
|
+
#wakeGeneration = 0;
|
|
587
|
+
#wakeGate = Promise.withResolvers();
|
|
588
|
+
#sessionSerial = Promise.resolve();
|
|
589
|
+
#renewalTimer;
|
|
234
590
|
#current;
|
|
235
591
|
#ownershipLost = false;
|
|
236
592
|
#failureLimitReached = false;
|
|
593
|
+
#stopped = false;
|
|
594
|
+
#reading = true;
|
|
595
|
+
#releaseFailure;
|
|
596
|
+
/**
|
|
597
|
+
* Returns a failed session release to an active direct close.
|
|
598
|
+
*
|
|
599
|
+
* @returns The original shard-release failure, if one occurred.
|
|
600
|
+
*/
|
|
601
|
+
get releaseFailure() {
|
|
602
|
+
return this.#releaseFailure;
|
|
603
|
+
}
|
|
237
604
|
/**
|
|
238
605
|
* Creates a drain for one acquired shard session.
|
|
239
606
|
*
|
|
@@ -244,6 +611,84 @@ class DeliveryDrain {
|
|
|
244
611
|
this.#current = input.session;
|
|
245
612
|
this.#deduplication = new DeliveryDeduplication(input.inbox);
|
|
246
613
|
}
|
|
614
|
+
/**
|
|
615
|
+
* Notifies a fresh durable scan without starting a second shard session.
|
|
616
|
+
*
|
|
617
|
+
* @returns Whether the wake was recorded for a live shard run.
|
|
618
|
+
*/
|
|
619
|
+
wake() {
|
|
620
|
+
if (!this.#reading)
|
|
621
|
+
return false;
|
|
622
|
+
this.#wakeGeneration += 1;
|
|
623
|
+
for (const target of this.#capacityBlockedTargets)
|
|
624
|
+
this.#blockedTargets.delete(target);
|
|
625
|
+
this.#capacityBlockedTargets.clear();
|
|
626
|
+
this.#wakeGate.resolve(undefined);
|
|
627
|
+
this.#wakeGate = Promise.withResolvers();
|
|
628
|
+
return true;
|
|
629
|
+
}
|
|
630
|
+
/**
|
|
631
|
+
* Stops new work and cancels entered Agent work for explicit shutdown.
|
|
632
|
+
*
|
|
633
|
+
* @param reason Cause of cancellation or failure.
|
|
634
|
+
*/
|
|
635
|
+
stop(reason) {
|
|
636
|
+
this.#stopped = true;
|
|
637
|
+
this.#agentController.abort(reason);
|
|
638
|
+
this.#clearRenewal();
|
|
639
|
+
this.wake();
|
|
640
|
+
}
|
|
641
|
+
/**
|
|
642
|
+
* Serializes validation, renewal, and release against one current session snapshot.
|
|
643
|
+
* @typeParam T Result of the admitted shard operation.
|
|
644
|
+
* @param onOperation Current-session operation serialized with renewal and release.
|
|
645
|
+
* @returns Result of the admitted session operation.
|
|
646
|
+
*/
|
|
647
|
+
#sessionOperation(onOperation) {
|
|
648
|
+
const attempt = this.#sessionSerial.then(onOperation);
|
|
649
|
+
this.#sessionSerial = attempt.then(() => undefined, () => undefined);
|
|
650
|
+
return attempt;
|
|
651
|
+
}
|
|
652
|
+
/**
|
|
653
|
+
* Schedules renewal before the issued lease expires while live work remains.
|
|
654
|
+
*/
|
|
655
|
+
#scheduleRenewal() {
|
|
656
|
+
if (this.#stopped || this.#current.kind !== "LEASED" || this.input.shards.renew === undefined)
|
|
657
|
+
return;
|
|
658
|
+
this.#clearRenewal();
|
|
659
|
+
const life = this.#current.expiresAt.getTime() - this.#current.pickedUpAt.getTime();
|
|
660
|
+
const delay = Math.max(1, Math.min(Math.floor(life / 2), this.#current.expiresAt.getTime() - Time.currentTimeMillis() - 1));
|
|
661
|
+
this.#renewalTimer = setTimeout(() => {
|
|
662
|
+
void this.#renewLease();
|
|
663
|
+
}, delay);
|
|
664
|
+
this.#renewalTimer.unref();
|
|
665
|
+
}
|
|
666
|
+
/**
|
|
667
|
+
* Updates one live lease and fences the run permanently on loss.
|
|
668
|
+
* @returns Completion of lease renewal or loss fencing.
|
|
669
|
+
*/
|
|
670
|
+
async #renewLease() {
|
|
671
|
+
await this.#sessionOperation(async () => {
|
|
672
|
+
const renew = this.input.shards.renew?.bind(this.input.shards);
|
|
673
|
+
if (this.#current.kind !== "LEASED" || this.#stopped || renew === undefined)
|
|
674
|
+
return;
|
|
675
|
+
const renewed = await safelyValue(() => renew(this.#current), undefined);
|
|
676
|
+
if (renewed === undefined)
|
|
677
|
+
this.stop(new Error("Shard ownership was lost."));
|
|
678
|
+
else
|
|
679
|
+
this.#current = renewed;
|
|
680
|
+
});
|
|
681
|
+
if (!this.#stopped && this.#liveAgents.size > 0)
|
|
682
|
+
this.#scheduleRenewal();
|
|
683
|
+
}
|
|
684
|
+
/**
|
|
685
|
+
* Cancels the live lease timer after completion or loss.
|
|
686
|
+
*/
|
|
687
|
+
#clearRenewal() {
|
|
688
|
+
if (this.#renewalTimer !== undefined)
|
|
689
|
+
clearTimeout(this.#renewalTimer);
|
|
690
|
+
this.#renewalTimer = undefined;
|
|
691
|
+
}
|
|
247
692
|
/**
|
|
248
693
|
* Executes delivery and releases the shard session.
|
|
249
694
|
*
|
|
@@ -255,9 +700,12 @@ class DeliveryDrain {
|
|
|
255
700
|
run = (await this.#canStart()) ? await this.#readPages() : this.#complete("STOPPED");
|
|
256
701
|
}
|
|
257
702
|
catch (error) {
|
|
703
|
+
this.#reading = false;
|
|
704
|
+
this.stop(error);
|
|
258
705
|
await this.#release();
|
|
259
706
|
throw error;
|
|
260
707
|
}
|
|
708
|
+
this.#reading = false;
|
|
261
709
|
return (await this.#release()) ? run : this.#complete("FAILED");
|
|
262
710
|
}
|
|
263
711
|
/**
|
|
@@ -266,7 +714,20 @@ class DeliveryDrain {
|
|
|
266
714
|
* @returns Whether the session was released.
|
|
267
715
|
*/
|
|
268
716
|
async #release() {
|
|
269
|
-
|
|
717
|
+
if (this.#stopped || this.#ownershipLost || this.input.options.operation?.signal?.aborted)
|
|
718
|
+
this.#agentController.abort();
|
|
719
|
+
await Promise.allSettled(this.#liveAgents);
|
|
720
|
+
this.#clearRenewal();
|
|
721
|
+
let released = false;
|
|
722
|
+
try {
|
|
723
|
+
released = await this.#sessionOperation(() => this.input.shards.release(this.#current, this.input.options.operation));
|
|
724
|
+
}
|
|
725
|
+
catch (error) {
|
|
726
|
+
this.#releaseFailure =
|
|
727
|
+
error instanceof Error ? error : new Error("Shard release failed.", { cause: error });
|
|
728
|
+
}
|
|
729
|
+
if (!released && this.#releaseFailure === undefined)
|
|
730
|
+
this.#releaseFailure = new Error("Shard release was not confirmed.");
|
|
270
731
|
if (released)
|
|
271
732
|
await safely(() => this.#completeMonitoring());
|
|
272
733
|
return released;
|
|
@@ -289,14 +750,19 @@ class DeliveryDrain {
|
|
|
289
750
|
async #readPages() {
|
|
290
751
|
let after;
|
|
291
752
|
let deliveredInScan = false;
|
|
753
|
+
let scanGeneration = this.#wakeGeneration;
|
|
292
754
|
for (;;) {
|
|
755
|
+
if (this.#stopped || this.#ownershipLost || this.input.options.operation?.signal?.aborted)
|
|
756
|
+
return this.#complete("STOPPED");
|
|
757
|
+
const wake = this.#wakeGate.promise;
|
|
293
758
|
const messages = await this.#readPage(after);
|
|
294
759
|
this.#removedInPage.clear();
|
|
295
760
|
if (messages.length === 0) {
|
|
296
|
-
if (
|
|
761
|
+
if (await this.#emptyPageDrained(deliveredInScan, scanGeneration, wake))
|
|
297
762
|
return this.#complete("DRAINED");
|
|
298
763
|
after = undefined;
|
|
299
764
|
deliveredInScan = false;
|
|
765
|
+
scanGeneration = this.#wakeGeneration;
|
|
300
766
|
continue;
|
|
301
767
|
}
|
|
302
768
|
const deliveredBefore = this.#statistics.delivered;
|
|
@@ -306,10 +772,11 @@ class DeliveryDrain {
|
|
|
306
772
|
return this.#complete("STOPPED");
|
|
307
773
|
deliveredInScan ||= this.#statistics.delivered !== deliveredBefore;
|
|
308
774
|
if (messages.length < this.input.pageSize) {
|
|
309
|
-
if (
|
|
775
|
+
if (await this.#shortPageDrained(deliveredInScan, scanGeneration, wake))
|
|
310
776
|
return this.#complete("DRAINED");
|
|
311
777
|
after = undefined;
|
|
312
778
|
deliveredInScan = false;
|
|
779
|
+
scanGeneration = this.#wakeGeneration;
|
|
313
780
|
continue;
|
|
314
781
|
}
|
|
315
782
|
const survivor = messages.findLast((message) => !this.#removedInPage.has(message));
|
|
@@ -317,6 +784,51 @@ class DeliveryDrain {
|
|
|
317
784
|
after = this.#continuation([survivor]);
|
|
318
785
|
}
|
|
319
786
|
}
|
|
787
|
+
/**
|
|
788
|
+
* Waits for admitted Agent work after an empty Inbox page.
|
|
789
|
+
* @param deliveredInScan Whether the current scan delivered a row.
|
|
790
|
+
* @param generation Wake generation captured before reading the page.
|
|
791
|
+
* @param wake Notification that new work became ready.
|
|
792
|
+
* @returns Whether the empty page ends this drain.
|
|
793
|
+
*/
|
|
794
|
+
async #emptyPageDrained(deliveredInScan, generation, wake) {
|
|
795
|
+
if (generation !== this.#wakeGeneration)
|
|
796
|
+
return false;
|
|
797
|
+
if (this.#liveAgents.size > 0) {
|
|
798
|
+
await Promise.race([...this.#liveAgents, wake]);
|
|
799
|
+
return false;
|
|
800
|
+
}
|
|
801
|
+
return !deliveredInScan && this.#endScan(generation);
|
|
802
|
+
}
|
|
803
|
+
/**
|
|
804
|
+
* Waits for live Agent work before deciding that a short page is drained.
|
|
805
|
+
* @param deliveredInScan Whether the current scan delivered a row.
|
|
806
|
+
* @param generation Wake generation captured before reading the page.
|
|
807
|
+
* @param wake Notification that new work became ready.
|
|
808
|
+
* @returns Whether the short page ends this drain.
|
|
809
|
+
*/
|
|
810
|
+
async #shortPageDrained(deliveredInScan, generation, wake) {
|
|
811
|
+
if (deliveredInScan)
|
|
812
|
+
return false;
|
|
813
|
+
if (generation !== this.#wakeGeneration)
|
|
814
|
+
return false;
|
|
815
|
+
if (this.#liveAgents.size === 0)
|
|
816
|
+
return this.#endScan(generation);
|
|
817
|
+
await Promise.race([...this.#liveAgents, wake]);
|
|
818
|
+
return false;
|
|
819
|
+
}
|
|
820
|
+
/**
|
|
821
|
+
* Checks the terminal scan decision against a concurrent durable wake.
|
|
822
|
+
*
|
|
823
|
+
* @param generation Wake generation captured before reading the page.
|
|
824
|
+
* @returns Whether this scan may finish without losing a notification.
|
|
825
|
+
*/
|
|
826
|
+
#endScan(generation) {
|
|
827
|
+
if (generation !== this.#wakeGeneration)
|
|
828
|
+
return false;
|
|
829
|
+
this.#reading = false;
|
|
830
|
+
return true;
|
|
831
|
+
}
|
|
320
832
|
/**
|
|
321
833
|
* Reads the next inbox page after an optional continuation.
|
|
322
834
|
*
|
|
@@ -354,7 +866,7 @@ class DeliveryDrain {
|
|
|
354
866
|
* @returns Whether processing may continue.
|
|
355
867
|
*/
|
|
356
868
|
async #processMessage(message, page) {
|
|
357
|
-
if (this.input.options.operation?.signal?.aborted)
|
|
869
|
+
if (this.#stopped || this.input.options.operation?.signal?.aborted)
|
|
358
870
|
return false;
|
|
359
871
|
if (this.#shouldSkip(message))
|
|
360
872
|
return true;
|
|
@@ -365,8 +877,11 @@ class DeliveryDrain {
|
|
|
365
877
|
this.#statistics.processed += 1;
|
|
366
878
|
if (!(await this.#mayProcess()))
|
|
367
879
|
return false;
|
|
368
|
-
if (page.isDuplicate(message))
|
|
369
|
-
|
|
880
|
+
if (page.isDuplicate(message)) {
|
|
881
|
+
const continued = await this.#removeDuplicate(message, target);
|
|
882
|
+
this.#reportOutcome(message);
|
|
883
|
+
return continued;
|
|
884
|
+
}
|
|
370
885
|
return await this.#deliver(message, target);
|
|
371
886
|
}
|
|
372
887
|
finally {
|
|
@@ -391,7 +906,11 @@ class DeliveryDrain {
|
|
|
391
906
|
* @returns The target type and ID key.
|
|
392
907
|
*/
|
|
393
908
|
#targetKey(message) {
|
|
394
|
-
return
|
|
909
|
+
return JSON.stringify([
|
|
910
|
+
deliveryTenantKey(message),
|
|
911
|
+
message.inboxId.targetTypeUrl,
|
|
912
|
+
InboxTargets.key(message.inboxId.targetId),
|
|
913
|
+
]);
|
|
395
914
|
}
|
|
396
915
|
/**
|
|
397
916
|
* Checks monitoring and verifies that the shard session remains current.
|
|
@@ -414,6 +933,7 @@ class DeliveryDrain {
|
|
|
414
933
|
if (!(await this.input.inbox.removeDuplicate(message, this.input.options.operation)))
|
|
415
934
|
throw new Error("Inbox duplicate was not removed.");
|
|
416
935
|
this.#removedInPage.add(message);
|
|
936
|
+
this.#messageOutcomes.set(message, { kind: "acknowledged", agent: false });
|
|
417
937
|
this.input.options.onDuplicateRemoved?.(message);
|
|
418
938
|
}
|
|
419
939
|
catch (error) {
|
|
@@ -432,8 +952,18 @@ class DeliveryDrain {
|
|
|
432
952
|
async #deliver(message, target) {
|
|
433
953
|
const acknowledgement = { completed: false, exhausted: false };
|
|
434
954
|
await this.#processAttempt(message, target, acknowledgement);
|
|
955
|
+
this.#reportOutcome(message);
|
|
435
956
|
return !this.#ownershipLost && !this.input.options.operation?.signal?.aborted;
|
|
436
957
|
}
|
|
958
|
+
/**
|
|
959
|
+
* Records one selected row after its endpoint and monitor policy settle.
|
|
960
|
+
* @param message Inbox row whose settled outcome is reported.
|
|
961
|
+
*/
|
|
962
|
+
#reportOutcome(message) {
|
|
963
|
+
const outcome = this.#messageOutcomes.get(message);
|
|
964
|
+
if (outcome !== undefined)
|
|
965
|
+
this.input.options.onMessageOutcome?.(message, outcome);
|
|
966
|
+
}
|
|
437
967
|
/**
|
|
438
968
|
* Executes one dispatch attempt and the monitor-selected response to a receptor failure.
|
|
439
969
|
*
|
|
@@ -444,24 +974,86 @@ class DeliveryDrain {
|
|
|
444
974
|
*/
|
|
445
975
|
async #processAttempt(message, target, acknowledgement) {
|
|
446
976
|
const observation = receptionObservation.create();
|
|
977
|
+
const generation = this.#wakeGeneration;
|
|
447
978
|
try {
|
|
979
|
+
const agent = await this.#dispatch(message, observation);
|
|
980
|
+
if (agent === dispatchNotAdmitted) {
|
|
981
|
+
acknowledgement.notAdmitted = true;
|
|
982
|
+
return;
|
|
983
|
+
}
|
|
448
984
|
this.#statistics.accepted += 1;
|
|
449
|
-
|
|
985
|
+
if (agent?.kind === "deferred") {
|
|
986
|
+
this.#recordDeferred(message, target, generation);
|
|
987
|
+
return;
|
|
988
|
+
}
|
|
989
|
+
if (agent?.kind === "error") {
|
|
990
|
+
this.#recordFailure(message, agent.error);
|
|
991
|
+
this.#blockedTargets.add(target);
|
|
992
|
+
return;
|
|
993
|
+
}
|
|
994
|
+
if (agent?.kind === "accepted") {
|
|
995
|
+
await this.#completeAgentAttempt(message, target, acknowledgement, agent);
|
|
996
|
+
return;
|
|
997
|
+
}
|
|
450
998
|
}
|
|
451
999
|
catch (error) {
|
|
452
|
-
|
|
453
|
-
|
|
1000
|
+
await this.#handleReceptionFailure(message, target, acknowledgement, observation, error);
|
|
1001
|
+
if (this.#ownershipLost || this.input.options.operation?.signal?.aborted)
|
|
454
1002
|
return;
|
|
455
|
-
}
|
|
456
|
-
if (!receptionObservation.isReceptorFailure(observation, error))
|
|
457
|
-
throw error;
|
|
458
|
-
this.#recordFailure(message, error);
|
|
459
|
-
const reception = new FailedReception(message, error, () => this.#acknowledgeOnce(message, acknowledgement), () => this.#processAttempt(message, target, acknowledgement));
|
|
460
|
-
const action = await this.input.monitor.onReceptionFailure(reception);
|
|
461
|
-
await action.execute();
|
|
462
1003
|
}
|
|
463
1004
|
await this.#completeAttempt(message, target, acknowledgement);
|
|
464
1005
|
}
|
|
1006
|
+
/**
|
|
1007
|
+
* Completes acknowledgement before starting an admitted Agent handler.
|
|
1008
|
+
* @param message Persisted Inbox row.
|
|
1009
|
+
* @param target Canonical target key.
|
|
1010
|
+
* @param acknowledgement Status shared with immediate repeats.
|
|
1011
|
+
* @param agent Admitted handler awaiting durable acknowledgement.
|
|
1012
|
+
* @returns Completion after acknowledgement and handler start or discard.
|
|
1013
|
+
*/
|
|
1014
|
+
async #completeAgentAttempt(message, target, acknowledgement, agent) {
|
|
1015
|
+
await this.#completeAttempt(message, target, acknowledgement);
|
|
1016
|
+
if (acknowledgement.completed) {
|
|
1017
|
+
this.#messageOutcomes.set(message, { kind: "acknowledged", agent: true });
|
|
1018
|
+
this.#startAgent(message, agent);
|
|
1019
|
+
}
|
|
1020
|
+
else
|
|
1021
|
+
agent.discard();
|
|
1022
|
+
}
|
|
1023
|
+
/**
|
|
1024
|
+
* Records a capacity deferral and revisits it if release raced with dispatch.
|
|
1025
|
+
* @param generation Wake generation captured before admission.
|
|
1026
|
+
* @param message Inbox row deferred for Agent capacity.
|
|
1027
|
+
* @param target Canonical target key blocked in this scan.
|
|
1028
|
+
*/
|
|
1029
|
+
#recordDeferred(message, target, generation) {
|
|
1030
|
+
this.#messageOutcomes.set(message, { kind: "deferred" });
|
|
1031
|
+
this.#blockedTargets.add(target);
|
|
1032
|
+
this.#capacityBlockedTargets.add(target);
|
|
1033
|
+
if (generation !== this.#wakeGeneration)
|
|
1034
|
+
this.wake();
|
|
1035
|
+
}
|
|
1036
|
+
/**
|
|
1037
|
+
* Applies the original receptor monitor only to an actual receptor failure.
|
|
1038
|
+
* @param acknowledgement Status shared with repeated attempts.
|
|
1039
|
+
* @param error Original reception failure.
|
|
1040
|
+
* @param message Inbox row whose receptor failed.
|
|
1041
|
+
* @param observation Whether the receptor callback was entered.
|
|
1042
|
+
* @param target Canonical target key for acknowledgement.
|
|
1043
|
+
* @returns Completion of the monitor-selected failure action.
|
|
1044
|
+
*/
|
|
1045
|
+
async #handleReceptionFailure(message, target, acknowledgement, observation, error) {
|
|
1046
|
+
if (this.#ownershipLost || this.input.options.operation?.signal?.aborted) {
|
|
1047
|
+
this.#recordFailure(message, error);
|
|
1048
|
+
return;
|
|
1049
|
+
}
|
|
1050
|
+
if (!receptionObservation.isReceptorFailure(observation, error))
|
|
1051
|
+
throw error;
|
|
1052
|
+
this.#recordFailure(message, error);
|
|
1053
|
+
const reception = new FailedReception(message, error, () => this.#acknowledgeOnce(message, acknowledgement), () => this.#processAttempt(message, target, acknowledgement));
|
|
1054
|
+
const action = await this.input.monitor.onReceptionFailure(reception);
|
|
1055
|
+
await action.execute();
|
|
1056
|
+
}
|
|
465
1057
|
/**
|
|
466
1058
|
* Marks completed processing once, including nested repeat recovery.
|
|
467
1059
|
*
|
|
@@ -473,6 +1065,7 @@ class DeliveryDrain {
|
|
|
473
1065
|
async #completeAttempt(message, target, acknowledgement) {
|
|
474
1066
|
if (acknowledgement.completed ||
|
|
475
1067
|
acknowledgement.exhausted ||
|
|
1068
|
+
acknowledgement.notAdmitted ||
|
|
476
1069
|
this.input.options.operation?.signal?.aborted)
|
|
477
1070
|
return;
|
|
478
1071
|
if (!(await this.#validate()))
|
|
@@ -540,13 +1133,49 @@ class DeliveryDrain {
|
|
|
540
1133
|
* @returns Completion after endpoint dispatch.
|
|
541
1134
|
*/
|
|
542
1135
|
async #dispatch(message, observation) {
|
|
543
|
-
|
|
1136
|
+
const operation = this.input.options.operation?.signal;
|
|
1137
|
+
const signal = operation === undefined
|
|
1138
|
+
? this.#agentController.signal
|
|
1139
|
+
: AbortSignal.any([this.#agentController.signal, operation]);
|
|
1140
|
+
const admission = { rejected: false };
|
|
1141
|
+
const outcome = await AgentDeliveryScope.run(() => receptionObservation.run(observation, () => withDeliveryCommitFence(async () => {
|
|
544
1142
|
if (!(await this.#validate()))
|
|
545
1143
|
throw new Error("Shard ownership was lost.");
|
|
546
1144
|
}, () => {
|
|
1145
|
+
if (this.input.options.acceptMessage?.(message) === false) {
|
|
1146
|
+
admission.rejected = true;
|
|
1147
|
+
return Promise.resolve();
|
|
1148
|
+
}
|
|
547
1149
|
observation.entered = true;
|
|
548
1150
|
return Promise.resolve(this.input.options.onMessage(message));
|
|
549
|
-
}));
|
|
1151
|
+
})), signal);
|
|
1152
|
+
return admission.rejected ? dispatchNotAdmitted : outcome;
|
|
1153
|
+
}
|
|
1154
|
+
/**
|
|
1155
|
+
* Records admitted Agent work under this shard session after Inbox acknowledgement.
|
|
1156
|
+
* @param agent Accepted Agent turn and discard callback.
|
|
1157
|
+
* @param message Acknowledged Inbox row.
|
|
1158
|
+
*/
|
|
1159
|
+
#startAgent(message, agent) {
|
|
1160
|
+
const operation = this.input.options.operation?.signal;
|
|
1161
|
+
const signal = operation === undefined
|
|
1162
|
+
? this.#agentController.signal
|
|
1163
|
+
: AbortSignal.any([this.#agentController.signal, operation]);
|
|
1164
|
+
const checkSession = async () => {
|
|
1165
|
+
if (!(await this.#validate()))
|
|
1166
|
+
throw new Error("Shard ownership was lost.");
|
|
1167
|
+
};
|
|
1168
|
+
const work = Promise.resolve()
|
|
1169
|
+
.then(() => agent.run(signal, checkSession))
|
|
1170
|
+
.catch((error) => {
|
|
1171
|
+
this.#recordFailure(message, error);
|
|
1172
|
+
})
|
|
1173
|
+
.finally(() => {
|
|
1174
|
+
this.#liveAgents.delete(work);
|
|
1175
|
+
this.wake();
|
|
1176
|
+
});
|
|
1177
|
+
this.#liveAgents.add(work);
|
|
1178
|
+
this.#scheduleRenewal();
|
|
550
1179
|
}
|
|
551
1180
|
/**
|
|
552
1181
|
* Marks a message delivered and updates local counts and callbacks.
|
|
@@ -559,6 +1188,7 @@ class DeliveryDrain {
|
|
|
559
1188
|
throw new Error("Inbox message was not marked delivered.");
|
|
560
1189
|
this.#deduplication.recordDelivered(message);
|
|
561
1190
|
this.#statistics.delivered += 1;
|
|
1191
|
+
this.#messageOutcomes.set(message, { kind: "acknowledged", agent: false });
|
|
562
1192
|
try {
|
|
563
1193
|
this.input.options.onDelivered?.(message);
|
|
564
1194
|
}
|
|
@@ -632,12 +1262,18 @@ class DeliveryDrain {
|
|
|
632
1262
|
* @returns Whether the shard session remains valid.
|
|
633
1263
|
*/
|
|
634
1264
|
async #validate() {
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
1265
|
+
return this.#sessionOperation(async () => {
|
|
1266
|
+
if (this.#stopped || this.#ownershipLost)
|
|
1267
|
+
return false;
|
|
1268
|
+
const current = await safelyValue(() => this.input.shards.validateOwnership(this.#current, this.input.options.operation), undefined);
|
|
1269
|
+
if (current === undefined) {
|
|
1270
|
+
this.#ownershipLost = true;
|
|
1271
|
+
this.stop(new Error("Shard ownership was lost."));
|
|
1272
|
+
}
|
|
1273
|
+
else
|
|
1274
|
+
this.#current = current;
|
|
1275
|
+
return current !== undefined;
|
|
1276
|
+
});
|
|
641
1277
|
}
|
|
642
1278
|
/**
|
|
643
1279
|
* Records an ephemeral delivery failure up to the configured limit.
|
|
@@ -647,6 +1283,8 @@ class DeliveryDrain {
|
|
|
647
1283
|
*/
|
|
648
1284
|
#recordFailure(message, error) {
|
|
649
1285
|
this.#statistics.failed += 1;
|
|
1286
|
+
if (this.#messageOutcomes.get(message)?.kind !== "acknowledged")
|
|
1287
|
+
this.#messageOutcomes.set(message, { kind: "failed", error });
|
|
650
1288
|
if (this.#failures.length < deliveryFailureLimit)
|
|
651
1289
|
this.#failures.push(Object.freeze({ message: snapshot(message), error }));
|
|
652
1290
|
this.#failureLimitReached = this.#failures.length === deliveryFailureLimit;
|
|
@@ -658,6 +1296,7 @@ class DeliveryDrain {
|
|
|
658
1296
|
* @returns The immutable delivery result.
|
|
659
1297
|
*/
|
|
660
1298
|
#complete(status) {
|
|
1299
|
+
this.#reading = false;
|
|
661
1300
|
return result(status, this.#statistics, this.#failures);
|
|
662
1301
|
}
|
|
663
1302
|
/**
|
|
@@ -848,6 +1487,7 @@ class RecentDeliveries {
|
|
|
848
1487
|
*/
|
|
849
1488
|
static key(message) {
|
|
850
1489
|
return JSON.stringify([
|
|
1490
|
+
deliveryTenantKey(message),
|
|
851
1491
|
message.signalId,
|
|
852
1492
|
message.inboxId.targetTypeUrl,
|
|
853
1493
|
InboxTargets.key(message.inboxId.targetId),
|