@zlink-systems/framework 0.29.0 → 0.30.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/.tsbuildinfo +1 -1
- package/dist/contracts/Configuration/Builders.d.ts +1 -2
- package/dist/contracts/Configuration/Configs.d.ts +0 -5
- package/dist/contracts/Configuration/RegistrationBuilders.js +6 -4
- package/dist/contracts/Configuration/RegistrationTypes.d.ts +1 -6
- package/dist/contracts/Configuration/RegistrationValidators.js +4 -4
- package/dist/contracts/Configuration/index.d.ts +1 -1
- package/dist/internal.d.ts +1 -0
- package/dist/internal.js +3 -1
- package/dist/runtime/actors/actor-handoff.d.ts +3 -2
- package/dist/runtime/actors/actor-handoff.js +9 -4
- 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/application-jobs/application-ingress-record-owner.d.ts +3 -1
- package/dist/runtime/application-jobs/application-ingress-record-owner.js +6 -1
- package/dist/runtime/application-jobs/application-job-queue-scope.d.ts +3 -1
- package/dist/runtime/application-jobs/application-job-queue-scope.js +5 -0
- package/dist/runtime/application-jobs/contracts.d.ts +5 -1
- package/dist/runtime/backend/contracts/index.d.ts +10 -19
- package/dist/runtime/backend/mesh-completion-table.d.ts +6 -4
- package/dist/runtime/backend/mesh-completion-table.js +28 -18
- package/dist/runtime/backend/mesh-dispatch-pump.js +21 -25
- package/dist/runtime/backend/node/node-backend-adapter-factory.js +6 -1
- package/dist/runtime/backend/node/node-backend-adapter-support.d.ts +3 -3
- package/dist/runtime/backend/node/node-backend-adapter-support.js +24 -29
- package/dist/runtime/backend/node/node-mesh-backend-adapter.d.ts +1 -9
- package/dist/runtime/backend/node/node-mesh-backend-adapter.js +1 -1
- package/dist/runtime/backend/node/node-raw-binding-port.js +11 -6
- package/dist/runtime/backend/node/node-raw-mesh-backend.d.ts +10 -12
- package/dist/runtime/backend/node/node-raw-mesh-backend.js +64 -98
- package/dist/runtime/backend/node/node-socket-backend-adapter.js +10 -5
- package/dist/runtime/backend/raw-binding-port.d.ts +9 -2
- package/dist/runtime/channels/channel-receive-loops.js +3 -3
- package/dist/runtime/channels/channel-runtime-lifecycle.js +2 -1
- package/dist/runtime/channels/channel-socket-registry.d.ts +10 -3
- package/dist/runtime/channels/channel-socket-registry.js +59 -82
- package/dist/runtime/diagnostics/message-flow.d.ts +1 -0
- package/dist/runtime/diagnostics/message-flow.js +3 -3
- package/dist/runtime/foundation/raw-service-mesh-runtime.d.ts +15 -1
- package/dist/runtime/foundation/raw-service-mesh-runtime.js +193 -64
- package/dist/runtime/foundation/service-liveness-registry.d.ts +20 -8
- package/dist/runtime/foundation/service-liveness-registry.js +57 -44
- package/dist/runtime/foundation/service-mailbox.d.ts +3 -0
- package/dist/runtime/foundation/service-mailbox.js +2 -1
- package/dist/runtime/foundation/service-stateful-runtime.d.ts +7 -6
- package/dist/runtime/foundation/service-stateful-runtime.js +60 -41
- package/dist/runtime/foundation/service-stateful-wire-codec.d.ts +5 -2
- package/dist/runtime/foundation/service-stateful-wire-codec.js +43 -61
- package/dist/runtime/foundation/service-topology-registry.d.ts +5 -3
- package/dist/runtime/foundation/service-topology-registry.js +9 -4
- package/dist/runtime/handlers/handler-instance-scope.js +1 -1
- package/dist/runtime/host/actor-packet-relay.js +7 -7
- 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 +2 -1
- package/dist/runtime/host/application-job-queue.d.ts +11 -4
- package/dist/runtime/host/application-job-queue.js +66 -10
- package/dist/runtime/host/entry-actor-runtime.js +1 -1
- package/dist/runtime/host/index.d.ts +1 -1
- package/dist/runtime/host/index.js +18 -13
- package/dist/runtime/host/instance-activation-authority.d.ts +2 -1
- package/dist/runtime/host/instance-activation-authority.js +89 -25
- package/dist/runtime/host/service-relocation-host-runtime.js +2 -2
- package/dist/runtime/host/spot-address-transport.d.ts +1 -0
- package/dist/runtime/host/spot-address-transport.js +25 -12
- package/dist/runtime/host/spot-runtime-options-factory.js +2 -2
- package/dist/runtime/host/user-spot-creation-coordinator.d.ts +1 -0
- package/dist/runtime/host/user-spot-creation-coordinator.js +1 -0
- package/dist/runtime/locations/domain-store-contract.d.ts +3 -2
- package/dist/runtime/locations/in-memory-authority-store.d.ts +5 -2
- package/dist/runtime/locations/in-memory-authority-store.js +107 -14
- package/dist/runtime/locations/in-memory-location-store.d.ts +3 -3
- package/dist/runtime/locations/in-memory-location-store.js +31 -13
- package/dist/runtime/locations/internal-location-contracts.d.ts +8 -0
- package/dist/runtime/locations/location-store-repository.d.ts +4 -2
- package/dist/runtime/locations/location-store-repository.js +209 -33
- package/dist/runtime/locations/resolvers.d.ts +2 -1
- package/dist/runtime/locations/resolvers.js +10 -1
- package/dist/runtime/messaging/submission-result.d.ts +1 -1
- package/dist/runtime/messaging/submission-result.js +2 -2
- package/dist/runtime/spots/formal-remote-actor-admission-registry.d.ts +2 -0
- package/dist/runtime/spots/index.d.ts +4 -3
- package/dist/runtime/spots/index.js +14 -14
- package/dist/runtime/spots/spot-activation.d.ts +2 -1
- package/dist/runtime/spots/spot-activation.js +4 -4
- package/dist/runtime/spots/spot-actor-admission-coordinator.d.ts +2 -1
- package/dist/runtime/spots/spot-actor-admission-coordinator.js +11 -10
- package/dist/runtime/spots/spot-actor-packet-dispatch.d.ts +4 -1
- package/dist/runtime/spots/spot-actor-packet-dispatch.js +7 -5
- package/dist/runtime/spots/spot-actor-packet-drain.js +2 -0
- package/dist/runtime/spots/spot-actor-packet-relay-dispatch.js +1 -0
- package/dist/runtime/spots/spot-entry-activation.d.ts +1 -0
- package/dist/runtime/spots/spot-entry-activation.js +6 -1
- package/dist/runtime/spots/spot-node-runtime-manager.d.ts +3 -1
- package/dist/runtime/spots/spot-node-runtime-manager.js +10 -11
- package/dist/runtime/spots/spot-routing-internal.d.ts +6 -0
- package/dist/runtime/spots/spot-runtime-ports.d.ts +2 -1
- package/dist/runtime/spots/spot-serial-executor.d.ts +1 -0
- package/dist/runtime/spots/spot-serial-executor.js +10 -1
- package/dist/runtime/streams/stream-session-runtime.js +1 -1
- package/package.json +3 -3
- package/src/contracts/Configuration/Builders.ts +1 -2
- package/src/contracts/Configuration/Configs.ts +0 -6
- package/src/contracts/Configuration/RegistrationBuilders.ts +8 -12
- package/src/contracts/Configuration/RegistrationTypes.ts +1 -6
- package/src/contracts/Configuration/RegistrationValidators.ts +9 -10
- package/src/contracts/Configuration/index.ts +1 -6
- package/src/internal.ts +1 -0
- package/src/runtime/actors/actor-handoff.ts +13 -4
- package/src/runtime/actors/actor-runtime-contracts.ts +4 -0
- package/src/runtime/actors/index.ts +77 -42
- package/src/runtime/application-jobs/application-ingress-record-owner.ts +12 -2
- package/src/runtime/application-jobs/application-job-queue-scope.ts +6 -1
- package/src/runtime/application-jobs/contracts.ts +14 -1
- package/src/runtime/backend/contracts/index.ts +10 -18
- package/src/runtime/backend/mesh-completion-table.ts +36 -19
- package/src/runtime/backend/mesh-dispatch-pump.ts +19 -22
- package/src/runtime/backend/node/node-backend-adapter-factory.ts +12 -5
- package/src/runtime/backend/node/node-backend-adapter-support.ts +31 -29
- package/src/runtime/backend/node/node-mesh-backend-adapter.ts +3 -10
- package/src/runtime/backend/node/node-raw-binding-port.ts +19 -10
- package/src/runtime/backend/node/node-raw-mesh-backend.ts +81 -111
- package/src/runtime/backend/node/node-socket-backend-adapter.ts +12 -8
- package/src/runtime/backend/raw-binding-port.ts +12 -2
- package/src/runtime/channels/channel-receive-loops.ts +3 -3
- package/src/runtime/channels/channel-runtime-lifecycle.ts +7 -1
- package/src/runtime/channels/channel-socket-registry.ts +84 -130
- package/src/runtime/diagnostics/message-flow.ts +1 -1
- package/src/runtime/foundation/raw-service-mesh-runtime.ts +268 -70
- package/src/runtime/foundation/service-liveness-registry.ts +68 -59
- package/src/runtime/foundation/service-mailbox.ts +7 -1
- package/src/runtime/foundation/service-stateful-runtime.ts +109 -77
- package/src/runtime/foundation/service-stateful-wire-codec.ts +55 -79
- package/src/runtime/foundation/service-topology-registry.ts +13 -7
- package/src/runtime/handlers/handler-instance-scope.ts +2 -1
- package/src/runtime/host/actor-packet-relay.ts +7 -0
- package/src/runtime/host/actor-placement-coordinator.ts +8 -2
- package/src/runtime/host/actor-runtime-options-factory.ts +7 -0
- package/src/runtime/host/application-job-queue.ts +77 -10
- package/src/runtime/host/entry-actor-runtime.ts +1 -0
- package/src/runtime/host/index.ts +30 -7
- package/src/runtime/host/instance-activation-authority.ts +132 -27
- package/src/runtime/host/service-relocation-host-runtime.ts +2 -0
- package/src/runtime/host/spot-address-transport.ts +45 -22
- package/src/runtime/host/spot-runtime-options-factory.ts +2 -0
- package/src/runtime/host/user-spot-creation-coordinator.ts +8 -0
- package/src/runtime/locations/domain-store-contract.ts +6 -2
- package/src/runtime/locations/in-memory-authority-store.ts +153 -12
- package/src/runtime/locations/in-memory-location-store.ts +39 -13
- package/src/runtime/locations/internal-location-contracts.ts +10 -0
- package/src/runtime/locations/location-store-repository.ts +295 -36
- package/src/runtime/locations/resolvers.ts +25 -2
- package/src/runtime/messaging/submission-result.ts +2 -2
- package/src/runtime/spots/formal-remote-actor-admission-registry.ts +2 -0
- package/src/runtime/spots/index.ts +16 -0
- package/src/runtime/spots/spot-activation.ts +5 -0
- package/src/runtime/spots/spot-actor-admission-coordinator.ts +13 -0
- package/src/runtime/spots/spot-actor-packet-dispatch.ts +10 -0
- package/src/runtime/spots/spot-actor-packet-drain.ts +2 -0
- package/src/runtime/spots/spot-actor-packet-relay-dispatch.ts +1 -0
- package/src/runtime/spots/spot-entry-activation.ts +7 -0
- package/src/runtime/spots/spot-node-runtime-manager.ts +14 -12
- package/src/runtime/spots/spot-routing-internal.ts +14 -0
- package/src/runtime/spots/spot-runtime-ports.ts +2 -0
- package/src/runtime/spots/spot-serial-executor.ts +10 -4
- package/src/runtime/streams/stream-session-runtime.ts +1 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { UINT64_MAX } from '@zlink-systems/stream-wire';
|
|
2
2
|
export interface ServiceProbe {
|
|
3
3
|
readonly nodeRoutingId: string;
|
|
4
|
-
readonly connectionId: string;
|
|
4
|
+
readonly connectionId: string | bigint;
|
|
5
5
|
readonly probeId: bigint;
|
|
6
6
|
}
|
|
7
7
|
|
|
@@ -10,20 +10,52 @@ export interface ServiceLivenessTick {
|
|
|
10
10
|
readonly timedOutNodes: readonly string[];
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
export const DEFAULT_SERVICE_PROBE_INTERVAL_MS = 5_000;
|
|
14
|
+
export const DEFAULT_SERVICE_PEER_TIMEOUT_MS = 15_000;
|
|
15
|
+
|
|
16
|
+
export class ServiceLivenessConnection {
|
|
15
17
|
deadlineMs: number;
|
|
16
18
|
nextProbeMs: number;
|
|
17
19
|
outstandingProbe?: bigint;
|
|
18
|
-
ready: boolean;
|
|
19
|
-
}
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
constructor(
|
|
22
|
+
readonly connectionId: string | bigint,
|
|
23
|
+
nowMs: number,
|
|
24
|
+
private readonly probeIntervalMs = DEFAULT_SERVICE_PROBE_INTERVAL_MS,
|
|
25
|
+
private readonly peerTimeoutMs = DEFAULT_SERVICE_PEER_TIMEOUT_MS
|
|
26
|
+
) {
|
|
27
|
+
this.deadlineMs = nowMs + peerTimeoutMs;
|
|
28
|
+
this.nextProbeMs = nowMs;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
readonly recordReceived = (nowMs = performance.now()): void => {
|
|
32
|
+
this.deadlineMs = Math.max(this.deadlineMs, nowMs + this.peerTimeoutMs);
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
isExpired(nowMs: number): boolean {
|
|
36
|
+
return nowMs >= this.deadlineMs;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
tryGetProbe(nowMs: number, allocateProbeId: () => bigint): bigint | undefined {
|
|
40
|
+
if (nowMs < this.nextProbeMs) return undefined;
|
|
41
|
+
this.outstandingProbe ??= allocateProbeId();
|
|
42
|
+
do {
|
|
43
|
+
this.nextProbeMs += this.probeIntervalMs;
|
|
44
|
+
} while (this.nextProbeMs <= nowMs);
|
|
45
|
+
return this.outstandingProbe;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
acknowledge(probeId: bigint, nowMs: number): boolean {
|
|
49
|
+
this.recordReceived(nowMs);
|
|
50
|
+
if (this.outstandingProbe !== probeId) return false;
|
|
51
|
+
this.outstandingProbe = undefined;
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
23
55
|
|
|
24
56
|
/** Uses monotonic milliseconds and never treats application traffic as a probe ACK. */
|
|
25
57
|
export class ServiceLivenessRegistry {
|
|
26
|
-
private readonly peers = new Map<string,
|
|
58
|
+
private readonly peers = new Map<string, ServiceLivenessConnection>();
|
|
27
59
|
private nextProbeId = 1n;
|
|
28
60
|
|
|
29
61
|
constructor(
|
|
@@ -40,33 +72,31 @@ export class ServiceLivenessRegistry {
|
|
|
40
72
|
}
|
|
41
73
|
}
|
|
42
74
|
|
|
43
|
-
admit(
|
|
75
|
+
admit(
|
|
76
|
+
nodeRoutingId: string,
|
|
77
|
+
connectionId: string | bigint,
|
|
78
|
+
nowMs: number
|
|
79
|
+
): ServiceLivenessConnection {
|
|
44
80
|
requireIdentity(nodeRoutingId, 'nodeRoutingId');
|
|
45
|
-
requireIdentity(connectionId, 'connectionId');
|
|
81
|
+
if (typeof connectionId === 'string') requireIdentity(connectionId, 'connectionId');
|
|
82
|
+
else if (connectionId === 0n) throw new TypeError('connectionId must be nonzero.');
|
|
46
83
|
const current = this.peers.get(nodeRoutingId);
|
|
47
|
-
if (current?.connectionId === connectionId) return;
|
|
48
|
-
|
|
84
|
+
if (current?.connectionId === connectionId) return current;
|
|
85
|
+
const admitted = new ServiceLivenessConnection(
|
|
49
86
|
connectionId,
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
87
|
+
nowMs,
|
|
88
|
+
this.probeIntervalMs,
|
|
89
|
+
this.peerTimeoutMs
|
|
90
|
+
);
|
|
91
|
+
this.peers.set(nodeRoutingId, admitted);
|
|
92
|
+
return admitted;
|
|
54
93
|
}
|
|
55
94
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
if (
|
|
59
|
-
current === undefined ||
|
|
60
|
-
current.connectionId !== connectionId ||
|
|
61
|
-
current.ready ||
|
|
62
|
-
current.outstandingProbe !== undefined
|
|
63
|
-
)
|
|
64
|
-
return false;
|
|
65
|
-
current.nextProbeMs = Math.min(current.nextProbeMs, nowMs);
|
|
66
|
-
return true;
|
|
95
|
+
recordReceived(connection: ServiceLivenessConnection, nowMs: number): void {
|
|
96
|
+
connection.recordReceived(nowMs);
|
|
67
97
|
}
|
|
68
98
|
|
|
69
|
-
disconnect(nodeRoutingId: string, connectionId: string): boolean {
|
|
99
|
+
disconnect(nodeRoutingId: string, connectionId: string | bigint): boolean {
|
|
70
100
|
const current = this.peers.get(nodeRoutingId);
|
|
71
101
|
if (current === undefined || current.connectionId !== connectionId) return false;
|
|
72
102
|
this.peers.delete(nodeRoutingId);
|
|
@@ -75,27 +105,18 @@ export class ServiceLivenessRegistry {
|
|
|
75
105
|
|
|
76
106
|
acknowledge(
|
|
77
107
|
nodeRoutingId: string,
|
|
78
|
-
connectionId: string,
|
|
108
|
+
connectionId: string | bigint,
|
|
79
109
|
probeId: bigint,
|
|
80
110
|
nowMs: number
|
|
81
111
|
): boolean {
|
|
82
112
|
const current = this.peers.get(nodeRoutingId);
|
|
83
|
-
if (
|
|
84
|
-
|
|
85
|
-
current.connectionId !== connectionId ||
|
|
86
|
-
current.outstandingProbe !== probeId
|
|
87
|
-
) {
|
|
88
|
-
return false;
|
|
89
|
-
}
|
|
90
|
-
current.outstandingProbe = undefined;
|
|
91
|
-
current.deadlineMs = nowMs + this.peerTimeoutMs;
|
|
92
|
-
current.ready = true;
|
|
93
|
-
return true;
|
|
113
|
+
if (current === undefined || current.connectionId !== connectionId) return false;
|
|
114
|
+
return current.acknowledge(probeId, nowMs);
|
|
94
115
|
}
|
|
95
116
|
|
|
96
117
|
acknowledgeProbe(
|
|
97
118
|
nodeRoutingId: string,
|
|
98
|
-
connectionId: string,
|
|
119
|
+
connectionId: string | bigint,
|
|
99
120
|
probeId: bigint
|
|
100
121
|
): ServiceProbe | undefined {
|
|
101
122
|
const current = this.peers.get(nodeRoutingId);
|
|
@@ -109,21 +130,14 @@ export class ServiceLivenessRegistry {
|
|
|
109
130
|
const probes: ServiceProbe[] = [];
|
|
110
131
|
const timedOutNodes: string[] = [];
|
|
111
132
|
for (const [nodeRoutingId, peer] of this.peers) {
|
|
112
|
-
if (peer.
|
|
133
|
+
if (peer.isExpired(nowMs)) {
|
|
113
134
|
timedOutNodes.push(nodeRoutingId);
|
|
114
135
|
this.peers.delete(nodeRoutingId);
|
|
115
136
|
continue;
|
|
116
137
|
}
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
nodeRoutingId,
|
|
121
|
-
connectionId: peer.connectionId,
|
|
122
|
-
probeId: peer.outstandingProbe
|
|
123
|
-
});
|
|
124
|
-
do {
|
|
125
|
-
peer.nextProbeMs += this.probeIntervalMs;
|
|
126
|
-
} while (peer.nextProbeMs <= nowMs);
|
|
138
|
+
const probeId = peer.tryGetProbe(nowMs, this.allocateProbeId);
|
|
139
|
+
if (probeId !== undefined)
|
|
140
|
+
probes.push({ nodeRoutingId, connectionId: peer.connectionId, probeId });
|
|
127
141
|
}
|
|
128
142
|
timedOutNodes.sort();
|
|
129
143
|
return { probes, timedOutNodes };
|
|
@@ -133,16 +147,11 @@ export class ServiceLivenessRegistry {
|
|
|
133
147
|
return this.peers.size;
|
|
134
148
|
}
|
|
135
149
|
|
|
136
|
-
|
|
137
|
-
const current = this.peers.get(nodeRoutingId);
|
|
138
|
-
return current?.connectionId === connectionId && current.ready;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
private allocateProbeId(): bigint {
|
|
150
|
+
private readonly allocateProbeId = (): bigint => {
|
|
142
151
|
const result = this.nextProbeId++;
|
|
143
152
|
if (this.nextProbeId > UINT64_MAX) this.nextProbeId = 1n;
|
|
144
153
|
return result;
|
|
145
|
-
}
|
|
154
|
+
};
|
|
146
155
|
}
|
|
147
156
|
|
|
148
157
|
function requireIdentity(value: string, field: string): void {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ApplicationJobRecordLease } from '../application-jobs/application-ingress-record-owner';
|
|
2
|
+
import type { ReceiveRecord } from './service-runtime-contracts';
|
|
2
3
|
|
|
3
4
|
export type ServiceMailboxDomain = 'application' | 'infrastructure';
|
|
4
5
|
|
|
@@ -23,6 +24,8 @@ export interface ServiceMailboxRecord {
|
|
|
23
24
|
readonly stateful?: unknown;
|
|
24
25
|
/** Host queue permit plus its ordinary Framework-owned ingress record. */
|
|
25
26
|
readonly applicationJob?: ApplicationJobRecordLease;
|
|
27
|
+
/** Unregistered raw-pull terminal, retained by the existing mailbox owner. */
|
|
28
|
+
readonly completion?: ReceiveRecord;
|
|
26
29
|
}
|
|
27
30
|
|
|
28
31
|
export interface ServiceMailboxClaim {
|
|
@@ -85,7 +88,10 @@ export class ServiceMailbox {
|
|
|
85
88
|
}
|
|
86
89
|
|
|
87
90
|
tryEnqueue(record: ServiceMailboxRecord): boolean {
|
|
88
|
-
if (
|
|
91
|
+
if (
|
|
92
|
+
record.owner.length === 0 ||
|
|
93
|
+
(record.parts.length === 0 && record.completion === undefined)
|
|
94
|
+
) {
|
|
89
95
|
throw new TypeError('Mailbox records require an owner and retained payload.');
|
|
90
96
|
}
|
|
91
97
|
const target = this.domain(record.domain);
|
|
@@ -90,6 +90,7 @@ import {
|
|
|
90
90
|
type ServiceBoundSessionActorAuthority,
|
|
91
91
|
type ServiceDirectSpotRouteFence,
|
|
92
92
|
type ServiceInstanceActivationTarget,
|
|
93
|
+
type ServiceInstanceColdActivationTarget,
|
|
93
94
|
type ServiceInstanceRouteFence,
|
|
94
95
|
type ServiceMessageFollowRoute,
|
|
95
96
|
type ServiceRetiredBoundSessionRouteFence,
|
|
@@ -313,7 +314,12 @@ export interface ServiceInstanceActivationAuthority {
|
|
|
313
314
|
* operation completes; the binding callback itself never blocks.
|
|
314
315
|
*/
|
|
315
316
|
export interface ServiceAsyncInstanceActivationAuthority {
|
|
316
|
-
read(
|
|
317
|
+
read(
|
|
318
|
+
target: ServiceInstanceActivationTarget,
|
|
319
|
+
instanceIntent?: true,
|
|
320
|
+
activation?: ServiceInstanceActivationRecoveryEnvelope,
|
|
321
|
+
acceptsLocal?: (route: ServiceInstanceRouteFence) => boolean
|
|
322
|
+
): Promise<ServiceInstanceAuthorityRead>;
|
|
317
323
|
reserve(
|
|
318
324
|
activation: Omit<ServiceInstanceActivationRecoveryEnvelope, 'targetMeshName'>,
|
|
319
325
|
signal?: AbortSignal
|
|
@@ -432,6 +438,7 @@ export class ServiceStatefulRuntime {
|
|
|
432
438
|
Promise<{
|
|
433
439
|
readonly spot: Pick<ServiceSpotState, 'ref'>;
|
|
434
440
|
readonly route: ServiceInstanceRouteFence;
|
|
441
|
+
readonly stableType: string;
|
|
435
442
|
}>
|
|
436
443
|
>();
|
|
437
444
|
private closed = false;
|
|
@@ -882,7 +889,7 @@ export class ServiceStatefulRuntime {
|
|
|
882
889
|
const record = {
|
|
883
890
|
kind: 'instanceSpot' as const,
|
|
884
891
|
activation: 'missing' as const,
|
|
885
|
-
target,
|
|
892
|
+
target: { ...target, targetMeshName: envelope.targetMeshName },
|
|
886
893
|
sourceNodeGeneration: envelope.sourceNodeGeneration,
|
|
887
894
|
sourceNodeRid: envelope.sourceNodeRid,
|
|
888
895
|
...(envelope.sourceSpotId === undefined ? {} : { sourceSpotId: envelope.sourceSpotId }),
|
|
@@ -901,7 +908,7 @@ export class ServiceStatefulRuntime {
|
|
|
901
908
|
sourceRoutingId: envelope.sourceNodeRid,
|
|
902
909
|
parts: [
|
|
903
910
|
encodeInstanceSpotActivationHeader(
|
|
904
|
-
target,
|
|
911
|
+
record.target,
|
|
905
912
|
envelope.sourceNodeGeneration,
|
|
906
913
|
envelope.sourceNodeRid,
|
|
907
914
|
envelope.sourceSpotId,
|
|
@@ -1441,7 +1448,7 @@ export class ServiceStatefulRuntime {
|
|
|
1441
1448
|
}
|
|
1442
1449
|
|
|
1443
1450
|
async sendToMissingInstanceSpot(
|
|
1444
|
-
target:
|
|
1451
|
+
target: ServiceInstanceColdActivationTarget,
|
|
1445
1452
|
payload: ServiceApplicationPayload,
|
|
1446
1453
|
deadlineUnixMs: bigint,
|
|
1447
1454
|
sourceSpotId?: string,
|
|
@@ -1457,7 +1464,7 @@ export class ServiceStatefulRuntime {
|
|
|
1457
1464
|
}
|
|
1458
1465
|
|
|
1459
1466
|
async sendToMissingInstanceSpotFrame(
|
|
1460
|
-
target:
|
|
1467
|
+
target: ServiceInstanceColdActivationTarget,
|
|
1461
1468
|
payloadFrame: Buffer,
|
|
1462
1469
|
deadlineUnixMs: bigint,
|
|
1463
1470
|
sourceSpotId?: string,
|
|
@@ -1522,7 +1529,7 @@ export class ServiceStatefulRuntime {
|
|
|
1522
1529
|
}
|
|
1523
1530
|
|
|
1524
1531
|
requestToMissingInstanceSpot(
|
|
1525
|
-
target:
|
|
1532
|
+
target: ServiceInstanceColdActivationTarget,
|
|
1526
1533
|
payload: ServiceApplicationPayload,
|
|
1527
1534
|
timeoutMs: number,
|
|
1528
1535
|
sourceSpotId?: string,
|
|
@@ -1540,7 +1547,7 @@ export class ServiceStatefulRuntime {
|
|
|
1540
1547
|
}
|
|
1541
1548
|
|
|
1542
1549
|
requestToMissingInstanceSpotFrame(
|
|
1543
|
-
target:
|
|
1550
|
+
target: ServiceInstanceColdActivationTarget,
|
|
1544
1551
|
payloadFrame: Buffer,
|
|
1545
1552
|
deadlineUnixMs: bigint,
|
|
1546
1553
|
sourceSpotId?: string,
|
|
@@ -1559,7 +1566,7 @@ export class ServiceStatefulRuntime {
|
|
|
1559
1566
|
this.nodeRid,
|
|
1560
1567
|
sourceSpotId,
|
|
1561
1568
|
'request',
|
|
1562
|
-
{ high:
|
|
1569
|
+
{ high: ZLINK_NATIVE_CORRELATION_OPERATION_NAMESPACE, low: pending.id },
|
|
1563
1570
|
deadlineUnixMs,
|
|
1564
1571
|
pending.id,
|
|
1565
1572
|
metadataFrame !== undefined
|
|
@@ -2520,27 +2527,24 @@ export class ServiceStatefulRuntime {
|
|
|
2520
2527
|
): Promise<void> {
|
|
2521
2528
|
try {
|
|
2522
2529
|
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
|
-
}
|
|
2530
|
+
await this.activateMissingInstanceAsync(record, payloadFrame, metadataFrame, (activation) => {
|
|
2531
|
+
if (this.closed) return;
|
|
2532
|
+
const terminal = this.activationTerminalCompletion(record.target, activation.route);
|
|
2533
|
+
const admitted = this.enqueueActivatedInstanceSpot(
|
|
2534
|
+
ingress,
|
|
2535
|
+
record,
|
|
2536
|
+
payloadFrame,
|
|
2537
|
+
activation.spot,
|
|
2538
|
+
localReply,
|
|
2539
|
+
terminal.onTerminalCompletion,
|
|
2540
|
+
metadataFrame,
|
|
2541
|
+
terminal.onHandlerTurnStarted,
|
|
2542
|
+
this.instanceApplicationTarget(record, activation.route.objectGeneration)
|
|
2543
|
+
);
|
|
2544
|
+
if (admitted !== 'application') {
|
|
2545
|
+
throw new Error('Activated Instance message was not admitted to the local queue.');
|
|
2546
|
+
}
|
|
2547
|
+
});
|
|
2544
2548
|
} catch (error) {
|
|
2545
2549
|
this.finishMissingInstanceActivationFailure(ingress, record, error, localReply);
|
|
2546
2550
|
}
|
|
@@ -2943,21 +2947,72 @@ export class ServiceStatefulRuntime {
|
|
|
2943
2947
|
return activation.spot;
|
|
2944
2948
|
}
|
|
2945
2949
|
|
|
2950
|
+
private localReadyInstance(
|
|
2951
|
+
target: ServiceInstanceActivationTarget,
|
|
2952
|
+
route: ServiceInstanceRouteFence
|
|
2953
|
+
): ServiceSpotState | undefined {
|
|
2954
|
+
const local = this.registry.spot(target.targetSpotId);
|
|
2955
|
+
if (
|
|
2956
|
+
local !== undefined &&
|
|
2957
|
+
(local.kind !== 'instance' || local.stableType !== target.stableType)
|
|
2958
|
+
) {
|
|
2959
|
+
throw new ServiceStaleGenerationError('spot', target.targetSpotId);
|
|
2960
|
+
}
|
|
2961
|
+
return local !== undefined &&
|
|
2962
|
+
local.state === 'ready' &&
|
|
2963
|
+
routeMatchesLocal(route, local, this.nodeRid, this.nodeGeneration)
|
|
2964
|
+
? local
|
|
2965
|
+
: undefined;
|
|
2966
|
+
}
|
|
2967
|
+
|
|
2946
2968
|
private activateMissingInstanceAsync(
|
|
2947
2969
|
record: Extract<
|
|
2948
2970
|
ServiceStatefulWireRecord,
|
|
2949
2971
|
{ readonly kind: 'instanceSpot'; readonly activation: 'missing' }
|
|
2950
2972
|
>,
|
|
2951
2973
|
payloadFrame: Buffer,
|
|
2952
|
-
metadataFrame
|
|
2953
|
-
|
|
2954
|
-
|
|
2955
|
-
|
|
2956
|
-
|
|
2957
|
-
|
|
2974
|
+
metadataFrame: Buffer | undefined,
|
|
2975
|
+
admit: (activation: {
|
|
2976
|
+
readonly spot: Pick<ServiceSpotState, 'ref'>;
|
|
2977
|
+
readonly route: ServiceInstanceRouteFence;
|
|
2978
|
+
}) => void
|
|
2979
|
+
): Promise<unknown> {
|
|
2980
|
+
const key = record.target.targetSpotId;
|
|
2958
2981
|
const pending = this.pendingInstanceActivations.get(key);
|
|
2959
|
-
|
|
2960
|
-
|
|
2982
|
+
const ready =
|
|
2983
|
+
pending ??
|
|
2984
|
+
this.runMissingInstanceActivation(record, payloadFrame, metadataFrame).then((activation) => ({
|
|
2985
|
+
...activation,
|
|
2986
|
+
stableType: record.target.stableType
|
|
2987
|
+
}));
|
|
2988
|
+
const operation = ready.then(async (value) => {
|
|
2989
|
+
if (pending !== undefined) {
|
|
2990
|
+
await this.asyncInstanceAuthority?.read(
|
|
2991
|
+
record.target,
|
|
2992
|
+
undefined,
|
|
2993
|
+
{
|
|
2994
|
+
...record,
|
|
2995
|
+
targetMeshName: record.target.targetMeshName,
|
|
2996
|
+
...(metadataFrame === undefined ? {} : { metadataFrame }),
|
|
2997
|
+
applicationPayloadFrame: payloadFrame
|
|
2998
|
+
},
|
|
2999
|
+
(route) => this.localReadyInstance(record.target, route) !== undefined
|
|
3000
|
+
);
|
|
3001
|
+
}
|
|
3002
|
+
if (value.stableType !== record.target.stableType) {
|
|
3003
|
+
throw new ZLinkFrameworkException(
|
|
3004
|
+
ZLinkFrameworkErrorKind.TypeMismatch,
|
|
3005
|
+
'Instance Spot type does not match.'
|
|
3006
|
+
);
|
|
3007
|
+
}
|
|
3008
|
+
admit(value);
|
|
3009
|
+
return value;
|
|
3010
|
+
});
|
|
3011
|
+
// The caller owns operation failure. Keep the activation available for later participants.
|
|
3012
|
+
const activation = operation.then(
|
|
3013
|
+
(value) => value,
|
|
3014
|
+
() => ready
|
|
3015
|
+
);
|
|
2961
3016
|
this.pendingInstanceActivations.set(key, activation);
|
|
2962
3017
|
const clear = () => {
|
|
2963
3018
|
if (this.pendingInstanceActivations.get(key) === activation) {
|
|
@@ -2965,7 +3020,7 @@ export class ServiceStatefulRuntime {
|
|
|
2965
3020
|
}
|
|
2966
3021
|
};
|
|
2967
3022
|
void activation.then(clear, clear);
|
|
2968
|
-
return
|
|
3023
|
+
return operation;
|
|
2969
3024
|
}
|
|
2970
3025
|
|
|
2971
3026
|
private async runMissingInstanceActivation(
|
|
@@ -2992,8 +3047,19 @@ export class ServiceStatefulRuntime {
|
|
|
2992
3047
|
throw new Error('Async Instance activation authority is not registered.');
|
|
2993
3048
|
}
|
|
2994
3049
|
|
|
3050
|
+
const activationRequest = {
|
|
3051
|
+
...record,
|
|
3052
|
+
targetMeshName: record.target.targetMeshName,
|
|
3053
|
+
...(metadataFrame === undefined ? {} : { metadataFrame }),
|
|
3054
|
+
applicationPayloadFrame: payloadFrame
|
|
3055
|
+
};
|
|
3056
|
+
const current = await authority.read(
|
|
3057
|
+
target,
|
|
3058
|
+
true,
|
|
3059
|
+
activationRequest,
|
|
3060
|
+
(route) => this.localReadyInstance(target, route) !== undefined
|
|
3061
|
+
);
|
|
2995
3062
|
const local = this.registry.spot(target.targetSpotId);
|
|
2996
|
-
const current = await authority.read(target);
|
|
2997
3063
|
if (
|
|
2998
3064
|
current.kind === 'creating' &&
|
|
2999
3065
|
current.authority?.allocation.state === 'active' &&
|
|
@@ -3043,17 +3109,9 @@ export class ServiceStatefulRuntime {
|
|
|
3043
3109
|
if (current.route.targetNodeGeneration !== this.nodeGeneration) {
|
|
3044
3110
|
throw new ServiceStaleGenerationError('spot', target.targetSpotId);
|
|
3045
3111
|
}
|
|
3046
|
-
|
|
3047
|
-
|
|
3048
|
-
|
|
3049
|
-
) {
|
|
3050
|
-
throw new ServiceStaleGenerationError('spot', target.targetSpotId);
|
|
3051
|
-
}
|
|
3052
|
-
if (
|
|
3053
|
-
local !== undefined &&
|
|
3054
|
-
routeMatchesLocal(current.route, local, this.nodeRid, this.nodeGeneration)
|
|
3055
|
-
) {
|
|
3056
|
-
return { spot: local, route: current.route };
|
|
3112
|
+
const readyLocal = this.localReadyInstance(target, current.route);
|
|
3113
|
+
if (readyLocal !== undefined) {
|
|
3114
|
+
return { spot: readyLocal, route: current.route };
|
|
3057
3115
|
}
|
|
3058
3116
|
return await this.reconcileReadyInstanceProjection(target, current.route, authority);
|
|
3059
3117
|
}
|
|
@@ -3095,21 +3153,7 @@ export class ServiceStatefulRuntime {
|
|
|
3095
3153
|
const deadline = operationDeadline(record.deadlineUnixMs);
|
|
3096
3154
|
let reserved: ServiceInstanceAuthorityReserve;
|
|
3097
3155
|
try {
|
|
3098
|
-
reserved = await authority.reserve(
|
|
3099
|
-
{
|
|
3100
|
-
target,
|
|
3101
|
-
sourceNodeRid: record.sourceNodeRid,
|
|
3102
|
-
sourceNodeGeneration: record.sourceNodeGeneration,
|
|
3103
|
-
...(record.sourceSpotId === undefined ? {} : { sourceSpotId: record.sourceSpotId }),
|
|
3104
|
-
operationKind: record.operationKind,
|
|
3105
|
-
operation: record.operation,
|
|
3106
|
-
...(record.replyRouteId === undefined ? {} : { replyRouteId: record.replyRouteId }),
|
|
3107
|
-
deadlineUnixMs: record.deadlineUnixMs,
|
|
3108
|
-
...(metadataFrame === undefined ? {} : { metadataFrame }),
|
|
3109
|
-
applicationPayloadFrame: payloadFrame
|
|
3110
|
-
},
|
|
3111
|
-
deadline.signal
|
|
3112
|
-
);
|
|
3156
|
+
reserved = await authority.reserve(activationRequest, deadline.signal);
|
|
3113
3157
|
} finally {
|
|
3114
3158
|
deadline.close();
|
|
3115
3159
|
}
|
|
@@ -5817,18 +5861,6 @@ function instanceOperationKey(
|
|
|
5817
5861
|
);
|
|
5818
5862
|
}
|
|
5819
5863
|
|
|
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
5864
|
function subscriptionKey(channelName: string, topicFilter: string): string {
|
|
5833
5865
|
if (channelName.length === 0 || topicFilter.length === 0) {
|
|
5834
5866
|
throw new TypeError('Channel name and topic filter must be non-empty.');
|