@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
|
@@ -89,6 +89,7 @@ export declare class ZLinkEntrySpotActivation {
|
|
|
89
89
|
*/
|
|
90
90
|
commitServiceActorJoin(actor: ZLinkActor, handoffBacklog?: readonly ZLinkActorHandoffPacket[], signal?: AbortSignal): Promise<void>;
|
|
91
91
|
executeActor<T>(actorId: string, operation: () => Promise<T>): Promise<T>;
|
|
92
|
+
terminateActor(actorId: string, terminal: () => Promise<void>): Promise<void>;
|
|
92
93
|
/**
|
|
93
94
|
* Entry Spot membership does not have an application admission callback.
|
|
94
95
|
* The shared core round-trip therefore accepts a valid returning Actor and
|
|
@@ -220,6 +220,9 @@ class ZLinkEntrySpotActivation {
|
|
|
220
220
|
executeActor(actorId, operation) {
|
|
221
221
|
return this.spotSerialExecutor.executeActor(actorId, operation);
|
|
222
222
|
}
|
|
223
|
+
terminateActor(actorId, terminal) {
|
|
224
|
+
return this.spotSerialExecutor.terminateActor(actorId, terminal);
|
|
225
|
+
}
|
|
223
226
|
/**
|
|
224
227
|
* Entry Spot membership does not have an application admission callback.
|
|
225
228
|
* The shared core round-trip therefore accepts a valid returning Actor and
|
|
@@ -104,6 +104,7 @@ export declare class ZLinkSpotNodeRuntimeManager {
|
|
|
104
104
|
get primaryMeshCompletions(): ZLinkMeshCompletionTable | undefined;
|
|
105
105
|
notifyEntrySpotActorCreated(nodeRid: RoutingId, actor: ZLinkActor, createRequest: ZLinkMessage, signal?: AbortSignal): Promise<import('../../contracts').ZLinkActorCreateResponse | undefined>;
|
|
106
106
|
notifyPrimaryEntrySpotActorJoined(actor: ZLinkActor, signal?: AbortSignal): Promise<void>;
|
|
107
|
+
terminateActorActivation(actor: ZLinkActor, terminal: () => Promise<void>): Promise<void>;
|
|
107
108
|
notifyPrimaryEntrySpotActorLeft(actor: ZLinkActor, signal?: AbortSignal, actorRef?: ActorRef, membershipEpoch?: bigint): Promise<void>;
|
|
108
109
|
notifyPrimaryEntrySpotActorDisconnected(actor: ZLinkActor, signal?: AbortSignal): Promise<void>;
|
|
109
110
|
notifyEntrySpotActorDisconnected(meshName: string, actor: ZLinkActor, signal?: AbortSignal): Promise<void>;
|
|
@@ -542,6 +542,12 @@ class ZLinkSpotNodeRuntimeManager {
|
|
|
542
542
|
const activation = this.primaryEntryActivation();
|
|
543
543
|
return activation?.notifyJoinActor(actor, signal) ?? Promise.resolve();
|
|
544
544
|
}
|
|
545
|
+
terminateActorActivation(actor, terminal) {
|
|
546
|
+
const activation = this.entryActivations.get(actor.context.meshName);
|
|
547
|
+
return activation === undefined
|
|
548
|
+
? terminal()
|
|
549
|
+
: activation.terminateActor(actor.context.actorId, terminal);
|
|
550
|
+
}
|
|
545
551
|
notifyPrimaryEntrySpotActorLeft(actor, signal, actorRef, membershipEpoch) {
|
|
546
552
|
const activation = this.primaryEntryActivation();
|
|
547
553
|
return (activation?.notifyLeaveActor(actor, signal, actorRef, membershipEpoch) ?? Promise.resolve());
|
|
@@ -26,6 +26,7 @@ export declare class ZLinkSpotSerialExecutor {
|
|
|
26
26
|
executeTimer<T>(timerName: string, operation: () => Promise<T> | T, workOptions?: ZLinkSerialWorkOptions): Promise<T>;
|
|
27
27
|
isTimerExecuting(timerName: string): boolean;
|
|
28
28
|
executeLifecycle<T>(operation: () => Promise<T> | T, workOptions?: ZLinkSerialWorkOptions): Promise<T>;
|
|
29
|
+
terminateActor(actorId: string, terminal: () => Promise<void>): Promise<void>;
|
|
29
30
|
/**
|
|
30
31
|
* Makes all child queues unreachable and completes them in one synchronous
|
|
31
32
|
* ownership turn. Already accepted work remains drainable.
|
|
@@ -42,7 +42,8 @@ class ZLinkSpotSerialExecutor {
|
|
|
42
42
|
return this.spotSerial.execute(operation, workOptions);
|
|
43
43
|
}
|
|
44
44
|
executeActor(actorId, operation, workOptions) {
|
|
45
|
-
|
|
45
|
+
const serial = this.actorSerial(actorId);
|
|
46
|
+
return this.actorExecutor(actorId).execute(() => operation(serial), workOptions);
|
|
46
47
|
}
|
|
47
48
|
/** @internal Restores a durable Actor FIFO prefix without starting it. */
|
|
48
49
|
admitActorDurablePrefix(actorId, records) {
|
|
@@ -65,6 +66,14 @@ class ZLinkSpotSerialExecutor {
|
|
|
65
66
|
executeLifecycle(operation, workOptions) {
|
|
66
67
|
return this.spotSerial.execute(operation, { ...workOptions, lane: 'lifecycle' });
|
|
67
68
|
}
|
|
69
|
+
async terminateActor(actorId, terminal) {
|
|
70
|
+
const executor = this.actorExecutor(actorId);
|
|
71
|
+
await executor.close();
|
|
72
|
+
await terminal();
|
|
73
|
+
if (this.actorExecutors.get(actorId) === executor)
|
|
74
|
+
this.actorExecutors.delete(actorId);
|
|
75
|
+
this.actorSerials.delete(actorId);
|
|
76
|
+
}
|
|
68
77
|
/**
|
|
69
78
|
* Makes all child queues unreachable and completes them in one synchronous
|
|
70
79
|
* ownership turn. Already accepted work remains drainable.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zlink-systems/framework",
|
|
3
|
-
"version": "0.29.
|
|
3
|
+
"version": "0.29.1",
|
|
4
4
|
"license": "SEE LICENSE IN LICENSE",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"dependencies": {
|
|
20
20
|
"@opentelemetry/api": "^1.9.1",
|
|
21
21
|
"@opentelemetry/api-logs": "^0.221.0",
|
|
22
|
-
"@zlink-systems/stream-wire": "0.29.
|
|
22
|
+
"@zlink-systems/stream-wire": "0.29.1",
|
|
23
23
|
"@zlink-systems/zlink": "1.17.0"
|
|
24
24
|
}
|
|
25
25
|
}
|
|
@@ -49,6 +49,10 @@ export interface ZLinkActorManagerOptions {
|
|
|
49
49
|
signal?: AbortSignal
|
|
50
50
|
) => Promise<ZLinkActorCreateResponse | undefined>;
|
|
51
51
|
readonly actorDestroyedCleanup?: (actorId: string) => void;
|
|
52
|
+
readonly terminateActorActivation?: (
|
|
53
|
+
actor: ZLinkActor,
|
|
54
|
+
terminal: () => Promise<void>
|
|
55
|
+
) => Promise<void>;
|
|
52
56
|
readonly publishActorAuthority?: (
|
|
53
57
|
actorType: string,
|
|
54
58
|
actorRef: ActorRef,
|
|
@@ -29,6 +29,7 @@ import {
|
|
|
29
29
|
wireReplyFailureException
|
|
30
30
|
} from '../framework-errors-internal';
|
|
31
31
|
import { disposeLifecycleHandlers } from '../handlers/handler-instance-scope';
|
|
32
|
+
import { currentZLinkActorExecution } from './actor-execution-context';
|
|
32
33
|
import { encodeFrameworkPayloadMessage } from '../messaging/payload-codec';
|
|
33
34
|
import { ZLinkActorCreationCoordinator, type ZLinkActorCreateRequest } from './actor-creation';
|
|
34
35
|
import type { ZLinkActorManagerOptions } from './actor-runtime-contracts';
|
|
@@ -217,34 +218,43 @@ export class DefaultZLinkActorManager implements ZLinkActorManager {
|
|
|
217
218
|
const completions = this.options.nativeActorCompletionTableProvider?.();
|
|
218
219
|
const entryNodeRid = state.entryNodeRid ?? current.nodeRid;
|
|
219
220
|
if (node === undefined || completions === undefined) return false;
|
|
220
|
-
const destroyTask = state.getOrStartDestroy(entryNodeRid,
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
221
|
+
const destroyTask = state.getOrStartDestroy(entryNodeRid, (nativeRef) =>
|
|
222
|
+
this.terminateActor(state, async () => {
|
|
223
|
+
if (nativeRef !== undefined) {
|
|
224
|
+
const completion = await completions.submit(
|
|
225
|
+
() => node.destroyActor(nativeRef, 0),
|
|
226
|
+
signal
|
|
227
|
+
);
|
|
228
|
+
try {
|
|
229
|
+
if (completion.terminalResult !== 0 || completion.failureErrno !== 0) {
|
|
230
|
+
// Classify the (terminal, fine) pair via the shared translator
|
|
231
|
+
// (spec 32-framework-error-model:83-118, 99-108) instead of
|
|
232
|
+
// collapsing every destroy failure to NotFound; genuine
|
|
233
|
+
// route-not-found fine codes still yield NotFound through the
|
|
234
|
+
// shared table.
|
|
235
|
+
throw wireReplyFailureException(
|
|
236
|
+
completion.terminalResult,
|
|
237
|
+
completion.failureErrno,
|
|
238
|
+
`Actor '${actor.actorId}' destroy failed with result '${completion.terminalResult}' and errno '${completion.failureErrno}'.`
|
|
239
|
+
);
|
|
240
|
+
}
|
|
241
|
+
} finally {
|
|
242
|
+
closeMeshCompletion(completion);
|
|
235
243
|
}
|
|
236
|
-
|
|
237
|
-
closeMeshCompletion(completion);
|
|
244
|
+
state.markNativeActorDestroyed(nativeRef);
|
|
238
245
|
}
|
|
239
|
-
state.
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
+
if (state.actorType !== undefined && state.ownsLocation) {
|
|
247
|
+
await this.options.locationLifecycle?.releaseActor(
|
|
248
|
+
state.actorType,
|
|
249
|
+
actor.actorId,
|
|
250
|
+
current
|
|
251
|
+
);
|
|
252
|
+
}
|
|
253
|
+
await this.finalizeDestroyedActor(actor.actorId, state);
|
|
254
|
+
})
|
|
255
|
+
);
|
|
246
256
|
try {
|
|
247
|
-
await destroyTask;
|
|
257
|
+
await this.awaitActorDestroy(state, destroyTask);
|
|
248
258
|
} catch (error) {
|
|
249
259
|
state.clearFailedDestroy(destroyTask);
|
|
250
260
|
throw error;
|
|
@@ -548,6 +558,29 @@ export class DefaultZLinkActorManager implements ZLinkActorManager {
|
|
|
548
558
|
this.options.metrics?.change(METRIC_NAMES.ActorCount, -1);
|
|
549
559
|
}
|
|
550
560
|
}
|
|
561
|
+
|
|
562
|
+
private terminateActor(
|
|
563
|
+
state: ZLinkActorRuntimeState,
|
|
564
|
+
terminal: () => Promise<void>
|
|
565
|
+
): Promise<void> {
|
|
566
|
+
const actor = state.actor;
|
|
567
|
+
const completion =
|
|
568
|
+
actor === undefined || this.options.terminateActorActivation === undefined
|
|
569
|
+
? terminal()
|
|
570
|
+
: this.options.terminateActorActivation(actor, terminal);
|
|
571
|
+
return completion;
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
private awaitActorDestroy(
|
|
575
|
+
state: ZLinkActorRuntimeState,
|
|
576
|
+
completion: Promise<void>
|
|
577
|
+
): Promise<void> {
|
|
578
|
+
if (currentZLinkActorExecution()?.actorId === state.actorId) {
|
|
579
|
+
this.detachedTaskRunner.runDetached('Actor terminal cleanup', () => completion);
|
|
580
|
+
return Promise.resolve();
|
|
581
|
+
}
|
|
582
|
+
return completion;
|
|
583
|
+
}
|
|
551
584
|
async getOrCreateActor(
|
|
552
585
|
actorId: string,
|
|
553
586
|
actorType: string,
|
|
@@ -648,25 +681,27 @@ export class DefaultZLinkActorManager implements ZLinkActorManager {
|
|
|
648
681
|
`Actor '${actor.context.actorId}' must leave its current SPOT before destroy.`
|
|
649
682
|
);
|
|
650
683
|
}
|
|
651
|
-
const destroyTask = state.getOrStartDestroy(entryNodeRid,
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
684
|
+
const destroyTask = state.getOrStartDestroy(entryNodeRid, (actorRef) =>
|
|
685
|
+
this.terminateActor(state, async () => {
|
|
686
|
+
const destroyedActorRef =
|
|
687
|
+
actorRef === undefined ? undefined : toFrameworkActorRef(actorRef, state.meshName ?? '');
|
|
688
|
+
if (actorRef !== undefined) {
|
|
689
|
+
await node.destroyActor(actorRef, 0, destroySignal);
|
|
690
|
+
state.markNativeActorDestroyed(actorRef);
|
|
691
|
+
}
|
|
692
|
+
if (state.actorType !== undefined && state.ownsLocation) {
|
|
693
|
+
await this.options.locationLifecycle?.releaseActor(
|
|
694
|
+
state.actorType,
|
|
695
|
+
actor.context.actorId,
|
|
696
|
+
destroyedActorRef
|
|
697
|
+
);
|
|
698
|
+
}
|
|
699
|
+
await this.finalizeDestroyedActor(actor.context.actorId, state);
|
|
700
|
+
})
|
|
701
|
+
);
|
|
667
702
|
|
|
668
703
|
try {
|
|
669
|
-
await destroyTask;
|
|
704
|
+
await this.awaitActorDestroy(state, destroyTask);
|
|
670
705
|
} catch (error) {
|
|
671
706
|
state.clearFailedDestroy(destroyTask);
|
|
672
707
|
throw error;
|
|
@@ -432,6 +432,7 @@ export class ServiceStatefulRuntime {
|
|
|
432
432
|
Promise<{
|
|
433
433
|
readonly spot: Pick<ServiceSpotState, 'ref'>;
|
|
434
434
|
readonly route: ServiceInstanceRouteFence;
|
|
435
|
+
readonly stableType: string;
|
|
435
436
|
}>
|
|
436
437
|
>();
|
|
437
438
|
private closed = false;
|
|
@@ -1559,7 +1560,7 @@ export class ServiceStatefulRuntime {
|
|
|
1559
1560
|
this.nodeRid,
|
|
1560
1561
|
sourceSpotId,
|
|
1561
1562
|
'request',
|
|
1562
|
-
{ high:
|
|
1563
|
+
{ high: ZLINK_NATIVE_CORRELATION_OPERATION_NAMESPACE, low: pending.id },
|
|
1563
1564
|
deadlineUnixMs,
|
|
1564
1565
|
pending.id,
|
|
1565
1566
|
metadataFrame !== undefined
|
|
@@ -2520,27 +2521,24 @@ export class ServiceStatefulRuntime {
|
|
|
2520
2521
|
): Promise<void> {
|
|
2521
2522
|
try {
|
|
2522
2523
|
this.validateInstanceIngress(ingress, record);
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
);
|
|
2541
|
-
if (admitted !== 'application') {
|
|
2542
|
-
throw new Error('Activated Instance message was not admitted to the local queue.');
|
|
2543
|
-
}
|
|
2524
|
+
await this.activateMissingInstanceAsync(record, payloadFrame, metadataFrame, (activation) => {
|
|
2525
|
+
if (this.closed) return;
|
|
2526
|
+
const terminal = this.activationTerminalCompletion(record.target, activation.route);
|
|
2527
|
+
const admitted = this.enqueueActivatedInstanceSpot(
|
|
2528
|
+
ingress,
|
|
2529
|
+
record,
|
|
2530
|
+
payloadFrame,
|
|
2531
|
+
activation.spot,
|
|
2532
|
+
localReply,
|
|
2533
|
+
terminal.onTerminalCompletion,
|
|
2534
|
+
metadataFrame,
|
|
2535
|
+
terminal.onHandlerTurnStarted,
|
|
2536
|
+
this.instanceApplicationTarget(record, activation.route.objectGeneration)
|
|
2537
|
+
);
|
|
2538
|
+
if (admitted !== 'application') {
|
|
2539
|
+
throw new Error('Activated Instance message was not admitted to the local queue.');
|
|
2540
|
+
}
|
|
2541
|
+
});
|
|
2544
2542
|
} catch (error) {
|
|
2545
2543
|
this.finishMissingInstanceActivationFailure(ingress, record, error, localReply);
|
|
2546
2544
|
}
|
|
@@ -2949,15 +2947,35 @@ export class ServiceStatefulRuntime {
|
|
|
2949
2947
|
{ readonly kind: 'instanceSpot'; readonly activation: 'missing' }
|
|
2950
2948
|
>,
|
|
2951
2949
|
payloadFrame: Buffer,
|
|
2952
|
-
metadataFrame
|
|
2953
|
-
|
|
2954
|
-
|
|
2955
|
-
|
|
2956
|
-
|
|
2957
|
-
|
|
2950
|
+
metadataFrame: Buffer | undefined,
|
|
2951
|
+
admit: (activation: {
|
|
2952
|
+
readonly spot: Pick<ServiceSpotState, 'ref'>;
|
|
2953
|
+
readonly route: ServiceInstanceRouteFence;
|
|
2954
|
+
}) => void
|
|
2955
|
+
): Promise<unknown> {
|
|
2956
|
+
const key = record.target.targetSpotId;
|
|
2958
2957
|
const pending = this.pendingInstanceActivations.get(key);
|
|
2959
|
-
|
|
2960
|
-
|
|
2958
|
+
const ready =
|
|
2959
|
+
pending ??
|
|
2960
|
+
this.runMissingInstanceActivation(record, payloadFrame, metadataFrame).then((activation) => ({
|
|
2961
|
+
...activation,
|
|
2962
|
+
stableType: record.target.stableType
|
|
2963
|
+
}));
|
|
2964
|
+
const operation = ready.then((value) => {
|
|
2965
|
+
if (value.stableType !== record.target.stableType) {
|
|
2966
|
+
throw new ZLinkFrameworkException(
|
|
2967
|
+
ZLinkFrameworkErrorKind.TypeMismatch,
|
|
2968
|
+
'Instance Spot type does not match.'
|
|
2969
|
+
);
|
|
2970
|
+
}
|
|
2971
|
+
admit(value);
|
|
2972
|
+
return value;
|
|
2973
|
+
});
|
|
2974
|
+
// The caller owns operation failure. Keep the activation available for later participants.
|
|
2975
|
+
const activation = operation.then(
|
|
2976
|
+
(value) => value,
|
|
2977
|
+
() => ready
|
|
2978
|
+
);
|
|
2961
2979
|
this.pendingInstanceActivations.set(key, activation);
|
|
2962
2980
|
const clear = () => {
|
|
2963
2981
|
if (this.pendingInstanceActivations.get(key) === activation) {
|
|
@@ -2965,7 +2983,7 @@ export class ServiceStatefulRuntime {
|
|
|
2965
2983
|
}
|
|
2966
2984
|
};
|
|
2967
2985
|
void activation.then(clear, clear);
|
|
2968
|
-
return
|
|
2986
|
+
return operation;
|
|
2969
2987
|
}
|
|
2970
2988
|
|
|
2971
2989
|
private async runMissingInstanceActivation(
|
|
@@ -5817,18 +5835,6 @@ function instanceOperationKey(
|
|
|
5817
5835
|
);
|
|
5818
5836
|
}
|
|
5819
5837
|
|
|
5820
|
-
function instanceActivationKey(
|
|
5821
|
-
record: Extract<
|
|
5822
|
-
ServiceStatefulWireRecord,
|
|
5823
|
-
{ readonly kind: 'instanceSpot'; readonly activation: 'missing' }
|
|
5824
|
-
>
|
|
5825
|
-
): string {
|
|
5826
|
-
return (
|
|
5827
|
-
`${record.target.targetNodeRid}\0${record.target.targetNodeGeneration}\0` +
|
|
5828
|
-
`${record.target.targetSpotId}\0${record.target.stableType}`
|
|
5829
|
-
);
|
|
5830
|
-
}
|
|
5831
|
-
|
|
5832
5838
|
function subscriptionKey(channelName: string, topicFilter: string): string {
|
|
5833
5839
|
if (channelName.length === 0 || topicFilter.length === 0) {
|
|
5834
5840
|
throw new TypeError('Channel name and topic filter must be non-empty.');
|
|
@@ -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,
|
|
@@ -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
|
|
@@ -7,6 +7,7 @@ import { UINT64_MAX } from '@zlink-systems/stream-wire';
|
|
|
7
7
|
import { ZLINK_MAX_ROUTING_ID_BYTES } from '../../contracts/Common/CoreTypes';
|
|
8
8
|
import { ZLINK_PROVIDER_MAX_PAGE_SIZE } from '../../contracts/Locations/Stores';
|
|
9
9
|
import { randomUUID } from 'node:crypto';
|
|
10
|
+
import { ZLinkFrameworkException, ZLinkFrameworkErrorKind } from '../../contracts';
|
|
10
11
|
import { SHA256_DIGEST_BYTES } from '../foundation/actor-join-recovery-codec';
|
|
11
12
|
import type {
|
|
12
13
|
ZLinkAggregateAbortResult,
|
|
@@ -41,13 +42,16 @@ import type {
|
|
|
41
42
|
} from './internal-location-contracts';
|
|
42
43
|
import { encodeAuthorityKey } from './authority-key-codec';
|
|
43
44
|
import { creationTerminalPreimage } from './opaque-record-key';
|
|
45
|
+
import { ServiceRelocationAuthorityPayloadCodec } from '../foundation/service-relocation-runtime';
|
|
44
46
|
|
|
45
47
|
const MAX_GENERATION = 0x7fff_ffff_ffff_ffffn;
|
|
46
48
|
|
|
47
49
|
const CREATION_TERMINAL_RETENTION_MS = 5 * 60 * 1000;
|
|
48
50
|
const MAX_U64 = UINT64_MAX;
|
|
51
|
+
const relocationAuthorityCodec = new ServiceRelocationAuthorityPayloadCodec();
|
|
49
52
|
|
|
50
53
|
export interface ZLinkInMemoryAuthorityValidation {
|
|
54
|
+
isOwnerLive(owner: ZLinkLocationOwnerToken): boolean;
|
|
51
55
|
isTargetLive(
|
|
52
56
|
descriptor: ZLinkMeshNodeDescriptorKey,
|
|
53
57
|
lifecycleGeneration: bigint,
|
|
@@ -287,14 +291,42 @@ export class ZLinkInMemoryAuthorityStore {
|
|
|
287
291
|
|
|
288
292
|
private tryReleaseEndedReservation(
|
|
289
293
|
key: ZLinkAuthorityKey,
|
|
290
|
-
expectedStoreVersion: string
|
|
294
|
+
expectedStoreVersion: string,
|
|
295
|
+
request?: ZLinkObjectReserveRequest
|
|
291
296
|
): boolean {
|
|
292
297
|
const current = this.rows.get(key.value);
|
|
293
298
|
if (
|
|
294
299
|
current === undefined ||
|
|
295
300
|
current.snapshot.storeVersion.value !== expectedStoreVersion ||
|
|
296
|
-
current.snapshot.allocation.state
|
|
297
|
-
|
|
301
|
+
(current.snapshot.allocation.state === 'active'
|
|
302
|
+
? request?.key.kind !== 'actor'
|
|
303
|
+
: current.creation === undefined)
|
|
304
|
+
)
|
|
305
|
+
return false;
|
|
306
|
+
if (current.snapshot.allocation.state === 'active') {
|
|
307
|
+
for (const aggregate of this.aggregates.values()) {
|
|
308
|
+
if (
|
|
309
|
+
aggregate.state === 'prepared' &&
|
|
310
|
+
aggregate.request.participants.some(
|
|
311
|
+
(participant) => participant.authorityKey.value === key.value
|
|
312
|
+
)
|
|
313
|
+
)
|
|
314
|
+
return false;
|
|
315
|
+
}
|
|
316
|
+
if (relocationAuthorityCodec.read(current.snapshot.payload) !== undefined) return false;
|
|
317
|
+
if (
|
|
318
|
+
this.validation.isOwnerLive({
|
|
319
|
+
ownerId: current.snapshot.ownerId,
|
|
320
|
+
leaseGeneration: current.snapshot.ownerLeaseGeneration
|
|
321
|
+
})
|
|
322
|
+
)
|
|
323
|
+
return false;
|
|
324
|
+
if (request?.actorRelocationPolicy !== 'disabled')
|
|
325
|
+
throw new ZLinkFrameworkException(
|
|
326
|
+
ZLinkFrameworkErrorKind.Unavailable,
|
|
327
|
+
'Actor owner lease is unavailable.'
|
|
328
|
+
);
|
|
329
|
+
} else if (
|
|
298
330
|
this.validation.isTargetLive(
|
|
299
331
|
current.snapshot.allocation.descriptor,
|
|
300
332
|
current.snapshot.allocation.descriptorLifecycleGeneration,
|
|
@@ -305,9 +337,14 @@ export class ZLinkInMemoryAuthorityStore {
|
|
|
305
337
|
)
|
|
306
338
|
)
|
|
307
339
|
return false;
|
|
308
|
-
this.adjustCapacity(
|
|
340
|
+
this.adjustCapacity(
|
|
341
|
+
current.snapshot.allocation.state === 'active' ? this.activeCapacity : this.pendingCapacity,
|
|
342
|
+
current.snapshot.allocation,
|
|
343
|
+
-1
|
|
344
|
+
);
|
|
309
345
|
this.rows.delete(key.value);
|
|
310
|
-
|
|
346
|
+
if (current.snapshot.allocation.state === 'reserved' && current.creation !== undefined)
|
|
347
|
+
this.creationTerminals.set(current.creation.reservationId, 'aborted');
|
|
311
348
|
this.scanRevision++;
|
|
312
349
|
return true;
|
|
313
350
|
}
|
|
@@ -330,16 +367,16 @@ export class ZLinkInMemoryAuthorityStore {
|
|
|
330
367
|
) {
|
|
331
368
|
return { kind: 'typeMismatch', current: this.snapshot(current.snapshot) };
|
|
332
369
|
}
|
|
333
|
-
if (current.snapshot.allocation.state === 'active') {
|
|
334
|
-
return { kind: 'alreadyExists', current: this.snapshot(current.snapshot) };
|
|
335
|
-
}
|
|
336
370
|
if (
|
|
337
371
|
!this.tryReleaseEndedReservation(
|
|
338
372
|
encodeAuthorityKey(request.key.kind, request.key.globalId),
|
|
339
|
-
current.snapshot.storeVersion.value
|
|
373
|
+
current.snapshot.storeVersion.value,
|
|
374
|
+
request
|
|
340
375
|
)
|
|
341
376
|
)
|
|
342
|
-
return
|
|
377
|
+
return current.snapshot.allocation.state === 'active'
|
|
378
|
+
? { kind: 'alreadyExists', current: this.snapshot(current.snapshot) }
|
|
379
|
+
: { kind: 'conflict', current: this.read(key) };
|
|
343
380
|
}
|
|
344
381
|
const target = creationTarget(request);
|
|
345
382
|
if (
|