@zlink-systems/framework 0.25.0 → 0.26.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/Channels/IZLinkFanoutClient.d.ts +0 -6
- package/dist/contracts/RouteMesh/RuntimeTopology.d.ts +8 -0
- package/dist/internal.d.ts +1 -0
- package/dist/internal.js +1 -0
- package/dist/runtime/activation-admission.d.ts +29 -0
- package/dist/runtime/activation-admission.js +61 -0
- package/dist/runtime/actors/actor-context.js +6 -6
- package/dist/runtime/actors/actor-handoff.d.ts +9 -3
- package/dist/runtime/actors/actor-handoff.js +28 -17
- package/dist/runtime/actors/actor-local-native-join.js +2 -18
- package/dist/runtime/actors/actor-remote-joiner.d.ts +2 -2
- package/dist/runtime/actors/actor-remote-joiner.js +6 -8
- package/dist/runtime/actors/actor-runtime-contracts.d.ts +4 -2
- package/dist/runtime/actors/actor-source-transfer.d.ts +5 -5
- package/dist/runtime/actors/deferred-join-completion.d.ts +14 -0
- package/dist/runtime/actors/deferred-join-completion.js +20 -0
- package/dist/runtime/actors/index.d.ts +1 -1
- package/dist/runtime/actors/index.js +15 -5
- package/dist/runtime/admission.d.ts +5 -1
- package/dist/runtime/admission.js +4 -0
- package/dist/runtime/backend/contracts/index.d.ts +2 -10
- package/dist/runtime/backend/mesh-actor-session-node-adapter.js +2 -9
- package/dist/runtime/backend/node/node-backend-adapter-factory.d.ts +1 -2
- package/dist/runtime/backend/node/node-backend-adapter-factory.js +4 -18
- package/dist/runtime/backend/node/node-backend-adapter-support.d.ts +6 -3
- package/dist/runtime/backend/node/node-backend-adapter-support.js +12 -28
- package/dist/runtime/backend/node/node-event-loop-poller.d.ts +1 -1
- package/dist/runtime/backend/node/node-event-loop-poller.js +8 -2
- package/dist/runtime/backend/node/node-raw-binding-port.d.ts +1 -1
- package/dist/runtime/backend/node/node-raw-binding-port.js +11 -3
- package/dist/runtime/backend/node/node-raw-mesh-backend.d.ts +12 -9
- package/dist/runtime/backend/node/node-raw-mesh-backend.js +61 -29
- package/dist/runtime/backend/node/node-socket-backend-adapter.js +1 -1
- package/dist/runtime/backend/raw-binding-port.d.ts +12 -0
- package/dist/runtime/channels/channel-clients.d.ts +0 -1
- package/dist/runtime/channels/channel-clients.js +0 -8
- package/dist/runtime/channels/channel-outbound-operations.js +2 -10
- package/dist/runtime/channels/channel-runtime-manager.d.ts +2 -5
- package/dist/runtime/channels/channel-runtime-manager.js +1 -12
- package/dist/runtime/channels/channel-socket-registry.d.ts +17 -4
- package/dist/runtime/channels/channel-socket-registry.js +49 -62
- package/dist/runtime/channels/channel-transports.d.ts +0 -3
- package/dist/runtime/channels/fanout-location-runtime.js +3 -9
- package/dist/runtime/execution/index.d.ts +18 -8
- package/dist/runtime/execution/index.js +55 -29
- package/dist/runtime/execution/serial-execution-queue.d.ts +5 -6
- package/dist/runtime/execution/serial-execution-queue.js +49 -15
- package/dist/runtime/foundation/listener-records.d.ts +11 -0
- package/dist/runtime/foundation/listener-records.js +20 -0
- package/dist/runtime/foundation/raw-service-mesh-runtime.d.ts +10 -21
- package/dist/runtime/foundation/raw-service-mesh-runtime.js +66 -392
- package/dist/runtime/foundation/service-authority-payload-codec.d.ts +1 -0
- package/dist/runtime/foundation/service-authority-payload-codec.js +8 -0
- package/dist/runtime/foundation/service-maintenance-runtime.js +23 -2
- package/dist/runtime/foundation/service-relocation-coordinator.d.ts +9 -0
- package/dist/runtime/foundation/service-relocation-coordinator.js +14 -1
- package/dist/runtime/foundation/service-relocation-object-owner.d.ts +8 -0
- package/dist/runtime/foundation/service-relocation-object-owner.js +16 -4
- package/dist/runtime/foundation/service-session-binding-ingress-port.d.ts +6 -13
- package/dist/runtime/foundation/service-stateful-runtime.d.ts +10 -7
- package/dist/runtime/foundation/service-stateful-runtime.js +100 -60
- package/dist/runtime/foundation/service-stateful-wire-codec.d.ts +9 -4
- package/dist/runtime/foundation/service-stateful-wire-codec.js +153 -90
- package/dist/runtime/foundation/service-topology-registry.d.ts +1 -2
- package/dist/runtime/foundation/service-topology-registry.js +6 -30
- package/dist/runtime/host/actor-transfer-runtime.d.ts +17 -15
- package/dist/runtime/host/actor-transfer-runtime.js +73 -86
- package/dist/runtime/host/index.d.ts +6 -2
- package/dist/runtime/host/index.js +63 -37
- package/dist/runtime/host/remote-bound-session-relay.js +4 -3
- package/dist/runtime/host/route-mesh-runtime.d.ts +2 -0
- package/dist/runtime/host/route-mesh-runtime.js +7 -5
- package/dist/runtime/host/service-relocation-host-runtime.d.ts +50 -8
- package/dist/runtime/host/service-relocation-host-runtime.js +343 -270
- package/dist/runtime/host/spot-runtime-options-factory.d.ts +7 -4
- package/dist/runtime/host/spot-runtime-options-factory.js +4 -8
- package/dist/runtime/host/stateful-authority-route-runtime.js +6 -2
- package/dist/runtime/host/user-spot-creation-coordinator.d.ts +11 -3
- package/dist/runtime/host/user-spot-creation-coordinator.js +85 -87
- package/dist/runtime/locations/in-memory-provider-location-store.js +8 -6
- package/dist/runtime/locations/lifecycle.d.ts +1 -1
- package/dist/runtime/locations/lifecycle.js +2 -2
- package/dist/runtime/locations/resolvers.js +7 -2
- package/dist/runtime/locations/spot-location-claims.d.ts +2 -2
- package/dist/runtime/locations/spot-location-claims.js +3 -27
- package/dist/runtime/protocol/service_wire_codec.generated.d.ts +1 -0
- package/dist/runtime/protocol/service_wire_codec.generated.js +5 -1
- package/dist/runtime/protocol/service_wire_pilot_codec.generated.d.ts +1 -0
- package/dist/runtime/protocol/service_wire_pilot_codec.generated.js +3 -2
- package/dist/runtime/spots/formal-remote-actor-admission-registry.d.ts +3 -3
- package/dist/runtime/spots/formal-remote-actor-admission-registry.js +4 -4
- package/dist/runtime/spots/formal-remote-actor-transfer-registry.d.ts +3 -3
- package/dist/runtime/spots/index.d.ts +25 -19
- package/dist/runtime/spots/index.js +207 -205
- package/dist/runtime/spots/spot-activation-registry.d.ts +4 -2
- package/dist/runtime/spots/spot-activation-registry.js +34 -31
- package/dist/runtime/spots/spot-activation-state.d.ts +2 -11
- package/dist/runtime/spots/spot-activation-state.js +4 -17
- package/dist/runtime/spots/spot-activation.d.ts +8 -6
- package/dist/runtime/spots/spot-activation.js +54 -49
- package/dist/runtime/spots/spot-actor-admission-coordinator.d.ts +1 -1
- package/dist/runtime/spots/spot-actor-admission-coordinator.js +1 -1
- package/dist/runtime/spots/spot-actor-join-dispatch.d.ts +2 -2
- package/dist/runtime/spots/spot-actor-join-dispatch.js +3 -5
- package/dist/runtime/spots/spot-actor-lifecycle-drain.js +2 -2
- package/dist/runtime/spots/spot-actor-membership.d.ts +4 -0
- package/dist/runtime/spots/spot-actor-membership.js +26 -12
- package/dist/runtime/spots/spot-entry-activation.d.ts +1 -2
- package/dist/runtime/spots/spot-entry-activation.js +0 -1
- package/dist/runtime/spots/spot-manager-public.js +1 -1
- package/dist/runtime/spots/spot-native-actor-join-admission.d.ts +1 -1
- package/dist/runtime/spots/spot-native-actor-join-admission.js +2 -2
- package/dist/runtime/spots/spot-node-runtime-manager.d.ts +5 -5
- package/dist/runtime/spots/spot-node-runtime-manager.js +7 -11
- package/dist/runtime/spots/spot-outbound.js +1 -29
- package/dist/runtime/spots/spot-routed-actor-admission.d.ts +1 -0
- package/dist/runtime/spots/spot-routed-actor-admission.js +10 -0
- package/dist/runtime/spots/spot-routed-frame-dispatch.d.ts +2 -6
- package/dist/runtime/spots/spot-routed-frame-dispatch.js +2 -70
- package/dist/runtime/spots/spot-runtime-ports.d.ts +3 -5
- package/dist/runtime/spots/spot-serial-executor.d.ts +5 -0
- package/dist/runtime/spots/spot-serial-executor.js +8 -1
- package/dist/runtime/spots/spot-serial-turn-executor.d.ts +3 -0
- package/dist/runtime/spots/spot-serial-turn-executor.js +8 -4
- package/dist/runtime/streams/actor-session-binding-registry.d.ts +13 -15
- package/dist/runtime/streams/actor-session-binding-registry.js +19 -171
- package/dist/runtime/streams/actor-session-binding-runtime-owner.d.ts +3 -4
- package/dist/runtime/streams/bound-session-service.js +1 -18
- package/dist/runtime/streams/index.d.ts +2 -0
- package/dist/runtime/streams/index.js +19 -19
- package/dist/runtime/streams/session-serial-executor.js +2 -2
- package/package.json +3 -3
- package/src/contracts/Channels/IZLinkFanoutClient.ts +0 -7
- package/src/contracts/RouteMesh/RuntimeTopology.ts +10 -0
- package/src/internal.ts +1 -0
- package/src/runtime/activation-admission.ts +78 -0
- package/src/runtime/actors/actor-context.ts +6 -6
- package/src/runtime/actors/actor-handoff.ts +31 -23
- package/src/runtime/actors/actor-local-native-join.ts +3 -42
- package/src/runtime/actors/actor-remote-joiner.ts +6 -25
- package/src/runtime/actors/actor-runtime-contracts.ts +4 -10
- package/src/runtime/actors/actor-source-transfer.ts +5 -18
- package/src/runtime/actors/deferred-join-completion.ts +40 -0
- package/src/runtime/actors/index.ts +22 -9
- package/src/runtime/admission.ts +5 -1
- package/src/runtime/backend/contracts/index.ts +2 -13
- package/src/runtime/backend/mesh-actor-session-node-adapter.ts +2 -10
- package/src/runtime/backend/node/node-backend-adapter-factory.ts +5 -25
- package/src/runtime/backend/node/node-backend-adapter-support.ts +9 -29
- package/src/runtime/backend/node/node-event-loop-poller.ts +14 -2
- package/src/runtime/backend/node/node-raw-binding-port.ts +17 -3
- package/src/runtime/backend/node/node-raw-mesh-backend.ts +65 -32
- package/src/runtime/backend/node/node-socket-backend-adapter.ts +2 -2
- package/src/runtime/backend/raw-binding-port.ts +13 -0
- package/src/runtime/channels/channel-clients.ts +0 -11
- package/src/runtime/channels/channel-outbound-operations.ts +3 -11
- package/src/runtime/channels/channel-runtime-manager.ts +4 -15
- package/src/runtime/channels/channel-socket-registry.ts +69 -71
- package/src/runtime/channels/channel-transports.ts +0 -3
- package/src/runtime/channels/fanout-location-runtime.ts +4 -16
- package/src/runtime/execution/index.ts +68 -30
- package/src/runtime/execution/serial-execution-queue.ts +52 -20
- package/src/runtime/foundation/listener-records.ts +21 -0
- package/src/runtime/foundation/raw-service-mesh-runtime.ts +66 -466
- package/src/runtime/foundation/service-authority-payload-codec.ts +10 -0
- package/src/runtime/foundation/service-maintenance-runtime.ts +30 -9
- package/src/runtime/foundation/service-relocation-coordinator.ts +13 -0
- package/src/runtime/foundation/service-relocation-object-owner.ts +18 -5
- package/src/runtime/foundation/service-session-binding-ingress-port.ts +6 -14
- package/src/runtime/foundation/service-stateful-runtime.ts +151 -89
- package/src/runtime/foundation/service-stateful-wire-codec.ts +203 -121
- package/src/runtime/foundation/service-topology-registry.ts +8 -48
- package/src/runtime/host/actor-transfer-runtime.ts +94 -166
- package/src/runtime/host/index.ts +119 -54
- package/src/runtime/host/remote-bound-session-relay.ts +4 -3
- package/src/runtime/host/route-mesh-runtime.ts +9 -5
- package/src/runtime/host/service-relocation-host-runtime.ts +495 -376
- package/src/runtime/host/spot-runtime-options-factory.ts +24 -13
- package/src/runtime/host/stateful-authority-route-runtime.ts +8 -4
- package/src/runtime/host/user-spot-creation-coordinator.ts +147 -142
- package/src/runtime/locations/in-memory-provider-location-store.ts +11 -7
- package/src/runtime/locations/lifecycle.ts +3 -2
- package/src/runtime/locations/resolvers.ts +19 -13
- package/src/runtime/locations/spot-location-claims.ts +5 -34
- package/src/runtime/protocol/service_wire_codec.generated.ts +9 -2
- package/src/runtime/protocol/service_wire_pilot_codec.generated.ts +4 -4
- package/src/runtime/spots/formal-remote-actor-admission-registry.ts +6 -6
- package/src/runtime/spots/formal-remote-actor-transfer-registry.ts +3 -3
- package/src/runtime/spots/index.ts +341 -295
- package/src/runtime/spots/spot-activation-registry.ts +41 -31
- package/src/runtime/spots/spot-activation-state.ts +4 -16
- package/src/runtime/spots/spot-activation.ts +102 -80
- package/src/runtime/spots/spot-actor-admission-coordinator.ts +2 -2
- package/src/runtime/spots/spot-actor-join-dispatch.ts +6 -7
- package/src/runtime/spots/spot-actor-lifecycle-drain.ts +20 -18
- package/src/runtime/spots/spot-actor-membership.ts +81 -17
- package/src/runtime/spots/spot-entry-activation.ts +0 -3
- package/src/runtime/spots/spot-manager-public.ts +8 -4
- package/src/runtime/spots/spot-native-actor-join-admission.ts +2 -2
- package/src/runtime/spots/spot-node-runtime-manager.ts +12 -18
- package/src/runtime/spots/spot-outbound.ts +4 -47
- package/src/runtime/spots/spot-routed-actor-admission.ts +20 -0
- package/src/runtime/spots/spot-routed-frame-dispatch.ts +6 -76
- package/src/runtime/spots/spot-runtime-ports.ts +6 -17
- package/src/runtime/spots/spot-serial-executor.ts +9 -1
- package/src/runtime/spots/spot-serial-turn-executor.ts +10 -4
- package/src/runtime/streams/actor-session-binding-registry.ts +20 -308
- package/src/runtime/streams/actor-session-binding-runtime-owner.ts +2 -12
- package/src/runtime/streams/bound-session-service.ts +2 -36
- package/src/runtime/streams/index.ts +29 -27
- package/src/runtime/streams/session-serial-executor.ts +2 -2
- package/dist/runtime/actors/deferred-join-accepted-journal.d.ts +0 -47
- package/dist/runtime/actors/deferred-join-accepted-journal.js +0 -992
- package/src/runtime/actors/deferred-join-accepted-journal.ts +0 -1346
|
@@ -1,9 +1,6 @@
|
|
|
1
|
-
import { randomUUID } from 'node:crypto';
|
|
2
1
|
import type {
|
|
3
2
|
ZLinkRawBindingPort,
|
|
4
3
|
ZLinkRawHostPort,
|
|
5
|
-
ZLinkRawMonitorRecord,
|
|
6
|
-
ZLinkRawMonitorPort,
|
|
7
4
|
ZLinkRawReceivedRecord,
|
|
8
5
|
ZLinkRawRouterPort
|
|
9
6
|
} from '../backend/raw-binding-port';
|
|
@@ -119,21 +116,9 @@ export interface RawServiceMeshRuntimeOptions {
|
|
|
119
116
|
}) => void;
|
|
120
117
|
}
|
|
121
118
|
|
|
122
|
-
const MONITOR_DISCONNECTED = 0x0200;
|
|
123
|
-
const MONITOR_CONNECTION_READY = 0x1000;
|
|
124
|
-
const MONITOR_CONNECTION_READY_EDGE = 1;
|
|
125
119
|
// Framework error code 13 (RequestTargetNotFound) is encoded as 14 on a
|
|
126
120
|
// RequestResult.NotFound reply. Boundary transport results keep failureCode 0.
|
|
127
121
|
const REQUEST_TARGET_NOT_FOUND_FAILURE_CODE = 14;
|
|
128
|
-
type PhysicalConnectionDirection = 'inbound' | 'outbound' | 'unknown';
|
|
129
|
-
|
|
130
|
-
interface PhysicalConnectionCandidate {
|
|
131
|
-
readonly connectionId: string;
|
|
132
|
-
readonly direction: PhysicalConnectionDirection;
|
|
133
|
-
readonly discriminator: string;
|
|
134
|
-
readonly localAddress: string;
|
|
135
|
-
readonly remoteAddress: string;
|
|
136
|
-
}
|
|
137
122
|
|
|
138
123
|
const livenessCodec = createServiceWireCodec({
|
|
139
124
|
magic: [0x5a, 0x4d],
|
|
@@ -141,9 +126,6 @@ const livenessCodec = createServiceWireCodec({
|
|
|
141
126
|
commands: M6aServiceWireCommand
|
|
142
127
|
});
|
|
143
128
|
|
|
144
|
-
const MAX_PENDING_MONITOR_EVENTS = 8192;
|
|
145
|
-
const MAX_UNRESOLVED_CONNECTION_CANDIDATES = 4096;
|
|
146
|
-
|
|
147
129
|
/**
|
|
148
130
|
* RouteMesh M6A runtime built only on the public raw binding package.
|
|
149
131
|
* It owns protocol, admission, mailbox, request and liveness state.
|
|
@@ -166,20 +148,11 @@ export class RawServiceMeshRuntime {
|
|
|
166
148
|
>();
|
|
167
149
|
private readonly endpointOnlyPeers = new Set<string>();
|
|
168
150
|
private readonly peerConnectionIntentRemoved = new Set<(nodeRoutingId: string) => void>();
|
|
169
|
-
private readonly connectionCandidates = new Map<
|
|
170
|
-
string,
|
|
171
|
-
Map<string, PhysicalConnectionCandidate>
|
|
172
|
-
>();
|
|
173
|
-
private readonly unresolvedConnectionCandidates: PhysicalConnectionCandidate[] = [];
|
|
174
|
-
private readonly connectionIds = new Map<string, string>();
|
|
175
151
|
/**
|
|
176
|
-
* The
|
|
177
|
-
*
|
|
178
|
-
* longer usable by the application route.
|
|
152
|
+
* The last observed Core selected route of each RID (Core ROUTER §10.1):
|
|
153
|
+
* RID -> route generation. Only `observeSelectedRoutes` replaces it.
|
|
179
154
|
*/
|
|
180
|
-
private
|
|
181
|
-
private monitorEvents: ZLinkRawMonitorRecord[] = [];
|
|
182
|
-
private monitorDrainBuffer: ZLinkRawMonitorRecord[] = [];
|
|
155
|
+
private selectedRoutes = new Map<string, bigint>();
|
|
183
156
|
private readonly bindingPort: ZLinkRawBindingPort;
|
|
184
157
|
private readonly applicationJobQueue: ApplicationJobQueuePort;
|
|
185
158
|
private readonly peerAdmissionSealed?: () => boolean;
|
|
@@ -192,7 +165,6 @@ export class RawServiceMeshRuntime {
|
|
|
192
165
|
private descriptor: ServiceNodeDescriptor;
|
|
193
166
|
private host?: ZLinkRawHostPort;
|
|
194
167
|
private router?: ZLinkRawRouterPort;
|
|
195
|
-
private monitor?: ZLinkRawMonitorPort;
|
|
196
168
|
private nextCorrelation = 1n;
|
|
197
169
|
private serviceIngress?: RawServiceIngressHandler;
|
|
198
170
|
private closed = false;
|
|
@@ -239,8 +211,6 @@ export class RawServiceMeshRuntime {
|
|
|
239
211
|
};
|
|
240
212
|
this.topology.publishLocal(next);
|
|
241
213
|
this.descriptor = next;
|
|
242
|
-
this.monitor = router.monitor();
|
|
243
|
-
this.pullMonitorEvents();
|
|
244
214
|
this.host = host;
|
|
245
215
|
this.router = router;
|
|
246
216
|
} catch (error) {
|
|
@@ -387,27 +357,25 @@ export class RawServiceMeshRuntime {
|
|
|
387
357
|
}
|
|
388
358
|
|
|
389
359
|
isPeerRouteReady(nodeRoutingId: string, lifecycleGeneration?: bigint): boolean {
|
|
360
|
+
// Admission is bound to the observed selected route: the route observer
|
|
361
|
+
// removes the peer when that route ends or is replaced.
|
|
390
362
|
const peer = this.topology.peer(nodeRoutingId);
|
|
391
|
-
|
|
392
|
-
const applicationRouteReady =
|
|
393
|
-
monitorConnection === undefined || monitorConnection === peer?.connectionId;
|
|
394
|
-
const ready =
|
|
363
|
+
return (
|
|
395
364
|
peer !== undefined &&
|
|
396
365
|
(lifecycleGeneration === undefined ||
|
|
397
366
|
peer.descriptor.lifecycleGeneration === lifecycleGeneration) &&
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
return ready;
|
|
367
|
+
this.liveness.isReady(nodeRoutingId, peer.connectionId)
|
|
368
|
+
);
|
|
401
369
|
}
|
|
402
370
|
|
|
403
371
|
async announceExpectedPeers(): Promise<number> {
|
|
404
372
|
let accepted = 0;
|
|
405
373
|
for (const nodeRoutingId of this.expectedPeers.keys()) {
|
|
406
|
-
// Admission is a one-time fence for the current
|
|
374
|
+
// Admission is a one-time fence for the current selected route.
|
|
407
375
|
// Re-sending Hello after the peer is admitted would re-run admission on
|
|
408
376
|
// every poll and reset the liveness record before application traffic
|
|
409
|
-
// can use the route. A
|
|
410
|
-
// and remains eligible for the next admission attempt.
|
|
377
|
+
// can use the route. A peer whose selected route ended is removed by the
|
|
378
|
+
// route observer and remains eligible for the next admission attempt.
|
|
411
379
|
if (this.topology.peer(nodeRoutingId) !== undefined) continue;
|
|
412
380
|
if (await this.announcePeer(nodeRoutingId)) accepted++;
|
|
413
381
|
}
|
|
@@ -629,7 +597,6 @@ export class RawServiceMeshRuntime {
|
|
|
629
597
|
nowMs = performance.now(),
|
|
630
598
|
observe?: RawServicePumpObserver
|
|
631
599
|
): Promise<RawServicePumpResult> {
|
|
632
|
-
this.pullMonitorEvents();
|
|
633
600
|
return this.receiveOne(nowMs, observe);
|
|
634
601
|
}
|
|
635
602
|
|
|
@@ -639,7 +606,7 @@ export class RawServiceMeshRuntime {
|
|
|
639
606
|
|
|
640
607
|
/** Returns whether a receive budget ended before no-data; idle ticks only maintain peers. */
|
|
641
608
|
async pumpBatch(receiveReady = true): Promise<boolean> {
|
|
642
|
-
await this.
|
|
609
|
+
await this.observeSelectedRoutes();
|
|
643
610
|
const startedAtMs = performance.now();
|
|
644
611
|
let messages = 0;
|
|
645
612
|
let bytes = 0;
|
|
@@ -711,24 +678,6 @@ export class RawServiceMeshRuntime {
|
|
|
711
678
|
}
|
|
712
679
|
}
|
|
713
680
|
|
|
714
|
-
private pullMonitorEvents(): void {
|
|
715
|
-
this.monitor?.drain((event) => {
|
|
716
|
-
this.observeMonitorEvent(event);
|
|
717
|
-
if (this.monitorEvents.length < MAX_PENDING_MONITOR_EVENTS) {
|
|
718
|
-
this.monitorEvents.push(event);
|
|
719
|
-
return;
|
|
720
|
-
}
|
|
721
|
-
const existingIndex = this.monitorEvents.findIndex((candidate) =>
|
|
722
|
-
sameMonitorConnection(candidate, event)
|
|
723
|
-
);
|
|
724
|
-
if (existingIndex >= 0) this.monitorEvents[existingIndex] = event;
|
|
725
|
-
else {
|
|
726
|
-
this.monitorEvents.shift();
|
|
727
|
-
this.monitorEvents.push(event);
|
|
728
|
-
}
|
|
729
|
-
});
|
|
730
|
-
}
|
|
731
|
-
|
|
732
681
|
private reportProtocolError(
|
|
733
682
|
received: import('../backend/node/node-raw-binding-port').ZLinkRawReceivedRecord
|
|
734
683
|
): void {
|
|
@@ -812,11 +761,12 @@ export class RawServiceMeshRuntime {
|
|
|
812
761
|
await this.send(received.sourceRid, [encodeReject(3)]);
|
|
813
762
|
return 'infrastructure';
|
|
814
763
|
}
|
|
815
|
-
const
|
|
816
|
-
|
|
817
|
-
|
|
764
|
+
const result = this.admitPeer(
|
|
765
|
+
descriptor,
|
|
766
|
+
routeConnectionId(received.routeGeneration),
|
|
767
|
+
nowMs,
|
|
768
|
+
expected
|
|
818
769
|
);
|
|
819
|
-
const result = this.admitPeer(descriptor, connection, nowMs, expected);
|
|
820
770
|
if (result !== 'admitted' && result !== 'notRequired') {
|
|
821
771
|
const detail =
|
|
822
772
|
result === 'invalidDescriptor'
|
|
@@ -849,10 +799,8 @@ export class RawServiceMeshRuntime {
|
|
|
849
799
|
if (header.command === M6aServiceWireCommand.reject) {
|
|
850
800
|
if (received.parts.length !== 1) return 'protocolError';
|
|
851
801
|
const reason = decodeReject(received.parts[0]!);
|
|
852
|
-
//
|
|
853
|
-
//
|
|
854
|
-
// peer: bilateral manual connect can reject the duplicate after the
|
|
855
|
-
// other candidate has become Ready.
|
|
802
|
+
// Keep an already admitted peer: a reject of an earlier Hello on the
|
|
803
|
+
// same selected route does not end the admission that succeeded.
|
|
856
804
|
if (reason === 4 && this.topology.peer(received.sourceRid) === undefined) {
|
|
857
805
|
const expected = this.expectedPeers.get(received.sourceRid);
|
|
858
806
|
const local = this.topology.localDescriptor();
|
|
@@ -1011,125 +959,44 @@ export class RawServiceMeshRuntime {
|
|
|
1011
959
|
for (const nodeRoutingId of result.timedOutNodes) {
|
|
1012
960
|
const peer = this.topology.peer(nodeRoutingId);
|
|
1013
961
|
if (peer === undefined) continue;
|
|
1014
|
-
// Liveness timeout is a semantic peer removal.
|
|
1015
|
-
// physical candidate state, and auto-connect intent must be retired by
|
|
1016
|
-
// the same owner so a failed endpoint cannot be selected again.
|
|
1017
|
-
this.removeConnectionCandidate(nodeRoutingId, peer.connectionId);
|
|
962
|
+
// Liveness timeout is a semantic peer removal of the admitted route.
|
|
1018
963
|
this.removePeer(peer);
|
|
1019
964
|
}
|
|
1020
965
|
return result;
|
|
1021
966
|
}
|
|
1022
967
|
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
this.
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
this.unresolvedConnectionCandidates.length < MAX_UNRESOLVED_CONNECTION_CANDIDATES
|
|
1044
|
-
) {
|
|
1045
|
-
this.unresolvedConnectionCandidates.push(candidate);
|
|
1046
|
-
}
|
|
1047
|
-
continue;
|
|
1048
|
-
}
|
|
1049
|
-
if (!this.acceptsExpectedMonitorEndpoint(nodeRoutingId, event)) {
|
|
1050
|
-
continue;
|
|
1051
|
-
}
|
|
1052
|
-
const candidate = this.createConnectionCandidate(nodeRoutingId, event);
|
|
1053
|
-
const existingPeer = this.topology.peer(nodeRoutingId);
|
|
1054
|
-
if (existingPeer !== undefined && existingPeer.connectionId === candidate.connectionId) {
|
|
1055
|
-
this.monitorConnectionStates.set(nodeRoutingId, candidate.connectionId);
|
|
1056
|
-
continue;
|
|
1057
|
-
}
|
|
1058
|
-
if (existingPeer !== undefined) {
|
|
1059
|
-
if (this.liveness.isReady(nodeRoutingId, existingPeer.connectionId)) {
|
|
1060
|
-
const existingCandidate = this.connectionCandidates
|
|
1061
|
-
.get(nodeRoutingId)
|
|
1062
|
-
?.get(existingPeer.connectionId);
|
|
1063
|
-
if (this.isDiscoveredLifecycleReplacement(existingPeer, nodeRoutingId)) {
|
|
1064
|
-
// A current discovery descriptor with the same RID but a
|
|
1065
|
-
// different lifecycle token denotes a replacement process.
|
|
1066
|
-
// Keep its physical pair provisional until its Hello passes
|
|
1067
|
-
// the descriptor fence; the old ready peer remains semantic
|
|
1068
|
-
// current until then.
|
|
1069
|
-
let provisionalCandidates = this.connectionCandidates.get(nodeRoutingId);
|
|
1070
|
-
if (provisionalCandidates === undefined) {
|
|
1071
|
-
provisionalCandidates = new Map();
|
|
1072
|
-
this.connectionCandidates.set(nodeRoutingId, provisionalCandidates);
|
|
1073
|
-
}
|
|
1074
|
-
provisionalCandidates.set(candidate.connectionId, candidate);
|
|
1075
|
-
await this.announcePeer(nodeRoutingId);
|
|
1076
|
-
continue;
|
|
1077
|
-
}
|
|
1078
|
-
if (existingCandidate?.connectionId === candidate.connectionId) {
|
|
1079
|
-
this.removeConnectionCandidate(nodeRoutingId, candidate.connectionId);
|
|
1080
|
-
this.monitorConnectionStates.set(nodeRoutingId, existingPeer.connectionId);
|
|
1081
|
-
continue;
|
|
1082
|
-
}
|
|
1083
|
-
// A non-replacement ready route is the current semantic owner.
|
|
1084
|
-
// A second physical candidate may be admitted only after the
|
|
1085
|
-
// current lease expires; this also prevents a late old-process
|
|
1086
|
-
// reconnect from replacing the ready replacement by arrival order.
|
|
1087
|
-
this.removeConnectionCandidate(nodeRoutingId, candidate.connectionId);
|
|
1088
|
-
continue;
|
|
1089
|
-
}
|
|
1090
|
-
// Keep the physical candidate until the wire descriptor decides
|
|
1091
|
-
// whether it is the current logical peer. READY events can race
|
|
1092
|
-
// with DISCONNECTED events during reconnect; rejecting the READY
|
|
1093
|
-
// here can terminate the replacement before admission evaluates its
|
|
1094
|
-
// lifecycle generation.
|
|
1095
|
-
let provisionalCandidates = this.connectionCandidates.get(nodeRoutingId);
|
|
1096
|
-
if (provisionalCandidates === undefined) {
|
|
1097
|
-
provisionalCandidates = new Map();
|
|
1098
|
-
this.connectionCandidates.set(nodeRoutingId, provisionalCandidates);
|
|
1099
|
-
}
|
|
1100
|
-
provisionalCandidates.set(candidate.connectionId, candidate);
|
|
1101
|
-
this.connectionIds.set(nodeRoutingId, candidate.connectionId);
|
|
1102
|
-
await this.announcePeer(nodeRoutingId);
|
|
1103
|
-
continue;
|
|
1104
|
-
}
|
|
1105
|
-
let candidates = this.connectionCandidates.get(nodeRoutingId);
|
|
1106
|
-
if (candidates === undefined) {
|
|
1107
|
-
candidates = new Map();
|
|
1108
|
-
this.connectionCandidates.set(nodeRoutingId, candidates);
|
|
1109
|
-
}
|
|
1110
|
-
candidates.set(candidate.connectionId, candidate);
|
|
1111
|
-
// Before wire admission the monitor candidate is the only physical
|
|
1112
|
-
// route evidence available to the admission message.
|
|
1113
|
-
this.connectionIds.set(nodeRoutingId, candidate.connectionId);
|
|
1114
|
-
await this.announcePeer(nodeRoutingId);
|
|
1115
|
-
} else if (event.event === MONITOR_DISCONNECTED && nodeRoutingId !== undefined) {
|
|
1116
|
-
const peer = this.topology.peer(nodeRoutingId);
|
|
1117
|
-
const disconnectedId = monitorConnectionId(event, nodeRoutingId);
|
|
1118
|
-
this.removeConnectionCandidate(nodeRoutingId, disconnectedId);
|
|
1119
|
-
if (peer !== undefined && peer.connectionId === disconnectedId) {
|
|
1120
|
-
this.removePeer(peer);
|
|
1121
|
-
}
|
|
1122
|
-
} else if (event.event === MONITOR_DISCONNECTED) {
|
|
1123
|
-
const disconnectedId = monitorConnectionId(event);
|
|
1124
|
-
const index = this.unresolvedConnectionCandidates.findIndex(
|
|
1125
|
-
(candidate) => candidate.connectionId === disconnectedId
|
|
1126
|
-
);
|
|
1127
|
-
if (index >= 0) this.unresolvedConnectionCandidates.splice(index, 1);
|
|
968
|
+
/**
|
|
969
|
+
* The socket's one route observer (Core ROUTER §10.1). It replaces the
|
|
970
|
+
* observation with Core's selected-route snapshot, ends the admission of
|
|
971
|
+
* every RID whose observed route disappeared or was replaced, and starts the
|
|
972
|
+
* handshake of every newly selected route. Returns the number of changes.
|
|
973
|
+
*/
|
|
974
|
+
async observeSelectedRoutes(): Promise<number> {
|
|
975
|
+
const router = this.router;
|
|
976
|
+
if (router === undefined || this.closed) return 0;
|
|
977
|
+
const observed = new Map<string, bigint>();
|
|
978
|
+
for (const route of router.routesSnapshot()) {
|
|
979
|
+
observed.set(route.routingId, route.routeGeneration);
|
|
980
|
+
}
|
|
981
|
+
let changes = 0;
|
|
982
|
+
for (const [nodeRoutingId, generation] of this.selectedRoutes) {
|
|
983
|
+
if (observed.get(nodeRoutingId) === generation) continue;
|
|
984
|
+
changes++;
|
|
985
|
+
const peer = this.topology.peer(nodeRoutingId);
|
|
986
|
+
if (peer !== undefined && peer.connectionId === routeConnectionId(generation)) {
|
|
987
|
+
this.removePeer(peer);
|
|
1128
988
|
}
|
|
1129
989
|
}
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
990
|
+
const selected: string[] = [];
|
|
991
|
+
for (const [nodeRoutingId, generation] of observed) {
|
|
992
|
+
if (this.selectedRoutes.get(nodeRoutingId) === generation) continue;
|
|
993
|
+
if (!this.selectedRoutes.has(nodeRoutingId)) changes++;
|
|
994
|
+
selected.push(nodeRoutingId);
|
|
995
|
+
}
|
|
996
|
+
this.selectedRoutes = observed;
|
|
997
|
+
// A new selected route admits only through a new handshake.
|
|
998
|
+
for (const nodeRoutingId of selected) await this.announcePeer(nodeRoutingId);
|
|
999
|
+
return changes;
|
|
1133
1000
|
}
|
|
1134
1001
|
|
|
1135
1002
|
close(): void {
|
|
@@ -1145,8 +1012,6 @@ export class RawServiceMeshRuntime {
|
|
|
1145
1012
|
if (router !== undefined) {
|
|
1146
1013
|
this.applicationJobQueue.unregisterReceiveFlowTarget?.(router);
|
|
1147
1014
|
}
|
|
1148
|
-
this.monitor?.close();
|
|
1149
|
-
this.monitor = undefined;
|
|
1150
1015
|
const host = this.host;
|
|
1151
1016
|
this.router = undefined;
|
|
1152
1017
|
this.host = undefined;
|
|
@@ -1241,17 +1106,13 @@ export class RawServiceMeshRuntime {
|
|
|
1241
1106
|
})().catch((error) => this.operations.fail(pending.id, error));
|
|
1242
1107
|
return pending;
|
|
1243
1108
|
}
|
|
1244
|
-
if (
|
|
1245
|
-
//
|
|
1246
|
-
//
|
|
1247
|
-
//
|
|
1248
|
-
// as RequestTargetNotFound without submitting an application frame.
|
|
1249
|
-
const knownTarget =
|
|
1250
|
-
this.topology.peer(selectedTargetNodeRoutingId) !== undefined ||
|
|
1251
|
-
this.topology.knownDescriptor(selectedTargetNodeRoutingId) !== undefined;
|
|
1109
|
+
if (this.topology.peer(selectedTargetNodeRoutingId) === undefined) {
|
|
1110
|
+
// Logical target selection is the requester's decision: an RID that is
|
|
1111
|
+
// not an admitted peer is not a target. Whether an admitted target's
|
|
1112
|
+
// route can carry the request is Core's REQUEST result.
|
|
1252
1113
|
this.operations.complete(pending.id, {
|
|
1253
|
-
terminalResult:
|
|
1254
|
-
failureCode:
|
|
1114
|
+
terminalResult: RequestResult.NotFound,
|
|
1115
|
+
failureCode: REQUEST_TARGET_NOT_FOUND_FAILURE_CODE
|
|
1255
1116
|
});
|
|
1256
1117
|
return pending;
|
|
1257
1118
|
}
|
|
@@ -1320,7 +1181,7 @@ export class RawServiceMeshRuntime {
|
|
|
1320
1181
|
|
|
1321
1182
|
private admitPeer(
|
|
1322
1183
|
descriptor: ServiceNodeDescriptor,
|
|
1323
|
-
|
|
1184
|
+
connectionId: string,
|
|
1324
1185
|
nowMs: number,
|
|
1325
1186
|
expected?: {
|
|
1326
1187
|
readonly endpoint?: string;
|
|
@@ -1340,232 +1201,16 @@ export class RawServiceMeshRuntime {
|
|
|
1340
1201
|
`RouteMesh peer '${descriptor.nodeRoutingId}' descriptor revision is stale or conflicting.`
|
|
1341
1202
|
);
|
|
1342
1203
|
}
|
|
1343
|
-
const result = this.topology.admit(
|
|
1344
|
-
descriptor,
|
|
1345
|
-
connection.connectionId,
|
|
1346
|
-
expected,
|
|
1347
|
-
connection.discriminator
|
|
1348
|
-
);
|
|
1204
|
+
const result = this.topology.admit(descriptor, connectionId, expected);
|
|
1349
1205
|
if (result === 'admitted') {
|
|
1350
|
-
this.
|
|
1351
|
-
this.liveness.
|
|
1352
|
-
this.liveness.requestProbe(descriptor.nodeRoutingId, connection.connectionId, nowMs);
|
|
1353
|
-
this.monitorConnectionStates.set(descriptor.nodeRoutingId, connection.connectionId);
|
|
1354
|
-
if (previous !== undefined && previous.connectionId !== connection.connectionId) {
|
|
1355
|
-
const previousCandidate = this.connectionCandidates
|
|
1356
|
-
.get(descriptor.nodeRoutingId)
|
|
1357
|
-
?.get(previous.connectionId);
|
|
1358
|
-
if (previousCandidate !== undefined) {
|
|
1359
|
-
this.removeConnectionCandidate(descriptor.nodeRoutingId, previousCandidate.connectionId);
|
|
1360
|
-
}
|
|
1361
|
-
}
|
|
1206
|
+
this.liveness.admit(descriptor.nodeRoutingId, connectionId, nowMs);
|
|
1207
|
+
this.liveness.requestProbe(descriptor.nodeRoutingId, connectionId, nowMs);
|
|
1362
1208
|
} else if (result === 'notRequired' && previous !== undefined) {
|
|
1363
1209
|
this.liveness.disconnect(descriptor.nodeRoutingId, previous.connectionId);
|
|
1364
|
-
} else if (previous !== undefined) {
|
|
1365
|
-
this.connectionIds.set(descriptor.nodeRoutingId, previous.connectionId);
|
|
1366
|
-
if (connection.connectionId !== previous.connectionId) {
|
|
1367
|
-
this.removeConnectionCandidate(descriptor.nodeRoutingId, connection.connectionId);
|
|
1368
|
-
}
|
|
1369
1210
|
}
|
|
1370
1211
|
return result;
|
|
1371
1212
|
}
|
|
1372
1213
|
|
|
1373
|
-
private currentConnectionCandidate(
|
|
1374
|
-
nodeRoutingId: string,
|
|
1375
|
-
advertisedEndpoint: string
|
|
1376
|
-
): PhysicalConnectionCandidate {
|
|
1377
|
-
let connectionId = this.connectionIds.get(nodeRoutingId);
|
|
1378
|
-
let candidate =
|
|
1379
|
-
connectionId === undefined
|
|
1380
|
-
? undefined
|
|
1381
|
-
: this.connectionCandidates.get(nodeRoutingId)?.get(connectionId);
|
|
1382
|
-
if (candidate === undefined) {
|
|
1383
|
-
candidate = this.promoteUnresolvedConnectionCandidate(nodeRoutingId, advertisedEndpoint);
|
|
1384
|
-
connectionId = candidate?.connectionId;
|
|
1385
|
-
}
|
|
1386
|
-
if (candidate === undefined) {
|
|
1387
|
-
const localRid = this.topology.localDescriptor().nodeRoutingId;
|
|
1388
|
-
const endpointOnly = this.endpointOnlyPeers.has(advertisedEndpoint);
|
|
1389
|
-
const direction: PhysicalConnectionDirection = this.expectedPeers.has(nodeRoutingId)
|
|
1390
|
-
? 'outbound'
|
|
1391
|
-
: endpointOnly
|
|
1392
|
-
? localRid.localeCompare(nodeRoutingId) <= 0
|
|
1393
|
-
? 'outbound'
|
|
1394
|
-
: 'inbound'
|
|
1395
|
-
: 'inbound';
|
|
1396
|
-
const initiator =
|
|
1397
|
-
endpointOnly && localRid.localeCompare(nodeRoutingId) > 0
|
|
1398
|
-
? nodeRoutingId
|
|
1399
|
-
: direction === 'outbound'
|
|
1400
|
-
? localRid
|
|
1401
|
-
: nodeRoutingId;
|
|
1402
|
-
connectionId = `unmonitored:${direction}:${randomUUID()}`;
|
|
1403
|
-
candidate = {
|
|
1404
|
-
connectionId,
|
|
1405
|
-
direction,
|
|
1406
|
-
discriminator: `initiator:${initiator}`,
|
|
1407
|
-
localAddress: '',
|
|
1408
|
-
remoteAddress: advertisedEndpoint
|
|
1409
|
-
};
|
|
1410
|
-
let candidates = this.connectionCandidates.get(nodeRoutingId);
|
|
1411
|
-
if (candidates === undefined) {
|
|
1412
|
-
candidates = new Map();
|
|
1413
|
-
this.connectionCandidates.set(nodeRoutingId, candidates);
|
|
1414
|
-
}
|
|
1415
|
-
candidates.set(connectionId, candidate);
|
|
1416
|
-
this.connectionIds.set(nodeRoutingId, connectionId);
|
|
1417
|
-
}
|
|
1418
|
-
return candidate;
|
|
1419
|
-
}
|
|
1420
|
-
|
|
1421
|
-
private createConnectionCandidate(
|
|
1422
|
-
nodeRoutingId: string,
|
|
1423
|
-
event: ZLinkRawMonitorRecord
|
|
1424
|
-
): PhysicalConnectionCandidate {
|
|
1425
|
-
const local = this.topology.localDescriptor();
|
|
1426
|
-
const expected = this.expectedPeers.get(nodeRoutingId);
|
|
1427
|
-
const direction: PhysicalConnectionDirection =
|
|
1428
|
-
event.localAddress === local.advertisedEndpoint
|
|
1429
|
-
? 'inbound'
|
|
1430
|
-
: expected?.endpoint === event.remoteAddress
|
|
1431
|
-
? 'outbound'
|
|
1432
|
-
: this.endpointOnlyPeers.has(event.remoteAddress)
|
|
1433
|
-
? 'outbound'
|
|
1434
|
-
: 'unknown';
|
|
1435
|
-
const initiator =
|
|
1436
|
-
direction === 'outbound'
|
|
1437
|
-
? local.nodeRoutingId
|
|
1438
|
-
: direction === 'inbound'
|
|
1439
|
-
? nodeRoutingId
|
|
1440
|
-
: undefined;
|
|
1441
|
-
return {
|
|
1442
|
-
connectionId: monitorConnectionId(event, nodeRoutingId),
|
|
1443
|
-
direction,
|
|
1444
|
-
discriminator:
|
|
1445
|
-
initiator === undefined
|
|
1446
|
-
? `unknown:${monitorConnectionId(event, nodeRoutingId)}`
|
|
1447
|
-
: `initiator:${initiator}`,
|
|
1448
|
-
localAddress: event.localAddress,
|
|
1449
|
-
remoteAddress: event.remoteAddress
|
|
1450
|
-
};
|
|
1451
|
-
}
|
|
1452
|
-
|
|
1453
|
-
private createUnresolvedConnectionCandidate(
|
|
1454
|
-
event: ZLinkRawMonitorRecord
|
|
1455
|
-
): PhysicalConnectionCandidate {
|
|
1456
|
-
const connectionId = monitorConnectionId(event);
|
|
1457
|
-
const local = this.topology.localDescriptor();
|
|
1458
|
-
const direction: PhysicalConnectionDirection =
|
|
1459
|
-
this.endpointOnlyPeers.has(event.remoteAddress) ||
|
|
1460
|
-
this.expectedPeerRoutingId(event.remoteAddress) !== undefined
|
|
1461
|
-
? 'outbound'
|
|
1462
|
-
: event.localAddress === local.advertisedEndpoint
|
|
1463
|
-
? 'inbound'
|
|
1464
|
-
: 'unknown';
|
|
1465
|
-
return {
|
|
1466
|
-
connectionId,
|
|
1467
|
-
direction,
|
|
1468
|
-
discriminator: `unresolved:${connectionId}`,
|
|
1469
|
-
localAddress: event.localAddress,
|
|
1470
|
-
remoteAddress: event.remoteAddress
|
|
1471
|
-
};
|
|
1472
|
-
}
|
|
1473
|
-
|
|
1474
|
-
private promoteUnresolvedConnectionCandidate(
|
|
1475
|
-
nodeRoutingId: string,
|
|
1476
|
-
advertisedEndpoint: string
|
|
1477
|
-
): PhysicalConnectionCandidate | undefined {
|
|
1478
|
-
const relevant = this.unresolvedConnectionCandidates
|
|
1479
|
-
.map((candidate, index) => ({ candidate, index }))
|
|
1480
|
-
.filter(
|
|
1481
|
-
({ candidate }) =>
|
|
1482
|
-
candidate.remoteAddress === advertisedEndpoint || candidate.direction === 'inbound'
|
|
1483
|
-
);
|
|
1484
|
-
if (relevant.length === 0) return undefined;
|
|
1485
|
-
const directions = new Set(relevant.map((value) => value.candidate.direction));
|
|
1486
|
-
const localRid = this.topology.localDescriptor().nodeRoutingId;
|
|
1487
|
-
const preferredDirection: PhysicalConnectionDirection =
|
|
1488
|
-
directions.has('inbound') && directions.has('outbound')
|
|
1489
|
-
? localRid.localeCompare(nodeRoutingId) <= 0
|
|
1490
|
-
? 'outbound'
|
|
1491
|
-
: 'inbound'
|
|
1492
|
-
: relevant.at(-1)!.candidate.direction;
|
|
1493
|
-
const selected =
|
|
1494
|
-
[...relevant].reverse().find((value) => value.candidate.direction === preferredDirection) ??
|
|
1495
|
-
relevant.at(-1)!;
|
|
1496
|
-
this.unresolvedConnectionCandidates.splice(selected.index, 1);
|
|
1497
|
-
const initiator =
|
|
1498
|
-
selected.candidate.direction === 'outbound'
|
|
1499
|
-
? localRid
|
|
1500
|
-
: selected.candidate.direction === 'inbound'
|
|
1501
|
-
? nodeRoutingId
|
|
1502
|
-
: undefined;
|
|
1503
|
-
const candidate: PhysicalConnectionCandidate = {
|
|
1504
|
-
...selected.candidate,
|
|
1505
|
-
discriminator:
|
|
1506
|
-
initiator === undefined
|
|
1507
|
-
? `unknown:${selected.candidate.connectionId}`
|
|
1508
|
-
: `initiator:${initiator}`
|
|
1509
|
-
};
|
|
1510
|
-
let candidates = this.connectionCandidates.get(nodeRoutingId);
|
|
1511
|
-
if (candidates === undefined) {
|
|
1512
|
-
candidates = new Map();
|
|
1513
|
-
this.connectionCandidates.set(nodeRoutingId, candidates);
|
|
1514
|
-
}
|
|
1515
|
-
candidates.set(candidate.connectionId, candidate);
|
|
1516
|
-
this.connectionIds.set(nodeRoutingId, candidate.connectionId);
|
|
1517
|
-
return candidate;
|
|
1518
|
-
}
|
|
1519
|
-
|
|
1520
|
-
private removeConnectionCandidate(nodeRoutingId: string, connectionId: string): void {
|
|
1521
|
-
const candidates = this.connectionCandidates.get(nodeRoutingId);
|
|
1522
|
-
candidates?.delete(connectionId);
|
|
1523
|
-
if (candidates?.size === 0) this.connectionCandidates.delete(nodeRoutingId);
|
|
1524
|
-
if (this.connectionIds.get(nodeRoutingId) === connectionId) {
|
|
1525
|
-
this.connectionIds.delete(nodeRoutingId);
|
|
1526
|
-
}
|
|
1527
|
-
}
|
|
1528
|
-
|
|
1529
|
-
private isDiscoveredLifecycleReplacement(
|
|
1530
|
-
admitted: AdmittedServicePeer,
|
|
1531
|
-
nodeRoutingId: string
|
|
1532
|
-
): boolean {
|
|
1533
|
-
const expected = this.expectedPeers.get(nodeRoutingId);
|
|
1534
|
-
// A same-RID replacement is permitted only from a complete discovery
|
|
1535
|
-
// fence. Manual/endpoint-only intent has no authority to replace a ready
|
|
1536
|
-
// peer, and lifecycle generations are opaque equality tokens.
|
|
1537
|
-
return (
|
|
1538
|
-
expected?.endpoint !== undefined &&
|
|
1539
|
-
expected.securityIdentity !== undefined &&
|
|
1540
|
-
expected.lifecycleGeneration !== undefined &&
|
|
1541
|
-
admitted.descriptor.lifecycleGeneration !== expected.lifecycleGeneration
|
|
1542
|
-
);
|
|
1543
|
-
}
|
|
1544
|
-
|
|
1545
|
-
private expectedPeerRoutingId(endpoint: string): string | undefined {
|
|
1546
|
-
for (const [nodeRoutingId, expected] of this.expectedPeers) {
|
|
1547
|
-
if (expected.endpoint === endpoint) return nodeRoutingId;
|
|
1548
|
-
}
|
|
1549
|
-
return undefined;
|
|
1550
|
-
}
|
|
1551
|
-
|
|
1552
|
-
private acceptsExpectedMonitorEndpoint(
|
|
1553
|
-
nodeRoutingId: string,
|
|
1554
|
-
event: Pick<ZLinkRawMonitorRecord, 'remoteAddress' | 'routingId'>
|
|
1555
|
-
): boolean {
|
|
1556
|
-
// A monitor address is the physical socket address, which may be an
|
|
1557
|
-
// ephemeral port. Once Core has resolved the peer routing ID, that ID is
|
|
1558
|
-
// the semantic identity; comparing the physical address with the
|
|
1559
|
-
// discovery advertised endpoint would reject valid READY events.
|
|
1560
|
-
if (event.routingId === nodeRoutingId) return true;
|
|
1561
|
-
const expected = this.expectedPeers.get(nodeRoutingId);
|
|
1562
|
-
return (
|
|
1563
|
-
expected === undefined ||
|
|
1564
|
-
expected.endpoint === undefined ||
|
|
1565
|
-
expected.endpoint === event.remoteAddress
|
|
1566
|
-
);
|
|
1567
|
-
}
|
|
1568
|
-
|
|
1569
1214
|
private retireNotRequiredExpectedPeer(nodeRoutingId: string, advertisedEndpoint?: string): void {
|
|
1570
1215
|
const expected = this.expectedPeers.get(nodeRoutingId);
|
|
1571
1216
|
const endpoint =
|
|
@@ -1597,9 +1242,6 @@ export class RawServiceMeshRuntime {
|
|
|
1597
1242
|
}
|
|
1598
1243
|
|
|
1599
1244
|
private removePeer(peer: AdmittedServicePeer): void {
|
|
1600
|
-
if (this.monitorConnectionStates.get(peer.descriptor.nodeRoutingId) === peer.connectionId) {
|
|
1601
|
-
this.monitorConnectionStates.set(peer.descriptor.nodeRoutingId, null);
|
|
1602
|
-
}
|
|
1603
1245
|
this.topology.disconnect(peer.descriptor.nodeRoutingId, peer.connectionId);
|
|
1604
1246
|
this.liveness.disconnect(peer.descriptor.nodeRoutingId, peer.connectionId);
|
|
1605
1247
|
this.onPeerDisconnected?.(
|
|
@@ -1609,25 +1251,6 @@ export class RawServiceMeshRuntime {
|
|
|
1609
1251
|
);
|
|
1610
1252
|
}
|
|
1611
1253
|
|
|
1612
|
-
private observeMonitorEvent(event: ZLinkRawMonitorRecord): void {
|
|
1613
|
-
const nodeRoutingId = event.routingId ?? this.expectedPeerRoutingId(event.remoteAddress);
|
|
1614
|
-
if (nodeRoutingId === undefined) return;
|
|
1615
|
-
const connectionId = monitorConnectionId(event, nodeRoutingId);
|
|
1616
|
-
if (event.event === MONITOR_CONNECTION_READY && isConnectionReadyEdge(event)) {
|
|
1617
|
-
if (!this.acceptsExpectedMonitorEndpoint(nodeRoutingId, event)) return;
|
|
1618
|
-
// The event is queued for admission. Updating monitor state here would
|
|
1619
|
-
// allow a late stale READY to overwrite the admitted replacement.
|
|
1620
|
-
return;
|
|
1621
|
-
}
|
|
1622
|
-
if (
|
|
1623
|
-
event.event === MONITOR_DISCONNECTED &&
|
|
1624
|
-
this.monitorConnectionStates.get(nodeRoutingId) === connectionId &&
|
|
1625
|
-
this.topology.peer(nodeRoutingId)?.connectionId === connectionId
|
|
1626
|
-
) {
|
|
1627
|
-
this.monitorConnectionStates.set(nodeRoutingId, null);
|
|
1628
|
-
}
|
|
1629
|
-
}
|
|
1630
|
-
|
|
1631
1254
|
private requireStarted(): ZLinkRawRouterPort {
|
|
1632
1255
|
if (this.router === undefined) throw new Error('Raw service runtime is not started.');
|
|
1633
1256
|
return this.router;
|
|
@@ -1680,35 +1303,12 @@ function isAlreadyDisconnectedError(error: unknown): boolean {
|
|
|
1680
1303
|
return (error as { readonly nativeErrno?: unknown }).nativeErrno === 2;
|
|
1681
1304
|
}
|
|
1682
1305
|
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
}
|
|
1690
|
-
return JSON.stringify([
|
|
1691
|
-
// Monitor `value` is event-specific: CONNECTION_READY reports the
|
|
1692
|
-
// socket's ready count and DISCONNECTED reports its reason. It is not a
|
|
1693
|
-
// connection identity. The public monitor record has no native
|
|
1694
|
-
// connection id, so use the stable routing and endpoint tuple instead.
|
|
1695
|
-
resolvedNodeRoutingId,
|
|
1696
|
-
event.localAddress,
|
|
1697
|
-
event.remoteAddress
|
|
1698
|
-
]);
|
|
1699
|
-
}
|
|
1700
|
-
|
|
1701
|
-
function sameMonitorConnection(left: ZLinkRawMonitorRecord, right: ZLinkRawMonitorRecord): boolean {
|
|
1702
|
-
return (
|
|
1703
|
-
monitorConnectionId(left) === monitorConnectionId(right) &&
|
|
1704
|
-
left.remoteAddress === right.remoteAddress
|
|
1705
|
-
);
|
|
1706
|
-
}
|
|
1707
|
-
|
|
1708
|
-
function isConnectionReadyEdge(event: ZLinkRawMonitorRecord): boolean {
|
|
1709
|
-
return event.flags === undefined
|
|
1710
|
-
? event.value > 0n
|
|
1711
|
-
: (event.flags & MONITOR_CONNECTION_READY_EDGE) !== 0;
|
|
1306
|
+
/**
|
|
1307
|
+
* An admission belongs to one Core selected route. Its connection identity is
|
|
1308
|
+
* that route's generation, an opaque token compared only for equality.
|
|
1309
|
+
*/
|
|
1310
|
+
function routeConnectionId(routeGeneration: bigint): string {
|
|
1311
|
+
return `route:${routeGeneration.toString()}`;
|
|
1712
1312
|
}
|
|
1713
1313
|
|
|
1714
1314
|
function describeInvalidAdmissionDescriptor(
|