@zlink-systems/framework 0.32.0 → 0.32.2
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-context.js +1 -1
- package/dist/runtime/actors/actor-creation.js +1 -1
- package/dist/runtime/actors/actor-runtime-state.d.ts +2 -1
- package/dist/runtime/actors/actor-runtime-state.js +24 -8
- package/dist/runtime/actors/index.d.ts +3 -3
- package/dist/runtime/actors/index.js +19 -32
- package/dist/runtime/host/entry-actor-runtime.d.ts +3 -9
- package/dist/runtime/host/entry-actor-runtime.js +2 -10
- package/dist/runtime/host/spot-runtime-options-factory.js +2 -1
- package/dist/runtime/locations/actor-location-claims.js +9 -10
- package/dist/runtime/spots/index.d.ts +4 -4
- package/dist/runtime/spots/index.js +24 -0
- package/dist/runtime/spots/spot-activation.d.ts +2 -0
- package/dist/runtime/spots/spot-actor-admission-coordinator.d.ts +1 -1
- package/dist/runtime/spots/spot-actor-admission-coordinator.js +2 -1
- package/dist/runtime/spots/spot-actor-ingress-admission.d.ts +11 -7
- package/dist/runtime/spots/spot-actor-ingress-admission.js +14 -6
- package/dist/runtime/spots/spot-actor-packet-dispatch.d.ts +3 -3
- package/dist/runtime/spots/spot-actor-packet-dispatch.js +2 -2
- package/dist/runtime/spots/spot-entry-activation.js +1 -1
- package/dist/runtime/spots/spot-runtime-ports.d.ts +2 -4
- package/package.json +2 -2
- package/src/runtime/actors/actor-context.ts +1 -1
- package/src/runtime/actors/actor-creation.ts +1 -1
- package/src/runtime/actors/actor-runtime-state.ts +27 -9
- package/src/runtime/actors/index.ts +30 -35
- package/src/runtime/host/entry-actor-runtime.ts +4 -34
- package/src/runtime/host/spot-runtime-options-factory.ts +2 -1
- package/src/runtime/locations/actor-location-claims.ts +14 -14
- package/src/runtime/spots/index.ts +34 -4
- package/src/runtime/spots/spot-activation.ts +2 -0
- package/src/runtime/spots/spot-actor-admission-coordinator.ts +3 -1
- package/src/runtime/spots/spot-actor-ingress-admission.ts +29 -11
- package/src/runtime/spots/spot-actor-packet-dispatch.ts +5 -5
- package/src/runtime/spots/spot-entry-activation.ts +1 -11
- package/src/runtime/spots/spot-runtime-ports.ts +2 -10
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zlink-systems/framework",
|
|
3
|
-
"version": "0.32.
|
|
3
|
+
"version": "0.32.2",
|
|
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.32.
|
|
22
|
+
"@zlink-systems/stream-wire": "0.32.2",
|
|
23
23
|
"@zlink-systems/zlink": "1.23.0"
|
|
24
24
|
}
|
|
25
25
|
}
|
|
@@ -97,7 +97,7 @@ export class DefaultZLinkActorContext implements ZLinkActorContext {
|
|
|
97
97
|
}
|
|
98
98
|
|
|
99
99
|
get objectGeneration(): bigint {
|
|
100
|
-
return this.state.
|
|
100
|
+
return this.state.currentActorRef().generation;
|
|
101
101
|
}
|
|
102
102
|
|
|
103
103
|
get spotId(): SpotId | undefined {
|
|
@@ -211,7 +211,7 @@ export class ZLinkActorCreationCoordinator {
|
|
|
211
211
|
await this.options.locationLifecycle?.setActorRef(
|
|
212
212
|
actorType,
|
|
213
213
|
actorId,
|
|
214
|
-
|
|
214
|
+
toFrameworkActorRef(state.currentActorRef(), meshName),
|
|
215
215
|
0n
|
|
216
216
|
);
|
|
217
217
|
}
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
|
|
10
10
|
import type { Message } from '../../contracts/Common/Message';
|
|
11
11
|
import type { ZLinkBackendActorRef, ZLinkBackendMeshNode } from '../backend/contracts';
|
|
12
|
+
import { ZLinkConfigurationException } from '../configuration';
|
|
12
13
|
import { isStructuralGuardEnabled } from '../execution/state-lane';
|
|
13
14
|
import {
|
|
14
15
|
ZLinkFrameworkInternalErrorKind,
|
|
@@ -172,7 +173,9 @@ export class ZLinkActorRuntimeState {
|
|
|
172
173
|
private spotIdValue: RoutingId | undefined;
|
|
173
174
|
private spotGenerationValue: bigint | undefined;
|
|
174
175
|
private spotMembershipEpochValue = 0n;
|
|
176
|
+
// Preserve the exact incarnation until teardown finalization, even after native removal.
|
|
175
177
|
private nativeActorRefValue: ZLinkBackendActorRef | undefined;
|
|
178
|
+
private nativeActorDestroyed = false;
|
|
176
179
|
private entryNodeRidValue: RoutingId | undefined;
|
|
177
180
|
boundSessionValue: ZLinkRemoteBoundSessionTarget | undefined;
|
|
178
181
|
private remoteActorPacketTargetValue: ZLinkRemoteActorPacketTarget | undefined;
|
|
@@ -228,6 +231,15 @@ export class ZLinkActorRuntimeState {
|
|
|
228
231
|
return this.nativeActorRefValue;
|
|
229
232
|
}
|
|
230
233
|
|
|
234
|
+
currentActorRef(): ZLinkBackendActorRef {
|
|
235
|
+
if (this.nativeActorRefValue === undefined) {
|
|
236
|
+
throw new ZLinkConfigurationException(
|
|
237
|
+
`Actor '${this.actorId}' lifecycle identity is not initialized.`
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
return this.nativeActorRefValue;
|
|
241
|
+
}
|
|
242
|
+
|
|
231
243
|
get boundSessionBindingGeneration(): bigint {
|
|
232
244
|
return this.boundSessionValue?.bindingGeneration ?? 0n;
|
|
233
245
|
}
|
|
@@ -355,24 +367,26 @@ export class ZLinkActorRuntimeState {
|
|
|
355
367
|
);
|
|
356
368
|
}
|
|
357
369
|
|
|
358
|
-
const task = Promise.resolve()
|
|
370
|
+
const task = Promise.resolve()
|
|
371
|
+
.then(() => destroy(this.nativeActorDestroyed ? undefined : actorRef))
|
|
372
|
+
.catch((error: unknown) => {
|
|
373
|
+
// A caller may start another attempt only after this attempt has failed.
|
|
374
|
+
if (this.destroyTask === task) {
|
|
375
|
+
this.destroyTask = undefined;
|
|
376
|
+
this.movingValue = false;
|
|
377
|
+
}
|
|
378
|
+
throw error;
|
|
379
|
+
});
|
|
359
380
|
this.destroyTask = task;
|
|
360
381
|
return task;
|
|
361
382
|
}
|
|
362
383
|
|
|
363
384
|
markNativeActorDestroyed(actorRef: ZLinkBackendActorRef): void {
|
|
364
385
|
if (this.nativeActorRefValue === actorRef) {
|
|
365
|
-
this.
|
|
386
|
+
this.nativeActorDestroyed = true;
|
|
366
387
|
}
|
|
367
388
|
}
|
|
368
389
|
|
|
369
|
-
clearFailedDestroy(task: Promise<void>): void {
|
|
370
|
-
if (this.destroyTask === task) {
|
|
371
|
-
this.destroyTask = undefined;
|
|
372
|
-
}
|
|
373
|
-
this.movingValue = false;
|
|
374
|
-
}
|
|
375
|
-
|
|
376
390
|
ensureContext(createContext: () => ZLinkActorContext): ZLinkActorContext {
|
|
377
391
|
this.context ??= createContext();
|
|
378
392
|
return this.context;
|
|
@@ -425,6 +439,7 @@ export class ZLinkActorRuntimeState {
|
|
|
425
439
|
this.createRequestPayloadValue = undefined;
|
|
426
440
|
this.configured = false;
|
|
427
441
|
this.nativeActorRefValue = undefined;
|
|
442
|
+
this.nativeActorDestroyed = false;
|
|
428
443
|
this.entryNodeRidValue = undefined;
|
|
429
444
|
return true;
|
|
430
445
|
}
|
|
@@ -474,6 +489,7 @@ export class ZLinkActorRuntimeState {
|
|
|
474
489
|
|
|
475
490
|
setNativeActorRef(actorRef: ZLinkBackendActorRef): void {
|
|
476
491
|
this.nativeActorRefValue = actorRef;
|
|
492
|
+
this.nativeActorDestroyed = false;
|
|
477
493
|
this.entryNodeRidValue ??= toFrameworkRoutingId(actorRef.nodeRid);
|
|
478
494
|
}
|
|
479
495
|
|
|
@@ -556,6 +572,7 @@ export class ZLinkActorRuntimeState {
|
|
|
556
572
|
this.spotGenerationValue = undefined;
|
|
557
573
|
this.spotMembershipEpochValue = 0n;
|
|
558
574
|
this.nativeActorRefValue = undefined;
|
|
575
|
+
this.nativeActorDestroyed = false;
|
|
559
576
|
this.entryNodeRidValue = undefined;
|
|
560
577
|
this.boundSessionValue = undefined;
|
|
561
578
|
this.remoteActorPacketTargetValue = undefined;
|
|
@@ -578,6 +595,7 @@ export class ZLinkActorRuntimeState {
|
|
|
578
595
|
this.spotValue = undefined;
|
|
579
596
|
this.spotIdValue = undefined;
|
|
580
597
|
this.nativeActorRefValue = undefined;
|
|
598
|
+
this.nativeActorDestroyed = false;
|
|
581
599
|
// The arriving relocation installs the binding it carries.
|
|
582
600
|
this.boundSessionValue = undefined;
|
|
583
601
|
this.remoteActorPacketTargetValue = undefined;
|
|
@@ -40,7 +40,6 @@ import {
|
|
|
40
40
|
} from './actor-runtime-state';
|
|
41
41
|
import { ZLinkTransferredActorRollbackCoordinator } from './transferred-actor-rollback';
|
|
42
42
|
const ACTOR_CREATION_RETRY_INTERVAL_MS = 5;
|
|
43
|
-
const LOCAL_DIAGNOSTIC_NODE_RID = 'local';
|
|
44
43
|
|
|
45
44
|
export { DefaultZLinkActorClient, type ZLinkActorClientOptions } from './actor-client';
|
|
46
45
|
export { DefaultZLinkActorContext, ZLINK_ACTOR_JOIN_ENTRY_SPOT_RUNTIME } from './actor-context';
|
|
@@ -167,7 +166,7 @@ export class DefaultZLinkActorManager implements ZLinkActorManager {
|
|
|
167
166
|
throwIfAborted(signal);
|
|
168
167
|
if (this.relocationStaged.has(actorId)) return undefined;
|
|
169
168
|
const state = this.states.get(actorId);
|
|
170
|
-
if (state?.actor !== undefined) {
|
|
169
|
+
if (state?.actor !== undefined && state.nativeActorRef !== undefined) {
|
|
171
170
|
return this.actorRefForState(state);
|
|
172
171
|
}
|
|
173
172
|
return await this.options.actorRefResolver?.resolveActorRef(actorId, signal);
|
|
@@ -182,7 +181,12 @@ export class DefaultZLinkActorManager implements ZLinkActorManager {
|
|
|
182
181
|
async findSpot(actorId: string, signal?: AbortSignal): Promise<SpotRef | undefined> {
|
|
183
182
|
throwIfAborted(signal);
|
|
184
183
|
const state = this.states.get(actorId);
|
|
185
|
-
if (
|
|
184
|
+
if (
|
|
185
|
+
state?.actor === undefined ||
|
|
186
|
+
state.spotId === undefined ||
|
|
187
|
+
state.nativeActorRef === undefined
|
|
188
|
+
)
|
|
189
|
+
return undefined;
|
|
186
190
|
const spotId = state.spotId;
|
|
187
191
|
const actorRef = this.actorRefForState(state);
|
|
188
192
|
return {
|
|
@@ -249,12 +253,7 @@ export class DefaultZLinkActorManager implements ZLinkActorManager {
|
|
|
249
253
|
await this.finalizeDestroyedActor(actor.actorId, state);
|
|
250
254
|
})
|
|
251
255
|
);
|
|
252
|
-
|
|
253
|
-
await this.awaitActorDestroy(state, destroyTask);
|
|
254
|
-
} catch (error) {
|
|
255
|
-
state.clearFailedDestroy(destroyTask);
|
|
256
|
-
throw error;
|
|
257
|
-
}
|
|
256
|
+
await this.awaitActorDestroy(state, destroyTask);
|
|
258
257
|
return true;
|
|
259
258
|
}
|
|
260
259
|
|
|
@@ -362,9 +361,10 @@ export class DefaultZLinkActorManager implements ZLinkActorManager {
|
|
|
362
361
|
if (result.status === 'failed') {
|
|
363
362
|
throw actorApplicationFailure(actorId, result.error);
|
|
364
363
|
}
|
|
364
|
+
const actorRef = result.actorRef ?? this.actorRefForState(this.states.get(actorId)!);
|
|
365
365
|
return result.reply === undefined
|
|
366
|
-
? { status: result.status, actor:
|
|
367
|
-
: { status: result.status, actor:
|
|
366
|
+
? { status: result.status, actor: actorRef }
|
|
367
|
+
: { status: result.status, actor: actorRef, reply: result.reply };
|
|
368
368
|
}
|
|
369
369
|
|
|
370
370
|
private ensureActorTypeBelongsToMesh(meshName: string, actorType: string): void {
|
|
@@ -638,7 +638,7 @@ export class DefaultZLinkActorManager implements ZLinkActorManager {
|
|
|
638
638
|
actorType: string,
|
|
639
639
|
createRequest?: unknown,
|
|
640
640
|
signal?: AbortSignal
|
|
641
|
-
): Promise<ZLinkActorLocalCreateResult
|
|
641
|
+
): Promise<ZLinkActorLocalCreateResult<ActorRef | undefined>> {
|
|
642
642
|
this.rememberActorMeshFromType(actorId, actorType);
|
|
643
643
|
return await this.createOrGet(actorId, actorType, false, createRequest, signal);
|
|
644
644
|
}
|
|
@@ -651,7 +651,7 @@ export class DefaultZLinkActorManager implements ZLinkActorManager {
|
|
|
651
651
|
nativeActorRef?: ZLinkBackendActorRef
|
|
652
652
|
): Promise<ZLinkActorLocalCreateResult> {
|
|
653
653
|
this.rememberActorMeshFromType(actorId, actorType);
|
|
654
|
-
|
|
654
|
+
const result = await this.createOrGet(
|
|
655
655
|
actorId,
|
|
656
656
|
actorType,
|
|
657
657
|
false,
|
|
@@ -661,6 +661,11 @@ export class DefaultZLinkActorManager implements ZLinkActorManager {
|
|
|
661
661
|
DEFAULT_REQUEST_TIMEOUT_MS,
|
|
662
662
|
nativeActorRef
|
|
663
663
|
);
|
|
664
|
+
if (result.status === 'failed' || result.status === 'rejected') return result;
|
|
665
|
+
return {
|
|
666
|
+
...result,
|
|
667
|
+
actorRef: this.actorRefForState(this.states.get(actorId)!)
|
|
668
|
+
};
|
|
664
669
|
}
|
|
665
670
|
|
|
666
671
|
adoptCreatedAuthority(
|
|
@@ -712,7 +717,7 @@ export class DefaultZLinkActorManager implements ZLinkActorManager {
|
|
|
712
717
|
const destroyTask = state.getOrStartDestroy(entryNodeRid, (actorRef) =>
|
|
713
718
|
this.terminateActor(state, async () => {
|
|
714
719
|
const destroyedActorRef =
|
|
715
|
-
|
|
720
|
+
state.nativeActorRef === undefined ? undefined : this.actorRefForState(state);
|
|
716
721
|
if (actorRef !== undefined) {
|
|
717
722
|
await node.destroyActor(actorRef, 0, destroySignal);
|
|
718
723
|
state.markNativeActorDestroyed(actorRef);
|
|
@@ -728,12 +733,7 @@ export class DefaultZLinkActorManager implements ZLinkActorManager {
|
|
|
728
733
|
})
|
|
729
734
|
);
|
|
730
735
|
|
|
731
|
-
|
|
732
|
-
await this.awaitActorDestroy(state, destroyTask);
|
|
733
|
-
} catch (error) {
|
|
734
|
-
state.clearFailedDestroy(destroyTask);
|
|
735
|
-
throw error;
|
|
736
|
-
}
|
|
736
|
+
await this.awaitActorDestroy(state, destroyTask);
|
|
737
737
|
}
|
|
738
738
|
|
|
739
739
|
private async createOrGet(
|
|
@@ -745,7 +745,7 @@ export class DefaultZLinkActorManager implements ZLinkActorManager {
|
|
|
745
745
|
claimLocation = true,
|
|
746
746
|
timeoutMs: number = DEFAULT_REQUEST_TIMEOUT_MS,
|
|
747
747
|
nativeActorRef?: ZLinkBackendActorRef
|
|
748
|
-
): Promise<ZLinkActorLocalCreateResult
|
|
748
|
+
): Promise<ZLinkActorLocalCreateResult<ActorRef | undefined>> {
|
|
749
749
|
const deadline = performance.now() + timeoutMs;
|
|
750
750
|
const requestedMeshName = this.actorMeshNames.get(actorId);
|
|
751
751
|
for (;;) {
|
|
@@ -801,7 +801,8 @@ export class DefaultZLinkActorManager implements ZLinkActorManager {
|
|
|
801
801
|
try {
|
|
802
802
|
const result = await operation.task;
|
|
803
803
|
if (result.status === 'created') {
|
|
804
|
-
const actorRef =
|
|
804
|
+
const actorRef =
|
|
805
|
+
state.nativeActorRef === undefined ? undefined : this.actorRefForState(state);
|
|
805
806
|
return {
|
|
806
807
|
status: operation.created ? 'created' : 'existing',
|
|
807
808
|
actor: result.actor,
|
|
@@ -886,16 +887,7 @@ export class DefaultZLinkActorManager implements ZLinkActorManager {
|
|
|
886
887
|
}
|
|
887
888
|
|
|
888
889
|
private actorRefForState(state: ZLinkActorRuntimeState): ActorRef {
|
|
889
|
-
|
|
890
|
-
if (nativeActorRef !== undefined) {
|
|
891
|
-
return toFrameworkActorRef(nativeActorRef, state.meshName ?? '');
|
|
892
|
-
}
|
|
893
|
-
return {
|
|
894
|
-
actorId: state.actorId,
|
|
895
|
-
objectGeneration: 1n,
|
|
896
|
-
meshName: state.meshName ?? '',
|
|
897
|
-
nodeRid: this.options.actorCreatedNodeRidProvider?.() ?? LOCAL_DIAGNOSTIC_NODE_RID
|
|
898
|
-
};
|
|
890
|
+
return toFrameworkActorRef(state.currentActorRef(), state.meshName ?? '');
|
|
899
891
|
}
|
|
900
892
|
}
|
|
901
893
|
|
|
@@ -994,11 +986,11 @@ class ZLinkActorGetOrCreateCallRuntime
|
|
|
994
986
|
extends ZLinkActorCreateCallRuntime
|
|
995
987
|
implements ZLinkActorGetOrCreateCall {}
|
|
996
988
|
|
|
997
|
-
export type ZLinkActorLocalCreateResult =
|
|
989
|
+
export type ZLinkActorLocalCreateResult<TActorRef extends ActorRef | undefined = ActorRef> =
|
|
998
990
|
| {
|
|
999
991
|
readonly status: 'existing' | 'created';
|
|
1000
992
|
readonly actor: ZLinkActor;
|
|
1001
|
-
readonly actorRef:
|
|
993
|
+
readonly actorRef: TActorRef;
|
|
1002
994
|
readonly reply?: unknown;
|
|
1003
995
|
}
|
|
1004
996
|
| {
|
|
@@ -1010,7 +1002,10 @@ export type ZLinkActorLocalCreateResult =
|
|
|
1010
1002
|
readonly error: unknown;
|
|
1011
1003
|
};
|
|
1012
1004
|
|
|
1013
|
-
function requireCreatedActor(
|
|
1005
|
+
function requireCreatedActor(
|
|
1006
|
+
result: ZLinkActorLocalCreateResult<ActorRef | undefined>,
|
|
1007
|
+
actorId: string
|
|
1008
|
+
): ZLinkActor {
|
|
1014
1009
|
if (result.status === 'existing' || result.status === 'created') return result.actor;
|
|
1015
1010
|
if (result.status === 'failed') {
|
|
1016
1011
|
throw actorApplicationFailure(actorId, result.error);
|
|
@@ -1,15 +1,12 @@
|
|
|
1
|
-
import type { ApplicationJobOrigin } from '../application-jobs/contracts';
|
|
2
|
-
import type { ZLinkActorRequestTerminal } from '../spots/spot-actor-packet-dispatch';
|
|
3
|
-
import type { ZLinkMessageFollowOrigin } from '../foundation/service-runtime-contracts';
|
|
4
1
|
import type { ActorRef, ZLinkActor } from '../../contracts';
|
|
5
|
-
import type { Message } from '../../contracts/Common/Message';
|
|
6
2
|
import type { ZLinkBackendActorRef, ZLinkBackendSpotNode } from '../backend';
|
|
7
|
-
import type { DefaultZLinkActorManager
|
|
3
|
+
import type { DefaultZLinkActorManager } from '../actors';
|
|
8
4
|
import { ZLinkPostCommitActorBinder } from '../actors/post-commit-actor-binder';
|
|
9
5
|
import type { DefaultZLinkSpotManager, ZLinkSpotNodeRuntimeManager } from '../spots';
|
|
10
6
|
import type { ZLinkEntryActorRuntime } from '../spots/spot-runtime-ports';
|
|
11
7
|
import type { ZLinkStreamActorLifecyclePort } from '../streams/stream-binding-runtime-ports';
|
|
12
8
|
import type { ZLinkBoundSessionRelay } from './bound-session-relay';
|
|
9
|
+
import type { ZLinkActorIngressMembership } from '../spots/spot-actor-ingress-admission';
|
|
13
10
|
|
|
14
11
|
export interface ZLinkEntryActorRuntimeOptions {
|
|
15
12
|
readonly actorManager: () => DefaultZLinkActorManager | undefined;
|
|
@@ -87,35 +84,8 @@ export class ZLinkEntryActorRuntimeService implements ZLinkEntryActorRuntime {
|
|
|
87
84
|
await this.requireActorManager().destroyActor(node, entryNodeRid, actor, signal);
|
|
88
85
|
}
|
|
89
86
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
parts: readonly Message[],
|
|
93
|
-
returnResponse?: boolean,
|
|
94
|
-
remoteBoundSessionTarget?: ZLinkRemoteBoundSessionTarget,
|
|
95
|
-
fallbackActorRef?: ActorRef,
|
|
96
|
-
origin: ApplicationJobOrigin = 'remote',
|
|
97
|
-
requestTerminal?: ZLinkActorRequestTerminal,
|
|
98
|
-
messageFollowOrigin?: ZLinkMessageFollowOrigin
|
|
99
|
-
): { readonly handled: boolean; readonly response?: unknown } {
|
|
100
|
-
const spotManager = this.options.spotManager();
|
|
101
|
-
const spotId = this.options.actorManager()?.getState(actorId)?.spotId;
|
|
102
|
-
if (spotId === undefined || spotManager === undefined) {
|
|
103
|
-
return { handled: false };
|
|
104
|
-
}
|
|
105
|
-
return {
|
|
106
|
-
handled: true,
|
|
107
|
-
response: spotManager.dispatchRoutedActorPacket(
|
|
108
|
-
origin,
|
|
109
|
-
spotId,
|
|
110
|
-
actorId,
|
|
111
|
-
parts,
|
|
112
|
-
returnResponse,
|
|
113
|
-
remoteBoundSessionTarget,
|
|
114
|
-
fallbackActorRef,
|
|
115
|
-
requestTerminal,
|
|
116
|
-
messageFollowOrigin
|
|
117
|
-
)
|
|
118
|
-
};
|
|
87
|
+
get actorIngressMembership(): ZLinkActorIngressMembership | undefined {
|
|
88
|
+
return this.options.spotManager()?.actorIngressMembership;
|
|
119
89
|
}
|
|
120
90
|
|
|
121
91
|
private requireActorManager(): DefaultZLinkActorManager {
|
|
@@ -315,7 +315,8 @@ export class ZLinkSpotRuntimeOptionsFactory {
|
|
|
315
315
|
state?.meshName === undefined ? undefined : this.meshNodeRoutingId(state.meshName);
|
|
316
316
|
if (
|
|
317
317
|
state?.actor === undefined ||
|
|
318
|
-
state.remoteActorPacketTarget !== undefined
|
|
318
|
+
(state.remoteActorPacketTarget !== undefined &&
|
|
319
|
+
!routingIdsEqual(state.remoteActorPacketTarget.targetNodeRid, localNodeRid)) ||
|
|
319
320
|
actorRef === undefined ||
|
|
320
321
|
localNodeRid === undefined ||
|
|
321
322
|
!routingIdsEqual(actorRef.nodeRid, localNodeRid)
|
|
@@ -426,30 +426,30 @@ export class ZLinkActorLocationClaims {
|
|
|
426
426
|
const current = await store.readAuthority(key);
|
|
427
427
|
if (!matchesActorAuthority(current, actorRef, ownerToken)) return;
|
|
428
428
|
|
|
429
|
-
|
|
429
|
+
let result = await store.compareExchangeAuthority(key, current.storeVersion, {
|
|
430
430
|
kind: 'delete'
|
|
431
431
|
});
|
|
432
|
-
if (
|
|
433
|
-
result.kind === 'deleted' ||
|
|
434
|
-
(result.kind === 'conflict' && result.current.kind === 'missing')
|
|
435
|
-
) {
|
|
436
|
-
return;
|
|
437
|
-
}
|
|
438
432
|
if (
|
|
439
433
|
result.kind === 'conflict' &&
|
|
440
434
|
result.current.kind === 'snapshot' &&
|
|
441
435
|
matchesActorAuthority(result.current, actorRef, ownerToken)
|
|
442
436
|
) {
|
|
443
|
-
|
|
437
|
+
result = await store.compareExchangeAuthority(key, result.current.storeVersion, {
|
|
444
438
|
kind: 'delete'
|
|
445
439
|
});
|
|
446
|
-
if (
|
|
447
|
-
retry.kind === 'deleted' ||
|
|
448
|
-
(retry.kind === 'conflict' && retry.current.kind === 'missing')
|
|
449
|
-
) {
|
|
450
|
-
return;
|
|
451
|
-
}
|
|
452
440
|
}
|
|
441
|
+
if (
|
|
442
|
+
result.kind === 'deleted' ||
|
|
443
|
+
(result.kind === 'conflict' && result.current.kind === 'missing')
|
|
444
|
+
) {
|
|
445
|
+
return;
|
|
446
|
+
}
|
|
447
|
+
throw new ZLinkFrameworkException(
|
|
448
|
+
result.kind === 'conflict'
|
|
449
|
+
? classifyMembershipAuthorityFailure(result.current, actorRef)
|
|
450
|
+
: ZLinkFrameworkErrorKind.InternalFailure,
|
|
451
|
+
`Actor '${actorId}' authority release was rejected with status ${result.kind}.`
|
|
452
|
+
);
|
|
453
453
|
}
|
|
454
454
|
}
|
|
455
455
|
|
|
@@ -4,6 +4,11 @@ import { encodeActorReply } from '../actors/actor-reply';
|
|
|
4
4
|
import { boundSessionErrorPayload } from '../streams/bound-session-response-target';
|
|
5
5
|
import { RELOCATED_REQUEST_OWNED_BY_REQUESTER } from '../actors/actor-handoff';
|
|
6
6
|
import { routingIdsEqual } from '../routing-id';
|
|
7
|
+
import type { ZLinkActorPacketDelivery } from './spot-actor-packet-dispatch';
|
|
8
|
+
import type {
|
|
9
|
+
ZLinkActorDispatchOwner,
|
|
10
|
+
ZLinkActorIngressMembership
|
|
11
|
+
} from './spot-actor-ingress-admission';
|
|
7
12
|
import type { ApplicationJobOrigin } from '../application-jobs/contracts';
|
|
8
13
|
import { dispatchReasonFromError } from '../diagnostics/dispatch-error-details';
|
|
9
14
|
import type {
|
|
@@ -231,10 +236,7 @@ export interface ZLinkSpotManagerOptions {
|
|
|
231
236
|
meshName: string,
|
|
232
237
|
spotType: Type<ZLinkSpot>
|
|
233
238
|
) => import('../../contracts').ZLinkSpotRelocationCoordinationMode;
|
|
234
|
-
readonly actorDispatchOwnerResolver?: (actorId: string) =>
|
|
235
|
-
readonly actorRef?: ActorRef;
|
|
236
|
-
readonly spotId?: RoutingId;
|
|
237
|
-
};
|
|
239
|
+
readonly actorDispatchOwnerResolver?: (actorId: string) => ZLinkActorDispatchOwner;
|
|
238
240
|
readonly channelClient?: ZLinkChannelClient;
|
|
239
241
|
readonly fanoutClient?: ZLinkFanoutClient;
|
|
240
242
|
readonly spotPublisherClient?: ZLinkSpotPublisherClient;
|
|
@@ -337,6 +339,7 @@ interface ZLinkTargetSpotCloseOperation {
|
|
|
337
339
|
}
|
|
338
340
|
|
|
339
341
|
export class DefaultZLinkSpotManager {
|
|
342
|
+
readonly actorIngressMembership: ZLinkActorIngressMembership;
|
|
340
343
|
[inspectRuntime]() {
|
|
341
344
|
return Object.freeze({
|
|
342
345
|
activeActivationCount: this.activations.activeActivations().length,
|
|
@@ -414,6 +417,10 @@ export class DefaultZLinkSpotManager {
|
|
|
414
417
|
spotRouteResolver: options.spotRouteResolver,
|
|
415
418
|
actorTransferRuntime: options.actorTransferRuntime
|
|
416
419
|
});
|
|
420
|
+
this.actorIngressMembership = {
|
|
421
|
+
resolveOwner: options.actorDispatchOwnerResolver,
|
|
422
|
+
dispatchToOwner: (spotId, delivery) => this.dispatchActorPacketToOwner(spotId, delivery)
|
|
423
|
+
};
|
|
417
424
|
this.activationLifecycle = new ZLinkSpotActivationLifecycle({
|
|
418
425
|
timerClock,
|
|
419
426
|
spotTimerHandlers: options.spotTimerHandlers,
|
|
@@ -444,6 +451,7 @@ export class DefaultZLinkSpotManager {
|
|
|
444
451
|
createTopicMessage: options.createTopicMessage,
|
|
445
452
|
nativeSpotNodeProvider: options.nativeSpotNodeProvider,
|
|
446
453
|
actorResolver: options.actorResolver,
|
|
454
|
+
actorIngressMembership: this.actorIngressMembership,
|
|
447
455
|
detachedTaskRunner: options.detachedTaskRunner,
|
|
448
456
|
actorTransferRuntime: options.actorTransferRuntime,
|
|
449
457
|
boundSessionRuntime: options.boundSessionRuntime,
|
|
@@ -1628,6 +1636,28 @@ export class DefaultZLinkSpotManager {
|
|
|
1628
1636
|
);
|
|
1629
1637
|
}
|
|
1630
1638
|
|
|
1639
|
+
private dispatchActorPacketToOwner(
|
|
1640
|
+
spotId: RoutingId | undefined,
|
|
1641
|
+
delivery: ZLinkActorPacketDelivery
|
|
1642
|
+
): Promise<unknown> {
|
|
1643
|
+
const args = [
|
|
1644
|
+
delivery.actorId,
|
|
1645
|
+
delivery.parts,
|
|
1646
|
+
delivery.returnResponse,
|
|
1647
|
+
delivery.remoteBoundSessionTarget,
|
|
1648
|
+
delivery.fallbackActorRef,
|
|
1649
|
+
delivery.requestTerminal,
|
|
1650
|
+
delivery.messageFollowOrigin
|
|
1651
|
+
] as const;
|
|
1652
|
+
if (spotId !== undefined) {
|
|
1653
|
+
return this.dispatchRoutedActorPacket(delivery.origin, spotId, ...args);
|
|
1654
|
+
}
|
|
1655
|
+
if (this.options.dispatchEntryActorPacket === undefined) {
|
|
1656
|
+
throw new ZLinkConfigurationException('Actor ingress requires an Entry FIFO port.');
|
|
1657
|
+
}
|
|
1658
|
+
return this.options.dispatchEntryActorPacket(delivery.origin, ...args);
|
|
1659
|
+
}
|
|
1660
|
+
|
|
1631
1661
|
/** @internal Replays a record already owned by the Actor handoff queue. */
|
|
1632
1662
|
dispatchRoutedActorPacketDirect(
|
|
1633
1663
|
origin: ApplicationJobOrigin,
|
|
@@ -82,6 +82,7 @@ import type {
|
|
|
82
82
|
} from './spot-actor-join-dispatch';
|
|
83
83
|
import { ZLinkSpotActorAdmissionCoordinator } from './spot-actor-admission-coordinator';
|
|
84
84
|
import type { ZLinkRouteToActorJoinPrewarm } from './spot-actor-packet-dispatch';
|
|
85
|
+
import type { ZLinkActorIngressMembership } from './spot-actor-ingress-admission';
|
|
85
86
|
import { ZLinkSpotActivation } from './spot-activation-state';
|
|
86
87
|
import type { ZLinkSpotLocationClaim } from './spot-location-claim';
|
|
87
88
|
import type {
|
|
@@ -132,6 +133,7 @@ export interface ZLinkSpotActivationLifecycleOptions {
|
|
|
132
133
|
readonly createTopicMessage?: () => ZLinkBackendTopicMessage;
|
|
133
134
|
readonly nativeSpotNodeProvider?: (meshName: string) => ZLinkBackendSpotNode | undefined;
|
|
134
135
|
readonly actorResolver?: (actorId: string) => ZLinkActor | undefined;
|
|
136
|
+
readonly actorIngressMembership?: ZLinkActorIngressMembership;
|
|
135
137
|
readonly actorTransferRuntime?: ZLinkSpotActorTransferRuntime;
|
|
136
138
|
readonly boundSessionRuntime?: ZLinkSpotBoundSessionRuntime;
|
|
137
139
|
readonly actorHandoffRuntime?: ZLinkSpotActorHandoffRuntime;
|
|
@@ -25,6 +25,7 @@ type AdmissionOptions = Pick<
|
|
|
25
25
|
| 'actorHandoffRuntime'
|
|
26
26
|
| 'admission'
|
|
27
27
|
| 'actorResolver'
|
|
28
|
+
| 'actorIngressMembership'
|
|
28
29
|
| 'actorTransferRuntime'
|
|
29
30
|
| 'boundSessionRuntime'
|
|
30
31
|
| 'detachedTaskRunner'
|
|
@@ -225,7 +226,8 @@ export class ZLinkSpotActorAdmissionCoordinator {
|
|
|
225
226
|
this.options.actorResolver === undefined
|
|
226
227
|
? activation.resolveJoinedActor(targetActorId)
|
|
227
228
|
: this.options.actorResolver(targetActorId),
|
|
228
|
-
|
|
229
|
+
membershipSpotId: activation.spotId,
|
|
230
|
+
membership: this.options.actorIngressMembership,
|
|
229
231
|
onDisconnectActor: (actor) =>
|
|
230
232
|
activation.serial.execute(() => activation.spot.onDisconnectActor?.(actor)),
|
|
231
233
|
actorResponseSender: this.options.boundSessionRuntime?.sendActorResponse.bind(
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { actorMessageFollowContext } from '../actors/actor-message-follow-context';
|
|
2
|
-
import type { ActorRef, ZLinkActor } from '../../contracts';
|
|
2
|
+
import type { ActorRef, RoutingId, ZLinkActor } from '../../contracts';
|
|
3
|
+
import { routingIdsEqual } from '../routing-id';
|
|
3
4
|
import type { Message } from '../../contracts/Common/Message';
|
|
4
5
|
import type {
|
|
5
6
|
ZLinkActorPacketDelivery,
|
|
@@ -30,9 +31,18 @@ export type ZLinkRouteToActorJoinPrewarm = (
|
|
|
30
31
|
}
|
|
31
32
|
) => 'parked' | 'not-found';
|
|
32
33
|
|
|
33
|
-
export
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
export interface ZLinkActorDispatchOwner {
|
|
35
|
+
readonly actorRef?: ActorRef;
|
|
36
|
+
readonly spotId?: RoutingId;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface ZLinkActorIngressMembership {
|
|
40
|
+
readonly resolveOwner?: (actorId: string) => ZLinkActorDispatchOwner;
|
|
41
|
+
readonly dispatchToOwner?: (
|
|
42
|
+
spotId: RoutingId | undefined,
|
|
43
|
+
delivery: ZLinkActorPacketDelivery
|
|
44
|
+
) => Promise<unknown>;
|
|
45
|
+
}
|
|
36
46
|
|
|
37
47
|
export type ZLinkActorIngressAdmission =
|
|
38
48
|
| { readonly kind: 'local'; readonly actor: ZLinkActor }
|
|
@@ -45,9 +55,9 @@ export function resolveActorIngress(
|
|
|
45
55
|
request: boolean,
|
|
46
56
|
options: {
|
|
47
57
|
readonly handoffRuntime?: ZLinkSpotActorHandoffRuntime;
|
|
48
|
-
readonly
|
|
58
|
+
readonly spotId?: RoutingId;
|
|
59
|
+
readonly membership?: ZLinkActorIngressMembership;
|
|
49
60
|
readonly resolveActor: (actorId: string) => ZLinkActor | undefined;
|
|
50
|
-
readonly hasDepartedActor?: (actorId: string) => boolean;
|
|
51
61
|
readonly routeToActorJoinPrewarm?: ZLinkRouteToActorJoinPrewarm;
|
|
52
62
|
}
|
|
53
63
|
): ZLinkActorIngressAdmission {
|
|
@@ -62,12 +72,20 @@ export function resolveActorIngress(
|
|
|
62
72
|
delivery.messageFollowOrigin
|
|
63
73
|
);
|
|
64
74
|
if (handoff !== undefined) return { kind: 'follow', terminal: handoff };
|
|
65
|
-
const
|
|
66
|
-
|
|
75
|
+
const membership = options.membership;
|
|
76
|
+
const owner = membership?.resolveOwner?.(delivery.actorId);
|
|
77
|
+
if (owner?.actorRef !== undefined && !routingIdsEqual(owner.spotId, options.spotId)) {
|
|
78
|
+
if (membership?.dispatchToOwner === undefined) {
|
|
79
|
+
throw new Error('Actor ingress requires the current membership dispatch port.');
|
|
80
|
+
}
|
|
81
|
+
// Transfer the accepted delivery before releasing the source FIFO claim.
|
|
82
|
+
// Its terminal is awaited outside that claim by dispatchActorPacketInMailbox.
|
|
83
|
+
return { kind: 'follow', terminal: membership.dispatchToOwner(owner.spotId, delivery) };
|
|
84
|
+
}
|
|
67
85
|
const actor =
|
|
68
|
-
|
|
69
|
-
?
|
|
70
|
-
:
|
|
86
|
+
membership?.resolveOwner === undefined || owner?.actorRef !== undefined
|
|
87
|
+
? options.resolveActor(delivery.actorId)
|
|
88
|
+
: undefined;
|
|
71
89
|
if (actor !== undefined) return { kind: 'local', actor };
|
|
72
90
|
if (
|
|
73
91
|
options.routeToActorJoinPrewarm !== undefined &&
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
resolveActorIngress,
|
|
3
3
|
type ZLinkRouteToActorJoinPrewarm,
|
|
4
|
-
type
|
|
4
|
+
type ZLinkActorIngressMembership
|
|
5
5
|
} from './spot-actor-ingress-admission';
|
|
6
6
|
import type { ZLinkSpotActorHandoffRuntime } from './spot-runtime-ports';
|
|
7
7
|
import { DefaultZLinkActorContext } from '../actors/actor-context';
|
|
@@ -97,8 +97,8 @@ interface ZLinkSpotActorPacketDispatchOptions {
|
|
|
97
97
|
readonly spotId: () => string;
|
|
98
98
|
readonly registry: ZLinkSpotActorHandlerRegistryRuntime;
|
|
99
99
|
readonly resolveActor: (actorId: string) => ZLinkActor | undefined;
|
|
100
|
-
readonly
|
|
101
|
-
readonly
|
|
100
|
+
readonly membershipSpotId?: RoutingId;
|
|
101
|
+
readonly membership?: ZLinkActorIngressMembership;
|
|
102
102
|
readonly onDisconnectActor: (actor: ZLinkActor) => Promise<void>;
|
|
103
103
|
readonly actorResponseSender?: (
|
|
104
104
|
actor: ZLinkActor,
|
|
@@ -226,9 +226,9 @@ export class ZLinkSpotActorPacketDispatch {
|
|
|
226
226
|
messageKind === ZLinkDispatchMessageKind.ActorRequest,
|
|
227
227
|
{
|
|
228
228
|
handoffRuntime,
|
|
229
|
-
|
|
229
|
+
spotId: this.options.membershipSpotId,
|
|
230
|
+
membership: this.options.membership,
|
|
230
231
|
resolveActor: this.options.resolveActor,
|
|
231
|
-
hasDepartedActor: this.options.hasDepartedActor,
|
|
232
232
|
routeToActorJoinPrewarm: this.options.routeToActorJoinPrewarm
|
|
233
233
|
}
|
|
234
234
|
);
|
|
@@ -196,17 +196,7 @@ export class ZLinkEntrySpotActivation {
|
|
|
196
196
|
spotId: () => String(this.options.nativeSpot.routingId),
|
|
197
197
|
registry: this.actorHandlers,
|
|
198
198
|
resolveActor: (targetActorId) => this.options.entryActorRuntime?.resolveActor(targetActorId),
|
|
199
|
-
|
|
200
|
-
this.options.entryActorRuntime?.routePacket(
|
|
201
|
-
delivery.actorId,
|
|
202
|
-
delivery.parts,
|
|
203
|
-
delivery.returnResponse,
|
|
204
|
-
delivery.remoteBoundSessionTarget,
|
|
205
|
-
delivery.fallbackActorRef,
|
|
206
|
-
delivery.origin,
|
|
207
|
-
delivery.requestTerminal,
|
|
208
|
-
delivery.messageFollowOrigin
|
|
209
|
-
),
|
|
199
|
+
membership: this.options.entryActorRuntime?.actorIngressMembership,
|
|
210
200
|
onDisconnectActor: (actor) => this.notifyDisconnectActor(actor),
|
|
211
201
|
actorResponseSender: this.options.boundSessionRuntime?.sendActorResponse.bind(
|
|
212
202
|
this.options.boundSessionRuntime
|