@zlink-systems/framework 0.29.0 → 0.29.1
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/runtime/actors/actor-runtime-contracts.d.ts +1 -0
- package/dist/runtime/actors/index.d.ts +2 -0
- package/dist/runtime/actors/index.js +21 -6
- package/dist/runtime/foundation/service-stateful-runtime.js +27 -19
- package/dist/runtime/handlers/handler-instance-scope.js +1 -1
- package/dist/runtime/host/actor-placement-coordinator.d.ts +2 -1
- package/dist/runtime/host/actor-placement-coordinator.js +2 -1
- package/dist/runtime/host/actor-runtime-options-factory.d.ts +2 -1
- package/dist/runtime/host/actor-runtime-options-factory.js +1 -0
- package/dist/runtime/host/index.d.ts +1 -1
- package/dist/runtime/host/index.js +3 -2
- package/dist/runtime/locations/domain-store-contract.d.ts +2 -1
- package/dist/runtime/locations/in-memory-authority-store.d.ts +1 -0
- package/dist/runtime/locations/in-memory-authority-store.js +32 -11
- package/dist/runtime/locations/in-memory-location-store.d.ts +1 -1
- package/dist/runtime/locations/in-memory-location-store.js +12 -10
- package/dist/runtime/locations/internal-location-contracts.d.ts +5 -0
- package/dist/runtime/locations/location-store-repository.d.ts +1 -0
- package/dist/runtime/locations/location-store-repository.js +54 -21
- package/dist/runtime/spots/spot-entry-activation.d.ts +1 -0
- package/dist/runtime/spots/spot-entry-activation.js +3 -0
- package/dist/runtime/spots/spot-node-runtime-manager.d.ts +1 -0
- package/dist/runtime/spots/spot-node-runtime-manager.js +6 -0
- package/dist/runtime/spots/spot-serial-executor.d.ts +1 -0
- package/dist/runtime/spots/spot-serial-executor.js +10 -1
- package/package.json +2 -2
- package/src/runtime/actors/actor-runtime-contracts.ts +4 -0
- package/src/runtime/actors/index.ts +77 -42
- package/src/runtime/foundation/service-stateful-runtime.ts +49 -43
- package/src/runtime/handlers/handler-instance-scope.ts +2 -1
- package/src/runtime/host/actor-placement-coordinator.ts +8 -2
- package/src/runtime/host/actor-runtime-options-factory.ts +6 -0
- package/src/runtime/host/index.ts +6 -2
- package/src/runtime/locations/domain-store-contract.ts +1 -1
- package/src/runtime/locations/in-memory-authority-store.ts +47 -10
- package/src/runtime/locations/in-memory-location-store.ts +14 -10
- package/src/runtime/locations/internal-location-contracts.ts +6 -0
- package/src/runtime/locations/location-store-repository.ts +82 -28
- package/src/runtime/spots/spot-entry-activation.ts +4 -0
- package/src/runtime/spots/spot-node-runtime-manager.ts +7 -0
- package/src/runtime/spots/spot-serial-executor.ts +10 -4
|
@@ -27,6 +27,7 @@ export interface ZLinkActorManagerOptions {
|
|
|
27
27
|
readonly actorRefResolver?: ZLinkActorRuntimeLocationLookup;
|
|
28
28
|
readonly actorCreatedNotifier?: (nodeRid: RoutingId, actor: ZLinkActor, createRequest: ZLinkMessage, signal?: AbortSignal) => Promise<ZLinkActorCreateResponse | undefined>;
|
|
29
29
|
readonly actorDestroyedCleanup?: (actorId: string) => void;
|
|
30
|
+
readonly terminateActorActivation?: (actor: ZLinkActor, terminal: () => Promise<void>) => Promise<void>;
|
|
30
31
|
readonly publishActorAuthority?: (actorType: string, actorRef: ActorRef, ownerNodeGeneration: bigint, signal?: AbortSignal) => Promise<void>;
|
|
31
32
|
readonly locationLifecycle?: ZLinkLocationLifecycle;
|
|
32
33
|
readonly boundSessionFactory?: ZLinkActorBoundSessionFactory;
|
|
@@ -55,6 +55,8 @@ export declare class DefaultZLinkActorManager implements ZLinkActorManager {
|
|
|
55
55
|
completeCoreRelocationSource(actorId: string): Promise<void>;
|
|
56
56
|
private finalizeActorState;
|
|
57
57
|
private finalizeDestroyedActor;
|
|
58
|
+
private terminateActor;
|
|
59
|
+
private awaitActorDestroy;
|
|
58
60
|
getOrCreateActor(actorId: string, actorType: string, signal?: AbortSignal): Promise<ZLinkActor>;
|
|
59
61
|
getOrCreateWithNativeRef(actorId: string, actorType: string, actorRef: ZLinkBackendActorRef, createRequest?: unknown, signal?: AbortSignal): Promise<ZLinkActor>;
|
|
60
62
|
getOrCreateActorResult(actorId: string, actorType: string, createRequest?: unknown, signal?: AbortSignal): Promise<ZLinkActorLocalCreateResult>;
|
|
@@ -11,6 +11,7 @@ const runtime_metrics_1 = require("../diagnostics/runtime-metrics");
|
|
|
11
11
|
const execution_1 = require("../execution");
|
|
12
12
|
const framework_errors_internal_1 = require("../framework-errors-internal");
|
|
13
13
|
const handler_instance_scope_1 = require("../handlers/handler-instance-scope");
|
|
14
|
+
const actor_execution_context_1 = require("./actor-execution-context");
|
|
14
15
|
const payload_codec_1 = require("../messaging/payload-codec");
|
|
15
16
|
const actor_creation_1 = require("./actor-creation");
|
|
16
17
|
const actor_runtime_state_1 = require("./actor-runtime-state");
|
|
@@ -139,7 +140,7 @@ class DefaultZLinkActorManager {
|
|
|
139
140
|
const entryNodeRid = state.entryNodeRid ?? current.nodeRid;
|
|
140
141
|
if (node === undefined || completions === undefined)
|
|
141
142
|
return false;
|
|
142
|
-
const destroyTask = state.getOrStartDestroy(entryNodeRid, async (
|
|
143
|
+
const destroyTask = state.getOrStartDestroy(entryNodeRid, (nativeRef) => this.terminateActor(state, async () => {
|
|
143
144
|
if (nativeRef !== undefined) {
|
|
144
145
|
const completion = await completions.submit(() => node.destroyActor(nativeRef, 0), signal);
|
|
145
146
|
try {
|
|
@@ -161,9 +162,9 @@ class DefaultZLinkActorManager {
|
|
|
161
162
|
await this.options.locationLifecycle?.releaseActor(state.actorType, actor.actorId, current);
|
|
162
163
|
}
|
|
163
164
|
await this.finalizeDestroyedActor(actor.actorId, state);
|
|
164
|
-
});
|
|
165
|
+
}));
|
|
165
166
|
try {
|
|
166
|
-
await destroyTask;
|
|
167
|
+
await this.awaitActorDestroy(state, destroyTask);
|
|
167
168
|
}
|
|
168
169
|
catch (error) {
|
|
169
170
|
state.clearFailedDestroy(destroyTask);
|
|
@@ -381,6 +382,20 @@ class DefaultZLinkActorManager {
|
|
|
381
382
|
this.options.metrics?.change(runtime_metrics_1.METRIC_NAMES.ActorCount, -1);
|
|
382
383
|
}
|
|
383
384
|
}
|
|
385
|
+
terminateActor(state, terminal) {
|
|
386
|
+
const actor = state.actor;
|
|
387
|
+
const completion = actor === undefined || this.options.terminateActorActivation === undefined
|
|
388
|
+
? terminal()
|
|
389
|
+
: this.options.terminateActorActivation(actor, terminal);
|
|
390
|
+
return completion;
|
|
391
|
+
}
|
|
392
|
+
awaitActorDestroy(state, completion) {
|
|
393
|
+
if ((0, actor_execution_context_1.currentZLinkActorExecution)()?.actorId === state.actorId) {
|
|
394
|
+
this.detachedTaskRunner.runDetached('Actor terminal cleanup', () => completion);
|
|
395
|
+
return Promise.resolve();
|
|
396
|
+
}
|
|
397
|
+
return completion;
|
|
398
|
+
}
|
|
384
399
|
async getOrCreateActor(actorId, actorType, signal) {
|
|
385
400
|
this.rememberActorMeshFromType(actorId, actorType);
|
|
386
401
|
const result = await this.createOrGet(actorId, actorType, false, undefined, signal);
|
|
@@ -429,7 +444,7 @@ class DefaultZLinkActorManager {
|
|
|
429
444
|
if (state.isJoined) {
|
|
430
445
|
throw (0, framework_errors_internal_1.createInternalFrameworkException)(framework_errors_internal_1.ZLinkFrameworkInternalErrorKind.ActorRouteNotFound, `Actor '${actor.context.actorId}' must leave its current SPOT before destroy.`);
|
|
431
446
|
}
|
|
432
|
-
const destroyTask = state.getOrStartDestroy(entryNodeRid, async (
|
|
447
|
+
const destroyTask = state.getOrStartDestroy(entryNodeRid, (actorRef) => this.terminateActor(state, async () => {
|
|
433
448
|
const destroyedActorRef = actorRef === undefined ? undefined : (0, actor_runtime_state_1.toFrameworkActorRef)(actorRef, state.meshName ?? '');
|
|
434
449
|
if (actorRef !== undefined) {
|
|
435
450
|
await node.destroyActor(actorRef, 0, destroySignal);
|
|
@@ -439,9 +454,9 @@ class DefaultZLinkActorManager {
|
|
|
439
454
|
await this.options.locationLifecycle?.releaseActor(state.actorType, actor.context.actorId, destroyedActorRef);
|
|
440
455
|
}
|
|
441
456
|
await this.finalizeDestroyedActor(actor.context.actorId, state);
|
|
442
|
-
});
|
|
457
|
+
}));
|
|
443
458
|
try {
|
|
444
|
-
await destroyTask;
|
|
459
|
+
await this.awaitActorDestroy(state, destroyTask);
|
|
445
460
|
}
|
|
446
461
|
catch (error) {
|
|
447
462
|
state.clearFailedDestroy(destroyTask);
|
|
@@ -754,7 +754,7 @@ class ServiceStatefulRuntime {
|
|
|
754
754
|
const timeoutMs = remainingDeadlineMs(deadlineUnixMs);
|
|
755
755
|
const pending = this.operations.register(timeoutMs);
|
|
756
756
|
this.submitRequest(pending, target.targetNodeRid, instanceOperationParts([
|
|
757
|
-
(0, service_stateful_wire_codec_1.encodeInstanceSpotActivationHeader)(target, this.nodeGeneration, this.nodeRid, sourceSpotId, 'request', { high:
|
|
757
|
+
(0, service_stateful_wire_codec_1.encodeInstanceSpotActivationHeader)(target, this.nodeGeneration, this.nodeRid, sourceSpotId, 'request', { high: operation_identity_1.ZLINK_NATIVE_CORRELATION_OPERATION_NAMESPACE, low: pending.id }, deadlineUnixMs, pending.id, metadataFrame !== undefined),
|
|
758
758
|
payloadFrame
|
|
759
759
|
], metadataFrame), timeoutMs, 'instanceSpotRequest');
|
|
760
760
|
return pending;
|
|
@@ -1436,14 +1436,15 @@ class ServiceStatefulRuntime {
|
|
|
1436
1436
|
async continueMissingInstanceActivation(ingress, record, payloadFrame, localReply, metadataFrame) {
|
|
1437
1437
|
try {
|
|
1438
1438
|
this.validateInstanceIngress(ingress, record);
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1439
|
+
await this.activateMissingInstanceAsync(record, payloadFrame, metadataFrame, (activation) => {
|
|
1440
|
+
if (this.closed)
|
|
1441
|
+
return;
|
|
1442
|
+
const terminal = this.activationTerminalCompletion(record.target, activation.route);
|
|
1443
|
+
const admitted = this.enqueueActivatedInstanceSpot(ingress, record, payloadFrame, activation.spot, localReply, terminal.onTerminalCompletion, metadataFrame, terminal.onHandlerTurnStarted, this.instanceApplicationTarget(record, activation.route.objectGeneration));
|
|
1444
|
+
if (admitted !== 'application') {
|
|
1445
|
+
throw new Error('Activated Instance message was not admitted to the local queue.');
|
|
1446
|
+
}
|
|
1447
|
+
});
|
|
1447
1448
|
}
|
|
1448
1449
|
catch (error) {
|
|
1449
1450
|
this.finishMissingInstanceActivationFailure(ingress, record, error, localReply);
|
|
@@ -1738,12 +1739,23 @@ class ServiceStatefulRuntime {
|
|
|
1738
1739
|
}
|
|
1739
1740
|
return activation.spot;
|
|
1740
1741
|
}
|
|
1741
|
-
activateMissingInstanceAsync(record, payloadFrame, metadataFrame) {
|
|
1742
|
-
const key =
|
|
1742
|
+
activateMissingInstanceAsync(record, payloadFrame, metadataFrame, admit) {
|
|
1743
|
+
const key = record.target.targetSpotId;
|
|
1743
1744
|
const pending = this.pendingInstanceActivations.get(key);
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1745
|
+
const ready = pending ??
|
|
1746
|
+
this.runMissingInstanceActivation(record, payloadFrame, metadataFrame).then((activation) => ({
|
|
1747
|
+
...activation,
|
|
1748
|
+
stableType: record.target.stableType
|
|
1749
|
+
}));
|
|
1750
|
+
const operation = ready.then((value) => {
|
|
1751
|
+
if (value.stableType !== record.target.stableType) {
|
|
1752
|
+
throw new contracts_1.ZLinkFrameworkException(contracts_1.ZLinkFrameworkErrorKind.TypeMismatch, 'Instance Spot type does not match.');
|
|
1753
|
+
}
|
|
1754
|
+
admit(value);
|
|
1755
|
+
return value;
|
|
1756
|
+
});
|
|
1757
|
+
// The caller owns operation failure. Keep the activation available for later participants.
|
|
1758
|
+
const activation = operation.then((value) => value, () => ready);
|
|
1747
1759
|
this.pendingInstanceActivations.set(key, activation);
|
|
1748
1760
|
const clear = () => {
|
|
1749
1761
|
if (this.pendingInstanceActivations.get(key) === activation) {
|
|
@@ -1751,7 +1763,7 @@ class ServiceStatefulRuntime {
|
|
|
1751
1763
|
}
|
|
1752
1764
|
};
|
|
1753
1765
|
void activation.then(clear, clear);
|
|
1754
|
-
return
|
|
1766
|
+
return operation;
|
|
1755
1767
|
}
|
|
1756
1768
|
async runMissingInstanceActivation(record, payloadFrame, metadataFrame) {
|
|
1757
1769
|
const target = record.target;
|
|
@@ -3855,10 +3867,6 @@ function instanceOperationKey(record) {
|
|
|
3855
3867
|
return (`${record.sourceNodeRid}\0${record.sourceNodeGeneration}\0` +
|
|
3856
3868
|
`${record.operation.high}\0${record.operation.low}`);
|
|
3857
3869
|
}
|
|
3858
|
-
function instanceActivationKey(record) {
|
|
3859
|
-
return (`${record.target.targetNodeRid}\0${record.target.targetNodeGeneration}\0` +
|
|
3860
|
-
`${record.target.targetSpotId}\0${record.target.stableType}`);
|
|
3861
|
-
}
|
|
3862
3870
|
function subscriptionKey(channelName, topicFilter) {
|
|
3863
3871
|
if (channelName.length === 0 || topicFilter.length === 0) {
|
|
3864
3872
|
throw new TypeError('Channel name and topic filter must be non-empty.');
|
|
@@ -158,7 +158,7 @@ class LifecycleHandlerInstanceScope {
|
|
|
158
158
|
}
|
|
159
159
|
}
|
|
160
160
|
async dispose(detachedTaskRunner) {
|
|
161
|
-
const disposeFromActiveInvocation = activeLifecycleScope.getStore() === this;
|
|
161
|
+
const disposeFromActiveInvocation = this.activeInvocations > 0 && activeLifecycleScope.getStore() === this;
|
|
162
162
|
const disposal = this.beginDisposeCore();
|
|
163
163
|
if (disposal.deferred !== undefined) {
|
|
164
164
|
startOutsideLifecycleInvocation(() => {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ActorRef, RoutingId, ZLinkActorCreateResult } from '../../contracts';
|
|
2
|
-
import type { ZLinkAuthoritySnapshot, ZLinkLocationOwnerToken } from '../locations/internal-location-contracts';
|
|
2
|
+
import type { ZLinkAuthoritySnapshot, ZLinkLocationOwnerToken, ZLinkObjectReserveRequest } from '../locations/internal-location-contracts';
|
|
3
3
|
import type { ZLinkAuthorityStore, ZLinkObjectCreationStore } from '../locations/internal-store-contracts';
|
|
4
4
|
import type { ServiceActorCreateRecord } from '../foundation/service-stateful-wire-codec';
|
|
5
5
|
import type { ServiceUserSpotOperationResult } from '../foundation/service-stateful-runtime';
|
|
@@ -14,6 +14,7 @@ export interface ZLinkActorPlacementTarget {
|
|
|
14
14
|
readonly sourceNodeGeneration: bigint;
|
|
15
15
|
}
|
|
16
16
|
export interface ZLinkActorPlacementCoordinatorOptions {
|
|
17
|
+
readonly actorRelocationPolicy?: (meshName: string, stableType: string) => ZLinkObjectReserveRequest['actorRelocationPolicy'];
|
|
17
18
|
readonly store: ZLinkObjectCreationStore & ZLinkAuthorityStore;
|
|
18
19
|
readonly target: (meshName: string | undefined, stableType: string, signal?: AbortSignal, excludedNodeRids?: ReadonlySet<string>) => Promise<ZLinkActorPlacementTarget | undefined>;
|
|
19
20
|
readonly remoteCreate: (meshName: string, targetNodeRid: string, request: Omit<ServiceActorCreateRecord, 'kind' | 'correlation'>, timeoutMs: number) => Promise<ServiceUserSpotOperationResult>;
|
|
@@ -61,7 +61,8 @@ class ZLinkActorPlacementCoordinator {
|
|
|
61
61
|
},
|
|
62
62
|
target: reservationTarget(target),
|
|
63
63
|
creatingPayload,
|
|
64
|
-
capacity: { actors: 1, spots: 0 }
|
|
64
|
+
capacity: { actors: 1, spots: 0 },
|
|
65
|
+
actorRelocationPolicy: this.options.actorRelocationPolicy?.(target.meshName, stableType)
|
|
65
66
|
}, deadline.signal, BigInt(deadlineUnixMs));
|
|
66
67
|
const existing = existingActor(reserved, actorId, stableType);
|
|
67
68
|
if (existing !== undefined) {
|
|
@@ -15,6 +15,7 @@ import type { ZLinkRuntimeAdmissionGate } from '../admission';
|
|
|
15
15
|
import type { ZLinkRemoteActorPacketTarget } from '../actors';
|
|
16
16
|
import type { ZLinkActorJoinRelocation } from '../actors/actor-join-relocation';
|
|
17
17
|
export interface ZLinkActorRuntimeOptionsFactoryOptions {
|
|
18
|
+
readonly terminateActorActivation: (actor: ZLinkActor, terminal: () => Promise<void>) => Promise<void>;
|
|
18
19
|
readonly registration: ZLinkFrameworkRegistration;
|
|
19
20
|
/** Message flow tracer, so a failed deferred Join records its cause on the flow. */
|
|
20
21
|
readonly messageFlow?: () => import('../diagnostics/message-flow').ZLinkMessageFlowTracer | undefined;
|
|
@@ -61,7 +62,7 @@ export interface ZLinkActorRuntimeOptionsFactoryOptions {
|
|
|
61
62
|
export declare class ZLinkActorRuntimeOptionsFactory {
|
|
62
63
|
private readonly options;
|
|
63
64
|
constructor(options: ZLinkActorRuntimeOptionsFactoryOptions);
|
|
64
|
-
createActorManagerOptions(): Pick<ZLinkActorManagerOptions, 'joinCoordinator' | 'actorMeshNameProvider' | 'actorLeaveSpot' | 'messageSerializers' | 'nativeActorNode' | 'nativeActorNodeProvider' | 'nativeActorCompletionTableProvider' | 'actorCreatedNodeRidProvider' | 'actorRefResolver' | 'actorCreatedNotifier' | 'actorDestroyedCleanup' | 'publishActorAuthority' | 'locationLifecycle' | 'boundSessionFactory' | 'actorTransferRegistry' | 'shutdownSignal' | 'metrics' | 'admission'>;
|
|
65
|
+
createActorManagerOptions(): Pick<ZLinkActorManagerOptions, 'joinCoordinator' | 'actorMeshNameProvider' | 'actorLeaveSpot' | 'messageSerializers' | 'nativeActorNode' | 'nativeActorNodeProvider' | 'nativeActorCompletionTableProvider' | 'actorCreatedNodeRidProvider' | 'actorRefResolver' | 'actorCreatedNotifier' | 'actorDestroyedCleanup' | 'terminateActorActivation' | 'publishActorAuthority' | 'locationLifecycle' | 'boundSessionFactory' | 'actorTransferRegistry' | 'shutdownSignal' | 'metrics' | 'admission'>;
|
|
65
66
|
createActorClientOptions(): ConstructorParameters<typeof DefaultZLinkActorClient>[0];
|
|
66
67
|
private actorBelongsToMesh;
|
|
67
68
|
}
|
|
@@ -13,6 +13,7 @@ class ZLinkActorRuntimeOptionsFactory {
|
|
|
13
13
|
createActorManagerOptions() {
|
|
14
14
|
const actorTransferRegistry = this.options.actorTransferRegistry;
|
|
15
15
|
return {
|
|
16
|
+
terminateActorActivation: this.options.terminateActorActivation,
|
|
16
17
|
actorMeshNameProvider: this.options.actorMeshName,
|
|
17
18
|
actorLeaveSpot: (meshName, spotId, actor, signal) => {
|
|
18
19
|
const spotManager = this.options.spotManager();
|
|
@@ -155,7 +155,7 @@ export declare class ZLinkFrameworkRuntimeHost implements ZLinkFrameworkRuntimeL
|
|
|
155
155
|
private registerInstanceApplicationLifecycle;
|
|
156
156
|
setActorManager(actorManager: DefaultZLinkActorManager): void;
|
|
157
157
|
setSpotManager(spotManager: DefaultZLinkSpotManager): void;
|
|
158
|
-
createActorManagerOptions(): Pick<ZLinkActorManagerOptions, 'joinCoordinator' | 'actorMeshNameProvider' | 'actorLeaveSpot' | 'messageSerializers' | 'nativeActorNode' | 'nativeActorNodeProvider' | 'actorCreatedNodeRidProvider' | 'actorRefResolver' | 'actorCreatedNotifier' | 'actorDestroyedCleanup' | 'locationLifecycle' | 'boundSessionFactory' | 'shutdownSignal' | 'admission' | 'activationAdmission' | 'placementCreate'>;
|
|
158
|
+
createActorManagerOptions(): Pick<ZLinkActorManagerOptions, 'joinCoordinator' | 'actorMeshNameProvider' | 'actorLeaveSpot' | 'messageSerializers' | 'nativeActorNode' | 'nativeActorNodeProvider' | 'actorCreatedNodeRidProvider' | 'actorRefResolver' | 'actorCreatedNotifier' | 'actorDestroyedCleanup' | 'terminateActorActivation' | 'locationLifecycle' | 'boundSessionFactory' | 'shutdownSignal' | 'admission' | 'activationAdmission' | 'placementCreate'>;
|
|
159
159
|
createActorClientOptions(): ConstructorParameters<typeof DefaultZLinkActorClient>[0];
|
|
160
160
|
createLocationHandleResolver(): ZLinkStoreLocationResolvers | undefined;
|
|
161
161
|
createSpotManagerOptions(): Partial<ZLinkSpotManagerOptions>;
|
|
@@ -1511,6 +1511,8 @@ class ZLinkFrameworkRuntimeHost {
|
|
|
1511
1511
|
}
|
|
1512
1512
|
this.actorPlacement ??= new actor_placement_coordinator_1.ZLinkActorPlacementCoordinator({
|
|
1513
1513
|
store: locationStore,
|
|
1514
|
+
actorRelocationPolicy: (meshName, stableType) => this.options.registration.spotNodes.get(meshName)?.actorFactoryRegistrations?.[stableType]
|
|
1515
|
+
?.relocation.kind,
|
|
1514
1516
|
remoteCreate: (meshName, targetNodeRid, request, timeoutMs) => {
|
|
1515
1517
|
const node = this.spotNodeRuntime?.meshNode(meshName);
|
|
1516
1518
|
if (node === undefined) {
|
|
@@ -1546,13 +1548,11 @@ class ZLinkFrameworkRuntimeHost {
|
|
|
1546
1548
|
const descriptors = await (0, location_store_pages_1.listAllMeshNodeDescriptors)(locationStore, meshName, signal);
|
|
1547
1549
|
const selected = await selectReadyLocationPlacementDescriptor(descriptors, meshNode, signal, (descriptor) => {
|
|
1548
1550
|
const capability = descriptor.objectCapabilities.find((candidate) => candidate.objectKind === 'actor' && candidate.stableType === stableType);
|
|
1549
|
-
const actors = descriptor.populationCapacity.actors;
|
|
1550
1551
|
return (descriptor.state === contracts_1.ZLinkFrameworkRuntimeState.Serving &&
|
|
1551
1552
|
descriptor.objectRole === 'server' &&
|
|
1552
1553
|
descriptor.placementWeight > 0 &&
|
|
1553
1554
|
descriptor.entrySpotId !== undefined &&
|
|
1554
1555
|
excludedNodeRids?.has(String(descriptor.rid)) !== true &&
|
|
1555
|
-
(actors.limit === 0 || actors.active + actors.reserved < actors.limit) &&
|
|
1556
1556
|
capability !== undefined);
|
|
1557
1557
|
});
|
|
1558
1558
|
if (selected === undefined)
|
|
@@ -1832,6 +1832,7 @@ class ZLinkFrameworkRuntimeHost {
|
|
|
1832
1832
|
}
|
|
1833
1833
|
actorRuntimeOptionsFactory() {
|
|
1834
1834
|
return new actor_runtime_options_factory_1.ZLinkActorRuntimeOptionsFactory({
|
|
1835
|
+
terminateActorActivation: (actor, terminal) => this.spotNodeRuntime?.terminateActorActivation(actor, terminal) ?? terminal(),
|
|
1835
1836
|
registration: this.options.registration,
|
|
1836
1837
|
messageFlow: () => this.createDispatchErrorReporter(this.runtimeOrPreStartErrorSink).flow,
|
|
1837
1838
|
routeTransport: this.routeTransport,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { ZLinkLocationOwnerToken, ZLinkLocationPage, ZLinkLocationWriteIntent, ZLinkLocationWriteResult, ZLinkLocationWriteStatus, ZLinkMeshNodeDescriptor, ZLinkMeshNodeDescriptorKey, ZLinkClientServerServerDescriptor, ZLinkClientServerServerDescriptorKey, ZLinkFanoutPublisherDescriptor, ZLinkFanoutPublisherDescriptorKey, ZLinkOwnerLeaseClaimResult, ZLinkOwnerLeaseReadResult, ZLinkOwnerLeaseReleaseResult, ZLinkOwnerLeaseRenewResult, ZLinkPageRequest } from '../../contracts/Locations/Models';
|
|
2
|
-
import type { ZLinkAggregateAbortResult, ZLinkAggregateCommitResult, ZLinkAggregateFence, ZLinkAggregatePrepareRequest, ZLinkAggregatePrepareResult, ZLinkAuthorityCompareExchangeResult, ZLinkAuthorityKey, ZLinkAuthorityMutation, ZLinkAuthorityReadResult, ZLinkAuthorityScanCursor, ZLinkAuthorityScanResult, ZLinkAuthorityStoreVersion, ZLinkCreationOperationIdentity, ZLinkCreationTerminalReadResult, ZLinkObjectAbortRequest, ZLinkObjectAbortResult, ZLinkObjectCommitRequest, ZLinkObjectCommitResult, ZLinkObjectCreationCompleteRequest, ZLinkObjectCreationCompleteResult,
|
|
2
|
+
import type { ZLinkAggregateAbortResult, ZLinkAggregateCommitResult, ZLinkAggregateFence, ZLinkAggregatePrepareRequest, ZLinkAggregatePrepareResult, ZLinkAuthorityCompareExchangeResult, ZLinkAuthorityKey, ZLinkAuthorityMutation, ZLinkAuthorityReadResult, ZLinkAuthorityScanCursor, ZLinkAuthorityScanResult, ZLinkAuthorityStoreVersion, ZLinkCreationOperationIdentity, ZLinkCreationTerminalReadResult, ZLinkObjectAbortRequest, ZLinkObjectAbortResult, ZLinkObjectCommitRequest, ZLinkObjectCommitResult, ZLinkObjectCreationCompleteRequest, ZLinkObjectCreationCompleteResult, ZLinkObjectReserveResult } from '../../contracts/Locations/Authority';
|
|
3
|
+
import type { ZLinkObjectReserveRequest } from './internal-location-contracts';
|
|
3
4
|
/**
|
|
4
5
|
* Framework-private domain repository. Provider implementations never
|
|
5
6
|
* implement this interface; the framework maps it to the opaque Store SPI.
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { ZLinkAggregateAbortResult, ZLinkAggregateCommitResult, ZLinkAggregateFence, ZLinkAggregatePrepareRequest, ZLinkAggregatePrepareResult, ZLinkAuthorityCompareExchangeResult, ZLinkAuthorityKey, ZLinkAuthorityMutation, ZLinkAuthorityReadResult, ZLinkAuthorityScanCursor, ZLinkAuthorityScanResult, ZLinkAuthorityStoreVersion, ZLinkCapacityVector, ZLinkLocationOwnerToken, ZLinkMeshNodeDescriptorKey, ZLinkObjectAbortRequest, ZLinkObjectAbortResult, ZLinkObjectCommitRequest, ZLinkObjectCommitResult, ZLinkObjectCreationCompleteRequest, ZLinkObjectCreationCompleteResult, ZLinkObjectReserveRequest, ZLinkObjectReserveResult, ZLinkCreationOperationIdentity, ZLinkCreationTerminalReadResult } from './internal-location-contracts';
|
|
2
2
|
export interface ZLinkInMemoryAuthorityValidation {
|
|
3
|
+
isOwnerLive(owner: ZLinkLocationOwnerToken): boolean;
|
|
3
4
|
isTargetLive(descriptor: ZLinkMeshNodeDescriptorKey, lifecycleGeneration: bigint, owner: ZLinkLocationOwnerToken): boolean;
|
|
4
5
|
placementCapacityAvailable?(descriptor: ZLinkMeshNodeDescriptorKey, requested: ZLinkCapacityVector, currentReserved: ZLinkCapacityVector, currentActive: ZLinkCapacityVector): boolean;
|
|
5
6
|
identityClaimed?(authorityKey: string): boolean;
|
|
@@ -6,12 +6,15 @@ const stream_wire_1 = require("@zlink-systems/stream-wire");
|
|
|
6
6
|
const CoreTypes_1 = require("../../contracts/Common/CoreTypes");
|
|
7
7
|
const Stores_1 = require("../../contracts/Locations/Stores");
|
|
8
8
|
const node_crypto_1 = require("node:crypto");
|
|
9
|
+
const contracts_1 = require("../../contracts");
|
|
9
10
|
const actor_join_recovery_codec_1 = require("../foundation/actor-join-recovery-codec");
|
|
10
11
|
const authority_key_codec_1 = require("./authority-key-codec");
|
|
11
12
|
const opaque_record_key_1 = require("./opaque-record-key");
|
|
13
|
+
const service_relocation_runtime_1 = require("../foundation/service-relocation-runtime");
|
|
12
14
|
const MAX_GENERATION = 0x7fffffffffffffffn;
|
|
13
15
|
const CREATION_TERMINAL_RETENTION_MS = 5 * 60 * 1000;
|
|
14
16
|
const MAX_U64 = stream_wire_1.UINT64_MAX;
|
|
17
|
+
const relocationAuthorityCodec = new service_relocation_runtime_1.ServiceRelocationAuthorityPayloadCodec();
|
|
15
18
|
/**
|
|
16
19
|
* In-memory reference provider for the provider-owned authority state machine.
|
|
17
20
|
* Opaque payload bytes are copied but never parsed.
|
|
@@ -175,20 +178,39 @@ class ZLinkInMemoryAuthorityStore {
|
|
|
175
178
|
signal?.throwIfAborted();
|
|
176
179
|
return this.tryReleaseEndedReservation(key, expectedStoreVersion);
|
|
177
180
|
}
|
|
178
|
-
tryReleaseEndedReservation(key, expectedStoreVersion) {
|
|
181
|
+
tryReleaseEndedReservation(key, expectedStoreVersion, request) {
|
|
179
182
|
const current = this.rows.get(key.value);
|
|
180
183
|
if (current === undefined ||
|
|
181
184
|
current.snapshot.storeVersion.value !== expectedStoreVersion ||
|
|
182
|
-
current.snapshot.allocation.state
|
|
183
|
-
|
|
184
|
-
|
|
185
|
+
(current.snapshot.allocation.state === 'active'
|
|
186
|
+
? request?.key.kind !== 'actor'
|
|
187
|
+
: current.creation === undefined))
|
|
188
|
+
return false;
|
|
189
|
+
if (current.snapshot.allocation.state === 'active') {
|
|
190
|
+
for (const aggregate of this.aggregates.values()) {
|
|
191
|
+
if (aggregate.state === 'prepared' &&
|
|
192
|
+
aggregate.request.participants.some((participant) => participant.authorityKey.value === key.value))
|
|
193
|
+
return false;
|
|
194
|
+
}
|
|
195
|
+
if (relocationAuthorityCodec.read(current.snapshot.payload) !== undefined)
|
|
196
|
+
return false;
|
|
197
|
+
if (this.validation.isOwnerLive({
|
|
185
198
|
ownerId: current.snapshot.ownerId,
|
|
186
199
|
leaseGeneration: current.snapshot.ownerLeaseGeneration
|
|
187
200
|
}))
|
|
201
|
+
return false;
|
|
202
|
+
if (request?.actorRelocationPolicy !== 'disabled')
|
|
203
|
+
throw new contracts_1.ZLinkFrameworkException(contracts_1.ZLinkFrameworkErrorKind.Unavailable, 'Actor owner lease is unavailable.');
|
|
204
|
+
}
|
|
205
|
+
else if (this.validation.isTargetLive(current.snapshot.allocation.descriptor, current.snapshot.allocation.descriptorLifecycleGeneration, {
|
|
206
|
+
ownerId: current.snapshot.ownerId,
|
|
207
|
+
leaseGeneration: current.snapshot.ownerLeaseGeneration
|
|
208
|
+
}))
|
|
188
209
|
return false;
|
|
189
|
-
this.adjustCapacity(this.pendingCapacity, current.snapshot.allocation, -1);
|
|
210
|
+
this.adjustCapacity(current.snapshot.allocation.state === 'active' ? this.activeCapacity : this.pendingCapacity, current.snapshot.allocation, -1);
|
|
190
211
|
this.rows.delete(key.value);
|
|
191
|
-
|
|
212
|
+
if (current.snapshot.allocation.state === 'reserved' && current.creation !== undefined)
|
|
213
|
+
this.creationTerminals.set(current.creation.reservationId, 'aborted');
|
|
192
214
|
this.scanRevision++;
|
|
193
215
|
return true;
|
|
194
216
|
}
|
|
@@ -205,11 +227,10 @@ class ZLinkInMemoryAuthorityStore {
|
|
|
205
227
|
current.snapshot.allocation.stableType !== request.intent.stableType) {
|
|
206
228
|
return { kind: 'typeMismatch', current: this.snapshot(current.snapshot) };
|
|
207
229
|
}
|
|
208
|
-
if (current.snapshot.
|
|
209
|
-
return
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
return { kind: 'conflict', current: this.read(key) };
|
|
230
|
+
if (!this.tryReleaseEndedReservation((0, authority_key_codec_1.encodeAuthorityKey)(request.key.kind, request.key.globalId), current.snapshot.storeVersion.value, request))
|
|
231
|
+
return current.snapshot.allocation.state === 'active'
|
|
232
|
+
? { kind: 'alreadyExists', current: this.snapshot(current.snapshot) }
|
|
233
|
+
: { kind: 'conflict', current: this.read(key) };
|
|
213
234
|
}
|
|
214
235
|
const target = creationTarget(request);
|
|
215
236
|
if (!this.validation.isTargetLive(target.descriptor, target.lifecycleGeneration, target.owner)) {
|
|
@@ -72,7 +72,7 @@ export declare class ZLinkInMemoryLocationStore implements ZLinkDomainLocationSt
|
|
|
72
72
|
private transitionActorTransfer;
|
|
73
73
|
private remove;
|
|
74
74
|
private removeByOwner;
|
|
75
|
-
private
|
|
75
|
+
private liveOwnerLease;
|
|
76
76
|
private bump;
|
|
77
77
|
private removeFanoutByOwner;
|
|
78
78
|
private bumpScope;
|
|
@@ -27,19 +27,19 @@ class ZLinkInMemoryLocationStore {
|
|
|
27
27
|
authority;
|
|
28
28
|
constructor(now = () => new Date()) {
|
|
29
29
|
this.now = now;
|
|
30
|
+
const isOwnerLive = (owner) => this.liveOwnerLease(owner.ownerId, this.now())?.token.leaseGeneration ===
|
|
31
|
+
owner.leaseGeneration;
|
|
30
32
|
this.authority = new in_memory_authority_store_1.ZLinkInMemoryAuthorityStore({
|
|
33
|
+
isOwnerLive,
|
|
31
34
|
isTargetLive: (key, lifecycleGeneration, owner) => {
|
|
32
35
|
const descriptor = this.meshNodes.rows.get(meshNodeKey(key.meshName, key.rid));
|
|
33
|
-
const lease = this.leases.get(owner.ownerId);
|
|
34
36
|
return (descriptor !== undefined &&
|
|
35
37
|
descriptor.lifecycleGeneration === lifecycleGeneration &&
|
|
36
38
|
descriptor.ownerId === owner.ownerId &&
|
|
37
39
|
descriptor.leaseGeneration === owner.leaseGeneration &&
|
|
38
40
|
descriptor.objectRole === internal_location_contracts_1.ZLinkObjectRole.Server &&
|
|
39
41
|
descriptor.state === internal_location_contracts_1.ZLinkFrameworkRuntimeState.Serving &&
|
|
40
|
-
|
|
41
|
-
lease.token.leaseGeneration === owner.leaseGeneration &&
|
|
42
|
-
lease.leaseExpiresAt.getTime() > this.now().getTime());
|
|
42
|
+
isOwnerLive(owner));
|
|
43
43
|
},
|
|
44
44
|
placementCapacityAvailable: (key, requested, reserved, active) => {
|
|
45
45
|
const descriptor = this.meshNodes.rows.get(meshNodeKey(key.meshName, key.rid));
|
|
@@ -316,7 +316,7 @@ class ZLinkInMemoryLocationStore {
|
|
|
316
316
|
async listFanoutPublishers(channelName, page = {}, signal) {
|
|
317
317
|
signal?.throwIfAborted();
|
|
318
318
|
const storeNow = this.now();
|
|
319
|
-
return pageRows(this.fanoutPublishers, (row) => row.channelName === channelName && this.
|
|
319
|
+
return pageRows(this.fanoutPublishers, (row) => row.channelName === channelName && this.liveOwnerLease(row.ownerId, storeNow) !== undefined, page);
|
|
320
320
|
}
|
|
321
321
|
async updatePeer(peer, intent) {
|
|
322
322
|
return this.write(this.peers, key_codec_1.ZLinkLocationKeyCodec.encodePeerKey({
|
|
@@ -342,7 +342,7 @@ class ZLinkInMemoryLocationStore {
|
|
|
342
342
|
const current = this.spots.rows.get(key);
|
|
343
343
|
if (intent === internal_location_contracts_1.ZLinkLocationWriteIntent.NewClaim &&
|
|
344
344
|
current !== undefined &&
|
|
345
|
-
this.
|
|
345
|
+
this.liveOwnerLease(current.ownerId, updatedAt) !== undefined) {
|
|
346
346
|
return rejectedConflict();
|
|
347
347
|
}
|
|
348
348
|
if (intent === internal_location_contracts_1.ZLinkLocationWriteIntent.Renew) {
|
|
@@ -391,7 +391,7 @@ class ZLinkInMemoryLocationStore {
|
|
|
391
391
|
const current = this.actors.rows.get(key);
|
|
392
392
|
if (intent === internal_location_contracts_1.ZLinkLocationWriteIntent.NewClaim &&
|
|
393
393
|
current !== undefined &&
|
|
394
|
-
this.
|
|
394
|
+
this.liveOwnerLease(current.ownerId, updatedAt) !== undefined) {
|
|
395
395
|
return rejectedConflict();
|
|
396
396
|
}
|
|
397
397
|
if (intent === internal_location_contracts_1.ZLinkLocationWriteIntent.Renew) {
|
|
@@ -651,7 +651,7 @@ class ZLinkInMemoryLocationStore {
|
|
|
651
651
|
const current = table.rows.get(key);
|
|
652
652
|
if (intent === internal_location_contracts_1.ZLinkLocationWriteIntent.NewClaim &&
|
|
653
653
|
current !== undefined &&
|
|
654
|
-
this.
|
|
654
|
+
this.liveOwnerLease(ownerOf(current), updatedAt) !== undefined) {
|
|
655
655
|
return rejectedConflict();
|
|
656
656
|
}
|
|
657
657
|
if (intent === internal_location_contracts_1.ZLinkLocationWriteIntent.NewClaim ||
|
|
@@ -714,9 +714,11 @@ class ZLinkInMemoryLocationStore {
|
|
|
714
714
|
}
|
|
715
715
|
return removed;
|
|
716
716
|
}
|
|
717
|
-
|
|
717
|
+
liveOwnerLease(ownerId, now) {
|
|
718
718
|
const lease = this.leases.get(ownerId);
|
|
719
|
-
return lease !== undefined && lease.leaseExpiresAt.getTime() > now.getTime()
|
|
719
|
+
return lease !== undefined && lease.leaseExpiresAt.getTime() > now.getTime()
|
|
720
|
+
? lease
|
|
721
|
+
: undefined;
|
|
720
722
|
}
|
|
721
723
|
bump(kind, meshName) {
|
|
722
724
|
this.bumpScope({ kind, meshName });
|
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
export * from '../../contracts/Locations';
|
|
2
|
+
import type { ZLinkObjectReserveRequest as ReserveRequest } from '../../contracts/Locations/Authority';
|
|
3
|
+
import type { ZLinkRelocationConfiguration } from '../../contracts/Configuration/RegistrationTypes';
|
|
4
|
+
export interface ZLinkObjectReserveRequest extends ReserveRequest {
|
|
5
|
+
readonly actorRelocationPolicy?: ZLinkRelocationConfiguration<unknown>['kind'];
|
|
6
|
+
}
|
|
2
7
|
export type * from '../../contracts/Locations/Authority';
|
|
3
8
|
export type { ZLinkActorLocation, ZLinkPeerLocation, ZLinkRouteLocation, ZLinkSpotLocation } from '../../contracts/Locations/Rows';
|
|
4
9
|
export type { ZLinkActorLocationFilter, ZLinkActorLocationKey, ZLinkLocationKey, ZLinkPeerLocationFilter, ZLinkPeerLocationKey, ZLinkRouteLocationFilter, ZLinkRouteLocationKey, ZLinkSpotLocationFilter, ZLinkSpotLocationKey } from '../../contracts/Locations/Keys';
|
|
@@ -21,6 +21,7 @@ export declare class ZLinkLocationStoreRepository extends ZLinkInMemoryLocationS
|
|
|
21
21
|
private commitAggregateCore;
|
|
22
22
|
abortAggregate(fence: ZLinkAggregateFence, signal?: AbortSignal): Promise<ZLinkAggregateAbortResult>;
|
|
23
23
|
releaseEndedReservation(key: ZLinkAuthorityKey, expectedStoreVersion: string, signal?: AbortSignal): Promise<boolean>;
|
|
24
|
+
private tryReleaseEndedReservation;
|
|
24
25
|
reserve(request: ZLinkObjectReserveRequest, signal?: AbortSignal, deadlineUnixMs?: bigint): Promise<ZLinkObjectReserveResult>;
|
|
25
26
|
commit(request: ZLinkObjectCommitRequest, signal?: AbortSignal, deadlineUnixMs?: bigint): Promise<ZLinkObjectCommitResult>;
|
|
26
27
|
abort(request: ZLinkObjectAbortRequest, signal?: AbortSignal, deadlineUnixMs?: bigint): Promise<ZLinkObjectAbortResult>;
|
|
@@ -815,17 +815,21 @@ class ZLinkLocationStoreRepository extends in_memory_location_store_1.ZLinkInMem
|
|
|
815
815
|
}
|
|
816
816
|
}
|
|
817
817
|
async releaseEndedReservation(key, expectedStoreVersion, signal) {
|
|
818
|
+
return ((await this.tryReleaseEndedReservation(key, expectedStoreVersion, signal)) === 'reclaimed');
|
|
819
|
+
}
|
|
820
|
+
async tryReleaseEndedReservation(key, expectedStoreVersion, signal, request) {
|
|
818
821
|
const rowKey = authorityKey(key.value);
|
|
819
822
|
const current = await this.provider.read(rowKey, signal);
|
|
820
823
|
if (current.kind === 'missing' || current.value.version.value !== expectedStoreVersion)
|
|
821
|
-
return
|
|
824
|
+
return 'conflict';
|
|
822
825
|
const record = decodeAuthorityRecord(current.value.bytes);
|
|
823
826
|
const snapshot = record.snapshot;
|
|
824
|
-
if (snapshot.allocation.state
|
|
825
|
-
|
|
827
|
+
if ((snapshot.allocation.state === 'active'
|
|
828
|
+
? request?.key.kind !== 'actor'
|
|
829
|
+
: snapshot.pendingCreation === undefined) ||
|
|
826
830
|
record.aggregate !== undefined ||
|
|
827
831
|
hasRelocationAuthority(snapshot.payload))
|
|
828
|
-
return
|
|
832
|
+
return 'notReclaimable';
|
|
829
833
|
const leaseKey = ownerKey(snapshot.ownerId);
|
|
830
834
|
const descriptorKey = meshKey(snapshot.allocation.descriptor.meshName, snapshot.allocation.descriptor.rid);
|
|
831
835
|
const capacityRowKey = capacityKey(snapshot.allocation.descriptor.meshName, String(snapshot.allocation.descriptor.rid));
|
|
@@ -836,23 +840,46 @@ class ZLinkLocationStoreRepository extends in_memory_location_store_1.ZLinkInMem
|
|
|
836
840
|
]);
|
|
837
841
|
if (lease.kind === 'found' && lease.value.expiresAt === undefined)
|
|
838
842
|
throw new Error('Location Store owner lease record is invalid.');
|
|
839
|
-
|
|
843
|
+
const ownerLive = sameLiveOwner(lease, snapshot);
|
|
844
|
+
if (snapshot.allocation.state === 'active') {
|
|
845
|
+
if (ownerLive)
|
|
846
|
+
return 'notReclaimable';
|
|
847
|
+
if (request?.actorRelocationPolicy !== 'disabled')
|
|
848
|
+
throw new contracts_1.ZLinkFrameworkException(contracts_1.ZLinkFrameworkErrorKind.Unavailable, 'Actor owner lease is unavailable.');
|
|
849
|
+
}
|
|
850
|
+
else if (ownerLive &&
|
|
840
851
|
descriptor.kind === 'found' &&
|
|
841
852
|
reviveMeshDescriptor(decodeCanonicalDescriptorRecord(descriptor.value.bytes).descriptor).lifecycleGeneration === snapshot.allocation.descriptorLifecycleGeneration)
|
|
842
|
-
return
|
|
853
|
+
return 'notReclaimable';
|
|
843
854
|
const mutations = [
|
|
844
855
|
{ kind: 'delete', key: rowKey }
|
|
845
856
|
];
|
|
846
857
|
if (capacity.kind === 'found') {
|
|
847
858
|
const stored = decodeJson(capacity.value.bytes);
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
859
|
+
try {
|
|
860
|
+
mutations.push({
|
|
861
|
+
kind: 'put',
|
|
862
|
+
key: capacityRowKey,
|
|
863
|
+
bytes: encodeJson({
|
|
864
|
+
active: snapshot.allocation.state === 'active'
|
|
865
|
+
? subtractCapacity(stored.active, snapshot.allocation.capacity)
|
|
866
|
+
: stored.active,
|
|
867
|
+
pending: snapshot.allocation.state === 'reserved'
|
|
868
|
+
? subtractCapacity(stored.pending, snapshot.allocation.capacity)
|
|
869
|
+
: stored.pending
|
|
870
|
+
})
|
|
871
|
+
});
|
|
872
|
+
}
|
|
873
|
+
catch (error) {
|
|
874
|
+
if (!(error instanceof contracts_1.ZLinkFrameworkException) ||
|
|
875
|
+
error.kind !== contracts_1.ZLinkFrameworkErrorKind.DataLost)
|
|
876
|
+
throw error;
|
|
877
|
+
const observed = await this.provider.read(rowKey, signal);
|
|
878
|
+
if (observed.kind === 'missing' ||
|
|
879
|
+
observed.value.version.value !== current.value.version.value)
|
|
880
|
+
return 'conflict';
|
|
881
|
+
throw error;
|
|
882
|
+
}
|
|
856
883
|
}
|
|
857
884
|
const result = await this.provider.write({
|
|
858
885
|
conditions: [
|
|
@@ -863,11 +890,12 @@ class ZLinkLocationStoreRepository extends in_memory_location_store_1.ZLinkInMem
|
|
|
863
890
|
],
|
|
864
891
|
mutations
|
|
865
892
|
}, signal);
|
|
866
|
-
return result.kind === 'applied';
|
|
893
|
+
return result.kind === 'applied' ? 'reclaimed' : 'conflict';
|
|
867
894
|
}
|
|
868
895
|
async reserve(request, signal, deadlineUnixMs) {
|
|
869
896
|
const encodedAuthorityKey = (0, authority_key_codec_1.encodeAuthorityKey)(request.key.kind, request.key.globalId);
|
|
870
897
|
const reservationId = (0, node_crypto_1.randomUUID)();
|
|
898
|
+
let reclaimVersion;
|
|
871
899
|
for (;;) {
|
|
872
900
|
throwIfOperationExpired(signal, deadlineUnixMs);
|
|
873
901
|
const rowKey = authorityKey(encodedAuthorityKey.value);
|
|
@@ -882,13 +910,18 @@ class ZLinkLocationStoreRepository extends in_memory_location_store_1.ZLinkInMem
|
|
|
882
910
|
snapshot.allocation.stableType !== request.intent.stableType) {
|
|
883
911
|
return { kind: 'typeMismatch', current: snapshot };
|
|
884
912
|
}
|
|
885
|
-
if (snapshot.
|
|
886
|
-
return { kind: 'alreadyExists', current: snapshot };
|
|
887
|
-
}
|
|
888
|
-
if (!(await this.releaseEndedReservation(encodedAuthorityKey, current.value.version.value, signal)))
|
|
913
|
+
if (reclaimVersion !== undefined && snapshot.storeVersion.value !== reclaimVersion)
|
|
889
914
|
return { kind: 'conflict', current: snapshot };
|
|
915
|
+
const reclaim = await this.tryReleaseEndedReservation(encodedAuthorityKey, current.value.version.value, signal, request);
|
|
916
|
+
if (reclaim === 'notReclaimable')
|
|
917
|
+
return snapshot.allocation.state === 'active'
|
|
918
|
+
? { kind: 'alreadyExists', current: snapshot }
|
|
919
|
+
: { kind: 'conflict', current: snapshot };
|
|
920
|
+
reclaimVersion = reclaim === 'conflict' ? snapshot.storeVersion.value : undefined;
|
|
890
921
|
continue;
|
|
891
922
|
}
|
|
923
|
+
if (reclaimVersion !== undefined)
|
|
924
|
+
return { kind: 'conflict', current: { kind: 'missing', storeNow: current.storeNow } };
|
|
892
925
|
const [descriptorRead, leaseRead] = await Promise.all([
|
|
893
926
|
this.provider.read(descriptorKey, signal),
|
|
894
927
|
this.provider.read(leaseKey, signal)
|
|
@@ -2941,7 +2974,7 @@ function adjustCapacity(usage, delta, direction) {
|
|
|
2941
2974
|
const key = capacityTypeKey(delta.spotType);
|
|
2942
2975
|
const next = (spotTypes[key] ?? 0) + direction * delta.spotType.count;
|
|
2943
2976
|
if (next < 0)
|
|
2944
|
-
throw new
|
|
2977
|
+
throw new contracts_1.ZLinkFrameworkException(contracts_1.ZLinkFrameworkErrorKind.DataLost, 'Provider capacity counter underflow.');
|
|
2945
2978
|
if (next === 0)
|
|
2946
2979
|
delete spotTypes[key];
|
|
2947
2980
|
else
|
|
@@ -2950,7 +2983,7 @@ function adjustCapacity(usage, delta, direction) {
|
|
|
2950
2983
|
const actors = usage.actors + direction * delta.actors;
|
|
2951
2984
|
const spots = usage.spots + direction * delta.spots;
|
|
2952
2985
|
if (actors < 0 || spots < 0)
|
|
2953
|
-
throw new
|
|
2986
|
+
throw new contracts_1.ZLinkFrameworkException(contracts_1.ZLinkFrameworkErrorKind.DataLost, 'Provider capacity counter underflow.');
|
|
2954
2987
|
return { actors, spots, spotTypes };
|
|
2955
2988
|
}
|
|
2956
2989
|
function capacityTypeKey(value) {
|