@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
|
@@ -181,10 +181,6 @@ class ZLinkHostServiceRelocationRuntime {
|
|
|
181
181
|
this.targetReadyFailures.clear();
|
|
182
182
|
this.targetReadyResponses.clear();
|
|
183
183
|
for (const window of this.sourceCutoverWindows.values()) {
|
|
184
|
-
if (window.windowTimer !== undefined)
|
|
185
|
-
clearTimeout(window.windowTimer);
|
|
186
|
-
if (window.retryTimer !== undefined)
|
|
187
|
-
clearTimeout(window.retryTimer);
|
|
188
184
|
if (window.followTimer !== undefined)
|
|
189
185
|
clearTimeout(window.followTimer);
|
|
190
186
|
}
|
|
@@ -302,19 +298,44 @@ class ZLinkHostServiceRelocationRuntime {
|
|
|
302
298
|
publishState: () => undefined,
|
|
303
299
|
forceStop: () => undefined
|
|
304
300
|
});
|
|
301
|
+
const started = [];
|
|
305
302
|
for (const unit of work) {
|
|
306
303
|
maintenance.enqueue({
|
|
307
304
|
id: unit.id,
|
|
308
305
|
ready: () => true,
|
|
309
|
-
relocate:
|
|
306
|
+
relocate: (relocationSignal) => {
|
|
307
|
+
const run = unit.relocate(relocationSignal);
|
|
308
|
+
started.push(run);
|
|
309
|
+
return run;
|
|
310
|
+
}
|
|
310
311
|
});
|
|
311
312
|
}
|
|
312
313
|
const operationSignal = signal ?? new AbortController().signal;
|
|
313
314
|
const deadlineMs = signal === undefined ? 30_000 : 24 * 60 * 60 * 1000;
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
315
|
+
let failure;
|
|
316
|
+
try {
|
|
317
|
+
const result = await raceAbort(maintenance.start('retire', deadlineMs, stopStartingSignal, operationSignal), operationSignal);
|
|
318
|
+
if (result.state === 'completed')
|
|
319
|
+
return;
|
|
320
|
+
failure = result.terminalError ?? new Error(`Host relocation ended in '${result.state}'.`);
|
|
317
321
|
}
|
|
322
|
+
catch (error) {
|
|
323
|
+
failure = error;
|
|
324
|
+
}
|
|
325
|
+
// The host result follows the settled authority of every started unit
|
|
326
|
+
// (spec 30 §13): a unit completes only after its target commit is
|
|
327
|
+
// confirmed, so a failure beside a completed unit leaves units on both
|
|
328
|
+
// sides.
|
|
329
|
+
const settled = await Promise.allSettled(started);
|
|
330
|
+
const targetSide = settled.filter((value) => value.status === 'fulfilled' || value.reason instanceof service_relocation_coordinator_1.ServiceRelocationPostCommitError).length;
|
|
331
|
+
const authorityError = settled.find((value) => value.status === 'rejected' && value.reason instanceof service_relocation_coordinator_1.ServiceRelocationAuthorityError);
|
|
332
|
+
if (authorityError !== undefined)
|
|
333
|
+
throw authorityError.reason;
|
|
334
|
+
// A unit that never started also stays on the source.
|
|
335
|
+
if (targetSide > 0 && targetSide < work.length) {
|
|
336
|
+
throw new service_relocation_coordinator_1.ServiceRelocationAuthorityError('Relocation units settled on both the source and the target.', { cause: failure });
|
|
337
|
+
}
|
|
338
|
+
throw failure;
|
|
318
339
|
}
|
|
319
340
|
async tryHandleControl(meshName, record, signal) {
|
|
320
341
|
if (record.parts.length === 0)
|
|
@@ -827,6 +848,9 @@ class ZLinkHostServiceRelocationRuntime {
|
|
|
827
848
|
}
|
|
828
849
|
}
|
|
829
850
|
async relocateSpotAggregate(meshName, activation, kind, actorStates, target, targetApplicationVersion, signal) {
|
|
851
|
+
await activation.serial.executeLifecycleOperation(() => this.relocateSpotAggregateCore(meshName, activation, kind, actorStates, target, targetApplicationVersion, signal));
|
|
852
|
+
}
|
|
853
|
+
async relocateSpotAggregateCore(meshName, activation, kind, actorStates, target, targetApplicationVersion, signal) {
|
|
830
854
|
const store = this.requireLocationStore();
|
|
831
855
|
const spotKey = (0, authority_key_codec_1.encodeAuthorityKey)(kind, String(activation.spotId));
|
|
832
856
|
const spotAuthority = await requireAuthority(store, spotKey, signal);
|
|
@@ -866,12 +890,12 @@ class ZLinkHostServiceRelocationRuntime {
|
|
|
866
890
|
objectGeneration: spotAuthority.objectGeneration,
|
|
867
891
|
authorityOwnerGeneration: spotAuthority.authorityOwnerGeneration,
|
|
868
892
|
seal: async (captureSignal) => {
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
893
|
+
const { spotCaptureOperation } = await this.afterSpotCloseDecision(meshName, activation.spotId, () => {
|
|
894
|
+
interruptionStartedAt = performance.now();
|
|
895
|
+
spotMessageFollowSeal = this.sealSpotMessageFollow(meshName, spotAuthority, activation);
|
|
896
|
+
// Both seals run in the same event-loop turn after the Close decision.
|
|
897
|
+
return { spotCaptureOperation: activation.captureRelocation(captureSignal) };
|
|
898
|
+
});
|
|
875
899
|
const preparedSessions = Promise.all(actorStates.map(async (state) => ({
|
|
876
900
|
state,
|
|
877
901
|
actor: state.actor,
|
|
@@ -986,7 +1010,9 @@ class ZLinkHostServiceRelocationRuntime {
|
|
|
986
1010
|
}
|
|
987
1011
|
catch (error) {
|
|
988
1012
|
outcome = 'failed';
|
|
989
|
-
if (readinessBoundaryConsumed &&
|
|
1013
|
+
if (readinessBoundaryConsumed &&
|
|
1014
|
+
!(error instanceof service_relocation_coordinator_1.ServiceRelocationPostCommitError) &&
|
|
1015
|
+
!(error instanceof service_relocation_coordinator_1.ServiceRelocationAuthorityError)) {
|
|
990
1016
|
await activation
|
|
991
1017
|
.completeConsumedRelocationBoundary(contracts_1.ZLinkSpotRelocationReadyOutcome.Continued)
|
|
992
1018
|
.catch(() => undefined);
|
|
@@ -1058,6 +1084,9 @@ class ZLinkHostServiceRelocationRuntime {
|
|
|
1058
1084
|
}
|
|
1059
1085
|
}
|
|
1060
1086
|
async relocatePerActorSpotShell(meshName, activation, target, targetApplicationVersion, signal) {
|
|
1087
|
+
await activation.serial.executeLifecycleOperation(() => this.relocatePerActorSpotShellCore(meshName, activation, target, targetApplicationVersion, signal));
|
|
1088
|
+
}
|
|
1089
|
+
async relocatePerActorSpotShellCore(meshName, activation, target, targetApplicationVersion, signal) {
|
|
1061
1090
|
const spotKey = (0, authority_key_codec_1.encodeAuthorityKey)('user_spot', String(activation.spotId));
|
|
1062
1091
|
const spotAuthority = await requireAuthority(this.requireLocationStore(), spotKey, signal);
|
|
1063
1092
|
const spotRegistration = this.spotRegistration(meshName, 'user_spot', spotAuthority.allocation.stableType);
|
|
@@ -1080,9 +1109,12 @@ class ZLinkHostServiceRelocationRuntime {
|
|
|
1080
1109
|
objectGeneration: spotAuthority.objectGeneration,
|
|
1081
1110
|
authorityOwnerGeneration: spotAuthority.authorityOwnerGeneration,
|
|
1082
1111
|
seal: async (captureSignal) => {
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1112
|
+
const { spotCaptureOperation } = await this.afterSpotCloseDecision(meshName, activation.spotId, () => {
|
|
1113
|
+
interruptionStartedAt = performance.now();
|
|
1114
|
+
spotMessageFollowSeal = this.sealSpotMessageFollow(meshName, spotAuthority, activation);
|
|
1115
|
+
return { spotCaptureOperation: activation.captureRelocation(captureSignal) };
|
|
1116
|
+
});
|
|
1117
|
+
spotCapture = await spotCaptureOperation;
|
|
1086
1118
|
return {
|
|
1087
1119
|
boundSessionState: Buffer.alloc(0),
|
|
1088
1120
|
queuedMessages: [],
|
|
@@ -1191,7 +1223,10 @@ class ZLinkHostServiceRelocationRuntime {
|
|
|
1191
1223
|
abortSeal: async () => {
|
|
1192
1224
|
if (standalone && ownSession !== undefined)
|
|
1193
1225
|
await ownSession.prepared.rollback();
|
|
1194
|
-
}
|
|
1226
|
+
},
|
|
1227
|
+
discardSeal: (reason) => (standalone
|
|
1228
|
+
? ownSession
|
|
1229
|
+
: sessions.find((value) => value.state === state))?.prepared.discard(reason)
|
|
1195
1230
|
};
|
|
1196
1231
|
}
|
|
1197
1232
|
reserveRelocationId() {
|
|
@@ -1264,10 +1299,13 @@ class ZLinkHostServiceRelocationRuntime {
|
|
|
1264
1299
|
// configured limit unchanged (spec 15 §4.2).
|
|
1265
1300
|
const chunkLimitBytes = (0, relocation_direct_transfer_1.effectiveActorJoinChunkLimitBytes)(limits.relocationPayloadChunkLimitBytes, advertisedReceiveChunkLimitBytes);
|
|
1266
1301
|
const plan = (0, relocation_direct_transfer_1.planRelocationChunks)(encoded, chunkLimitBytes);
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1302
|
+
// The Restore absolute deadline is this unit's relocation deadline: the
|
|
1303
|
+
// caller's signal, or the control deadline without one. From it the
|
|
1304
|
+
// source settles the authority with its Preserve fence (Location runtime
|
|
1305
|
+
// §10). The target keeps no deadline of its own.
|
|
1306
|
+
const restoreDeadlineReached = () => signal?.aborted === true || performance.now() >= controlDeadlineAtMs;
|
|
1270
1307
|
let readyReceived = false;
|
|
1308
|
+
let settlement;
|
|
1271
1309
|
let sourceCommitted = false;
|
|
1272
1310
|
try {
|
|
1273
1311
|
const prepare = {
|
|
@@ -1343,9 +1381,9 @@ class ZLinkHostServiceRelocationRuntime {
|
|
|
1343
1381
|
boundaryRecordCount: BigInt(boundaryBatch.length),
|
|
1344
1382
|
boundaryChecksumCrc32c: (0, service_relocation_runtime_1.crc32c)(Buffer.concat(boundaryBatch.map((value) => value.canonicalBytes)))
|
|
1345
1383
|
});
|
|
1346
|
-
// The
|
|
1347
|
-
//
|
|
1348
|
-
//
|
|
1384
|
+
// The cutover submit result is not the authority result. The memory
|
|
1385
|
+
// copy of the boundary batch and cutover stays until the authority
|
|
1386
|
+
// settlement below (spec 28 §4.4).
|
|
1349
1387
|
let submitFailure;
|
|
1350
1388
|
try {
|
|
1351
1389
|
for (const record of boundaryBatch) {
|
|
@@ -1357,11 +1395,30 @@ class ZLinkHostServiceRelocationRuntime {
|
|
|
1357
1395
|
submitFailure = error;
|
|
1358
1396
|
console.warn('[zlink.runtime.relocation.cutover_submit_failed]', relayAuthorityId, error);
|
|
1359
1397
|
}
|
|
1360
|
-
this.beginSourceCutoverWindow(meshName, target.rid, relayAuthorityId, [...boundaryBatch.map((value) => value.frame), cutoverFrame], submitFailure !== undefined, limits);
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1398
|
+
const window = this.beginSourceCutoverWindow(meshName, target.rid, relayAuthorityId, [...boundaryBatch.map((value) => value.frame), cutoverFrame], submitFailure !== undefined, limits);
|
|
1399
|
+
try {
|
|
1400
|
+
settlement = await this.settleSourceAuthority(captured.envelope, primary, targetFence, restoreDeadlineReached, window);
|
|
1401
|
+
}
|
|
1402
|
+
finally {
|
|
1403
|
+
this.closeSourceCutoverWindow(relayAuthorityId, window);
|
|
1404
|
+
}
|
|
1405
|
+
relocationDebug('coordinator.authority_settled', {
|
|
1406
|
+
aggregateId: captured.envelope.aggregateId,
|
|
1407
|
+
settlement
|
|
1364
1408
|
});
|
|
1409
|
+
if (settlement === 'source') {
|
|
1410
|
+
// Preserve won: the captured queue, timers and held records return to
|
|
1411
|
+
// the source queue in their accepted order, and dispatch reopens.
|
|
1412
|
+
await captured.abortSource();
|
|
1413
|
+
throw new Error(`Relocation '${relayAuthorityId}' ended by the source Preserve fence; the source kept the authority.`);
|
|
1414
|
+
}
|
|
1415
|
+
if (settlement === 'lost') {
|
|
1416
|
+
// Expired owner: dispatch and Store changes stop for good and every
|
|
1417
|
+
// pending request without a terminal gets one Unavailable.
|
|
1418
|
+
const unavailable = (0, framework_errors_internal_1.createInternalFrameworkException)(framework_errors_internal_1.ZLinkFrameworkInternalErrorKind.RouteNotConnected, `Relocation '${relayAuthorityId}' source owner lease ended before its authority settled.`, true);
|
|
1419
|
+
await captured.discardSource(unavailable);
|
|
1420
|
+
throw new service_relocation_coordinator_1.ServiceRelocationAuthorityError(unavailable.message, { cause: unavailable });
|
|
1421
|
+
}
|
|
1365
1422
|
for (const session of sessions)
|
|
1366
1423
|
session.prepared.setReplayResults([]);
|
|
1367
1424
|
await captured.commitSource();
|
|
@@ -1369,26 +1426,18 @@ class ZLinkHostServiceRelocationRuntime {
|
|
|
1369
1426
|
await this.options.reconcileStatefulAuthorityRoutes?.(signal);
|
|
1370
1427
|
}
|
|
1371
1428
|
catch (error) {
|
|
1372
|
-
if (
|
|
1429
|
+
if (settlement !== 'target')
|
|
1373
1430
|
throw error;
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
sourceCommitted = true;
|
|
1381
|
-
}
|
|
1382
|
-
await this.options.reconcileStatefulAuthorityRoutes?.();
|
|
1383
|
-
const authority = await requireAuthority(this.requireLocationStore(), { value: primaryKey(captured.envelope) }, undefined);
|
|
1384
|
-
throw new service_relocation_coordinator_1.ServiceRelocationPostCommitError(authority, { id: `${captured.envelope.aggregateId}:${captured.envelope.aggregateGeneration}` }, error);
|
|
1385
|
-
}
|
|
1386
|
-
catch (convergenceError) {
|
|
1387
|
-
if (convergenceError instanceof service_relocation_coordinator_1.ServiceRelocationPostCommitError) {
|
|
1388
|
-
throw convergenceError;
|
|
1389
|
-
}
|
|
1390
|
-
throw new AggregateError([error, convergenceError], 'Relocation became irreversible after Ready and target convergence failed.');
|
|
1431
|
+
// Target commit is confirmed; later source cleanup failures cannot move
|
|
1432
|
+
// the authority back (spec 28 §9).
|
|
1433
|
+
if (!sourceCommitted) {
|
|
1434
|
+
for (const session of sessions)
|
|
1435
|
+
session.prepared.setReplayResults([]);
|
|
1436
|
+
await captured.commitSource().catch(() => undefined);
|
|
1391
1437
|
}
|
|
1438
|
+
await this.options.reconcileStatefulAuthorityRoutes?.().catch(() => undefined);
|
|
1439
|
+
const authority = await requireAuthority(this.requireLocationStore(), { value: primaryKey(captured.envelope) }, undefined);
|
|
1440
|
+
throw new service_relocation_coordinator_1.ServiceRelocationPostCommitError(authority, { id: `${captured.envelope.aggregateId}:${captured.envelope.aggregateGeneration}` }, error);
|
|
1392
1441
|
}
|
|
1393
1442
|
finally {
|
|
1394
1443
|
if (!readyReceived) {
|
|
@@ -1461,12 +1510,11 @@ class ZLinkHostServiceRelocationRuntime {
|
|
|
1461
1510
|
return budget;
|
|
1462
1511
|
}
|
|
1463
1512
|
/**
|
|
1464
|
-
* Starts the source
|
|
1465
|
-
*
|
|
1466
|
-
*
|
|
1467
|
-
*
|
|
1468
|
-
*
|
|
1469
|
-
* route removal point — closes the unit for SafeToShutdown.
|
|
1513
|
+
* Starts the source cutover window after the cutover submit terminal. The
|
|
1514
|
+
* boundary batch and cutover copies live in framework memory (not charged
|
|
1515
|
+
* to the in-flight budget) until the authority settlement closes the
|
|
1516
|
+
* window. S4 — the Message Follow route removal point — closes the unit for
|
|
1517
|
+
* SafeToShutdown.
|
|
1470
1518
|
*/
|
|
1471
1519
|
beginSourceCutoverWindow(meshName, targetNodeRid, relocationKey, frames, initialSubmitFailed, limits) {
|
|
1472
1520
|
const submittedAt = performance.now();
|
|
@@ -1474,54 +1522,134 @@ class ZLinkHostServiceRelocationRuntime {
|
|
|
1474
1522
|
meshName,
|
|
1475
1523
|
targetNodeRid,
|
|
1476
1524
|
frames,
|
|
1525
|
+
resendPending: initialSubmitFailed,
|
|
1477
1526
|
cutoverSubmittedAtMs: submittedAt,
|
|
1478
1527
|
windowClosed: false,
|
|
1479
1528
|
followExpired: false
|
|
1480
1529
|
};
|
|
1481
1530
|
this.sourceCutoverWindows.set(relocationKey, window);
|
|
1482
|
-
const settle = () => {
|
|
1483
|
-
if (window.windowClosed && window.followExpired) {
|
|
1484
|
-
this.sourceCutoverWindows.delete(relocationKey);
|
|
1485
|
-
}
|
|
1486
|
-
};
|
|
1487
|
-
window.windowTimer = setTimeout(() => {
|
|
1488
|
-
window.windowClosed = true;
|
|
1489
|
-
window.frames = undefined;
|
|
1490
|
-
if (window.retryTimer !== undefined)
|
|
1491
|
-
clearTimeout(window.retryTimer);
|
|
1492
|
-
settle();
|
|
1493
|
-
}, limits.relocationCutoverWaitTimeoutMs);
|
|
1494
|
-
window.windowTimer.unref();
|
|
1495
1531
|
window.followTimer = setTimeout(() => {
|
|
1496
1532
|
window.followExpired = true;
|
|
1497
1533
|
// S1→S4 route convergence: the span the source must keep its Message
|
|
1498
1534
|
// Follow route (spec 30 §7.1).
|
|
1499
1535
|
this.options.metrics?.duration('zlink.relocation.route_convergence', Math.max(0, performance.now() - submittedAt) / 1000, { mesh_name: meshName });
|
|
1500
|
-
|
|
1536
|
+
this.releaseSourceCutoverWindow(relocationKey, window);
|
|
1501
1537
|
}, limits.messageFollowDurationMs);
|
|
1502
1538
|
window.followTimer.unref();
|
|
1503
|
-
|
|
1504
|
-
|
|
1539
|
+
return window;
|
|
1540
|
+
}
|
|
1541
|
+
/** The authority settlement ended: the retained batch copy is released. */
|
|
1542
|
+
closeSourceCutoverWindow(relocationKey, window) {
|
|
1543
|
+
window.windowClosed = true;
|
|
1544
|
+
window.frames = undefined;
|
|
1545
|
+
this.releaseSourceCutoverWindow(relocationKey, window);
|
|
1546
|
+
}
|
|
1547
|
+
releaseSourceCutoverWindow(relocationKey, window) {
|
|
1548
|
+
if (window.windowClosed &&
|
|
1549
|
+
window.followExpired &&
|
|
1550
|
+
this.sourceCutoverWindows.get(relocationKey) === window) {
|
|
1551
|
+
this.sourceCutoverWindows.delete(relocationKey);
|
|
1552
|
+
}
|
|
1505
1553
|
}
|
|
1506
|
-
|
|
1507
|
-
|
|
1554
|
+
/**
|
|
1555
|
+
* Sends the whole boundary batch and cutover again after a failed submit
|
|
1556
|
+
* or an observed loss of the target connection (spec 28 §4.4). The target
|
|
1557
|
+
* replaces its partial span with the whole batch.
|
|
1558
|
+
*/
|
|
1559
|
+
async resendSourceCutover(window) {
|
|
1560
|
+
const frames = window.frames;
|
|
1561
|
+
if (frames === undefined)
|
|
1508
1562
|
return;
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
}
|
|
1524
|
-
|
|
1563
|
+
const peers = this.requireMeshNode(window.meshName).peers();
|
|
1564
|
+
const peer = peers.find((value) => value.routingId !== null && String(value.routingId) === String(window.targetNodeRid));
|
|
1565
|
+
// Backend peer state 3 is Ready.
|
|
1566
|
+
if (peer !== undefined && peer.state !== 3) {
|
|
1567
|
+
window.resendPending = true;
|
|
1568
|
+
return;
|
|
1569
|
+
}
|
|
1570
|
+
if (!window.resendPending)
|
|
1571
|
+
return;
|
|
1572
|
+
try {
|
|
1573
|
+
for (const frame of frames) {
|
|
1574
|
+
await this.submitControlFrame(window.meshName, window.targetNodeRid, frame, 'cutover retransmission');
|
|
1575
|
+
}
|
|
1576
|
+
window.resendPending = false;
|
|
1577
|
+
}
|
|
1578
|
+
catch {
|
|
1579
|
+
// The next settlement round retries the whole batch.
|
|
1580
|
+
}
|
|
1581
|
+
}
|
|
1582
|
+
/**
|
|
1583
|
+
* The one source authority settlement after Ready (Location runtime
|
|
1584
|
+
* §6.1·§10, spec 28 §4.4). It reads the Store until the target commit or
|
|
1585
|
+
* the Restore deadline. From the Restore deadline it runs the source
|
|
1586
|
+
* Preserve fence: a CAS on the StoreVersion expected by target NewOwner
|
|
1587
|
+
* excludes a late target commit. `target`: the
|
|
1588
|
+
* target committed. `source`: Preserve fenced the target out while the
|
|
1589
|
+
* source owner lease was valid. `lost`: the source owner lease ended first
|
|
1590
|
+
* or another authority owns the object. An uncertain Store or lease result
|
|
1591
|
+
* keeps the work retained and repeats the same decision.
|
|
1592
|
+
*/
|
|
1593
|
+
async settleSourceAuthority(envelope, primary, target, restoreDeadlineReached, window) {
|
|
1594
|
+
const owner = { ownerId: primary.ownerId, leaseGeneration: primary.ownerLeaseGeneration };
|
|
1595
|
+
const store = this.requireLocationStore();
|
|
1596
|
+
for (;;) {
|
|
1597
|
+
if (this.disposed)
|
|
1598
|
+
throw new Error('Relocation runtime stopped.');
|
|
1599
|
+
await this.resendSourceCutover(window);
|
|
1600
|
+
const observed = await this.readSourceSettlement(envelope, primary, target).catch(() => undefined);
|
|
1601
|
+
if (observed?.kind === 'target')
|
|
1602
|
+
return 'target';
|
|
1603
|
+
if (observed?.kind === 'other')
|
|
1604
|
+
return 'lost';
|
|
1605
|
+
if (restoreDeadlineReached() && observed?.kind === 'source') {
|
|
1606
|
+
// An unreadable lease is an uncertain result, not an expiry.
|
|
1607
|
+
if (await this.exactSourceLeaseExpired(owner).catch(() => false))
|
|
1608
|
+
return 'lost';
|
|
1609
|
+
const publication = this.codec.read(observed.current.payload);
|
|
1610
|
+
const preserved = await store
|
|
1611
|
+
.compareExchangeAuthority({ value: primaryKey(envelope) }, observed.current.storeVersion, {
|
|
1612
|
+
kind: 'put',
|
|
1613
|
+
generationTransition: 'preserve',
|
|
1614
|
+
payload: publication === undefined
|
|
1615
|
+
? observed.current.payload
|
|
1616
|
+
: this.codec.clear(observed.current.payload, publication.reference)
|
|
1617
|
+
})
|
|
1618
|
+
.catch(() => undefined);
|
|
1619
|
+
if (preserved?.kind === 'stored')
|
|
1620
|
+
return 'source';
|
|
1621
|
+
}
|
|
1622
|
+
// The caller awaits this settlement, so the retry timer keeps the loop alive.
|
|
1623
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
1624
|
+
}
|
|
1625
|
+
}
|
|
1626
|
+
async readSourceSettlement(envelope, primary, target) {
|
|
1627
|
+
const primaryAuthorityKey = primaryKey(envelope);
|
|
1628
|
+
let committed = 0;
|
|
1629
|
+
let primaryCurrent;
|
|
1630
|
+
for (const participant of envelope.participants) {
|
|
1631
|
+
const current = await this.requireLocationStore().readAuthority({
|
|
1632
|
+
value: participant.key
|
|
1633
|
+
});
|
|
1634
|
+
if (current.kind !== 'snapshot')
|
|
1635
|
+
return { kind: 'other' };
|
|
1636
|
+
if (current.objectGeneration === participant.objectGeneration &&
|
|
1637
|
+
current.ownerId === target.ownerId &&
|
|
1638
|
+
current.ownerLeaseGeneration === target.ownerLeaseGeneration &&
|
|
1639
|
+
current.authorityOwnerGeneration > participant.authorityOwnerGeneration &&
|
|
1640
|
+
String(current.allocation.descriptor.rid) === target.nodeRid &&
|
|
1641
|
+
current.allocation.descriptorLifecycleGeneration === target.nodeGeneration) {
|
|
1642
|
+
committed += 1;
|
|
1643
|
+
}
|
|
1644
|
+
if (participant.key === primaryAuthorityKey)
|
|
1645
|
+
primaryCurrent = current;
|
|
1646
|
+
}
|
|
1647
|
+
if (committed === envelope.participants.length)
|
|
1648
|
+
return { kind: 'target' };
|
|
1649
|
+
if (primaryCurrent !== undefined && isSourceOwner(primaryCurrent, primary)) {
|
|
1650
|
+
return { kind: 'source', current: primaryCurrent };
|
|
1651
|
+
}
|
|
1652
|
+
return { kind: 'other' };
|
|
1525
1653
|
}
|
|
1526
1654
|
/**
|
|
1527
1655
|
* True when every relocation this source started reached its Message
|
|
@@ -1611,6 +1739,7 @@ class ZLinkHostServiceRelocationRuntime {
|
|
|
1611
1739
|
const envelope = (0, service_relocation_runtime_1.decodeServiceRelocationEnvelope)(payload, request.targetAttemptGeneration);
|
|
1612
1740
|
const inventoryEnvelope = attachPrepareBoundSessions(await this.resolveTargetParticipantInventory(request, envelope, signal), request.nodeInternalBoundSessions);
|
|
1613
1741
|
validatePrepareEnvelope(request, inventoryEnvelope);
|
|
1742
|
+
const releaseActivation = (await this.options.activationAdmission?.acquire(meshName, signal)) ?? (() => undefined);
|
|
1614
1743
|
let materialized;
|
|
1615
1744
|
let reservation;
|
|
1616
1745
|
try {
|
|
@@ -1620,13 +1749,12 @@ class ZLinkHostServiceRelocationRuntime {
|
|
|
1620
1749
|
// rejects unrelated authority CAS until commit/abort.
|
|
1621
1750
|
materialized = await this.materializeTargetEnvelope(meshName, inventoryEnvelope, signal);
|
|
1622
1751
|
await materialized.owner.restoreSavedWork(materialized.staging, signal);
|
|
1623
|
-
reservation = await this.reserveTargetForPrepare(meshName, request, inventoryEnvelope,
|
|
1752
|
+
reservation = await this.reserveTargetForPrepare(meshName, request, inventoryEnvelope, signal);
|
|
1624
1753
|
const offer = {
|
|
1625
1754
|
prepare: request,
|
|
1626
1755
|
prepareFingerprint: fingerprint,
|
|
1627
1756
|
authenticatedSourceNodeRid: String(sourceNodeRid),
|
|
1628
1757
|
envelope: inventoryEnvelope,
|
|
1629
|
-
restoreDeadlineAtMs: performance.now() + RELOCATION_OPERATION_RETENTION_MS,
|
|
1630
1758
|
reservation
|
|
1631
1759
|
};
|
|
1632
1760
|
const stage = {
|
|
@@ -1635,11 +1763,13 @@ class ZLinkHostServiceRelocationRuntime {
|
|
|
1635
1763
|
phase: 'ready',
|
|
1636
1764
|
lane: Promise.resolve(),
|
|
1637
1765
|
cutoverReceived: false,
|
|
1638
|
-
boundaryRelay: []
|
|
1766
|
+
boundaryRelay: [],
|
|
1767
|
+
releaseActivation
|
|
1639
1768
|
};
|
|
1640
1769
|
this.targetStages.set(stagingId, stage);
|
|
1641
1770
|
}
|
|
1642
1771
|
catch (error) {
|
|
1772
|
+
releaseActivation();
|
|
1643
1773
|
if (reservation !== undefined) {
|
|
1644
1774
|
await this.abortTargetReservation(reservation, signal).catch(() => undefined);
|
|
1645
1775
|
}
|
|
@@ -1650,12 +1780,25 @@ class ZLinkHostServiceRelocationRuntime {
|
|
|
1650
1780
|
}
|
|
1651
1781
|
return relocationReady(request);
|
|
1652
1782
|
}
|
|
1783
|
+
/**
|
|
1784
|
+
* The cutover wait of a Ready stage (spec 28 §4.4) is a Warning only: it
|
|
1785
|
+
* records `cutover_timeout` and never submits the target CAS or opens
|
|
1786
|
+
* dispatch. From then on the target settlement reads the Location Store so
|
|
1787
|
+
* a source Preserve fence or a lost target lease ends the staging; only a
|
|
1788
|
+
* verified cutover lets that settlement submit the CAS.
|
|
1789
|
+
*/
|
|
1653
1790
|
armTargetCutoverFallback(meshName, stagingId) {
|
|
1654
1791
|
const stage = this.targetStages.get(stagingId);
|
|
1655
1792
|
if (stage === undefined || stage.fallback !== undefined || stage.finalize !== undefined)
|
|
1656
1793
|
return;
|
|
1657
1794
|
stage.fallback = setTimeout(() => {
|
|
1658
|
-
|
|
1795
|
+
if (stage.finalize !== undefined)
|
|
1796
|
+
return;
|
|
1797
|
+
console.warn('[zlink.runtime.relocation.cutover_timeout]', stagingId);
|
|
1798
|
+
this.options.metrics?.count('zlink.relocation.cutover_timeout', 1, {
|
|
1799
|
+
mesh_name: meshName
|
|
1800
|
+
});
|
|
1801
|
+
void this.beginTargetFinalize(meshName, stagingId, stage).catch((error) => console.error('[zlink.runtime.relocation.location_update_failed]', error));
|
|
1659
1802
|
}, this.relocationLimits().relocationCutoverWaitTimeoutMs);
|
|
1660
1803
|
stage.fallback.unref();
|
|
1661
1804
|
}
|
|
@@ -1675,20 +1818,28 @@ class ZLinkHostServiceRelocationRuntime {
|
|
|
1675
1818
|
throw new Error(`Relocation staging '${stagingId}' is missing.`);
|
|
1676
1819
|
}
|
|
1677
1820
|
validateTargetOneWayControl(stage, request, sourceNodeRid);
|
|
1821
|
+
// A verified cutover closes the boundary batch. A later record or cutover
|
|
1822
|
+
// is a duplicate retransmission and never changes the staged batch.
|
|
1823
|
+
if (stage.cutoverReceived || (stage.phase !== 'ready' && stage.phase !== 'finalizing')) {
|
|
1824
|
+
if (request.kind === 'cutover') {
|
|
1825
|
+
console.warn('[zlink.runtime.relocation.late_cutover]', stagingId);
|
|
1826
|
+
}
|
|
1827
|
+
return;
|
|
1828
|
+
}
|
|
1678
1829
|
if (request.kind === 'data') {
|
|
1679
1830
|
stage.lane = stage.lane.then(() => this.stageBoundaryRelay(stage, request));
|
|
1680
1831
|
await stage.lane;
|
|
1681
1832
|
return;
|
|
1682
1833
|
}
|
|
1683
|
-
if (stage.cutoverReceived || stage.phase !== 'ready') {
|
|
1684
|
-
console.warn('[zlink.runtime.relocation.late_cutover]', stagingId);
|
|
1685
|
-
return;
|
|
1686
|
-
}
|
|
1687
1834
|
this.reconcileBoundaryRelay(stage, request, stagingId);
|
|
1688
1835
|
stage.cutoverReceived = true;
|
|
1836
|
+
// A settlement started by the cutover wait Warning is still reading the
|
|
1837
|
+
// Store; it submits the CAS on its next round now that cutover verified.
|
|
1838
|
+
if (stage.finalize !== undefined)
|
|
1839
|
+
return;
|
|
1689
1840
|
if (stage.fallback !== undefined)
|
|
1690
1841
|
clearTimeout(stage.fallback);
|
|
1691
|
-
await this.beginTargetFinalize(meshName, stagingId, stage,
|
|
1842
|
+
await this.beginTargetFinalize(meshName, stagingId, stage, signal);
|
|
1692
1843
|
}
|
|
1693
1844
|
/** Validates one boundary relay record and buffers it until cutover applies it. */
|
|
1694
1845
|
async stageBoundaryRelay(stage, request) {
|
|
@@ -1707,14 +1858,13 @@ class ZLinkHostServiceRelocationRuntime {
|
|
|
1707
1858
|
}
|
|
1708
1859
|
const expectedCount = Number(request.boundaryRecordCount);
|
|
1709
1860
|
const staged = stage.boundaryRelay;
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
console.warn('[zlink.runtime.relocation.boundary_batch_replaced]', stagingId);
|
|
1861
|
+
// The batch is the last `boundaryRecordCount` records: the source sends
|
|
1862
|
+
// the whole batch right before this cutover on the ordered connection,
|
|
1863
|
+
// so a retransmission replaces an earlier partial span as a whole.
|
|
1864
|
+
if (staged.length >= expectedCount) {
|
|
1865
|
+
const batch = staged.slice(staged.length - expectedCount);
|
|
1866
|
+
if ((0, service_relocation_runtime_1.crc32c)(Buffer.concat(batch.map((value) => value.frozenRecord.canonicalBytes))) ===
|
|
1867
|
+
request.boundaryChecksumCrc32c) {
|
|
1718
1868
|
staged.splice(0, staged.length - expectedCount);
|
|
1719
1869
|
return;
|
|
1720
1870
|
}
|
|
@@ -1724,18 +1874,14 @@ class ZLinkHostServiceRelocationRuntime {
|
|
|
1724
1874
|
throw new Error(`Relocation cutover boundary confirmation mismatch for '${stagingId}' ` +
|
|
1725
1875
|
`(staged=${staged.length} declared=${expectedCount}).`);
|
|
1726
1876
|
}
|
|
1727
|
-
beginTargetFinalize(meshName, stagingId, stage,
|
|
1877
|
+
beginTargetFinalize(meshName, stagingId, stage, signal) {
|
|
1728
1878
|
if (stage.finalize !== undefined)
|
|
1729
1879
|
return stage.finalize;
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
mesh_name: meshName
|
|
1734
|
-
});
|
|
1735
|
-
}
|
|
1880
|
+
// Records staged before this point finish first. The settlement is not
|
|
1881
|
+
// put on the lane, so a cutover that arrives while it waits for the
|
|
1882
|
+
// authority result is still reconciled.
|
|
1736
1883
|
const finalize = stage.lane.then(() => this.finalizeTargetStage(meshName, stagingId, stage, signal));
|
|
1737
1884
|
stage.finalize = finalize;
|
|
1738
|
-
stage.lane = finalize;
|
|
1739
1885
|
return finalize;
|
|
1740
1886
|
}
|
|
1741
1887
|
async finalizeTargetStage(meshName, stagingId, stage, signal) {
|
|
@@ -1746,14 +1892,15 @@ class ZLinkHostServiceRelocationRuntime {
|
|
|
1746
1892
|
let authority;
|
|
1747
1893
|
let resumeStartedAt;
|
|
1748
1894
|
try {
|
|
1749
|
-
// The boundary relay span is applied to the temporary queue before the
|
|
1750
|
-
// owner CAS so the merged order is saved work, pre-boundary relay, then
|
|
1751
|
-
// post-boundary temporary records (spec 28 §4.6).
|
|
1752
|
-
for (const relay of stage.boundaryRelay.splice(0)) {
|
|
1753
|
-
await this.applyRelocationData(stage, relay, signal);
|
|
1754
|
-
}
|
|
1755
1895
|
authority = await this.commitTargetReservation(stage, stage.offer.reservation, signal);
|
|
1896
|
+
if (authority === undefined) {
|
|
1897
|
+
// Source Preserve, a definitive conflict or the target lease ended
|
|
1898
|
+
// this staging before any target commit (Location runtime §10).
|
|
1899
|
+
console.warn('[zlink.runtime.relocation.target_staging_discarded]', stagingId);
|
|
1900
|
+
throw new Error(`Relocation '${stagingId}' target staging lost the authority settlement.`);
|
|
1901
|
+
}
|
|
1756
1902
|
authorityCommitted = true;
|
|
1903
|
+
stage.releaseActivation();
|
|
1757
1904
|
resumeStartedAt = performance.now();
|
|
1758
1905
|
stage.phase = 'committed';
|
|
1759
1906
|
await stage.owner.normalize(stage.staging, authority, signal);
|
|
@@ -1768,13 +1915,10 @@ class ZLinkHostServiceRelocationRuntime {
|
|
|
1768
1915
|
}
|
|
1769
1916
|
}
|
|
1770
1917
|
await this.finalizeActorJoinProfiles(meshName, stage, signal);
|
|
1771
|
-
//
|
|
1772
|
-
//
|
|
1773
|
-
//
|
|
1774
|
-
//
|
|
1775
|
-
// roll back already committed target ownership or strand its staged
|
|
1776
|
-
// object: the next reservation recognizes the retained journal root and
|
|
1777
|
-
// continues its existing cleanup/retry path.
|
|
1918
|
+
// Clear the final relocation publication before admission becomes
|
|
1919
|
+
// externally visible so a later Join cannot reserve against the
|
|
1920
|
+
// previous root. A bounded CAS conflict must not roll back already
|
|
1921
|
+
// committed target ownership or strand its staged object.
|
|
1778
1922
|
try {
|
|
1779
1923
|
await this.clearTargetRelocationPublication(stage, authority, signal);
|
|
1780
1924
|
}
|
|
@@ -1907,58 +2051,23 @@ class ZLinkHostServiceRelocationRuntime {
|
|
|
1907
2051
|
throw new Error(`Relocation ${kind} was not accepted by RouteMesh.`);
|
|
1908
2052
|
}
|
|
1909
2053
|
}
|
|
1910
|
-
async waitForTargetAuthorities(envelope, target, deadlineAtMs, signal) {
|
|
1911
|
-
for (;;) {
|
|
1912
|
-
signal?.throwIfAborted();
|
|
1913
|
-
let exact = true;
|
|
1914
|
-
for (const participant of envelope.participants) {
|
|
1915
|
-
const current = await this.requireLocationStore().readAuthority({ value: participant.key }, signal);
|
|
1916
|
-
if (current.kind !== 'snapshot' ||
|
|
1917
|
-
current.objectGeneration !== participant.objectGeneration ||
|
|
1918
|
-
current.ownerId !== target.ownerId ||
|
|
1919
|
-
current.ownerLeaseGeneration !== target.ownerLeaseGeneration ||
|
|
1920
|
-
current.authorityOwnerGeneration <= participant.authorityOwnerGeneration ||
|
|
1921
|
-
String(current.allocation.descriptor.rid) !== target.nodeRid ||
|
|
1922
|
-
current.allocation.descriptorLifecycleGeneration !== target.nodeGeneration) {
|
|
1923
|
-
exact = false;
|
|
1924
|
-
break;
|
|
1925
|
-
}
|
|
1926
|
-
}
|
|
1927
|
-
if (exact)
|
|
1928
|
-
return;
|
|
1929
|
-
if (performance.now() >= deadlineAtMs) {
|
|
1930
|
-
throw new Error('Relocation target owner was not confirmed before its original deadline.');
|
|
1931
|
-
}
|
|
1932
|
-
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
1933
|
-
}
|
|
1934
|
-
}
|
|
1935
2054
|
async clearTargetRelocationPublication(stage, primary, signal) {
|
|
1936
2055
|
await this.releaseParticipantAuthorities(stage.staging.envelope, stage.staging.primaryAuthorityKey.value, signal);
|
|
1937
|
-
// The
|
|
1938
|
-
//
|
|
1939
|
-
//
|
|
1940
|
-
//
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
generationTransition: 'preserve',
|
|
1952
|
-
payload: this.codec.clear(current.payload, publication.reference)
|
|
1953
|
-
}, signal);
|
|
1954
|
-
if (result.kind === 'stored')
|
|
1955
|
-
return;
|
|
1956
|
-
const read = await this.requireLocationStore().readAuthority(stage.staging.primaryAuthorityKey, signal);
|
|
1957
|
-
if (read.kind !== 'snapshot')
|
|
1958
|
-
break;
|
|
1959
|
-
current = read;
|
|
2056
|
+
// The target owner is the only writer of the committed primary
|
|
2057
|
+
// authority between its commit and this clear, so one CAS against the
|
|
2058
|
+
// committed snapshot decides. A conflict means another owner changed the
|
|
2059
|
+
// row; the caller records it and leaves the committed ownership intact.
|
|
2060
|
+
const publication = this.codec.read(primary.payload);
|
|
2061
|
+
if (publication === undefined)
|
|
2062
|
+
return;
|
|
2063
|
+
const result = await this.requireLocationStore().compareExchangeAuthority(stage.staging.primaryAuthorityKey, primary.storeVersion, {
|
|
2064
|
+
kind: 'put',
|
|
2065
|
+
generationTransition: 'preserve',
|
|
2066
|
+
payload: this.codec.clear(primary.payload, publication.reference)
|
|
2067
|
+
}, signal);
|
|
2068
|
+
if (result.kind !== 'stored') {
|
|
2069
|
+
throw new Error('Relocation target authority normalization CAS failed.');
|
|
1960
2070
|
}
|
|
1961
|
-
throw new Error('Relocation target authority normalization CAS failed.');
|
|
1962
2071
|
}
|
|
1963
2072
|
async handleReplyRelay(meshName, relay, sourceNodeRid, signal) {
|
|
1964
2073
|
if (sourceNodeRid === null) {
|
|
@@ -2234,39 +2343,9 @@ class ZLinkHostServiceRelocationRuntime {
|
|
|
2234
2343
|
}
|
|
2235
2344
|
return authorities;
|
|
2236
2345
|
}
|
|
2237
|
-
async reserveTargetForPrepare(meshName, prepare, envelope,
|
|
2346
|
+
async reserveTargetForPrepare(meshName, prepare, envelope, signal) {
|
|
2238
2347
|
const authorities = await this.readTargetParticipantAuthorities(envelope, signal);
|
|
2239
2348
|
const publication = relocationPublication(prepare, envelope);
|
|
2240
|
-
// Carries the exact reference + CRC of a deferred-Join publication the
|
|
2241
|
-
// target reservation must keep connected through the CAS (spec 28 —
|
|
2242
|
-
// clearing/replacing it here would strand the journal's own
|
|
2243
|
-
// prepared->committed transition with nothing left for it to find).
|
|
2244
|
-
const retainedDeferredJoinRoots = new Map();
|
|
2245
|
-
for (const participant of envelope.participants) {
|
|
2246
|
-
const staged = targetPort.deferredJoinRoot(participant.key);
|
|
2247
|
-
if (staged !== undefined) {
|
|
2248
|
-
retainedDeferredJoinRoots.set(participant.key, {
|
|
2249
|
-
reference: staged.reference.value,
|
|
2250
|
-
checksumCrc32c: staged.checksumCrc32c
|
|
2251
|
-
});
|
|
2252
|
-
continue;
|
|
2253
|
-
}
|
|
2254
|
-
const current = this.codec.read(authorities.get(participant.key).payload);
|
|
2255
|
-
if (participant.objectKind === 'actor' &&
|
|
2256
|
-
current?.canonical === true &&
|
|
2257
|
-
(await this.options.actorTransfer.isDeferredJoinAcceptedRootPublication(current.reference, current.checksumCrc32c, {
|
|
2258
|
-
authorityKey: participant.key,
|
|
2259
|
-
objectKind: 'actor',
|
|
2260
|
-
objectGeneration: participant.objectGeneration,
|
|
2261
|
-
aggregateId: current.aggregateId,
|
|
2262
|
-
aggregateGeneration: current.aggregateGeneration
|
|
2263
|
-
}, signal))) {
|
|
2264
|
-
retainedDeferredJoinRoots.set(participant.key, {
|
|
2265
|
-
reference: current.reference,
|
|
2266
|
-
checksumCrc32c: current.checksumCrc32c
|
|
2267
|
-
});
|
|
2268
|
-
}
|
|
2269
|
-
}
|
|
2270
2349
|
const participants = envelope.participants.map((participant) => {
|
|
2271
2350
|
const expected = authorities.get(participant.key);
|
|
2272
2351
|
const membership = actorMembershipTarget(envelope, participant.key);
|
|
@@ -2278,19 +2357,11 @@ class ZLinkHostServiceRelocationRuntime {
|
|
|
2278
2357
|
entrySpotId === undefined) {
|
|
2279
2358
|
throw new Error(`Relocation target RouteMesh '${meshName}' has no Entry Spot.`);
|
|
2280
2359
|
}
|
|
2281
|
-
const retainedDeferredJoinRoot = retainedDeferredJoinRoots.get(participant.key);
|
|
2282
|
-
const participantPublication = retainedDeferredJoinRoot === undefined
|
|
2283
|
-
? publication
|
|
2284
|
-
: {
|
|
2285
|
-
...publication,
|
|
2286
|
-
reference: retainedDeferredJoinRoot.reference,
|
|
2287
|
-
checksumCrc32c: retainedDeferredJoinRoot.checksumCrc32c
|
|
2288
|
-
};
|
|
2289
2360
|
return {
|
|
2290
2361
|
key: { value: participant.key },
|
|
2291
2362
|
expected,
|
|
2292
2363
|
ownerTransition: 'newOwner',
|
|
2293
|
-
authorityPayload: this.authorityPayloadForPublication(expected.payload,
|
|
2364
|
+
authorityPayload: this.authorityPayloadForPublication(expected.payload, publication, {
|
|
2294
2365
|
owner: {
|
|
2295
2366
|
ownerId: prepare.target.ownerId,
|
|
2296
2367
|
leaseGeneration: prepare.target.ownerLeaseGeneration
|
|
@@ -2313,7 +2384,7 @@ class ZLinkHostServiceRelocationRuntime {
|
|
|
2313
2384
|
...membership,
|
|
2314
2385
|
actorSpotKind: contracts_1.ZLinkSpotKind.User
|
|
2315
2386
|
})
|
|
2316
|
-
}
|
|
2387
|
+
}),
|
|
2317
2388
|
membershipMutation: encodeMembershipMutation(envelope.memberships, participant.key)
|
|
2318
2389
|
};
|
|
2319
2390
|
});
|
|
@@ -2332,19 +2403,13 @@ class ZLinkHostServiceRelocationRuntime {
|
|
|
2332
2403
|
prepared: await new service_relocation_aggregate_committer_1.ServiceRelocationAggregateCommitter(this.requireLocationStore()).prepare(plan, signal)
|
|
2333
2404
|
};
|
|
2334
2405
|
}
|
|
2335
|
-
authorityPayloadForPublication(payload, publication, target
|
|
2406
|
+
authorityPayloadForPublication(payload, publication, target) {
|
|
2336
2407
|
const existing = this.codec.read(payload);
|
|
2337
2408
|
const canonicalIdentity = (0, service_relocation_runtime_1.serviceRelocationAuthoritySlotIdentity)(payload);
|
|
2338
|
-
const replacesCanonicalJournal = replacesDeferredJoinRoot && existing?.canonical === true;
|
|
2339
2409
|
if (canonicalIdentity !== undefined &&
|
|
2340
2410
|
(existing === undefined ||
|
|
2341
|
-
(existing.canonical === true &&
|
|
2342
|
-
|
|
2343
|
-
const published = existing === undefined
|
|
2344
|
-
? this.codec.publish(payload, publication)
|
|
2345
|
-
: replacesCanonicalJournal && existing.aggregateId !== publication.aggregateId
|
|
2346
|
-
? this.codec.publish(this.codec.clear(payload, existing.reference), publication)
|
|
2347
|
-
: payload;
|
|
2411
|
+
(existing.canonical === true && existing.aggregateId === publication.aggregateId))) {
|
|
2412
|
+
const published = existing === undefined ? this.codec.publish(payload, publication) : payload;
|
|
2348
2413
|
if (target === undefined)
|
|
2349
2414
|
return published;
|
|
2350
2415
|
return (0, service_relocation_runtime_1.projectServiceRelocationAuthorityTargetReady)((0, service_relocation_runtime_1.replaceServiceRelocationAuthorityApplicationPayload)(published, rewriteAuthorityApplicationRoute(published, target)), {
|
|
@@ -2394,6 +2459,7 @@ class ZLinkHostServiceRelocationRuntime {
|
|
|
2394
2459
|
async abortTargetStage(stage, signal) {
|
|
2395
2460
|
if (stage.fallback !== undefined)
|
|
2396
2461
|
clearTimeout(stage.fallback);
|
|
2462
|
+
stage.releaseActivation();
|
|
2397
2463
|
const failures = [];
|
|
2398
2464
|
try {
|
|
2399
2465
|
await this.abortTargetReservation(stage.offer.reservation, signal);
|
|
@@ -2417,19 +2483,39 @@ class ZLinkHostServiceRelocationRuntime {
|
|
|
2417
2483
|
async commitTargetReservation(stage, reservation, signal) {
|
|
2418
2484
|
return await this.commitTargetAggregate(stage, reservation.prepared, signal);
|
|
2419
2485
|
}
|
|
2486
|
+
/**
|
|
2487
|
+
* The one target authority settlement (Location runtime §10, spec 28
|
|
2488
|
+
* §4.4-§4.5). Only a verified cutover lets the target submit its NewOwner
|
|
2489
|
+
* CAS; a transient or indeterminate result resubmits the same RelocationId
|
|
2490
|
+
* and expected StoreVersion while the target owner lease is valid (no
|
|
2491
|
+
* separate timeout, no fixed count). Before a verified cutover the target
|
|
2492
|
+
* only reads. It returns the committed authority, or undefined when a
|
|
2493
|
+
* definitive Conflict excludes this commit after reconciliation, a source
|
|
2494
|
+
* Preserve fence moved the source row, or the target lease ended; the
|
|
2495
|
+
* caller then discards the staging.
|
|
2496
|
+
*/
|
|
2420
2497
|
async commitTargetAggregate(stage, prepared, signal) {
|
|
2421
|
-
|
|
2498
|
+
const target = stage.offer.prepare.target;
|
|
2422
2499
|
for (;;) {
|
|
2423
2500
|
signal?.throwIfAborted();
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2501
|
+
if (this.disposed)
|
|
2502
|
+
throw new Error('Relocation runtime stopped.');
|
|
2503
|
+
let conflict = false;
|
|
2504
|
+
if (stage.cutoverReceived) {
|
|
2505
|
+
// The boundary relay span is applied to the temporary queue before
|
|
2506
|
+
// the owner CAS so the merged order is saved work, pre-boundary relay,
|
|
2507
|
+
// then post-boundary temporary records (spec 28 §4.6).
|
|
2508
|
+
for (const relay of stage.boundaryRelay.splice(0)) {
|
|
2509
|
+
await this.applyRelocationData(stage, relay, signal);
|
|
2510
|
+
}
|
|
2511
|
+
// A thrown provider call has an unknown result; the read below decides.
|
|
2512
|
+
const result = await this.requireLocationStore()
|
|
2513
|
+
.commitAggregate(prepared.fence, signal)
|
|
2514
|
+
.catch(() => undefined);
|
|
2515
|
+
if (result?.kind === 'generationExhausted') {
|
|
2516
|
+
throw new Error('location_update_failed: relocation aggregate commit exhausted.');
|
|
2517
|
+
}
|
|
2518
|
+
conflict = result?.kind === 'stale';
|
|
2433
2519
|
}
|
|
2434
2520
|
const observed = await this.readAggregateForCommitRetry(prepared, signal);
|
|
2435
2521
|
if (observed.kind === 'committed') {
|
|
@@ -2439,12 +2525,13 @@ class ZLinkHostServiceRelocationRuntime {
|
|
|
2439
2525
|
}
|
|
2440
2526
|
return primary;
|
|
2441
2527
|
}
|
|
2442
|
-
if (observed.kind === 'stale')
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2528
|
+
if (observed.kind === 'stale' || (conflict && observed.kind === 'source'))
|
|
2529
|
+
return undefined;
|
|
2530
|
+
// An unreadable lease is an uncertain result, not an expiry.
|
|
2531
|
+
if (await this.exactSourceLeaseExpired({ ownerId: target.ownerId, leaseGeneration: target.ownerLeaseGeneration }, signal).catch(() => false)) {
|
|
2532
|
+
return undefined;
|
|
2446
2533
|
}
|
|
2447
|
-
await
|
|
2534
|
+
await waitForRelocationRetry(25, signal);
|
|
2448
2535
|
}
|
|
2449
2536
|
}
|
|
2450
2537
|
async readAggregateForCommitRetry(prepared, signal) {
|
|
@@ -2708,6 +2795,17 @@ class ZLinkHostServiceRelocationRuntime {
|
|
|
2708
2795
|
throw new Error('Host relocation requires the Spot manager.');
|
|
2709
2796
|
return value;
|
|
2710
2797
|
}
|
|
2798
|
+
afterSpotCloseDecision(meshName, spotId, seal) {
|
|
2799
|
+
const manager = this.requireSpotManager();
|
|
2800
|
+
const sealAfterDecision = () => {
|
|
2801
|
+
if (manager.isSpotClosing(meshName, spotId)) {
|
|
2802
|
+
throw (0, framework_errors_internal_1.createInternalFrameworkException)(framework_errors_internal_1.ZLinkFrameworkInternalErrorKind.SpotMoving, `Spot '${String(spotId)}' is closing.`, true);
|
|
2803
|
+
}
|
|
2804
|
+
return seal();
|
|
2805
|
+
};
|
|
2806
|
+
const decision = manager.pendingSpotCloseDecision(meshName, spotId);
|
|
2807
|
+
return decision === undefined ? sealAfterDecision() : decision.then(sealAfterDecision);
|
|
2808
|
+
}
|
|
2711
2809
|
requireActorManager() {
|
|
2712
2810
|
const value = this.options.actorManager();
|
|
2713
2811
|
if (value === undefined)
|
|
@@ -2802,7 +2900,6 @@ class LocalTargetPort {
|
|
|
2802
2900
|
options;
|
|
2803
2901
|
meshName;
|
|
2804
2902
|
envelope;
|
|
2805
|
-
deferredJoinRoots = new Map();
|
|
2806
2903
|
constructor(options, meshName, envelope) {
|
|
2807
2904
|
this.options = options;
|
|
2808
2905
|
this.meshName = meshName;
|
|
@@ -2955,10 +3052,7 @@ class LocalTargetPort {
|
|
|
2955
3052
|
if (recovery.request.actorId !== hidden.actor.context.actorId) {
|
|
2956
3053
|
throw new Error('Canonical Actor Join recovery names a different staged Actor.');
|
|
2957
3054
|
}
|
|
2958
|
-
|
|
2959
|
-
if (deferred !== undefined) {
|
|
2960
|
-
this.deferredJoinRoots.set(hidden.authorityKey, deferred);
|
|
2961
|
-
}
|
|
3055
|
+
await this.requireSpotManager().restoreCanonicalActorJoinRecovery(recovery);
|
|
2962
3056
|
return;
|
|
2963
3057
|
}
|
|
2964
3058
|
const packet = decodeQueuedHandoffPacket(message);
|
|
@@ -2967,9 +3061,6 @@ class LocalTargetPort {
|
|
|
2967
3061
|
throw new Error('Relocated Actor state is not staged.');
|
|
2968
3062
|
hidden.replayPackets.push(packet);
|
|
2969
3063
|
}
|
|
2970
|
-
deferredJoinRoot(authorityKey) {
|
|
2971
|
-
return this.deferredJoinRoots.get(authorityKey);
|
|
2972
|
-
}
|
|
2973
3064
|
async openAdmission(hidden) {
|
|
2974
3065
|
if (hidden.actor === undefined)
|
|
2975
3066
|
return;
|
|
@@ -3324,8 +3415,11 @@ function sameCoordinator(left, right) {
|
|
|
3324
3415
|
left.expectedAuthorityStoreVersion === right.expectedAuthorityStoreVersion);
|
|
3325
3416
|
}
|
|
3326
3417
|
function isExactSourceAuthority(current, expected) {
|
|
3327
|
-
return (current.storeVersion.value === expected.storeVersion.value &&
|
|
3328
|
-
|
|
3418
|
+
return (current.storeVersion.value === expected.storeVersion.value && isSourceOwner(current, expected));
|
|
3419
|
+
}
|
|
3420
|
+
/** The same source tenure owns the row, at any StoreVersion. */
|
|
3421
|
+
function isSourceOwner(current, expected) {
|
|
3422
|
+
return (current.objectGeneration === expected.objectGeneration &&
|
|
3329
3423
|
current.authorityOwnerGeneration === expected.authorityOwnerGeneration &&
|
|
3330
3424
|
current.ownerId === expected.ownerId &&
|
|
3331
3425
|
current.ownerLeaseGeneration === expected.ownerLeaseGeneration &&
|
|
@@ -3345,26 +3439,6 @@ function isExactCommittedTarget(current, expected, target, payload) {
|
|
|
3345
3439
|
current.allocation.descriptorLifecycleGeneration === target.nodeGeneration &&
|
|
3346
3440
|
Buffer.from(current.payload).equals(Buffer.from(payload)));
|
|
3347
3441
|
}
|
|
3348
|
-
async function waitForLocationRetry(deadlineAtMs, signal, cause) {
|
|
3349
|
-
signal?.throwIfAborted();
|
|
3350
|
-
if (performance.now() >= deadlineAtMs) {
|
|
3351
|
-
throw new Error('location_update_failed: relocation Restore validity expired before exact target owner confirmation.', { cause });
|
|
3352
|
-
}
|
|
3353
|
-
await new Promise((resolve, reject) => {
|
|
3354
|
-
let timer;
|
|
3355
|
-
const aborted = () => {
|
|
3356
|
-
if (timer !== undefined)
|
|
3357
|
-
clearTimeout(timer);
|
|
3358
|
-
reject(signal?.reason);
|
|
3359
|
-
};
|
|
3360
|
-
signal?.addEventListener('abort', aborted, { once: true });
|
|
3361
|
-
timer = setTimeout(() => {
|
|
3362
|
-
signal?.removeEventListener('abort', aborted);
|
|
3363
|
-
resolve();
|
|
3364
|
-
}, Math.min(25, Math.max(1, deadlineAtMs - performance.now())));
|
|
3365
|
-
timer.unref();
|
|
3366
|
-
});
|
|
3367
|
-
}
|
|
3368
3442
|
async function waitForRelocationRetry(delayMs, signal) {
|
|
3369
3443
|
signal?.throwIfAborted();
|
|
3370
3444
|
await new Promise((resolve, reject) => {
|
|
@@ -3379,7 +3453,6 @@ async function waitForRelocationRetry(delayMs, signal) {
|
|
|
3379
3453
|
signal?.removeEventListener('abort', aborted);
|
|
3380
3454
|
resolve();
|
|
3381
3455
|
}, delayMs);
|
|
3382
|
-
timer.unref();
|
|
3383
3456
|
});
|
|
3384
3457
|
}
|
|
3385
3458
|
function sameRequestSource(left, right) {
|