@zlink-systems/framework 0.28.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/contracts/Configuration/Builders.d.ts +2 -0
- package/dist/contracts/Configuration/InternalDefaults.d.ts +3 -0
- package/dist/contracts/Configuration/InternalDefaults.js +4 -1
- package/dist/contracts/Configuration/RegistrationBuilders.js +9 -0
- package/dist/contracts/Configuration/RegistrationNormalizers.d.ts +1 -0
- package/dist/contracts/Configuration/RegistrationNormalizers.js +16 -0
- package/dist/contracts/Configuration/RegistrationTypes.d.ts +3 -0
- package/dist/runtime/actors/actor-mailbox.d.ts +2 -2
- package/dist/runtime/actors/actor-mailbox.js +2 -2
- 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/backend/node/node-raw-mesh-backend.js +1 -3
- package/dist/runtime/execution/serial-execution-queue.d.ts +12 -3
- package/dist/runtime/execution/serial-execution-queue.js +116 -28
- package/dist/runtime/foundation/operation-registry.d.ts +3 -2
- package/dist/runtime/foundation/operation-registry.js +5 -1
- package/dist/runtime/foundation/service-authority-payload-codec.d.ts +0 -2
- package/dist/runtime/foundation/service-authority-payload-codec.js +0 -43
- package/dist/runtime/foundation/service-stateful-registry.d.ts +4 -3
- package/dist/runtime/foundation/service-stateful-registry.js +5 -2
- package/dist/runtime/foundation/service-stateful-runtime.d.ts +1 -8
- package/dist/runtime/foundation/service-stateful-runtime.js +105 -145
- package/dist/runtime/foundation/service-stateful-wire-codec.d.ts +0 -11
- package/dist/runtime/foundation/service-stateful-wire-codec.js +1 -23
- package/dist/runtime/foundation/service-topology-registry.d.ts +5 -0
- package/dist/runtime/foundation/service-topology-registry.js +16 -17
- 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/host/instance-activation-authority.d.ts +0 -1
- package/dist/runtime/host/instance-activation-authority.js +46 -97
- package/dist/runtime/host/spot-address-transport.js +1 -1
- package/dist/runtime/host/stateful-authority-route-runtime.d.ts +1 -0
- package/dist/runtime/host/stateful-authority-route-runtime.js +17 -9
- package/dist/runtime/locations/domain-store-contract.d.ts +3 -1
- package/dist/runtime/locations/in-memory-authority-store.d.ts +3 -0
- package/dist/runtime/locations/in-memory-authority-store.js +47 -12
- package/dist/runtime/locations/in-memory-location-store.d.ts +2 -1
- package/dist/runtime/locations/in-memory-location-store.js +15 -10
- package/dist/runtime/locations/internal-location-contracts.d.ts +5 -0
- package/dist/runtime/locations/internal-store-contracts.d.ts +1 -1
- package/dist/runtime/locations/location-store-repository.d.ts +2 -0
- package/dist/runtime/locations/location-store-repository.js +130 -54
- 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 +14 -2
- package/dist/runtime/spots/spot-serial-turn-executor.d.ts +3 -0
- package/dist/runtime/spots/spot-serial-turn-executor.js +5 -0
- package/dist/runtime/streams/index.d.ts +0 -1
- package/dist/runtime/streams/index.js +4 -3
- package/dist/runtime/streams/protocol.js +0 -9
- package/dist/runtime/streams/session-context.d.ts +0 -4
- package/dist/runtime/streams/session-context.js +0 -26
- package/dist/runtime/streams/session-serial-executor.d.ts +0 -1
- package/dist/runtime/streams/session-serial-executor.js +0 -3
- package/dist/runtime/streams/stream-message-utils.d.ts +0 -1
- package/dist/runtime/streams/stream-message-utils.js +0 -20
- package/dist/runtime/streams/stream-session-runtime.d.ts +6 -3
- package/dist/runtime/streams/stream-session-runtime.js +40 -85
- package/package.json +3 -3
- package/src/contracts/Configuration/Builders.ts +2 -0
- package/src/contracts/Configuration/InternalDefaults.ts +4 -0
- package/src/contracts/Configuration/RegistrationBuilders.ts +13 -0
- package/src/contracts/Configuration/RegistrationNormalizers.ts +25 -0
- package/src/contracts/Configuration/RegistrationTypes.ts +3 -0
- package/src/runtime/actors/actor-mailbox.ts +7 -2
- package/src/runtime/actors/actor-runtime-contracts.ts +4 -0
- package/src/runtime/actors/index.ts +77 -42
- package/src/runtime/backend/node/node-raw-mesh-backend.ts +1 -7
- package/src/runtime/execution/serial-execution-queue.ts +117 -29
- package/src/runtime/foundation/operation-registry.ts +16 -9
- package/src/runtime/foundation/service-authority-payload-codec.ts +0 -46
- package/src/runtime/foundation/service-stateful-registry.ts +10 -5
- package/src/runtime/foundation/service-stateful-runtime.ts +152 -224
- package/src/runtime/foundation/service-stateful-wire-codec.ts +0 -25
- package/src/runtime/foundation/service-topology-registry.ts +30 -20
- 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/host/instance-activation-authority.ts +58 -125
- package/src/runtime/host/spot-address-transport.ts +1 -1
- package/src/runtime/host/stateful-authority-route-runtime.ts +22 -9
- package/src/runtime/locations/domain-store-contract.ts +6 -1
- package/src/runtime/locations/in-memory-authority-store.ts +83 -12
- package/src/runtime/locations/in-memory-location-store.ts +22 -10
- package/src/runtime/locations/internal-location-contracts.ts +6 -0
- package/src/runtime/locations/internal-store-contracts.ts +1 -0
- package/src/runtime/locations/location-store-repository.ts +182 -69
- package/src/runtime/protocol/service_wire_pilot_codec.generated.ts +1 -1
- 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 +14 -5
- package/src/runtime/spots/spot-serial-turn-executor.ts +10 -0
- package/src/runtime/streams/index.ts +3 -1
- package/src/runtime/streams/protocol.ts +0 -13
- package/src/runtime/streams/session-context.ts +0 -32
- package/src/runtime/streams/session-serial-executor.ts +0 -8
- package/src/runtime/streams/stream-message-utils.ts +1 -27
- package/src/runtime/streams/stream-session-runtime.ts +54 -97
- package/dist/runtime/streams/session-requests.d.ts +0 -25
- package/dist/runtime/streams/session-requests.js +0 -101
- package/src/runtime/streams/session-requests.ts +0 -110
|
@@ -167,8 +167,6 @@ export type ServiceWireRelocationObject =
|
|
|
167
167
|
readonly objectGeneration: bigint;
|
|
168
168
|
};
|
|
169
169
|
|
|
170
|
-
/** Shared CRC-32C wire bounds for the direct relocation payload transfer. */
|
|
171
|
-
export const RELOCATION_PAYLOAD_TOTAL_LENGTH_MAX = 274_877_906_944n;
|
|
172
170
|
export const RELOCATION_PAYLOAD_CHUNK_COUNT_MAX = 4096;
|
|
173
171
|
export { RELOCATION_STATE_CHUNK_DATA_MAX_BYTES } from '../../contracts/Configuration/InternalDefaults';
|
|
174
172
|
|
|
@@ -2658,29 +2656,6 @@ export function decodeServiceWireFrozenRecord(bytes: Uint8Array): ServiceWireFro
|
|
|
2658
2656
|
return { ...record, canonicalBytes: Buffer.from(bytes) };
|
|
2659
2657
|
}
|
|
2660
2658
|
|
|
2661
|
-
/**
|
|
2662
|
-
* Consumes exactly one canonical frozen record at `offset`. Relocation
|
|
2663
|
-
* saved-work is a vector of back-to-back records, so its boundary is the
|
|
2664
|
-
* record grammar itself rather than an enclosing length field.
|
|
2665
|
-
*/
|
|
2666
|
-
export function decodeServiceWireFrozenRecordPrefix(
|
|
2667
|
-
bytes: Uint8Array,
|
|
2668
|
-
offset = 0
|
|
2669
|
-
): { readonly record: ServiceWireFrozenRecord; readonly length: number } {
|
|
2670
|
-
if (!Number.isSafeInteger(offset) || offset < 0 || offset >= bytes.byteLength) {
|
|
2671
|
-
fail('Frozen record offset is outside the encoded bytes.');
|
|
2672
|
-
}
|
|
2673
|
-
const reader = new FrozenReader(bytes.subarray(offset));
|
|
2674
|
-
const record = decodeServiceWireFrozenRecordFrom(reader);
|
|
2675
|
-
return {
|
|
2676
|
-
record: {
|
|
2677
|
-
...record,
|
|
2678
|
-
canonicalBytes: Buffer.from(bytes.subarray(offset, offset + reader.position))
|
|
2679
|
-
},
|
|
2680
|
-
length: reader.position
|
|
2681
|
-
};
|
|
2682
|
-
}
|
|
2683
|
-
|
|
2684
2659
|
function decodeServiceWireFrozenRecordFrom(reader: FrozenReader): ServiceWireFrozenRecord {
|
|
2685
2660
|
const recordKind = reader.u8('recordKind');
|
|
2686
2661
|
if (recordKind < 1 || recordKind > 14) fail('Invalid frozen record kind.');
|
|
@@ -305,35 +305,46 @@ export class ServiceTopologyRegistry {
|
|
|
305
305
|
stableType: string,
|
|
306
306
|
isReady: (descriptor: ServiceNodeDescriptor) => boolean = () => true
|
|
307
307
|
): ServiceNodeDescriptor | undefined {
|
|
308
|
+
return this.selectObjectPlacementWithStatus(stableType, isReady).descriptor;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
selectObjectPlacementWithStatus(
|
|
312
|
+
stableType: string,
|
|
313
|
+
isReady: (descriptor: ServiceNodeDescriptor) => boolean = () => true
|
|
314
|
+
): {
|
|
315
|
+
readonly status: ServiceObjectPlacementStatus;
|
|
316
|
+
readonly descriptor?: ServiceNodeDescriptor;
|
|
317
|
+
} {
|
|
308
318
|
requireText(stableType, 'stableType');
|
|
309
|
-
|
|
319
|
+
const candidates = this.objectPlacementCandidates(stableType, isReady);
|
|
320
|
+
if (candidates.status !== 'available') return { status: candidates.status };
|
|
321
|
+
const descriptor = this.selectWeightedCycle(
|
|
310
322
|
`object:${stableType}`,
|
|
311
|
-
() =>
|
|
312
|
-
const capability = `object-type:${stableType}`;
|
|
313
|
-
return [this.local, ...this.peersByRid.values()]
|
|
314
|
-
.map((value) => ('descriptor' in value ? value.descriptor : value))
|
|
315
|
-
.filter(
|
|
316
|
-
(descriptor) =>
|
|
317
|
-
descriptor.state === 'serving' &&
|
|
318
|
-
descriptor.objectRole === 'server' &&
|
|
319
|
-
descriptor.placementWeight > 0 &&
|
|
320
|
-
descriptor.activeCapacityUsed < descriptor.activeCapacityLimit &&
|
|
321
|
-
descriptor.pendingCapacityUsed < descriptor.pendingCapacityLimit &&
|
|
322
|
-
descriptor.protocolCapabilities.includes(capability)
|
|
323
|
-
)
|
|
324
|
-
.map(cloneDescriptor);
|
|
325
|
-
},
|
|
323
|
+
() => this.objectPlacementCandidates(stableType).eligible.map(cloneDescriptor),
|
|
326
324
|
(descriptor) => descriptor.placementWeight,
|
|
327
325
|
(descriptor) => descriptor.nodeRoutingId,
|
|
328
326
|
(left, right) => compareOrdinal(left.nodeRoutingId, right.nodeRoutingId),
|
|
329
327
|
isReady
|
|
330
328
|
);
|
|
329
|
+
return descriptor === undefined
|
|
330
|
+
? { status: 'unavailable' }
|
|
331
|
+
: { status: 'available', descriptor };
|
|
331
332
|
}
|
|
332
333
|
|
|
333
334
|
objectPlacementStatus(
|
|
334
335
|
stableType: string,
|
|
335
336
|
isReady: (descriptor: ServiceNodeDescriptor) => boolean = () => true
|
|
336
337
|
): ServiceObjectPlacementStatus {
|
|
338
|
+
return this.objectPlacementCandidates(stableType, isReady).status;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
private objectPlacementCandidates(
|
|
342
|
+
stableType: string,
|
|
343
|
+
isReady: (descriptor: ServiceNodeDescriptor) => boolean = () => true
|
|
344
|
+
): {
|
|
345
|
+
readonly status: ServiceObjectPlacementStatus;
|
|
346
|
+
readonly eligible: readonly ServiceNodeDescriptor[];
|
|
347
|
+
} {
|
|
337
348
|
requireText(stableType, 'stableType');
|
|
338
349
|
const capability = `object-type:${stableType}`;
|
|
339
350
|
const supported = [
|
|
@@ -345,16 +356,15 @@ export class ServiceTopologyRegistry {
|
|
|
345
356
|
descriptor.objectRole === 'server' &&
|
|
346
357
|
descriptor.protocolCapabilities.includes(capability)
|
|
347
358
|
);
|
|
348
|
-
if (supported.length === 0) return 'unsupported';
|
|
359
|
+
if (supported.length === 0) return { status: 'unsupported', eligible: [] };
|
|
349
360
|
const withCapacity = supported.filter(
|
|
350
361
|
(descriptor) =>
|
|
351
362
|
descriptor.activeCapacityUsed < descriptor.activeCapacityLimit &&
|
|
352
363
|
descriptor.pendingCapacityUsed < descriptor.pendingCapacityLimit
|
|
353
364
|
);
|
|
354
|
-
if (withCapacity.length === 0) return 'capacity';
|
|
365
|
+
if (withCapacity.length === 0) return { status: 'capacity', eligible: [] };
|
|
355
366
|
const weighted = withCapacity.filter((descriptor) => descriptor.placementWeight > 0);
|
|
356
|
-
|
|
357
|
-
return weighted.some(isReady) ? 'available' : 'unavailable';
|
|
367
|
+
return { status: weighted.some(isReady) ? 'available' : 'unavailable', eligible: weighted };
|
|
358
368
|
}
|
|
359
369
|
|
|
360
370
|
instanceSpotPlacementTypes(): readonly string[] {
|
|
@@ -212,7 +212,8 @@ class LifecycleHandlerInstanceScope {
|
|
|
212
212
|
}
|
|
213
213
|
|
|
214
214
|
async dispose(detachedTaskRunner: ZLinkDetachedTaskRunner): Promise<void> {
|
|
215
|
-
const disposeFromActiveInvocation =
|
|
215
|
+
const disposeFromActiveInvocation =
|
|
216
|
+
this.activeInvocations > 0 && activeLifecycleScope.getStore() === this;
|
|
216
217
|
const disposal = this.beginDisposeCore();
|
|
217
218
|
if (disposal.deferred !== undefined) {
|
|
218
219
|
startOutsideLifecycleInvocation(() => {
|
|
@@ -11,7 +11,8 @@ import type {
|
|
|
11
11
|
ZLinkAuthoritySnapshot,
|
|
12
12
|
ZLinkCreationOperationIdentity,
|
|
13
13
|
ZLinkLocationOwnerToken,
|
|
14
|
-
ZLinkObjectReserveResult
|
|
14
|
+
ZLinkObjectReserveResult,
|
|
15
|
+
ZLinkObjectReserveRequest
|
|
15
16
|
} from '../locations/internal-location-contracts';
|
|
16
17
|
import type {
|
|
17
18
|
ZLinkAuthorityStore,
|
|
@@ -57,6 +58,10 @@ export interface ZLinkActorPlacementTarget {
|
|
|
57
58
|
}
|
|
58
59
|
|
|
59
60
|
export interface ZLinkActorPlacementCoordinatorOptions {
|
|
61
|
+
readonly actorRelocationPolicy?: (
|
|
62
|
+
meshName: string,
|
|
63
|
+
stableType: string
|
|
64
|
+
) => ZLinkObjectReserveRequest['actorRelocationPolicy'];
|
|
60
65
|
readonly store: ZLinkObjectCreationStore & ZLinkAuthorityStore;
|
|
61
66
|
readonly target: (
|
|
62
67
|
meshName: string | undefined,
|
|
@@ -130,7 +135,8 @@ export class ZLinkActorPlacementCoordinator {
|
|
|
130
135
|
},
|
|
131
136
|
target: reservationTarget(target),
|
|
132
137
|
creatingPayload,
|
|
133
|
-
capacity: { actors: 1, spots: 0 }
|
|
138
|
+
capacity: { actors: 1, spots: 0 },
|
|
139
|
+
actorRelocationPolicy: this.options.actorRelocationPolicy?.(target.meshName, stableType)
|
|
134
140
|
},
|
|
135
141
|
deadline.signal,
|
|
136
142
|
BigInt(deadlineUnixMs)
|
|
@@ -34,6 +34,10 @@ import type { ZLinkRemoteActorPacketTarget } from '../actors';
|
|
|
34
34
|
import type { ZLinkActorJoinRelocation } from '../actors/actor-join-relocation';
|
|
35
35
|
|
|
36
36
|
export interface ZLinkActorRuntimeOptionsFactoryOptions {
|
|
37
|
+
readonly terminateActorActivation: (
|
|
38
|
+
actor: ZLinkActor,
|
|
39
|
+
terminal: () => Promise<void>
|
|
40
|
+
) => Promise<void>;
|
|
37
41
|
readonly registration: ZLinkFrameworkRegistration;
|
|
38
42
|
/** Message flow tracer, so a failed deferred Join records its cause on the flow. */
|
|
39
43
|
readonly messageFlow?: () =>
|
|
@@ -101,6 +105,7 @@ export class ZLinkActorRuntimeOptionsFactory {
|
|
|
101
105
|
| 'actorRefResolver'
|
|
102
106
|
| 'actorCreatedNotifier'
|
|
103
107
|
| 'actorDestroyedCleanup'
|
|
108
|
+
| 'terminateActorActivation'
|
|
104
109
|
| 'publishActorAuthority'
|
|
105
110
|
| 'locationLifecycle'
|
|
106
111
|
| 'boundSessionFactory'
|
|
@@ -111,6 +116,7 @@ export class ZLinkActorRuntimeOptionsFactory {
|
|
|
111
116
|
> {
|
|
112
117
|
const actorTransferRegistry = this.options.actorTransferRegistry;
|
|
113
118
|
return {
|
|
119
|
+
terminateActorActivation: this.options.terminateActorActivation,
|
|
114
120
|
actorMeshNameProvider: this.options.actorMeshName,
|
|
115
121
|
actorLeaveSpot: (meshName, spotId, actor, signal) => {
|
|
116
122
|
const spotManager = this.options.spotManager();
|
|
@@ -2016,6 +2016,7 @@ export class ZLinkFrameworkRuntimeHost
|
|
|
2016
2016
|
| 'actorRefResolver'
|
|
2017
2017
|
| 'actorCreatedNotifier'
|
|
2018
2018
|
| 'actorDestroyedCleanup'
|
|
2019
|
+
| 'terminateActorActivation'
|
|
2019
2020
|
| 'locationLifecycle'
|
|
2020
2021
|
| 'boundSessionFactory'
|
|
2021
2022
|
| 'shutdownSignal'
|
|
@@ -2139,6 +2140,9 @@ export class ZLinkFrameworkRuntimeHost
|
|
|
2139
2140
|
}
|
|
2140
2141
|
this.actorPlacement ??= new ZLinkActorPlacementCoordinator({
|
|
2141
2142
|
store: locationStore,
|
|
2143
|
+
actorRelocationPolicy: (meshName, stableType) =>
|
|
2144
|
+
this.options.registration.spotNodes.get(meshName)?.actorFactoryRegistrations?.[stableType]
|
|
2145
|
+
?.relocation.kind,
|
|
2142
2146
|
remoteCreate: (meshName, targetNodeRid, request, timeoutMs) => {
|
|
2143
2147
|
const node = this.spotNodeRuntime?.meshNode(meshName);
|
|
2144
2148
|
if (node === undefined) {
|
|
@@ -2194,14 +2198,12 @@ export class ZLinkFrameworkRuntimeHost
|
|
|
2194
2198
|
const capability = descriptor.objectCapabilities.find(
|
|
2195
2199
|
(candidate) => candidate.objectKind === 'actor' && candidate.stableType === stableType
|
|
2196
2200
|
);
|
|
2197
|
-
const actors = descriptor.populationCapacity.actors;
|
|
2198
2201
|
return (
|
|
2199
2202
|
descriptor.state === ZLinkFrameworkRuntimeState.Serving &&
|
|
2200
2203
|
descriptor.objectRole === 'server' &&
|
|
2201
2204
|
descriptor.placementWeight > 0 &&
|
|
2202
2205
|
descriptor.entrySpotId !== undefined &&
|
|
2203
2206
|
excludedNodeRids?.has(String(descriptor.rid)) !== true &&
|
|
2204
|
-
(actors.limit === 0 || actors.active + actors.reserved < actors.limit) &&
|
|
2205
2207
|
capability !== undefined
|
|
2206
2208
|
);
|
|
2207
2209
|
}
|
|
@@ -2592,6 +2594,8 @@ export class ZLinkFrameworkRuntimeHost
|
|
|
2592
2594
|
|
|
2593
2595
|
private actorRuntimeOptionsFactory(): ZLinkActorRuntimeOptionsFactory {
|
|
2594
2596
|
return new ZLinkActorRuntimeOptionsFactory({
|
|
2597
|
+
terminateActorActivation: (actor, terminal) =>
|
|
2598
|
+
this.spotNodeRuntime?.terminateActorActivation(actor, terminal) ?? terminal(),
|
|
2595
2599
|
registration: this.options.registration,
|
|
2596
2600
|
messageFlow: () => this.createDispatchErrorReporter(this.runtimeOrPreStartErrorSink).flow,
|
|
2597
2601
|
routeTransport: this.routeTransport,
|
|
@@ -16,7 +16,7 @@ import type {
|
|
|
16
16
|
ZLinkAuthorityStore,
|
|
17
17
|
ZLinkObjectCreationStore
|
|
18
18
|
} from '../locations/internal-store-contracts';
|
|
19
|
-
import { ZLinkFrameworkException } from '../../contracts';
|
|
19
|
+
import { ZLinkFrameworkErrorKind, ZLinkFrameworkException } from '../../contracts';
|
|
20
20
|
import {
|
|
21
21
|
decodeServiceInstanceAuthorityPayload,
|
|
22
22
|
decodeServiceReadySpotAuthority,
|
|
@@ -46,7 +46,6 @@ import {
|
|
|
46
46
|
|
|
47
47
|
const CREATION_REQUEST_RETENTION_MS = 24 * 60 * 60 * 1_000;
|
|
48
48
|
const MAX_CREATION_REQUEST_BYTES = 1024 * 1024;
|
|
49
|
-
const ACTIVATION_JOIN_POLL_INTERVAL_MS = 10;
|
|
50
49
|
|
|
51
50
|
interface PendingReservation {
|
|
52
51
|
readonly reservationId: string;
|
|
@@ -115,87 +114,81 @@ export class ZLinkInstanceActivationAuthority implements ServiceAsyncInstanceAct
|
|
|
115
114
|
}
|
|
116
115
|
let reserved: Awaited<ReturnType<ZLinkObjectCreationStore['reserve']>>;
|
|
117
116
|
try {
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
{
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
117
|
+
reserved = await this.options.store.reserve(
|
|
118
|
+
{
|
|
119
|
+
key: { kind: 'instance_spot', globalId: target.targetSpotId },
|
|
120
|
+
intent: {
|
|
121
|
+
stableType: target.stableType,
|
|
122
|
+
requestContentReference: stored.reference.value,
|
|
123
|
+
requestSha256: requestHash,
|
|
124
|
+
requestEncodedSize: BigInt(requestBytes.byteLength)
|
|
125
|
+
},
|
|
126
|
+
target: {
|
|
127
|
+
meshName: this.options.meshName,
|
|
128
|
+
nodeRid: target.targetNodeRid,
|
|
129
|
+
nodeLifecycleGeneration: target.targetNodeGeneration,
|
|
130
|
+
owner
|
|
131
|
+
},
|
|
132
|
+
creatingPayload: encodeServiceInstanceAuthorityPayload({
|
|
133
|
+
state: 'coldActivating',
|
|
134
|
+
stableType: target.stableType,
|
|
135
|
+
spotId: target.targetSpotId,
|
|
136
|
+
ownerId: owner.ownerId,
|
|
137
|
+
ownerLeaseGeneration: owner.leaseGeneration,
|
|
138
|
+
ownerMeshName: this.options.meshName,
|
|
139
|
+
ownerNodeRid: target.targetNodeRid,
|
|
140
|
+
ownerNodeGeneration: target.targetNodeGeneration
|
|
141
|
+
}),
|
|
142
|
+
capacity: {
|
|
143
|
+
actors: 0,
|
|
144
|
+
spots: 1,
|
|
145
|
+
spotType: {
|
|
146
|
+
objectKind: 'instance_spot',
|
|
136
147
|
stableType: target.stableType,
|
|
137
|
-
|
|
138
|
-
ownerId: owner.ownerId,
|
|
139
|
-
ownerLeaseGeneration: owner.leaseGeneration,
|
|
140
|
-
ownerMeshName: this.options.meshName,
|
|
141
|
-
ownerNodeRid: target.targetNodeRid,
|
|
142
|
-
ownerNodeGeneration: target.targetNodeGeneration
|
|
143
|
-
}),
|
|
144
|
-
capacity: {
|
|
145
|
-
actors: 0,
|
|
146
|
-
spots: 1,
|
|
147
|
-
spotType: {
|
|
148
|
-
objectKind: 'instance_spot',
|
|
149
|
-
stableType: target.stableType,
|
|
150
|
-
count: 1
|
|
151
|
-
}
|
|
148
|
+
count: 1
|
|
152
149
|
}
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
this.options.metrics?.recordInstanceSpotClaimConflict(
|
|
159
|
-
this.options.meshName,
|
|
160
|
-
target.stableType,
|
|
161
|
-
'already_exists'
|
|
162
|
-
);
|
|
163
|
-
await this.deleteOrphan(stored.reference);
|
|
164
|
-
return readyExisting(reserved.current, target);
|
|
165
|
-
}
|
|
150
|
+
}
|
|
151
|
+
},
|
|
152
|
+
undefined,
|
|
153
|
+
activation.deadlineUnixMs
|
|
154
|
+
);
|
|
166
155
|
} catch (error) {
|
|
167
156
|
await this.deleteOrphan(stored.reference);
|
|
168
157
|
throw error;
|
|
169
158
|
}
|
|
170
|
-
if (
|
|
171
|
-
reserved.kind === 'conflict' &&
|
|
172
|
-
reserved.current.kind === 'snapshot' &&
|
|
173
|
-
reserved.current.allocation.objectKind === 'instance_spot' &&
|
|
174
|
-
reserved.current.allocation.stableType === target.stableType &&
|
|
175
|
-
reserved.current.allocation.state === 'reserved'
|
|
176
|
-
) {
|
|
177
|
-
this.options.metrics?.recordInstanceSpotClaimConflict(
|
|
178
|
-
this.options.meshName,
|
|
179
|
-
target.stableType,
|
|
180
|
-
'activation_in_progress'
|
|
181
|
-
);
|
|
182
|
-
await this.deleteOrphan(stored.reference);
|
|
183
|
-
return await this.awaitConcurrentActivation(target, activation.deadlineUnixMs);
|
|
184
|
-
}
|
|
185
159
|
if (reserved.kind !== 'reserved') {
|
|
160
|
+
const conflictReason =
|
|
161
|
+
reserved.kind === 'alreadyExists'
|
|
162
|
+
? 'already_exists'
|
|
163
|
+
: reserved.kind === 'conflict' &&
|
|
164
|
+
reserved.current.kind === 'snapshot' &&
|
|
165
|
+
reserved.current.allocation.objectKind === 'instance_spot' &&
|
|
166
|
+
reserved.current.allocation.stableType === target.stableType &&
|
|
167
|
+
reserved.current.allocation.state === 'reserved'
|
|
168
|
+
? 'activation_in_progress'
|
|
169
|
+
: reserved.kind;
|
|
186
170
|
this.options.metrics?.recordInstanceSpotClaimConflict(
|
|
187
171
|
this.options.meshName,
|
|
188
172
|
target.stableType,
|
|
189
|
-
|
|
173
|
+
conflictReason
|
|
190
174
|
);
|
|
191
175
|
await this.deleteOrphan(stored.reference);
|
|
176
|
+
if (reserved.kind === 'typeMismatch') {
|
|
177
|
+
throw createInternalFrameworkException(
|
|
178
|
+
ZLinkFrameworkInternalErrorKind.SpotTypeMismatch,
|
|
179
|
+
`Instance activation reservation failed: ${reserved.kind}.`
|
|
180
|
+
);
|
|
181
|
+
}
|
|
192
182
|
if (reserved.kind === 'placementCapacityExhausted') {
|
|
193
183
|
throw createInternalFrameworkException(
|
|
194
184
|
ZLinkFrameworkInternalErrorKind.PlacementCapacityExhausted,
|
|
195
185
|
`Instance Spot placement capacity is exhausted for '${String(target.targetSpotId)}'.`
|
|
196
186
|
);
|
|
197
187
|
}
|
|
198
|
-
throw new
|
|
188
|
+
throw new ZLinkFrameworkException(
|
|
189
|
+
ZLinkFrameworkErrorKind.Unavailable,
|
|
190
|
+
`Instance activation reservation failed: ${reserved.kind}.`
|
|
191
|
+
);
|
|
199
192
|
}
|
|
200
193
|
const reservation: ServiceInstanceActivationReservation = {
|
|
201
194
|
attempt: reserved.creating.objectGeneration,
|
|
@@ -455,34 +448,6 @@ export class ZLinkInstanceActivationAuthority implements ServiceAsyncInstanceAct
|
|
|
455
448
|
}
|
|
456
449
|
}
|
|
457
450
|
|
|
458
|
-
private async awaitConcurrentActivation(
|
|
459
|
-
target: ServiceInstanceActivationTarget,
|
|
460
|
-
deadlineUnixMs: bigint
|
|
461
|
-
): Promise<Extract<ServiceInstanceAuthorityRead, { readonly kind: 'ready' }>> {
|
|
462
|
-
const key = authorityKey(target.targetSpotId);
|
|
463
|
-
const deadlineMs = performance.now() + Number(deadlineUnixMs - BigInt(Date.now()));
|
|
464
|
-
while (performance.now() <= deadlineMs) {
|
|
465
|
-
const current = await this.options.store.readAuthority(key);
|
|
466
|
-
if (current.kind === 'snapshot') {
|
|
467
|
-
if (
|
|
468
|
-
current.allocation.objectKind !== 'instance_spot' ||
|
|
469
|
-
current.allocation.stableType !== target.stableType
|
|
470
|
-
) {
|
|
471
|
-
throw new Error('Concurrent Instance activation resolved to a different Spot type.');
|
|
472
|
-
}
|
|
473
|
-
if (current.allocation.state === 'active') {
|
|
474
|
-
return readyExisting(current, target);
|
|
475
|
-
}
|
|
476
|
-
} else {
|
|
477
|
-
throw new Error('Concurrent Instance activation ended before the Ready barrier.');
|
|
478
|
-
}
|
|
479
|
-
await waitForActivationJoin(deadlineMs);
|
|
480
|
-
}
|
|
481
|
-
throw new Error(
|
|
482
|
-
`Concurrent Instance activation for '${target.targetSpotId}' did not reach Ready before deadline.`
|
|
483
|
-
);
|
|
484
|
-
}
|
|
485
|
-
|
|
486
451
|
private requirePending(reservation: ServiceInstanceActivationReservation): PendingReservation {
|
|
487
452
|
const pending = this.pending.get(reservation.token);
|
|
488
453
|
if (pending === undefined || pending.creating.objectGeneration !== reservation.attempt) {
|
|
@@ -539,38 +504,6 @@ function readyRead(
|
|
|
539
504
|
return { kind: 'ready', route: routeFromSnapshot(snapshot), authority: snapshot };
|
|
540
505
|
}
|
|
541
506
|
|
|
542
|
-
function readyExisting(
|
|
543
|
-
snapshot: ZLinkAuthoritySnapshot,
|
|
544
|
-
target: ServiceInstanceActivationTarget
|
|
545
|
-
): Extract<ServiceInstanceAuthorityRead, { readonly kind: 'ready' }> {
|
|
546
|
-
const decoded =
|
|
547
|
-
decodeServiceReadySpotAuthority(snapshot.payload) ??
|
|
548
|
-
decodeServiceClosingSpotAuthority(snapshot.payload);
|
|
549
|
-
if (
|
|
550
|
-
decoded?.kind !== 'instance_spot' ||
|
|
551
|
-
decoded.stableType !== target.stableType ||
|
|
552
|
-
decoded.spotId !== target.targetSpotId ||
|
|
553
|
-
snapshot.allocation.objectKind !== 'instance_spot' ||
|
|
554
|
-
snapshot.allocation.state !== 'active' ||
|
|
555
|
-
decoded.ownerId !== snapshot.ownerId ||
|
|
556
|
-
decoded.ownerLeaseGeneration !== snapshot.ownerLeaseGeneration ||
|
|
557
|
-
decoded.ownerMeshName !== snapshot.allocation.descriptor.meshName ||
|
|
558
|
-
decoded.ownerNodeGeneration !== snapshot.allocation.descriptorLifecycleGeneration ||
|
|
559
|
-
!routingIdsEqual(decoded.ownerNodeRid, snapshot.allocation.descriptor.rid)
|
|
560
|
-
) {
|
|
561
|
-
throw new Error('Existing Instance authority is not a matching Ready allocation.');
|
|
562
|
-
}
|
|
563
|
-
return { kind: 'ready', route: routeFromSnapshot(snapshot), authority: snapshot };
|
|
564
|
-
}
|
|
565
|
-
|
|
566
|
-
async function waitForActivationJoin(deadlineMs: number): Promise<void> {
|
|
567
|
-
const remaining = deadlineMs - performance.now();
|
|
568
|
-
if (remaining <= 0) return;
|
|
569
|
-
await new Promise<void>((resolve) => {
|
|
570
|
-
setTimeout(resolve, Math.min(ACTIVATION_JOIN_POLL_INTERVAL_MS, remaining));
|
|
571
|
-
});
|
|
572
|
-
}
|
|
573
|
-
|
|
574
507
|
function requireCommitIdentity(
|
|
575
508
|
target: ServiceInstanceActivationTarget,
|
|
576
509
|
reservation: ServiceInstanceActivationReservation,
|
|
@@ -576,7 +576,7 @@ export class ZLinkHostSpotAddressTransport implements ZLinkSpotAddressTransport
|
|
|
576
576
|
if (stableType === undefined) {
|
|
577
577
|
if (distinctTypes.length === 0) return { kind: 'unsupported' };
|
|
578
578
|
throw createInternalFrameworkException(
|
|
579
|
-
ZLinkFrameworkInternalErrorKind.
|
|
579
|
+
ZLinkFrameworkInternalErrorKind.InvalidOperation,
|
|
580
580
|
'Instance Spot type is required when multiple types are registered.'
|
|
581
581
|
);
|
|
582
582
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { encodeAuthorityKey } from '../locations/authority-key-codec';
|
|
1
2
|
import { createHash } from 'node:crypto';
|
|
2
3
|
import type {
|
|
3
4
|
ZLinkAuthorityKey,
|
|
@@ -175,12 +176,25 @@ export class ZLinkStatefulAuthorityRouteRuntime {
|
|
|
175
176
|
|
|
176
177
|
const status = sink.status();
|
|
177
178
|
for (const pending of snapshot.pending) {
|
|
179
|
+
if (pending.targetMeshName !== meshName) continue;
|
|
178
180
|
if (
|
|
179
|
-
pending.targetMeshName === meshName &&
|
|
180
181
|
routingIdsEqual(status.routingId, pending.targetNodeRid) &&
|
|
181
182
|
status.lifecycleGeneration === pending.targetNodeGeneration
|
|
182
183
|
) {
|
|
183
184
|
await this.recoverPendingInstanceActivation(sink, pending, signal);
|
|
185
|
+
} else {
|
|
186
|
+
const release = this.options.creationStore?.releaseEndedReservation;
|
|
187
|
+
if (release === undefined)
|
|
188
|
+
throw new Error('Reservation lifecycle recovery requires a Location repository.');
|
|
189
|
+
if (
|
|
190
|
+
await release.call(
|
|
191
|
+
this.options.creationStore,
|
|
192
|
+
encodeAuthorityKey('instance_spot', pending.spotId),
|
|
193
|
+
pending.pending.storeVersion,
|
|
194
|
+
signal
|
|
195
|
+
)
|
|
196
|
+
)
|
|
197
|
+
await this.clearRecoveryRoot(pending.pending.requestReference, signal);
|
|
184
198
|
}
|
|
185
199
|
}
|
|
186
200
|
|
|
@@ -386,14 +400,7 @@ export class ZLinkStatefulAuthorityRouteRuntime {
|
|
|
386
400
|
signal
|
|
387
401
|
);
|
|
388
402
|
if (aborted.kind === 'aborted' || aborted.kind === 'alreadyAborted') {
|
|
389
|
-
|
|
390
|
-
await this.options.relocationStore?.delete(
|
|
391
|
-
relocationBlobReference(recovery.pending.requestReference)
|
|
392
|
-
);
|
|
393
|
-
} catch {
|
|
394
|
-
// The authority no longer publishes this root. Retention owns a
|
|
395
|
-
// failed best-effort orphan cleanup.
|
|
396
|
-
}
|
|
403
|
+
await this.clearRecoveryRoot(recovery.pending.requestReference, signal);
|
|
397
404
|
}
|
|
398
405
|
return;
|
|
399
406
|
}
|
|
@@ -401,6 +408,12 @@ export class ZLinkStatefulAuthorityRouteRuntime {
|
|
|
401
408
|
return;
|
|
402
409
|
}
|
|
403
410
|
|
|
411
|
+
private async clearRecoveryRoot(reference: string, signal?: AbortSignal): Promise<void> {
|
|
412
|
+
if (this.options.relocationStore === undefined)
|
|
413
|
+
throw new Error('Instance activation recovery requires a Relocation Store.');
|
|
414
|
+
await this.options.relocationStore.delete(relocationBlobReference(reference), signal);
|
|
415
|
+
}
|
|
416
|
+
|
|
404
417
|
private async readRecoveryEnvelope(
|
|
405
418
|
recovery: ServiceActivationRecoveryState,
|
|
406
419
|
signal?: AbortSignal
|
|
@@ -37,9 +37,9 @@ import type {
|
|
|
37
37
|
ZLinkObjectCommitResult,
|
|
38
38
|
ZLinkObjectCreationCompleteRequest,
|
|
39
39
|
ZLinkObjectCreationCompleteResult,
|
|
40
|
-
ZLinkObjectReserveRequest,
|
|
41
40
|
ZLinkObjectReserveResult
|
|
42
41
|
} from '../../contracts/Locations/Authority';
|
|
42
|
+
import type { ZLinkObjectReserveRequest } from './internal-location-contracts';
|
|
43
43
|
|
|
44
44
|
/**
|
|
45
45
|
* Framework-private domain repository. Provider implementations never
|
|
@@ -140,6 +140,11 @@ export interface ZLinkDomainLocationStore {
|
|
|
140
140
|
request: ZLinkObjectCreationCompleteRequest,
|
|
141
141
|
signal?: AbortSignal
|
|
142
142
|
): Promise<ZLinkObjectCreationCompleteResult>;
|
|
143
|
+
releaseEndedReservation?(
|
|
144
|
+
key: ZLinkAuthorityKey,
|
|
145
|
+
expectedStoreVersion: string,
|
|
146
|
+
signal?: AbortSignal
|
|
147
|
+
): Promise<boolean>;
|
|
143
148
|
abort(
|
|
144
149
|
request: ZLinkObjectAbortRequest,
|
|
145
150
|
signal?: AbortSignal,
|