@spine-event-engine/server 2.0.0-snapshot.23 → 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 +278 -141
- 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/agent/agent-live-inputs.js +15 -0
- 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 +82 -104
- package/dist/bus/event-bus.d.ts.map +1 -1
- package/dist/bus/event-bus.js +87 -127
- package/dist/bus/event-bus.js.map +1 -1
- package/dist/context/bounded-context.d.ts +143 -91
- package/dist/context/bounded-context.d.ts.map +1 -1
- package/dist/context/bounded-context.js +753 -359
- 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 +43 -20
- package/dist/context/entity-inbox.js.map +1 -1
- package/dist/context/local-inbox-handoff.d.ts +35 -34
- package/dist/context/local-inbox-handoff.d.ts.map +1 -1
- package/dist/context/local-inbox-handoff.js +123 -135
- 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 +12 -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 +12 -9
- package/dist/delivery/delivery-monitor.d.ts.map +1 -1
- package/dist/delivery/delivery-monitor.js +15 -9
- package/dist/delivery/delivery-monitor.js.map +1 -1
- package/dist/delivery/delivery-ports.d.ts +6 -13
- package/dist/delivery/delivery-ports.d.ts.map +1 -1
- package/dist/delivery/delivery-run-control.d.ts +21 -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 +33 -5
- package/dist/delivery/delivery-supervisor.d.ts.map +1 -1
- package/dist/delivery/delivery-supervisor.js +178 -26
- 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 +803 -89
- 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 +14 -11
- package/dist/delivery/inbox-storage.d.ts.map +1 -1
- package/dist/delivery/inbox-storage.js +72 -78
- package/dist/delivery/inbox-storage.js.map +1 -1
- package/dist/delivery/inbox.d.ts +9 -12
- package/dist/delivery/inbox.d.ts.map +1 -1
- package/dist/delivery/inbox.js +13 -15
- package/dist/delivery/inbox.js.map +1 -1
- package/dist/delivery/reception-observation.d.ts +64 -0
- package/dist/delivery/reception-observation.d.ts.map +1 -0
- package/dist/delivery/reception-observation.js +78 -0
- package/dist/delivery/reception-observation.js.map +1 -0
- 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 +33 -62
- package/dist/repository/repository.d.ts.map +1 -1
- package/dist/repository/repository.js +1475 -1827
- package/dist/repository/repository.js.map +1 -1
- package/dist/runtime/runtime.d.ts +24 -0
- package/dist/runtime/runtime.d.ts.map +1 -1
- package/dist/runtime/runtime.js +31 -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 +8 -15
- package/dist/runtime/signal-metadata.js.map +1 -1
- package/dist/runtime/signal-publisher.d.ts +8 -36
- package/dist/runtime/signal-publisher.d.ts.map +1 -1
- package/dist/runtime/signal-publisher.js +68 -61
- 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 +14 -130
- 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 +2 -2
- package/dist/server/managed-server-application.d.ts.map +1 -1
- package/dist/server/managed-server-application.js +471 -178
- package/dist/server/managed-server-application.js.map +1 -1
- package/dist/server/node-coordinator.d.ts +14 -0
- package/dist/server/node-coordinator.d.ts.map +1 -1
- package/dist/server/node-coordinator.js +266 -45
- package/dist/server/node-coordinator.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 +18 -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 +62 -203
- 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 +399 -370
- package/dist/services/spine-services.js.map +1 -1
- package/dist/stand/event-origin-filter.d.ts +16 -0
- package/dist/stand/event-origin-filter.d.ts.map +1 -0
- package/dist/stand/event-origin-filter.js +124 -0
- package/dist/stand/event-origin-filter.js.map +1 -0
- 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 +61 -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 +23 -4
- package/dist/stand/subscription-runtime.d.ts.map +1 -1
- package/dist/stand/subscription-runtime.js +84 -13
- package/dist/stand/subscription-runtime.js.map +1 -1
- package/dist/testing/index.d.ts.map +1 -1
- package/dist/testing/index.js +3 -2
- package/dist/testing/index.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +15 -15
- 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 -96
- package/dist/bus/saved-dispatcher-binding.d.ts.map +0 -1
- package/dist/bus/saved-dispatcher-binding.js +0 -35
- 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 -673
- 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,10 +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";
|
|
28
|
+
import { receptionObservation } from "./reception-observation.js";
|
|
29
|
+
import { AgentDeliveryScope } from "./agent-delivery-scope.js";
|
|
30
|
+
import { emitServerError } from "../server/server-log.js";
|
|
26
31
|
const deliveryFailureLimit = 100;
|
|
32
|
+
const controlledMessageSettlers = new WeakMap();
|
|
33
|
+
const dispatchNotAdmitted = Symbol("Delivery recipient closed before dispatch");
|
|
27
34
|
/**
|
|
28
|
-
*
|
|
35
|
+
* Runs one retained direct-delivery worker and its durable Inbox.
|
|
29
36
|
*/
|
|
30
37
|
export class Delivery {
|
|
31
38
|
// prettier-ignore
|
|
@@ -62,18 +69,146 @@ export class Delivery {
|
|
|
62
69
|
*/
|
|
63
70
|
shards;
|
|
64
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
|
+
}
|
|
65
197
|
/**
|
|
66
198
|
* Creates direct delivery from immutable options.
|
|
67
199
|
*
|
|
68
200
|
* @param options The delivery configuration.
|
|
69
201
|
*/
|
|
70
202
|
constructor(options) {
|
|
203
|
+
if (options.context.multitenant)
|
|
204
|
+
throw new TypeError("Delivery requires a single-tenant storage context.");
|
|
71
205
|
if (options.worker !== undefined &&
|
|
72
206
|
options.node !== undefined &&
|
|
73
207
|
options.worker.nodeId?.value !== options.node) {
|
|
74
208
|
throw new Error("Delivery worker node must match the configured delivery node.");
|
|
75
209
|
}
|
|
76
|
-
this.context =
|
|
210
|
+
this.context = _a.#contextSnapshot(options.context);
|
|
211
|
+
this.#logger = options.logger;
|
|
77
212
|
this.storageFactory = options.storageFactory;
|
|
78
213
|
this.strategy = options.strategy ?? { shardCount: 1, shardFor: () => ShardIndex.single() };
|
|
79
214
|
this.worker =
|
|
@@ -118,19 +253,42 @@ export class Delivery {
|
|
|
118
253
|
*/
|
|
119
254
|
runControlled(options) {
|
|
120
255
|
const shard = options.shard;
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
256
|
+
const drainOptions = this.#controlledOptions(options);
|
|
257
|
+
if (options.onMessageSettled !== undefined)
|
|
258
|
+
controlledMessageSettlers.set(drainOptions, options.onMessageSettled);
|
|
259
|
+
const completed = this.#replay === undefined
|
|
260
|
+
? this.drain(shard, drainOptions)
|
|
261
|
+
: this.#startShared(shard, drainOptions);
|
|
262
|
+
return completed
|
|
263
|
+
.then((run) => {
|
|
264
|
+
options.onDrainResult?.(run);
|
|
265
|
+
return Object.freeze({
|
|
266
|
+
status: run.status === "SKIPPED"
|
|
267
|
+
? "SKIPPED"
|
|
268
|
+
: run.status === "FAILED"
|
|
269
|
+
? "FAILED"
|
|
270
|
+
: run.status === "STOPPED"
|
|
271
|
+
? "STOPPED"
|
|
272
|
+
: "COMPLETED",
|
|
273
|
+
});
|
|
274
|
+
})
|
|
275
|
+
.finally(() => {
|
|
276
|
+
controlledMessageSettlers.delete(drainOptions);
|
|
277
|
+
});
|
|
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 } };
|
|
134
292
|
}
|
|
135
293
|
/**
|
|
136
294
|
* Delivers the selected message's shard for local handoff integration.
|
|
@@ -155,22 +313,148 @@ export class Delivery {
|
|
|
155
313
|
: { name: context.name, multitenant: false });
|
|
156
314
|
}
|
|
157
315
|
async #drainDirect(message, input) {
|
|
158
|
-
|
|
159
|
-
const
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
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);
|
|
171
404
|
},
|
|
172
405
|
});
|
|
173
|
-
|
|
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
|
+
};
|
|
174
458
|
}
|
|
175
459
|
/**
|
|
176
460
|
* Executes one owned shard drain while containing monitor and endpoint failures.
|
|
@@ -179,8 +463,37 @@ export class Delivery {
|
|
|
179
463
|
* @param options The endpoint and optional operation signal.
|
|
180
464
|
* @returns The terminal direct-delivery result.
|
|
181
465
|
*/
|
|
182
|
-
|
|
183
|
-
|
|
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))
|
|
184
497
|
return result("STOPPED");
|
|
185
498
|
let session;
|
|
186
499
|
try {
|
|
@@ -194,11 +507,41 @@ export class Delivery {
|
|
|
194
507
|
await safely(async () => (await this.#monitor.onShardAlreadyPicked(new AlreadyPickedUp(shard))).execute());
|
|
195
508
|
return result("SKIPPED");
|
|
196
509
|
}
|
|
197
|
-
if (options
|
|
198
|
-
|
|
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.");
|
|
199
526
|
return result(released ? "STOPPED" : "FAILED");
|
|
200
527
|
}
|
|
201
|
-
|
|
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({
|
|
202
545
|
inbox: this.inbox,
|
|
203
546
|
monitor: this.#monitor,
|
|
204
547
|
options,
|
|
@@ -206,22 +549,58 @@ export class Delivery {
|
|
|
206
549
|
session,
|
|
207
550
|
shards: this.shards,
|
|
208
551
|
shard,
|
|
209
|
-
})
|
|
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;
|
|
210
569
|
}
|
|
211
570
|
}
|
|
571
|
+
_a = Delivery;
|
|
212
572
|
/**
|
|
213
573
|
* Processes inbox pages while retaining one shard session.
|
|
214
574
|
*/
|
|
215
575
|
class DeliveryDrain {
|
|
216
576
|
input;
|
|
217
577
|
#statistics = counts();
|
|
578
|
+
#agentController = new AbortController();
|
|
579
|
+
#liveAgents = new Set();
|
|
218
580
|
#failures = [];
|
|
219
581
|
#deduplication;
|
|
220
582
|
#blockedTargets = new Set();
|
|
583
|
+
#capacityBlockedTargets = new Set();
|
|
221
584
|
#removedInPage = new Set();
|
|
585
|
+
#messageOutcomes = new WeakMap();
|
|
586
|
+
#wakeGeneration = 0;
|
|
587
|
+
#wakeGate = Promise.withResolvers();
|
|
588
|
+
#sessionSerial = Promise.resolve();
|
|
589
|
+
#renewalTimer;
|
|
222
590
|
#current;
|
|
223
591
|
#ownershipLost = false;
|
|
224
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
|
+
}
|
|
225
604
|
/**
|
|
226
605
|
* Creates a drain for one acquired shard session.
|
|
227
606
|
*
|
|
@@ -232,6 +611,84 @@ class DeliveryDrain {
|
|
|
232
611
|
this.#current = input.session;
|
|
233
612
|
this.#deduplication = new DeliveryDeduplication(input.inbox);
|
|
234
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
|
+
}
|
|
235
692
|
/**
|
|
236
693
|
* Executes delivery and releases the shard session.
|
|
237
694
|
*
|
|
@@ -243,10 +700,12 @@ class DeliveryDrain {
|
|
|
243
700
|
run = (await this.#canStart()) ? await this.#readPages() : this.#complete("STOPPED");
|
|
244
701
|
}
|
|
245
702
|
catch (error) {
|
|
246
|
-
|
|
247
|
-
|
|
703
|
+
this.#reading = false;
|
|
704
|
+
this.stop(error);
|
|
705
|
+
await this.#release();
|
|
248
706
|
throw error;
|
|
249
707
|
}
|
|
708
|
+
this.#reading = false;
|
|
250
709
|
return (await this.#release()) ? run : this.#complete("FAILED");
|
|
251
710
|
}
|
|
252
711
|
/**
|
|
@@ -255,7 +714,20 @@ class DeliveryDrain {
|
|
|
255
714
|
* @returns Whether the session was released.
|
|
256
715
|
*/
|
|
257
716
|
async #release() {
|
|
258
|
-
|
|
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.");
|
|
259
731
|
if (released)
|
|
260
732
|
await safely(() => this.#completeMonitoring());
|
|
261
733
|
return released;
|
|
@@ -278,14 +750,19 @@ class DeliveryDrain {
|
|
|
278
750
|
async #readPages() {
|
|
279
751
|
let after;
|
|
280
752
|
let deliveredInScan = false;
|
|
753
|
+
let scanGeneration = this.#wakeGeneration;
|
|
281
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;
|
|
282
758
|
const messages = await this.#readPage(after);
|
|
283
759
|
this.#removedInPage.clear();
|
|
284
760
|
if (messages.length === 0) {
|
|
285
|
-
if (
|
|
761
|
+
if (await this.#emptyPageDrained(deliveredInScan, scanGeneration, wake))
|
|
286
762
|
return this.#complete("DRAINED");
|
|
287
763
|
after = undefined;
|
|
288
764
|
deliveredInScan = false;
|
|
765
|
+
scanGeneration = this.#wakeGeneration;
|
|
289
766
|
continue;
|
|
290
767
|
}
|
|
291
768
|
const deliveredBefore = this.#statistics.delivered;
|
|
@@ -295,10 +772,11 @@ class DeliveryDrain {
|
|
|
295
772
|
return this.#complete("STOPPED");
|
|
296
773
|
deliveredInScan ||= this.#statistics.delivered !== deliveredBefore;
|
|
297
774
|
if (messages.length < this.input.pageSize) {
|
|
298
|
-
if (
|
|
775
|
+
if (await this.#shortPageDrained(deliveredInScan, scanGeneration, wake))
|
|
299
776
|
return this.#complete("DRAINED");
|
|
300
777
|
after = undefined;
|
|
301
778
|
deliveredInScan = false;
|
|
779
|
+
scanGeneration = this.#wakeGeneration;
|
|
302
780
|
continue;
|
|
303
781
|
}
|
|
304
782
|
const survivor = messages.findLast((message) => !this.#removedInPage.has(message));
|
|
@@ -306,6 +784,51 @@ class DeliveryDrain {
|
|
|
306
784
|
after = this.#continuation([survivor]);
|
|
307
785
|
}
|
|
308
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
|
+
}
|
|
309
832
|
/**
|
|
310
833
|
* Reads the next inbox page after an optional continuation.
|
|
311
834
|
*
|
|
@@ -343,19 +866,27 @@ class DeliveryDrain {
|
|
|
343
866
|
* @returns Whether processing may continue.
|
|
344
867
|
*/
|
|
345
868
|
async #processMessage(message, page) {
|
|
346
|
-
if (this.input.options.operation?.signal?.aborted)
|
|
869
|
+
if (this.#stopped || this.input.options.operation?.signal?.aborted)
|
|
347
870
|
return false;
|
|
348
871
|
if (this.#shouldSkip(message))
|
|
349
872
|
return true;
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
873
|
+
try {
|
|
874
|
+
const target = this.#targetKey(message);
|
|
875
|
+
if (this.#blockedTargets.has(target))
|
|
876
|
+
return true;
|
|
877
|
+
this.#statistics.processed += 1;
|
|
878
|
+
if (!(await this.#mayProcess()))
|
|
879
|
+
return false;
|
|
880
|
+
if (page.isDuplicate(message)) {
|
|
881
|
+
const continued = await this.#removeDuplicate(message, target);
|
|
882
|
+
this.#reportOutcome(message);
|
|
883
|
+
return continued;
|
|
884
|
+
}
|
|
885
|
+
return await this.#deliver(message, target);
|
|
886
|
+
}
|
|
887
|
+
finally {
|
|
888
|
+
controlledMessageSettlers.get(this.input.options)?.(message);
|
|
889
|
+
}
|
|
359
890
|
}
|
|
360
891
|
/**
|
|
361
892
|
* Determines whether an inbox message is ineligible for endpoint delivery.
|
|
@@ -375,7 +906,11 @@ class DeliveryDrain {
|
|
|
375
906
|
* @returns The target type and ID key.
|
|
376
907
|
*/
|
|
377
908
|
#targetKey(message) {
|
|
378
|
-
return
|
|
909
|
+
return JSON.stringify([
|
|
910
|
+
deliveryTenantKey(message),
|
|
911
|
+
message.inboxId.targetTypeUrl,
|
|
912
|
+
InboxTargets.key(message.inboxId.targetId),
|
|
913
|
+
]);
|
|
379
914
|
}
|
|
380
915
|
/**
|
|
381
916
|
* Checks monitoring and verifies that the shard session remains current.
|
|
@@ -395,9 +930,10 @@ class DeliveryDrain {
|
|
|
395
930
|
*/
|
|
396
931
|
async #removeDuplicate(message, target) {
|
|
397
932
|
try {
|
|
398
|
-
if (!(await this.input.inbox.removeDuplicate(message, this
|
|
933
|
+
if (!(await this.input.inbox.removeDuplicate(message, this.input.options.operation)))
|
|
399
934
|
throw new Error("Inbox duplicate was not removed.");
|
|
400
935
|
this.#removedInPage.add(message);
|
|
936
|
+
this.#messageOutcomes.set(message, { kind: "acknowledged", agent: false });
|
|
401
937
|
this.input.options.onDuplicateRemoved?.(message);
|
|
402
938
|
}
|
|
403
939
|
catch (error) {
|
|
@@ -414,35 +950,156 @@ class DeliveryDrain {
|
|
|
414
950
|
* @returns Whether processing may continue.
|
|
415
951
|
*/
|
|
416
952
|
async #deliver(message, target) {
|
|
953
|
+
const acknowledgement = { completed: false, exhausted: false };
|
|
954
|
+
await this.#processAttempt(message, target, acknowledgement);
|
|
955
|
+
this.#reportOutcome(message);
|
|
956
|
+
return !this.#ownershipLost && !this.input.options.operation?.signal?.aborted;
|
|
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
|
+
}
|
|
967
|
+
/**
|
|
968
|
+
* Executes one dispatch attempt and the monitor-selected response to a receptor failure.
|
|
969
|
+
*
|
|
970
|
+
* @param message The persisted Inbox message.
|
|
971
|
+
* @param target The target key used to isolate acknowledgement failure.
|
|
972
|
+
* @param acknowledgement Shared status across immediate repeats.
|
|
973
|
+
* @returns Completion of the attempt and its selected action.
|
|
974
|
+
*/
|
|
975
|
+
async #processAttempt(message, target, acknowledgement) {
|
|
976
|
+
const observation = receptionObservation.create();
|
|
977
|
+
const generation = this.#wakeGeneration;
|
|
417
978
|
try {
|
|
979
|
+
const agent = await this.#dispatch(message, observation);
|
|
980
|
+
if (agent === dispatchNotAdmitted) {
|
|
981
|
+
acknowledgement.notAdmitted = true;
|
|
982
|
+
return;
|
|
983
|
+
}
|
|
418
984
|
this.#statistics.accepted += 1;
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
return
|
|
422
|
-
|
|
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
|
+
}
|
|
423
998
|
}
|
|
424
999
|
catch (error) {
|
|
425
|
-
await this.#handleReceptionFailure(message, target, error);
|
|
426
|
-
if (this.#ownershipLost)
|
|
427
|
-
return
|
|
1000
|
+
await this.#handleReceptionFailure(message, target, acknowledgement, observation, error);
|
|
1001
|
+
if (this.#ownershipLost || this.input.options.operation?.signal?.aborted)
|
|
1002
|
+
return;
|
|
428
1003
|
}
|
|
429
|
-
|
|
1004
|
+
await this.#completeAttempt(message, target, acknowledgement);
|
|
430
1005
|
}
|
|
431
1006
|
/**
|
|
432
|
-
*
|
|
433
|
-
*
|
|
434
|
-
* @param
|
|
435
|
-
* @param
|
|
436
|
-
* @param
|
|
437
|
-
* @returns Completion after
|
|
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.
|
|
438
1028
|
*/
|
|
439
|
-
|
|
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;
|
|
440
1052
|
this.#recordFailure(message, error);
|
|
441
|
-
const reception = new FailedReception(message, error, () => this.#
|
|
442
|
-
const action = await
|
|
443
|
-
|
|
444
|
-
|
|
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
|
+
}
|
|
1057
|
+
/**
|
|
1058
|
+
* Marks completed processing once, including nested repeat recovery.
|
|
1059
|
+
*
|
|
1060
|
+
* @param message The processed Inbox message.
|
|
1061
|
+
* @param target The target key used to isolate acknowledgement failure.
|
|
1062
|
+
* @param acknowledgement Shared status across immediate repeats.
|
|
1063
|
+
* @returns Completion after acknowledgement or exhausted retry containment.
|
|
1064
|
+
*/
|
|
1065
|
+
async #completeAttempt(message, target, acknowledgement) {
|
|
1066
|
+
if (acknowledgement.completed ||
|
|
1067
|
+
acknowledgement.exhausted ||
|
|
1068
|
+
acknowledgement.notAdmitted ||
|
|
1069
|
+
this.input.options.operation?.signal?.aborted)
|
|
1070
|
+
return;
|
|
1071
|
+
if (!(await this.#validate()))
|
|
1072
|
+
return;
|
|
1073
|
+
try {
|
|
1074
|
+
await this.#acknowledgeWithRetry(message, acknowledgement);
|
|
1075
|
+
}
|
|
1076
|
+
catch (error) {
|
|
1077
|
+
acknowledgement.exhausted = true;
|
|
1078
|
+
this.#recordFailure(message, error);
|
|
445
1079
|
this.#blockedTargets.add(target);
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
/**
|
|
1083
|
+
* Retries only durable acknowledgement after processing has completed.
|
|
1084
|
+
*
|
|
1085
|
+
* @param message Supplies the successfully processed message.
|
|
1086
|
+
* @param acknowledgement Shared status across immediate repeats.
|
|
1087
|
+
* @returns Completion after acknowledgement.
|
|
1088
|
+
*/
|
|
1089
|
+
async #acknowledgeWithRetry(message, acknowledgement) {
|
|
1090
|
+
let firstError;
|
|
1091
|
+
for (let attempt = 0; attempt < 3; attempt += 1) {
|
|
1092
|
+
try {
|
|
1093
|
+
await this.#acknowledgeOnce(message, acknowledgement);
|
|
1094
|
+
return;
|
|
1095
|
+
}
|
|
1096
|
+
catch (error) {
|
|
1097
|
+
firstError ??= error;
|
|
1098
|
+
if (this.#ownershipLost || this.input.options.operation?.signal?.aborted)
|
|
1099
|
+
break;
|
|
1100
|
+
}
|
|
1101
|
+
}
|
|
1102
|
+
throw firstError;
|
|
446
1103
|
}
|
|
447
1104
|
/**
|
|
448
1105
|
* Marks a message delivered after checking the shard session.
|
|
@@ -456,26 +1113,69 @@ class DeliveryDrain {
|
|
|
456
1113
|
await this.#markDelivered(message);
|
|
457
1114
|
}
|
|
458
1115
|
/**
|
|
459
|
-
*
|
|
1116
|
+
* Marks a message delivered once across nested monitor-selected repeats.
|
|
460
1117
|
*
|
|
461
|
-
* @param message
|
|
462
|
-
* @
|
|
1118
|
+
* @param message The message to mark delivered.
|
|
1119
|
+
* @param acknowledgement Shared status across immediate repeats.
|
|
1120
|
+
* @returns Completion after acknowledgement.
|
|
463
1121
|
*/
|
|
464
|
-
async #
|
|
465
|
-
|
|
1122
|
+
async #acknowledgeOnce(message, acknowledgement) {
|
|
1123
|
+
if (acknowledgement.completed)
|
|
1124
|
+
return;
|
|
466
1125
|
await this.#acknowledge(message);
|
|
1126
|
+
acknowledgement.completed = true;
|
|
467
1127
|
}
|
|
468
1128
|
/**
|
|
469
1129
|
* Dispatches a message while validating the shard session before the call.
|
|
470
1130
|
*
|
|
471
1131
|
* @param message Supplies the inbox message for the endpoint.
|
|
1132
|
+
* @param observation Records failure origin for this attempt.
|
|
472
1133
|
* @returns Completion after endpoint dispatch.
|
|
473
1134
|
*/
|
|
474
|
-
async #dispatch(message) {
|
|
475
|
-
|
|
1135
|
+
async #dispatch(message, observation) {
|
|
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 () => {
|
|
476
1142
|
if (!(await this.#validate()))
|
|
477
1143
|
throw new Error("Shard ownership was lost.");
|
|
478
|
-
}, () =>
|
|
1144
|
+
}, () => {
|
|
1145
|
+
if (this.input.options.acceptMessage?.(message) === false) {
|
|
1146
|
+
admission.rejected = true;
|
|
1147
|
+
return Promise.resolve();
|
|
1148
|
+
}
|
|
1149
|
+
observation.entered = true;
|
|
1150
|
+
return Promise.resolve(this.input.options.onMessage(message));
|
|
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();
|
|
479
1179
|
}
|
|
480
1180
|
/**
|
|
481
1181
|
* Marks a message delivered and updates local counts and callbacks.
|
|
@@ -488,7 +1188,13 @@ class DeliveryDrain {
|
|
|
488
1188
|
throw new Error("Inbox message was not marked delivered.");
|
|
489
1189
|
this.#deduplication.recordDelivered(message);
|
|
490
1190
|
this.#statistics.delivered += 1;
|
|
491
|
-
this.
|
|
1191
|
+
this.#messageOutcomes.set(message, { kind: "acknowledged", agent: false });
|
|
1192
|
+
try {
|
|
1193
|
+
this.input.options.onDelivered?.(message);
|
|
1194
|
+
}
|
|
1195
|
+
catch {
|
|
1196
|
+
// A post-ack observer cannot undo durable delivery or repeat processing.
|
|
1197
|
+
}
|
|
492
1198
|
}
|
|
493
1199
|
/**
|
|
494
1200
|
* Removes expired delivered messages from one page when supported.
|
|
@@ -525,9 +1231,7 @@ class DeliveryDrain {
|
|
|
525
1231
|
return false;
|
|
526
1232
|
if (this.input.options.operation?.signal?.aborted || !(await this.#validate()))
|
|
527
1233
|
return undefined;
|
|
528
|
-
const removed = await this.input.inbox.removeDelivered(message, this
|
|
529
|
-
if (this.input.options.operation?.signal?.aborted || (!removed && !(await this.#validate())))
|
|
530
|
-
return undefined;
|
|
1234
|
+
const removed = await this.input.inbox.removeDelivered(message, this.input.options.operation);
|
|
531
1235
|
return removed;
|
|
532
1236
|
}
|
|
533
1237
|
/**
|
|
@@ -558,12 +1262,18 @@ class DeliveryDrain {
|
|
|
558
1262
|
* @returns Whether the shard session remains valid.
|
|
559
1263
|
*/
|
|
560
1264
|
async #validate() {
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
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
|
+
});
|
|
567
1277
|
}
|
|
568
1278
|
/**
|
|
569
1279
|
* Records an ephemeral delivery failure up to the configured limit.
|
|
@@ -573,6 +1283,8 @@ class DeliveryDrain {
|
|
|
573
1283
|
*/
|
|
574
1284
|
#recordFailure(message, error) {
|
|
575
1285
|
this.#statistics.failed += 1;
|
|
1286
|
+
if (this.#messageOutcomes.get(message)?.kind !== "acknowledged")
|
|
1287
|
+
this.#messageOutcomes.set(message, { kind: "failed", error });
|
|
576
1288
|
if (this.#failures.length < deliveryFailureLimit)
|
|
577
1289
|
this.#failures.push(Object.freeze({ message: snapshot(message), error }));
|
|
578
1290
|
this.#failureLimitReached = this.#failures.length === deliveryFailureLimit;
|
|
@@ -584,6 +1296,7 @@ class DeliveryDrain {
|
|
|
584
1296
|
* @returns The immutable delivery result.
|
|
585
1297
|
*/
|
|
586
1298
|
#complete(status) {
|
|
1299
|
+
this.#reading = false;
|
|
587
1300
|
return result(status, this.#statistics, this.#failures);
|
|
588
1301
|
}
|
|
589
1302
|
/**
|
|
@@ -774,6 +1487,7 @@ class RecentDeliveries {
|
|
|
774
1487
|
*/
|
|
775
1488
|
static key(message) {
|
|
776
1489
|
return JSON.stringify([
|
|
1490
|
+
deliveryTenantKey(message),
|
|
777
1491
|
message.signalId,
|
|
778
1492
|
message.inboxId.targetTypeUrl,
|
|
779
1493
|
InboxTargets.key(message.inboxId.targetId),
|