@zlink-systems/framework 0.25.0 → 0.26.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/.tsbuildinfo +1 -1
- package/dist/contracts/Channels/IZLinkFanoutClient.d.ts +0 -6
- package/dist/contracts/RouteMesh/RuntimeTopology.d.ts +8 -0
- package/dist/internal.d.ts +1 -0
- package/dist/internal.js +1 -0
- package/dist/runtime/activation-admission.d.ts +29 -0
- package/dist/runtime/activation-admission.js +61 -0
- package/dist/runtime/actors/actor-context.js +6 -6
- package/dist/runtime/actors/actor-handoff.d.ts +9 -3
- package/dist/runtime/actors/actor-handoff.js +28 -17
- package/dist/runtime/actors/actor-local-native-join.js +2 -18
- package/dist/runtime/actors/actor-remote-joiner.d.ts +2 -2
- package/dist/runtime/actors/actor-remote-joiner.js +6 -8
- package/dist/runtime/actors/actor-runtime-contracts.d.ts +4 -2
- package/dist/runtime/actors/actor-source-transfer.d.ts +5 -5
- package/dist/runtime/actors/deferred-join-completion.d.ts +14 -0
- package/dist/runtime/actors/deferred-join-completion.js +20 -0
- package/dist/runtime/actors/index.d.ts +1 -1
- package/dist/runtime/actors/index.js +15 -5
- package/dist/runtime/admission.d.ts +5 -1
- package/dist/runtime/admission.js +4 -0
- package/dist/runtime/backend/contracts/index.d.ts +2 -10
- package/dist/runtime/backend/mesh-actor-session-node-adapter.js +2 -9
- package/dist/runtime/backend/node/node-backend-adapter-factory.d.ts +1 -2
- package/dist/runtime/backend/node/node-backend-adapter-factory.js +4 -18
- package/dist/runtime/backend/node/node-backend-adapter-support.d.ts +6 -3
- package/dist/runtime/backend/node/node-backend-adapter-support.js +12 -28
- package/dist/runtime/backend/node/node-event-loop-poller.d.ts +1 -1
- package/dist/runtime/backend/node/node-event-loop-poller.js +8 -2
- package/dist/runtime/backend/node/node-raw-binding-port.d.ts +1 -1
- package/dist/runtime/backend/node/node-raw-binding-port.js +11 -3
- package/dist/runtime/backend/node/node-raw-mesh-backend.d.ts +12 -9
- package/dist/runtime/backend/node/node-raw-mesh-backend.js +61 -29
- package/dist/runtime/backend/node/node-socket-backend-adapter.js +1 -1
- package/dist/runtime/backend/raw-binding-port.d.ts +12 -0
- package/dist/runtime/channels/channel-clients.d.ts +0 -1
- package/dist/runtime/channels/channel-clients.js +0 -8
- package/dist/runtime/channels/channel-outbound-operations.js +2 -10
- package/dist/runtime/channels/channel-runtime-manager.d.ts +2 -5
- package/dist/runtime/channels/channel-runtime-manager.js +1 -12
- package/dist/runtime/channels/channel-socket-registry.d.ts +17 -4
- package/dist/runtime/channels/channel-socket-registry.js +49 -62
- package/dist/runtime/channels/channel-transports.d.ts +0 -3
- package/dist/runtime/channels/fanout-location-runtime.js +3 -9
- package/dist/runtime/execution/index.d.ts +18 -8
- package/dist/runtime/execution/index.js +55 -29
- package/dist/runtime/execution/serial-execution-queue.d.ts +5 -6
- package/dist/runtime/execution/serial-execution-queue.js +49 -15
- package/dist/runtime/foundation/listener-records.d.ts +11 -0
- package/dist/runtime/foundation/listener-records.js +20 -0
- package/dist/runtime/foundation/raw-service-mesh-runtime.d.ts +10 -21
- package/dist/runtime/foundation/raw-service-mesh-runtime.js +66 -392
- package/dist/runtime/foundation/service-authority-payload-codec.d.ts +1 -0
- package/dist/runtime/foundation/service-authority-payload-codec.js +8 -0
- package/dist/runtime/foundation/service-maintenance-runtime.js +23 -2
- package/dist/runtime/foundation/service-relocation-coordinator.d.ts +9 -0
- package/dist/runtime/foundation/service-relocation-coordinator.js +14 -1
- package/dist/runtime/foundation/service-relocation-object-owner.d.ts +8 -0
- package/dist/runtime/foundation/service-relocation-object-owner.js +16 -4
- package/dist/runtime/foundation/service-session-binding-ingress-port.d.ts +6 -13
- package/dist/runtime/foundation/service-stateful-runtime.d.ts +10 -7
- package/dist/runtime/foundation/service-stateful-runtime.js +100 -60
- package/dist/runtime/foundation/service-stateful-wire-codec.d.ts +9 -4
- package/dist/runtime/foundation/service-stateful-wire-codec.js +153 -90
- package/dist/runtime/foundation/service-topology-registry.d.ts +1 -2
- package/dist/runtime/foundation/service-topology-registry.js +6 -30
- package/dist/runtime/host/actor-transfer-runtime.d.ts +17 -15
- package/dist/runtime/host/actor-transfer-runtime.js +73 -86
- package/dist/runtime/host/index.d.ts +6 -2
- package/dist/runtime/host/index.js +63 -37
- package/dist/runtime/host/remote-bound-session-relay.js +4 -3
- package/dist/runtime/host/route-mesh-runtime.d.ts +2 -0
- package/dist/runtime/host/route-mesh-runtime.js +7 -5
- package/dist/runtime/host/service-relocation-host-runtime.d.ts +50 -8
- package/dist/runtime/host/service-relocation-host-runtime.js +343 -270
- package/dist/runtime/host/spot-runtime-options-factory.d.ts +7 -4
- package/dist/runtime/host/spot-runtime-options-factory.js +4 -8
- package/dist/runtime/host/stateful-authority-route-runtime.js +6 -2
- package/dist/runtime/host/user-spot-creation-coordinator.d.ts +11 -3
- package/dist/runtime/host/user-spot-creation-coordinator.js +85 -87
- package/dist/runtime/locations/in-memory-provider-location-store.js +8 -6
- package/dist/runtime/locations/lifecycle.d.ts +1 -1
- package/dist/runtime/locations/lifecycle.js +2 -2
- package/dist/runtime/locations/resolvers.js +7 -2
- package/dist/runtime/locations/spot-location-claims.d.ts +2 -2
- package/dist/runtime/locations/spot-location-claims.js +3 -27
- package/dist/runtime/protocol/service_wire_codec.generated.d.ts +1 -0
- package/dist/runtime/protocol/service_wire_codec.generated.js +5 -1
- package/dist/runtime/protocol/service_wire_pilot_codec.generated.d.ts +1 -0
- package/dist/runtime/protocol/service_wire_pilot_codec.generated.js +3 -2
- package/dist/runtime/spots/formal-remote-actor-admission-registry.d.ts +3 -3
- package/dist/runtime/spots/formal-remote-actor-admission-registry.js +4 -4
- package/dist/runtime/spots/formal-remote-actor-transfer-registry.d.ts +3 -3
- package/dist/runtime/spots/index.d.ts +25 -19
- package/dist/runtime/spots/index.js +207 -205
- package/dist/runtime/spots/spot-activation-registry.d.ts +4 -2
- package/dist/runtime/spots/spot-activation-registry.js +34 -31
- package/dist/runtime/spots/spot-activation-state.d.ts +2 -11
- package/dist/runtime/spots/spot-activation-state.js +4 -17
- package/dist/runtime/spots/spot-activation.d.ts +8 -6
- package/dist/runtime/spots/spot-activation.js +54 -49
- package/dist/runtime/spots/spot-actor-admission-coordinator.d.ts +1 -1
- package/dist/runtime/spots/spot-actor-admission-coordinator.js +1 -1
- package/dist/runtime/spots/spot-actor-join-dispatch.d.ts +2 -2
- package/dist/runtime/spots/spot-actor-join-dispatch.js +3 -5
- package/dist/runtime/spots/spot-actor-lifecycle-drain.js +2 -2
- package/dist/runtime/spots/spot-actor-membership.d.ts +4 -0
- package/dist/runtime/spots/spot-actor-membership.js +26 -12
- package/dist/runtime/spots/spot-entry-activation.d.ts +1 -2
- package/dist/runtime/spots/spot-entry-activation.js +0 -1
- package/dist/runtime/spots/spot-manager-public.js +1 -1
- package/dist/runtime/spots/spot-native-actor-join-admission.d.ts +1 -1
- package/dist/runtime/spots/spot-native-actor-join-admission.js +2 -2
- package/dist/runtime/spots/spot-node-runtime-manager.d.ts +5 -5
- package/dist/runtime/spots/spot-node-runtime-manager.js +7 -11
- package/dist/runtime/spots/spot-outbound.js +1 -29
- package/dist/runtime/spots/spot-routed-actor-admission.d.ts +1 -0
- package/dist/runtime/spots/spot-routed-actor-admission.js +10 -0
- package/dist/runtime/spots/spot-routed-frame-dispatch.d.ts +2 -6
- package/dist/runtime/spots/spot-routed-frame-dispatch.js +2 -70
- package/dist/runtime/spots/spot-runtime-ports.d.ts +3 -5
- package/dist/runtime/spots/spot-serial-executor.d.ts +5 -0
- package/dist/runtime/spots/spot-serial-executor.js +8 -1
- package/dist/runtime/spots/spot-serial-turn-executor.d.ts +3 -0
- package/dist/runtime/spots/spot-serial-turn-executor.js +8 -4
- package/dist/runtime/streams/actor-session-binding-registry.d.ts +13 -15
- package/dist/runtime/streams/actor-session-binding-registry.js +19 -171
- package/dist/runtime/streams/actor-session-binding-runtime-owner.d.ts +3 -4
- package/dist/runtime/streams/bound-session-service.js +1 -18
- package/dist/runtime/streams/index.d.ts +2 -0
- package/dist/runtime/streams/index.js +19 -19
- package/dist/runtime/streams/session-serial-executor.js +2 -2
- package/package.json +3 -3
- package/src/contracts/Channels/IZLinkFanoutClient.ts +0 -7
- package/src/contracts/RouteMesh/RuntimeTopology.ts +10 -0
- package/src/internal.ts +1 -0
- package/src/runtime/activation-admission.ts +78 -0
- package/src/runtime/actors/actor-context.ts +6 -6
- package/src/runtime/actors/actor-handoff.ts +31 -23
- package/src/runtime/actors/actor-local-native-join.ts +3 -42
- package/src/runtime/actors/actor-remote-joiner.ts +6 -25
- package/src/runtime/actors/actor-runtime-contracts.ts +4 -10
- package/src/runtime/actors/actor-source-transfer.ts +5 -18
- package/src/runtime/actors/deferred-join-completion.ts +40 -0
- package/src/runtime/actors/index.ts +22 -9
- package/src/runtime/admission.ts +5 -1
- package/src/runtime/backend/contracts/index.ts +2 -13
- package/src/runtime/backend/mesh-actor-session-node-adapter.ts +2 -10
- package/src/runtime/backend/node/node-backend-adapter-factory.ts +5 -25
- package/src/runtime/backend/node/node-backend-adapter-support.ts +9 -29
- package/src/runtime/backend/node/node-event-loop-poller.ts +14 -2
- package/src/runtime/backend/node/node-raw-binding-port.ts +17 -3
- package/src/runtime/backend/node/node-raw-mesh-backend.ts +65 -32
- package/src/runtime/backend/node/node-socket-backend-adapter.ts +2 -2
- package/src/runtime/backend/raw-binding-port.ts +13 -0
- package/src/runtime/channels/channel-clients.ts +0 -11
- package/src/runtime/channels/channel-outbound-operations.ts +3 -11
- package/src/runtime/channels/channel-runtime-manager.ts +4 -15
- package/src/runtime/channels/channel-socket-registry.ts +69 -71
- package/src/runtime/channels/channel-transports.ts +0 -3
- package/src/runtime/channels/fanout-location-runtime.ts +4 -16
- package/src/runtime/execution/index.ts +68 -30
- package/src/runtime/execution/serial-execution-queue.ts +52 -20
- package/src/runtime/foundation/listener-records.ts +21 -0
- package/src/runtime/foundation/raw-service-mesh-runtime.ts +66 -466
- package/src/runtime/foundation/service-authority-payload-codec.ts +10 -0
- package/src/runtime/foundation/service-maintenance-runtime.ts +30 -9
- package/src/runtime/foundation/service-relocation-coordinator.ts +13 -0
- package/src/runtime/foundation/service-relocation-object-owner.ts +18 -5
- package/src/runtime/foundation/service-session-binding-ingress-port.ts +6 -14
- package/src/runtime/foundation/service-stateful-runtime.ts +151 -89
- package/src/runtime/foundation/service-stateful-wire-codec.ts +203 -121
- package/src/runtime/foundation/service-topology-registry.ts +8 -48
- package/src/runtime/host/actor-transfer-runtime.ts +94 -166
- package/src/runtime/host/index.ts +119 -54
- package/src/runtime/host/remote-bound-session-relay.ts +4 -3
- package/src/runtime/host/route-mesh-runtime.ts +9 -5
- package/src/runtime/host/service-relocation-host-runtime.ts +495 -376
- package/src/runtime/host/spot-runtime-options-factory.ts +24 -13
- package/src/runtime/host/stateful-authority-route-runtime.ts +8 -4
- package/src/runtime/host/user-spot-creation-coordinator.ts +147 -142
- package/src/runtime/locations/in-memory-provider-location-store.ts +11 -7
- package/src/runtime/locations/lifecycle.ts +3 -2
- package/src/runtime/locations/resolvers.ts +19 -13
- package/src/runtime/locations/spot-location-claims.ts +5 -34
- package/src/runtime/protocol/service_wire_codec.generated.ts +9 -2
- package/src/runtime/protocol/service_wire_pilot_codec.generated.ts +4 -4
- package/src/runtime/spots/formal-remote-actor-admission-registry.ts +6 -6
- package/src/runtime/spots/formal-remote-actor-transfer-registry.ts +3 -3
- package/src/runtime/spots/index.ts +341 -295
- package/src/runtime/spots/spot-activation-registry.ts +41 -31
- package/src/runtime/spots/spot-activation-state.ts +4 -16
- package/src/runtime/spots/spot-activation.ts +102 -80
- package/src/runtime/spots/spot-actor-admission-coordinator.ts +2 -2
- package/src/runtime/spots/spot-actor-join-dispatch.ts +6 -7
- package/src/runtime/spots/spot-actor-lifecycle-drain.ts +20 -18
- package/src/runtime/spots/spot-actor-membership.ts +81 -17
- package/src/runtime/spots/spot-entry-activation.ts +0 -3
- package/src/runtime/spots/spot-manager-public.ts +8 -4
- package/src/runtime/spots/spot-native-actor-join-admission.ts +2 -2
- package/src/runtime/spots/spot-node-runtime-manager.ts +12 -18
- package/src/runtime/spots/spot-outbound.ts +4 -47
- package/src/runtime/spots/spot-routed-actor-admission.ts +20 -0
- package/src/runtime/spots/spot-routed-frame-dispatch.ts +6 -76
- package/src/runtime/spots/spot-runtime-ports.ts +6 -17
- package/src/runtime/spots/spot-serial-executor.ts +9 -1
- package/src/runtime/spots/spot-serial-turn-executor.ts +10 -4
- package/src/runtime/streams/actor-session-binding-registry.ts +20 -308
- package/src/runtime/streams/actor-session-binding-runtime-owner.ts +2 -12
- package/src/runtime/streams/bound-session-service.ts +2 -36
- package/src/runtime/streams/index.ts +29 -27
- package/src/runtime/streams/session-serial-executor.ts +2 -2
- package/dist/runtime/actors/deferred-join-accepted-journal.d.ts +0 -47
- package/dist/runtime/actors/deferred-join-accepted-journal.js +0 -992
- package/src/runtime/actors/deferred-join-accepted-journal.ts +0 -1346
|
@@ -189,6 +189,16 @@ export function decodeServiceReadySpotAuthority(
|
|
|
189
189
|
return decoded?.state === 'ready' && decoded.operationKind === 0 ? decoded : undefined;
|
|
190
190
|
}
|
|
191
191
|
|
|
192
|
+
export function decodeServiceClosingSpotAuthority(
|
|
193
|
+
payload: Uint8Array
|
|
194
|
+
): ServiceReadySpotAuthority | undefined {
|
|
195
|
+
const decoded = decodeSpotAuthority(payload);
|
|
196
|
+
return decoded?.operationKind === 3 &&
|
|
197
|
+
(decoded.kind === 'user_spot' || decoded.state === 'closing')
|
|
198
|
+
? decoded
|
|
199
|
+
: undefined;
|
|
200
|
+
}
|
|
201
|
+
|
|
192
202
|
export function decodeServiceInstanceAuthorityPayload(
|
|
193
203
|
payload: Uint8Array
|
|
194
204
|
): ServiceDecodedInstanceAuthority | undefined {
|
|
@@ -145,12 +145,27 @@ export class ServiceMaintenanceRuntime {
|
|
|
145
145
|
|
|
146
146
|
private async runUnits(signal: AbortSignal, stopStartingSignal?: AbortSignal): Promise<void> {
|
|
147
147
|
const pending = new Set<Promise<void>>();
|
|
148
|
+
// A unit failure is recorded before its promise leaves `pending`, so no
|
|
149
|
+
// failure is lost. Every started unit runs to its own terminal before the
|
|
150
|
+
// first failure is reported: the host result follows the settled
|
|
151
|
+
// authority of each unit (spec 30 §13).
|
|
152
|
+
const failures: unknown[] = [];
|
|
153
|
+
const reportFailure = async (): Promise<void> => {
|
|
154
|
+
if (failures.length === 0) return;
|
|
155
|
+
await Promise.allSettled(pending);
|
|
156
|
+
throw failures[0];
|
|
157
|
+
};
|
|
148
158
|
for (;;) {
|
|
159
|
+
await reportFailure();
|
|
149
160
|
const queued = await this.lane.run(() => this.units.length > 0);
|
|
150
|
-
if (!queued && pending.size === 0)
|
|
161
|
+
if (!queued && pending.size === 0) {
|
|
162
|
+
await reportFailure();
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
151
165
|
signal.throwIfAborted();
|
|
152
166
|
if (stopStartingSignal?.aborted === true) {
|
|
153
167
|
await Promise.allSettled(pending);
|
|
168
|
+
await reportFailure();
|
|
154
169
|
stopStartingSignal.throwIfAborted();
|
|
155
170
|
}
|
|
156
171
|
let admitted = false;
|
|
@@ -173,19 +188,25 @@ export class ServiceMaintenanceRuntime {
|
|
|
173
188
|
admitted = true;
|
|
174
189
|
let running!: Promise<void>;
|
|
175
190
|
running = startOutsideStateLane(() =>
|
|
176
|
-
unit
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
191
|
+
unit
|
|
192
|
+
.relocate(signal)
|
|
193
|
+
.catch((error: unknown) => {
|
|
194
|
+
failures.push(error);
|
|
195
|
+
})
|
|
196
|
+
.finally(async () => {
|
|
197
|
+
const publication = await this.lane.run(() => {
|
|
198
|
+
this.activeOutbound--;
|
|
199
|
+
pending.delete(running);
|
|
200
|
+
return this.publishCore();
|
|
201
|
+
});
|
|
202
|
+
this.notify(publication);
|
|
203
|
+
})
|
|
184
204
|
);
|
|
185
205
|
pending.add(running);
|
|
186
206
|
this.notify(claim.publication);
|
|
187
207
|
}
|
|
188
208
|
if (pending.size === 0) {
|
|
209
|
+
await reportFailure();
|
|
189
210
|
if (!admitted) throw new Error('No relocation unit is ready to start.');
|
|
190
211
|
} else {
|
|
191
212
|
await Promise.race(pending);
|
|
@@ -32,3 +32,16 @@ export class ServiceRelocationPostCommitError extends Error {
|
|
|
32
32
|
this.name = 'ServiceRelocationPostCommitError';
|
|
33
33
|
}
|
|
34
34
|
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* A relocation whose settled authority leaves the host in `Error` with
|
|
38
|
+
* `Blocked/RelocationFailed` (spec 30 §13): the source owner lease ended
|
|
39
|
+
* before its Preserve fence succeeded, or units settled on both the source
|
|
40
|
+
* and the target.
|
|
41
|
+
*/
|
|
42
|
+
export class ServiceRelocationAuthorityError extends Error {
|
|
43
|
+
constructor(message: string, options?: ErrorOptions) {
|
|
44
|
+
super(message, options);
|
|
45
|
+
this.name = 'ServiceRelocationAuthorityError';
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -30,10 +30,12 @@ export interface ServiceRelocationCaptureUnit {
|
|
|
30
30
|
captureApplicationState(signal?: AbortSignal): Promise<Uint8Array>;
|
|
31
31
|
commitSeal(): Promise<void> | void;
|
|
32
32
|
abortSeal(): Promise<void> | void;
|
|
33
|
+
/** Expired-owner terminal: pending requests fail once and dispatch stays closed. */
|
|
34
|
+
discardSeal?(reason: unknown): Promise<void> | void;
|
|
33
35
|
}
|
|
34
36
|
|
|
35
37
|
export class ServiceCapturedObjectRelocation {
|
|
36
|
-
private terminal: 'open' | 'committed' | 'aborted' = 'open';
|
|
38
|
+
private terminal: 'open' | 'committed' | 'aborted' | 'discarded' = 'open';
|
|
37
39
|
|
|
38
40
|
constructor(
|
|
39
41
|
readonly envelope: ServiceRelocationEnvelope,
|
|
@@ -42,8 +44,8 @@ export class ServiceCapturedObjectRelocation {
|
|
|
42
44
|
|
|
43
45
|
async commitSource(): Promise<void> {
|
|
44
46
|
if (this.terminal === 'committed') return;
|
|
45
|
-
if (this.terminal
|
|
46
|
-
throw new Error(
|
|
47
|
+
if (this.terminal !== 'open') {
|
|
48
|
+
throw new Error(`Relocation source capture was already ${this.terminal}.`);
|
|
47
49
|
}
|
|
48
50
|
for (const unit of this.units) await unit.commitSeal();
|
|
49
51
|
this.terminal = 'committed';
|
|
@@ -51,8 +53,8 @@ export class ServiceCapturedObjectRelocation {
|
|
|
51
53
|
|
|
52
54
|
async abortSource(): Promise<void> {
|
|
53
55
|
if (this.terminal === 'aborted') return;
|
|
54
|
-
if (this.terminal
|
|
55
|
-
throw new Error(
|
|
56
|
+
if (this.terminal !== 'open') {
|
|
57
|
+
throw new Error(`A ${this.terminal} relocation source capture cannot be aborted.`);
|
|
56
58
|
}
|
|
57
59
|
const failures: unknown[] = [];
|
|
58
60
|
for (const unit of [...this.units].reverse()) {
|
|
@@ -67,6 +69,17 @@ export class ServiceCapturedObjectRelocation {
|
|
|
67
69
|
throw new AggregateError(failures, 'Relocation source rollback was incomplete.');
|
|
68
70
|
}
|
|
69
71
|
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* The source owner lease ended before the authority settled (spec 28 §4.4):
|
|
75
|
+
* the source neither resumes nor commits. Retained work is dropped and
|
|
76
|
+
* every pending request without a terminal fails once with `reason`.
|
|
77
|
+
*/
|
|
78
|
+
async discardSource(reason: unknown): Promise<void> {
|
|
79
|
+
if (this.terminal !== 'open') return;
|
|
80
|
+
this.terminal = 'discarded';
|
|
81
|
+
for (const unit of this.units) await unit.discardSeal?.(reason);
|
|
82
|
+
}
|
|
70
83
|
}
|
|
71
84
|
|
|
72
85
|
/** Captures the two valid relocation units: User Spot aggregate or one Actor. */
|
|
@@ -3,25 +3,17 @@ export interface ServiceSessionBindingRetainedDelivery {
|
|
|
3
3
|
fail(error: unknown): void;
|
|
4
4
|
}
|
|
5
5
|
|
|
6
|
-
export interface ServiceSessionBindingAdmissionClaim {
|
|
7
|
-
readonly actorId: string;
|
|
8
|
-
readonly objectGeneration: bigint;
|
|
9
|
-
readonly actorNodeRid: string;
|
|
10
|
-
readonly actorNodeGeneration: bigint;
|
|
11
|
-
readonly authorityOwnerGeneration: bigint;
|
|
12
|
-
readonly ownerLeaseGeneration: bigint;
|
|
13
|
-
readonly producerNodeRid: string;
|
|
14
|
-
readonly producerNodeGeneration: bigint;
|
|
15
|
-
readonly sessionIdentity: string;
|
|
16
|
-
readonly bindingGeneration: bigint;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
6
|
export type ServiceSessionBindingAdmissionResult = 'passThrough' | 'retained' | 'rejected';
|
|
20
7
|
|
|
21
8
|
export interface ServiceSessionBindingIngressPort {
|
|
22
9
|
actorSlot(actorId: string, sessionRid: string): Promise<number | undefined>;
|
|
10
|
+
/**
|
|
11
|
+
* Holds a current-binding push while its relocation seal is open
|
|
12
|
+
* (Session–Actor binding §8.1). The caller has already decided that the push
|
|
13
|
+
* names the current binding.
|
|
14
|
+
*/
|
|
23
15
|
retainOutbound(
|
|
24
|
-
|
|
16
|
+
actorId: string,
|
|
25
17
|
delivery: ServiceSessionBindingRetainedDelivery
|
|
26
18
|
): Promise<ServiceSessionBindingAdmissionResult>;
|
|
27
19
|
clearOutbound(actorId: string, error: unknown): Promise<void>;
|
|
@@ -12,9 +12,12 @@ import { RequestResult, SubmitResult, isZLinkBackendResultError } from '../backe
|
|
|
12
12
|
import type { ZLinkDispatchErrorReporter } from '../channels/dispatch-error-reporter';
|
|
13
13
|
import {
|
|
14
14
|
ZLinkRuntimeDispatchErrorAction as ZLinkDispatchErrorAction,
|
|
15
|
+
ZLinkRuntimeDispatchErrorReason as ZLinkDispatchErrorReason,
|
|
16
|
+
ZLinkRuntimeMessageFlowOutcome as ZLinkMessageFlowOutcome,
|
|
15
17
|
ZLinkDispatchErrorSurface,
|
|
16
18
|
ZLinkDispatchMessageKind
|
|
17
19
|
} from '../../contracts/Dispatch/ZLinkDispatchOptions';
|
|
20
|
+
import { flowIfEnabled } from '../diagnostics/message-flow';
|
|
18
21
|
import type {
|
|
19
22
|
RawServiceIngressRecord,
|
|
20
23
|
RawServiceMeshRuntime,
|
|
@@ -80,7 +83,6 @@ import {
|
|
|
80
83
|
type ServiceUserSpotCloseRecord,
|
|
81
84
|
type ServiceUserSpotCreateRecord
|
|
82
85
|
} from './service-stateful-wire-codec';
|
|
83
|
-
import { routingIdsEqual } from '../routing-id';
|
|
84
86
|
import {
|
|
85
87
|
decodeActorJoin28,
|
|
86
88
|
encodeActorJoin28,
|
|
@@ -90,6 +92,7 @@ import { canonicalActorJoinHandoffId, routingIdBytes } from './actor-join-recove
|
|
|
90
92
|
|
|
91
93
|
import {
|
|
92
94
|
decodeApplicationPayload,
|
|
95
|
+
decodeApplicationPayloadView,
|
|
93
96
|
encodeApplicationPayload,
|
|
94
97
|
type ServiceApplicationPayload,
|
|
95
98
|
ServiceWireProtocolError
|
|
@@ -98,6 +101,7 @@ import type { ServiceInstanceActivationRecoveryEnvelope } from './service-instan
|
|
|
98
101
|
import { validateServiceMetadataFrame } from './service-metadata-codec';
|
|
99
102
|
import type { ServiceSessionBindingIngressPort } from './service-session-binding-ingress-port';
|
|
100
103
|
import { ZLinkFrameworkException } from '../../contracts';
|
|
104
|
+
import { ZLinkFrameworkRuntimeState } from '../../contracts/Locations';
|
|
101
105
|
|
|
102
106
|
const ACTOR_ROUTE_STALE = 21;
|
|
103
107
|
const SPOT_MOVING = 34;
|
|
@@ -110,12 +114,6 @@ export interface ServiceSpotMessageFollowSeal {
|
|
|
110
114
|
readonly serial: bigint;
|
|
111
115
|
}
|
|
112
116
|
|
|
113
|
-
export interface ServiceBoundSessionSendFence {
|
|
114
|
-
readonly targetNodeGeneration: bigint;
|
|
115
|
-
readonly authorityOwnerGeneration: bigint;
|
|
116
|
-
readonly ownerLeaseGeneration: bigint;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
117
|
interface ServiceSpotMessageFollowRecord {
|
|
120
118
|
readonly ingress: RawServiceIngressRecord;
|
|
121
119
|
readonly wire: Extract<ServiceStatefulWireRecord, { readonly kind: 'spotSend' | 'spotRequest' }>;
|
|
@@ -408,6 +406,11 @@ export class ServiceStatefulRuntime {
|
|
|
408
406
|
readonly owner: string;
|
|
409
407
|
}) => void;
|
|
410
408
|
private dispatchErrors?: ZLinkDispatchErrorReporter;
|
|
409
|
+
private spotAdmissionProvider?: {
|
|
410
|
+
readonly isClosing: (spotId: string) => boolean;
|
|
411
|
+
readonly runtimeState: () => ZLinkFrameworkRuntimeState;
|
|
412
|
+
readonly awaitCloseDecision?: (spotId: string) => Promise<void> | undefined;
|
|
413
|
+
};
|
|
411
414
|
private dispatchErrorMeshName?: string;
|
|
412
415
|
private readonly admittedUserSpotOperations = new Map<
|
|
413
416
|
string,
|
|
@@ -437,6 +440,14 @@ export class ServiceStatefulRuntime {
|
|
|
437
440
|
raw.setServiceIngress((record) => this.ingress(record));
|
|
438
441
|
}
|
|
439
442
|
|
|
443
|
+
setSpotAdmissionProvider(provider: {
|
|
444
|
+
readonly isClosing: (spotId: string) => boolean;
|
|
445
|
+
readonly runtimeState: () => ZLinkFrameworkRuntimeState;
|
|
446
|
+
readonly awaitCloseDecision?: (spotId: string) => Promise<void> | undefined;
|
|
447
|
+
}): void {
|
|
448
|
+
this.spotAdmissionProvider = provider;
|
|
449
|
+
}
|
|
450
|
+
|
|
440
451
|
setMailboxDropHandler(
|
|
441
452
|
handler: (record: {
|
|
442
453
|
readonly kind: 'spot_multicast' | 'actor_control' | 'actor_binding';
|
|
@@ -1296,7 +1307,10 @@ export class ServiceStatefulRuntime {
|
|
|
1296
1307
|
if (target === undefined) {
|
|
1297
1308
|
return SubmitResult.NotFound;
|
|
1298
1309
|
}
|
|
1299
|
-
const header = encodeSpotHeader('spotSend', sourceSpotId, target
|
|
1310
|
+
const header = encodeSpotHeader('spotSend', sourceSpotId, target, undefined, {
|
|
1311
|
+
high: this.nodeGeneration,
|
|
1312
|
+
low: this.nextMessageFollowOperation++
|
|
1313
|
+
});
|
|
1300
1314
|
return this.submitOneWay(target.targetNodeRid, [header, encodeApplicationPayload(payload)]);
|
|
1301
1315
|
}
|
|
1302
1316
|
|
|
@@ -1315,7 +1329,10 @@ export class ServiceStatefulRuntime {
|
|
|
1315
1329
|
});
|
|
1316
1330
|
return pending;
|
|
1317
1331
|
}
|
|
1318
|
-
const header = encodeSpotHeader('spotRequest', sourceSpotId, target, pending.id
|
|
1332
|
+
const header = encodeSpotHeader('spotRequest', sourceSpotId, target, pending.id, {
|
|
1333
|
+
high: this.nodeGeneration,
|
|
1334
|
+
low: this.nextMessageFollowOperation++
|
|
1335
|
+
});
|
|
1319
1336
|
this.submitRequest(
|
|
1320
1337
|
pending,
|
|
1321
1338
|
target.targetNodeRid,
|
|
@@ -1346,7 +1363,8 @@ export class ServiceStatefulRuntime {
|
|
|
1346
1363
|
: {
|
|
1347
1364
|
...boundSession,
|
|
1348
1365
|
sequence: this.nextSessionSequence++
|
|
1349
|
-
}
|
|
1366
|
+
},
|
|
1367
|
+
{ high: this.nodeGeneration, low: this.nextMessageFollowOperation++ }
|
|
1350
1368
|
);
|
|
1351
1369
|
return this.submitOneWay(target.nodeRid, [header, encodeApplicationPayload(payload)]);
|
|
1352
1370
|
}
|
|
@@ -1379,7 +1397,8 @@ export class ServiceStatefulRuntime {
|
|
|
1379
1397
|
: {
|
|
1380
1398
|
...boundSession,
|
|
1381
1399
|
sequence: this.nextSessionSequence++
|
|
1382
|
-
}
|
|
1400
|
+
},
|
|
1401
|
+
{ high: this.nodeGeneration, low: this.nextMessageFollowOperation++ }
|
|
1383
1402
|
);
|
|
1384
1403
|
this.submitRequest(
|
|
1385
1404
|
pending,
|
|
@@ -1935,16 +1954,19 @@ export class ServiceStatefulRuntime {
|
|
|
1935
1954
|
async sendBoundSession(
|
|
1936
1955
|
actor: ServiceActorRef,
|
|
1937
1956
|
expectedBindingGeneration: bigint,
|
|
1938
|
-
payload: ServiceApplicationPayload
|
|
1939
|
-
senderFence?: ServiceBoundSessionSendFence
|
|
1957
|
+
payload: ServiceApplicationPayload
|
|
1940
1958
|
): Promise<number> {
|
|
1959
|
+
// Session–Actor binding §3 item 3: the source sends by binding identity to
|
|
1960
|
+
// the registered route. The header's owner lifecycle fields are this node's
|
|
1961
|
+
// current route values, not send conditions; the Session owner alone
|
|
1962
|
+
// decides whether the binding is current.
|
|
1941
1963
|
let binding: ServiceSessionBinding;
|
|
1942
1964
|
try {
|
|
1943
1965
|
binding = this.registry.validateBoundSession(actor, expectedBindingGeneration);
|
|
1944
1966
|
} catch {
|
|
1945
1967
|
return SubmitResult.InvalidState;
|
|
1946
1968
|
}
|
|
1947
|
-
const actorFence = this.
|
|
1969
|
+
const actorFence = this.tryActorFence(actor);
|
|
1948
1970
|
if (actorFence === undefined) return SubmitResult.InvalidState;
|
|
1949
1971
|
const header = encodeBoundSessionSendHeader(actorFence, expectedBindingGeneration);
|
|
1950
1972
|
return this.submitOneWay(binding.sessionOwnerNodeRid, [
|
|
@@ -2026,7 +2048,12 @@ export class ServiceStatefulRuntime {
|
|
|
2026
2048
|
}
|
|
2027
2049
|
decoded = statefulActorJoinFromCanonical(canonicalActorJoin);
|
|
2028
2050
|
} else {
|
|
2029
|
-
|
|
2051
|
+
const metadata = (record.flags & M6bServiceWireFlag.metadata) !== 0;
|
|
2052
|
+
decoded = decodeStatefulHeader(
|
|
2053
|
+
record.parts[0]!,
|
|
2054
|
+
record.parts[metadata ? 2 : 1],
|
|
2055
|
+
metadata ? record.parts[1] : undefined
|
|
2056
|
+
);
|
|
2030
2057
|
}
|
|
2031
2058
|
const hasPayload = [
|
|
2032
2059
|
'spotSend',
|
|
@@ -2039,6 +2066,12 @@ export class ServiceStatefulRuntime {
|
|
|
2039
2066
|
].includes(decoded.kind);
|
|
2040
2067
|
const instanceMetadata =
|
|
2041
2068
|
decoded.kind === 'instanceSpot' && (record.flags & M6bServiceWireFlag.metadata) !== 0;
|
|
2069
|
+
const statefulMetadata =
|
|
2070
|
+
(decoded.kind === 'spotSend' ||
|
|
2071
|
+
decoded.kind === 'spotRequest' ||
|
|
2072
|
+
decoded.kind === 'actorSend' ||
|
|
2073
|
+
decoded.kind === 'actorRequest') &&
|
|
2074
|
+
(record.flags & M6bServiceWireFlag.metadata) !== 0;
|
|
2042
2075
|
const userSpotInfrastructure =
|
|
2043
2076
|
decoded.kind === 'userSpotCreate' ||
|
|
2044
2077
|
decoded.kind === 'userSpotClose' ||
|
|
@@ -2047,16 +2080,19 @@ export class ServiceStatefulRuntime {
|
|
|
2047
2080
|
decoded.kind === 'boundSessionReplaced';
|
|
2048
2081
|
if (
|
|
2049
2082
|
record.parts.length < 1 ||
|
|
2050
|
-
record.parts.length > (instanceMetadata ? 3 : 2) ||
|
|
2051
|
-
(hasPayload &&
|
|
2083
|
+
record.parts.length > (instanceMetadata || statefulMetadata ? 3 : 2) ||
|
|
2084
|
+
(hasPayload &&
|
|
2085
|
+
decoded.kind !== 'actorJoin' &&
|
|
2086
|
+
record.parts.length !== ((record.flags & M6bServiceWireFlag.metadata) !== 0 ? 3 : 2)) ||
|
|
2052
2087
|
(decoded.kind === 'instanceSpot' && record.parts.length !== (instanceMetadata ? 3 : 2)) ||
|
|
2053
2088
|
(userSpotInfrastructure && record.parts.length !== 1)
|
|
2054
2089
|
) {
|
|
2055
2090
|
return 'protocolError';
|
|
2056
2091
|
}
|
|
2057
|
-
const metadataFrame =
|
|
2058
|
-
|
|
2059
|
-
|
|
2092
|
+
const metadataFrame =
|
|
2093
|
+
instanceMetadata || statefulMetadata
|
|
2094
|
+
? validateServiceMetadataFrame(record.parts[1]!)
|
|
2095
|
+
: undefined;
|
|
2060
2096
|
const canonicalPayload =
|
|
2061
2097
|
canonicalActorJoin?.payload === undefined
|
|
2062
2098
|
? undefined
|
|
@@ -2162,6 +2198,8 @@ export class ServiceStatefulRuntime {
|
|
|
2162
2198
|
}
|
|
2163
2199
|
case 'spotSend':
|
|
2164
2200
|
case 'spotRequest': {
|
|
2201
|
+
await this.spotAdmissionProvider?.awaitCloseDecision?.(record.target.spot.spotId);
|
|
2202
|
+
this.requireDirectSpotAdmission(record.target.spot.spotId);
|
|
2165
2203
|
const followed = this.holdOrRelaySpotMessage(ingress, record);
|
|
2166
2204
|
if (followed !== undefined) return followed;
|
|
2167
2205
|
this.validateDirectSpotFence(record.target);
|
|
@@ -3627,15 +3665,18 @@ export class ServiceStatefulRuntime {
|
|
|
3627
3665
|
record: Extract<ServiceStatefulWireRecord, { readonly kind: 'boundSessionSend' }>,
|
|
3628
3666
|
payloadFrame: Uint8Array | undefined
|
|
3629
3667
|
): Promise<RawServicePumpResult> {
|
|
3668
|
+
// Session–Actor binding §3 item 3, §8.1: this Session owner's binding
|
|
3669
|
+
// record alone decides whether command 36 names the current binding
|
|
3670
|
+
// (ActorId, ObjectGeneration, binding generation; SessionRid is the
|
|
3671
|
+
// record's own). The transport boundary has already authenticated the
|
|
3672
|
+
// source peer, and the header's route fields are not compared here.
|
|
3673
|
+
if (payloadFrame === undefined) return 'protocolError';
|
|
3630
3674
|
const delivery = this.sessionDeliveries.get(actorKey(record.actor.actor));
|
|
3631
3675
|
if (
|
|
3632
3676
|
delivery === undefined ||
|
|
3633
|
-
delivery.binding.bindingGeneration !== record.expectedBindingGeneration
|
|
3634
|
-
payloadFrame === undefined ||
|
|
3635
|
-
ingress.sourceNodeGeneration === undefined ||
|
|
3636
|
-
!routingIdsEqual(ingress.sourceRoutingId, record.actor.actor.nodeRid) ||
|
|
3637
|
-
ingress.sourceNodeGeneration !== record.actor.targetNodeGeneration
|
|
3677
|
+
delivery.binding.bindingGeneration !== record.expectedBindingGeneration
|
|
3638
3678
|
) {
|
|
3679
|
+
this.traceRejectedBoundSessionSend(ingress, record, payloadFrame);
|
|
3639
3680
|
return 'protocolError';
|
|
3640
3681
|
}
|
|
3641
3682
|
const sessionRid = delivery.binding.sessionRid;
|
|
@@ -3659,18 +3700,7 @@ export class ServiceStatefulRuntime {
|
|
|
3659
3700
|
}
|
|
3660
3701
|
};
|
|
3661
3702
|
const decision = await delivery.bindingIngress?.retainOutbound(
|
|
3662
|
-
|
|
3663
|
-
actorId: record.actor.actor.actorId,
|
|
3664
|
-
objectGeneration: record.actor.actor.generation,
|
|
3665
|
-
actorNodeRid: record.actor.actor.nodeRid,
|
|
3666
|
-
actorNodeGeneration: record.actor.targetNodeGeneration,
|
|
3667
|
-
authorityOwnerGeneration: record.actor.authorityOwnerGeneration,
|
|
3668
|
-
ownerLeaseGeneration: record.actor.ownerLeaseGeneration,
|
|
3669
|
-
producerNodeRid: ingress.sourceRoutingId,
|
|
3670
|
-
producerNodeGeneration: ingress.sourceNodeGeneration,
|
|
3671
|
-
sessionIdentity: sessionRid,
|
|
3672
|
-
bindingGeneration: record.expectedBindingGeneration
|
|
3673
|
-
},
|
|
3703
|
+
record.actor.actor.actorId,
|
|
3674
3704
|
operation
|
|
3675
3705
|
);
|
|
3676
3706
|
if (decision === 'retained') return 'application';
|
|
@@ -3698,6 +3728,37 @@ export class ServiceStatefulRuntime {
|
|
|
3698
3728
|
return 'application';
|
|
3699
3729
|
}
|
|
3700
3730
|
|
|
3731
|
+
// Session–Actor binding §3 item 4: a push refused as not current is
|
|
3732
|
+
// recorded with the closed flow vocabulary (dropped, stream_session, send,
|
|
3733
|
+
// stale_target) instead of disappearing. Nothing is built while tracing is
|
|
3734
|
+
// off.
|
|
3735
|
+
private traceRejectedBoundSessionSend(
|
|
3736
|
+
ingress: RawServiceIngressRecord,
|
|
3737
|
+
record: Extract<ServiceStatefulWireRecord, { readonly kind: 'boundSessionSend' }>,
|
|
3738
|
+
payloadFrame: Uint8Array
|
|
3739
|
+
): void {
|
|
3740
|
+
const tracePoint = flowIfEnabled(this.dispatchErrors?.flow, ZLinkMessageFlowOutcome.Dropped);
|
|
3741
|
+
if (tracePoint === undefined) return;
|
|
3742
|
+
let packetName: string | undefined;
|
|
3743
|
+
try {
|
|
3744
|
+
packetName = decodeApplicationPayloadView(payloadFrame).packetName;
|
|
3745
|
+
} catch (error) {
|
|
3746
|
+
// A malformed payload is still a refused push; it is traced without a
|
|
3747
|
+
// packet name.
|
|
3748
|
+
if (!(error instanceof ServiceWireProtocolError)) throw error;
|
|
3749
|
+
}
|
|
3750
|
+
tracePoint.trace({
|
|
3751
|
+
outcome: ZLinkMessageFlowOutcome.Dropped,
|
|
3752
|
+
surface: ZLinkDispatchErrorSurface.StreamSession,
|
|
3753
|
+
messageKind: ZLinkDispatchMessageKind.Send,
|
|
3754
|
+
...(packetName === undefined ? {} : { packetName }),
|
|
3755
|
+
...(this.dispatchErrorMeshName === undefined ? {} : { meshName: this.dispatchErrorMeshName }),
|
|
3756
|
+
sourceRid: ingress.sourceRoutingId,
|
|
3757
|
+
actorId: record.actor.actor.actorId,
|
|
3758
|
+
errorReason: ZLinkDispatchErrorReason.StaleTarget
|
|
3759
|
+
});
|
|
3760
|
+
}
|
|
3761
|
+
|
|
3701
3762
|
private enqueueApplicationFrame(
|
|
3702
3763
|
ingress: RawServiceIngressRecord,
|
|
3703
3764
|
owner: string,
|
|
@@ -4311,7 +4372,13 @@ export class ServiceStatefulRuntime {
|
|
|
4311
4372
|
applicationJobOwner
|
|
4312
4373
|
};
|
|
4313
4374
|
try {
|
|
4314
|
-
this.submitLocalRequest(
|
|
4375
|
+
await this.submitLocalRequest(
|
|
4376
|
+
localIngress,
|
|
4377
|
+
pending,
|
|
4378
|
+
operationKind,
|
|
4379
|
+
actor,
|
|
4380
|
+
deadlineUnixMs
|
|
4381
|
+
);
|
|
4315
4382
|
} catch (error) {
|
|
4316
4383
|
this.operations.reply(pending.id, failure(error));
|
|
4317
4384
|
} finally {
|
|
@@ -4412,7 +4479,7 @@ export class ServiceStatefulRuntime {
|
|
|
4412
4479
|
);
|
|
4413
4480
|
}
|
|
4414
4481
|
|
|
4415
|
-
private submitLocalRequest(
|
|
4482
|
+
private async submitLocalRequest(
|
|
4416
4483
|
ingress: RawServiceIngressRecord,
|
|
4417
4484
|
pending: ServiceStatefulPendingOperation,
|
|
4418
4485
|
operationKind:
|
|
@@ -4426,7 +4493,7 @@ export class ServiceStatefulRuntime {
|
|
|
4426
4493
|
| 'instanceSpotRequest',
|
|
4427
4494
|
actor?: ServiceActorRef,
|
|
4428
4495
|
deadlineUnixMs?: bigint
|
|
4429
|
-
): void {
|
|
4496
|
+
): Promise<void> {
|
|
4430
4497
|
const canonicalActorJoin =
|
|
4431
4498
|
operationKind === 'actorJoin' ? decodeActorJoin28(ingress.parts) : undefined;
|
|
4432
4499
|
const decoded =
|
|
@@ -4477,6 +4544,7 @@ export class ServiceStatefulRuntime {
|
|
|
4477
4544
|
this.resultFromReply(terminalResult, failureCode, replyPayload, tail, this.nodeRid, actor)
|
|
4478
4545
|
);
|
|
4479
4546
|
if (decoded.kind === 'spotRequest') {
|
|
4547
|
+
await this.spotAdmissionProvider?.awaitCloseDecision?.(decoded.target.spot.spotId);
|
|
4480
4548
|
this.validateDirectSpotFence(decoded.target);
|
|
4481
4549
|
this.enqueueApplicationFrame(ingress, `spot:${decoded.target.spot.spotId}`, payloadFrame!, {
|
|
4482
4550
|
receiveKind: ReceiveKind.SpotRequest,
|
|
@@ -4502,7 +4570,9 @@ export class ServiceStatefulRuntime {
|
|
|
4502
4570
|
operationKind: OperationKind.ActorRequest,
|
|
4503
4571
|
correlation: decoded.correlation,
|
|
4504
4572
|
targetActor: current.ref,
|
|
4505
|
-
...(decoded.sourceActor === undefined
|
|
4573
|
+
...(decoded.sourceActor === undefined
|
|
4574
|
+
? {}
|
|
4575
|
+
: { sourceActor: { ...decoded.sourceActor, nodeRid: ingress.sourceRoutingId } }),
|
|
4506
4576
|
...(decoded.boundSession === undefined
|
|
4507
4577
|
? {}
|
|
4508
4578
|
: { sourceBindingGeneration: decoded.boundSession.bindingGeneration }),
|
|
@@ -4801,7 +4871,17 @@ export class ServiceStatefulRuntime {
|
|
|
4801
4871
|
return spot;
|
|
4802
4872
|
}
|
|
4803
4873
|
|
|
4804
|
-
private validateDirectSpotFence(fence:
|
|
4874
|
+
private validateDirectSpotFence(fence: ServiceSpotRouteFence): ServiceSpotState {
|
|
4875
|
+
if (
|
|
4876
|
+
fence.targetNodeRid !== this.nodeRid ||
|
|
4877
|
+
fence.targetNodeGeneration !== this.nodeGeneration
|
|
4878
|
+
) {
|
|
4879
|
+
throw createInternalFrameworkException(
|
|
4880
|
+
ZLinkFrameworkInternalErrorKind.SpotMoving,
|
|
4881
|
+
`Spot '${fence.spot.spotId}' targets an unavailable owner route.`
|
|
4882
|
+
);
|
|
4883
|
+
}
|
|
4884
|
+
this.requireDirectSpotAdmission(fence.spot.spotId);
|
|
4805
4885
|
const spot = this.registry.spot(fence.spot.spotId);
|
|
4806
4886
|
if (spot === undefined) {
|
|
4807
4887
|
throw createInternalFrameworkException(
|
|
@@ -4815,15 +4895,6 @@ export class ServiceStatefulRuntime {
|
|
|
4815
4895
|
`Spot '${fence.spot.spotId}' is not Ready.`
|
|
4816
4896
|
);
|
|
4817
4897
|
}
|
|
4818
|
-
if (
|
|
4819
|
-
fence.targetNodeRid !== this.nodeRid ||
|
|
4820
|
-
fence.targetNodeGeneration !== this.nodeGeneration
|
|
4821
|
-
) {
|
|
4822
|
-
throw createInternalFrameworkException(
|
|
4823
|
-
ZLinkFrameworkInternalErrorKind.SpotMoving,
|
|
4824
|
-
`Spot '${fence.spot.spotId}' targets an unavailable owner route.`
|
|
4825
|
-
);
|
|
4826
|
-
}
|
|
4827
4898
|
if (spot.kind === 'entry') {
|
|
4828
4899
|
if (!isEntrySpotFence(fence)) {
|
|
4829
4900
|
throw createInternalFrameworkException(
|
|
@@ -4855,12 +4926,27 @@ export class ServiceStatefulRuntime {
|
|
|
4855
4926
|
return spot;
|
|
4856
4927
|
}
|
|
4857
4928
|
|
|
4929
|
+
private requireDirectSpotAdmission(spotId: string): void {
|
|
4930
|
+
if (this.spotAdmissionProvider?.runtimeState() === ZLinkFrameworkRuntimeState.Draining) {
|
|
4931
|
+
throw createInternalFrameworkException(
|
|
4932
|
+
ZLinkFrameworkInternalErrorKind.RuntimeShutdown,
|
|
4933
|
+
`Spot '${spotId}' owner is Draining.`
|
|
4934
|
+
);
|
|
4935
|
+
}
|
|
4936
|
+
if (this.spotAdmissionProvider?.isClosing(spotId) === true) {
|
|
4937
|
+
throw createInternalFrameworkException(
|
|
4938
|
+
ZLinkFrameworkInternalErrorKind.RequestRejected,
|
|
4939
|
+
`Spot '${spotId}' authority is Closing.`
|
|
4940
|
+
);
|
|
4941
|
+
}
|
|
4942
|
+
}
|
|
4943
|
+
|
|
4858
4944
|
private holdOrRelaySpotMessage(
|
|
4859
4945
|
ingress: RawServiceIngressRecord,
|
|
4860
4946
|
wire: Extract<ServiceStatefulWireRecord, { readonly kind: 'spotSend' | 'spotRequest' }>
|
|
4861
4947
|
): RawServicePumpResult | undefined {
|
|
4862
4948
|
const state = this.spotMessageFollow.get(spotKey(wire.target.spot));
|
|
4863
|
-
if (state === undefined || !
|
|
4949
|
+
if (state === undefined || !sameDirectSpotRouteIdentity(state.source, wire.target)) {
|
|
4864
4950
|
return undefined;
|
|
4865
4951
|
}
|
|
4866
4952
|
if (state.expiresAtMs !== undefined && state.expiresAtMs <= performance.now()) {
|
|
@@ -4912,14 +4998,19 @@ export class ServiceStatefulRuntime {
|
|
|
4912
4998
|
}
|
|
4913
4999
|
const current = this.peekSpotMessageFollow(state)!;
|
|
4914
5000
|
try {
|
|
5001
|
+
const metadata = (current.ingress.parts[0]![4]! & M6bServiceWireFlag.metadata) !== 0;
|
|
4915
5002
|
const parts = [
|
|
4916
5003
|
encodeSpotHeader(
|
|
4917
5004
|
current.wire.kind,
|
|
4918
5005
|
current.wire.sourceSpotId,
|
|
4919
5006
|
state.target,
|
|
4920
|
-
current.wire.correlation
|
|
5007
|
+
current.wire.correlation,
|
|
5008
|
+
current.wire.operation,
|
|
5009
|
+
current.wire.messageFollowHopCount + 1,
|
|
5010
|
+
metadata ? current.ingress.parts[1] : undefined
|
|
4921
5011
|
),
|
|
4922
|
-
current.ingress.parts[1]!
|
|
5012
|
+
...(metadata ? [current.ingress.parts[1]!] : []),
|
|
5013
|
+
current.ingress.parts[metadata ? 2 : 1]!
|
|
4923
5014
|
];
|
|
4924
5015
|
if (current.wire.kind === 'spotSend') {
|
|
4925
5016
|
if (!(await this.raw.sendService(state.target.targetNodeRid, parts))) continue;
|
|
@@ -5058,34 +5149,6 @@ export class ServiceStatefulRuntime {
|
|
|
5058
5149
|
return route;
|
|
5059
5150
|
}
|
|
5060
5151
|
|
|
5061
|
-
private tryBoundSessionSendFence(
|
|
5062
|
-
actor: ServiceActorRef,
|
|
5063
|
-
senderFence: ServiceBoundSessionSendFence | undefined
|
|
5064
|
-
): ServiceActorRouteFence | undefined {
|
|
5065
|
-
if (
|
|
5066
|
-
senderFence === undefined ||
|
|
5067
|
-
senderFence.targetNodeGeneration !== this.nodeGeneration ||
|
|
5068
|
-
senderFence.authorityOwnerGeneration <= 0n ||
|
|
5069
|
-
senderFence.ownerLeaseGeneration <= 0n
|
|
5070
|
-
) {
|
|
5071
|
-
return undefined;
|
|
5072
|
-
}
|
|
5073
|
-
const local = actor.nodeRid === this.nodeRid ? this.registry.actor(actor.actorId) : undefined;
|
|
5074
|
-
if (
|
|
5075
|
-
local === undefined ||
|
|
5076
|
-
!sameActorRef(local.ref, actor) ||
|
|
5077
|
-
local.authorityOwnerGeneration !== senderFence.authorityOwnerGeneration
|
|
5078
|
-
) {
|
|
5079
|
-
return undefined;
|
|
5080
|
-
}
|
|
5081
|
-
return {
|
|
5082
|
-
actor,
|
|
5083
|
-
targetNodeGeneration: senderFence.targetNodeGeneration,
|
|
5084
|
-
authorityOwnerGeneration: senderFence.authorityOwnerGeneration,
|
|
5085
|
-
ownerLeaseGeneration: senderFence.ownerLeaseGeneration
|
|
5086
|
-
};
|
|
5087
|
-
}
|
|
5088
|
-
|
|
5089
5152
|
private tryActorFence(actor: ServiceActorRef): ServiceActorRouteFence | undefined {
|
|
5090
5153
|
const local = actor.nodeRid === this.nodeRid ? this.registry.actor(actor.actorId) : undefined;
|
|
5091
5154
|
if (local !== undefined && sameActorRef(local.ref, actor)) {
|
|
@@ -5317,13 +5380,12 @@ function serviceSpotPublishFailureReason(
|
|
|
5317
5380
|
}
|
|
5318
5381
|
}
|
|
5319
5382
|
|
|
5320
|
-
function isEntrySpotFence(fence:
|
|
5383
|
+
function isEntrySpotFence(fence: ServiceSpotRouteFence): boolean {
|
|
5321
5384
|
return (
|
|
5322
5385
|
fence.spot.spotId === fence.targetNodeRid &&
|
|
5323
5386
|
fence.spot.generation === fence.targetNodeGeneration &&
|
|
5324
5387
|
fence.authorityOwnerGeneration === fence.targetNodeGeneration &&
|
|
5325
|
-
fence.ownerLeaseGeneration === fence.targetNodeGeneration
|
|
5326
|
-
fence.storeVersion === `entry:${fence.targetNodeGeneration}`
|
|
5388
|
+
fence.ownerLeaseGeneration === fence.targetNodeGeneration
|
|
5327
5389
|
);
|
|
5328
5390
|
}
|
|
5329
5391
|
|
|
@@ -5744,15 +5806,15 @@ function sameMessageFollowSpotSource(
|
|
|
5744
5806
|
}
|
|
5745
5807
|
|
|
5746
5808
|
function sameDirectSpotRouteIdentity(
|
|
5747
|
-
left:
|
|
5748
|
-
right:
|
|
5809
|
+
left: ServiceSpotRouteFence,
|
|
5810
|
+
right: ServiceSpotRouteFence
|
|
5749
5811
|
): boolean {
|
|
5750
5812
|
return sameDirectSpotOwnerIdentity(left, right) && left.spot.generation === right.spot.generation;
|
|
5751
5813
|
}
|
|
5752
5814
|
|
|
5753
5815
|
function sameDirectSpotOwnerIdentity(
|
|
5754
|
-
left:
|
|
5755
|
-
right:
|
|
5816
|
+
left: ServiceSpotRouteFence,
|
|
5817
|
+
right: ServiceSpotRouteFence
|
|
5756
5818
|
): boolean {
|
|
5757
5819
|
return (
|
|
5758
5820
|
left.spot.spotId === right.spot.spotId &&
|