@zlink-systems/framework 0.30.0 → 0.30.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +150 -68
- package/dist/.tsbuildinfo +1 -1
- package/dist/contracts/Actors/ZLinkActorClient.d.ts +2 -1
- package/dist/contracts/Channels/Calls.d.ts +2 -1
- package/dist/contracts/Common/ZLinkMessageMetadata.js +11 -5
- package/dist/contracts/Handlers/Attributes.d.ts +5 -13
- package/dist/contracts/Handlers/Attributes.js +7 -11
- package/dist/contracts/Handlers/JsonContract.d.ts +2 -46
- package/dist/contracts/Handlers/JsonContract.js +4 -153
- package/dist/contracts/Spots/Contracts.d.ts +2 -1
- package/dist/nest-integration.d.ts +1 -1
- package/dist/nest-integration.js +2 -1
- package/dist/runtime/actors/actor-client.d.ts +1 -1
- package/dist/runtime/actors/actor-client.js +14 -12
- package/dist/runtime/actors/actor-handoff.d.ts +1 -1
- package/dist/runtime/actors/actor-handoff.js +7 -4
- package/dist/runtime/actors/actor-join-relocation.d.ts +1 -0
- package/dist/runtime/actors/actor-local-native-join.js +9 -12
- package/dist/runtime/actors/actor-remote-wire.js +15 -0
- package/dist/runtime/actors/actor-routed-join-transport.d.ts +6 -2
- package/dist/runtime/actors/actor-routed-json-request.d.ts +2 -0
- package/dist/runtime/actors/index.d.ts +2 -1
- package/dist/runtime/actors/index.js +18 -6
- package/dist/runtime/actors/spot-actor-dispatch.js +2 -2
- package/dist/runtime/admission.d.ts +1 -1
- package/dist/runtime/admission.js +15 -5
- package/dist/runtime/application-jobs/application-ingress-record-owner.d.ts +6 -8
- package/dist/runtime/application-jobs/application-ingress-record-owner.js +39 -22
- package/dist/runtime/application-jobs/application-job-queue-scope.d.ts +2 -1
- package/dist/runtime/application-jobs/application-job-queue-scope.js +37 -22
- package/dist/runtime/application-jobs/contracts.d.ts +10 -1
- package/dist/runtime/backend/contracts/index.d.ts +18 -9
- package/dist/runtime/backend/mesh-completion-table.d.ts +9 -1
- package/dist/runtime/backend/mesh-completion-table.js +149 -49
- package/dist/runtime/backend/mesh-dispatch-pump.d.ts +3 -0
- package/dist/runtime/backend/mesh-dispatch-pump.js +32 -11
- package/dist/runtime/backend/node/node-backend-adapter-support.d.ts +2 -2
- package/dist/runtime/backend/node/node-backend-adapter-support.js +16 -9
- package/dist/runtime/backend/node/node-dealer-channel-client-transport.d.ts +1 -1
- package/dist/runtime/backend/node/node-dealer-channel-client-transport.js +5 -2
- package/dist/runtime/backend/node/node-raw-binding-port.js +42 -22
- package/dist/runtime/backend/node/node-raw-mesh-backend.d.ts +13 -5
- package/dist/runtime/backend/node/node-raw-mesh-backend.js +69 -51
- package/dist/runtime/backend/node/node-socket-backend-adapter.js +6 -6
- package/dist/runtime/backend/raw-binding-port.d.ts +2 -2
- package/dist/runtime/backend/runtime-message.d.ts +1 -0
- package/dist/runtime/backend/runtime-message.js +5 -0
- package/dist/runtime/backend/runtime-values.d.ts +1 -0
- package/dist/runtime/backend/runtime-values.js +2 -1
- package/dist/runtime/channels/channel-clients.d.ts +1 -0
- package/dist/runtime/channels/channel-clients.js +58 -21
- package/dist/runtime/channels/channel-dispatch-pipeline.d.ts +3 -2
- package/dist/runtime/channels/channel-dispatch-pipeline.js +20 -11
- package/dist/runtime/channels/channel-dispatch-services.d.ts +1 -0
- package/dist/runtime/channels/channel-dispatch-services.js +45 -0
- package/dist/runtime/channels/channel-dispatchers.d.ts +1 -1
- package/dist/runtime/channels/channel-dispatchers.js +27 -26
- package/dist/runtime/channels/channel-envelope-inspection.d.ts +1 -2
- package/dist/runtime/channels/channel-envelope-inspection.js +1 -16
- package/dist/runtime/channels/channel-envelope.d.ts +1 -0
- package/dist/runtime/channels/channel-envelope.js +83 -8
- package/dist/runtime/channels/channel-outbound-operations.d.ts +2 -2
- package/dist/runtime/channels/channel-outbound-operations.js +8 -8
- package/dist/runtime/channels/channel-runtime-manager.d.ts +6 -6
- package/dist/runtime/channels/channel-runtime-manager.js +12 -12
- package/dist/runtime/channels/channel-transports.d.ts +18 -15
- package/dist/runtime/channels/channel-transports.js +48 -31
- package/dist/runtime/channels/local-spot-route-transport.d.ts +1 -1
- package/dist/runtime/channels/local-spot-route-transport.js +2 -1
- package/dist/runtime/channels/router-socket-spot-route-transport.d.ts +2 -2
- package/dist/runtime/channels/router-socket-spot-route-transport.js +4 -4
- package/dist/runtime/channels/source-spot-router.d.ts +2 -2
- package/dist/runtime/channels/source-spot-router.js +4 -4
- package/dist/runtime/channels/spot-node-route-transport.d.ts +2 -2
- package/dist/runtime/channels/spot-node-route-transport.js +4 -4
- package/dist/runtime/channels/spot-route-bridge-transport.d.ts +2 -2
- package/dist/runtime/channels/spot-route-bridge-transport.js +4 -4
- package/dist/runtime/channels/spot-route-dispatch-strategy.d.ts +5 -4
- package/dist/runtime/channels/spot-route-dispatch-strategy.js +18 -18
- package/dist/runtime/execution/serial-execution-queue.d.ts +1 -0
- package/dist/runtime/execution/serial-execution-queue.js +1 -0
- package/dist/runtime/foundation/actor-join-recovery-codec.js +2 -2
- package/dist/runtime/foundation/durable-service-request.d.ts +14 -0
- package/dist/runtime/foundation/durable-service-request.js +98 -0
- package/dist/runtime/foundation/operation-identity.d.ts +1 -0
- package/dist/runtime/foundation/operation-identity.js +5 -1
- package/dist/runtime/foundation/raw-service-mesh-runtime.d.ts +22 -5
- package/dist/runtime/foundation/raw-service-mesh-runtime.js +356 -232
- package/dist/runtime/foundation/service-relocation-object-owner.d.ts +1 -0
- package/dist/runtime/foundation/service-relocation-object-owner.js +1 -0
- package/dist/runtime/foundation/service-relocation-runtime.d.ts +2 -0
- package/dist/runtime/foundation/service-relocation-runtime.js +13 -1
- package/dist/runtime/foundation/service-runtime-contracts.d.ts +2 -0
- package/dist/runtime/foundation/service-stateful-runtime.d.ts +4 -4
- package/dist/runtime/foundation/service-stateful-runtime.js +44 -103
- package/dist/runtime/foundation/service-stateful-wire-codec.d.ts +0 -5
- package/dist/runtime/foundation/service-topology-registry.d.ts +12 -2
- package/dist/runtime/foundation/service-topology-registry.js +61 -27
- package/dist/runtime/foundation/service-wire-m6a-codec.d.ts +3 -3
- package/dist/runtime/foundation/service-wire-m6a-codec.js +36 -38
- package/dist/runtime/handlers/handler-instance-scope.d.ts +8 -0
- package/dist/runtime/handlers/handler-instance-scope.js +54 -11
- package/dist/runtime/host/actor-runtime-options-factory.js +2 -2
- package/dist/runtime/host/application-job-queue.d.ts +19 -7
- package/dist/runtime/host/application-job-queue.js +150 -42
- package/dist/runtime/host/entry-actor-runtime.d.ts +1 -0
- package/dist/runtime/host/entry-actor-runtime.js +3 -0
- package/dist/runtime/host/index.d.ts +7 -1
- package/dist/runtime/host/index.js +142 -118
- package/dist/runtime/host/remote-bound-session-relay.js +2 -18
- package/dist/runtime/host/route-mesh-runtime.js +1 -1
- package/dist/runtime/host/service-relocation-control.d.ts +1 -0
- package/dist/runtime/host/service-relocation-control.js +10 -4
- package/dist/runtime/host/service-relocation-host-runtime.d.ts +6 -8
- package/dist/runtime/host/service-relocation-host-runtime.js +213 -241
- package/dist/runtime/host/spot-address-transport.js +3 -2
- package/dist/runtime/host/spot-runtime-options-factory.js +1 -0
- package/dist/runtime/locations/actor-location-claims.d.ts +2 -2
- package/dist/runtime/locations/actor-location-claims.js +34 -19
- package/dist/runtime/locations/lifecycle.d.ts +1 -2
- package/dist/runtime/locations/lifecycle.js +2 -5
- package/dist/runtime/locations/runtime.d.ts +1 -1
- package/dist/runtime/locations/runtime.js +24 -23
- package/dist/runtime/messaging/framework-json-v1.d.ts +4 -6
- package/dist/runtime/messaging/framework-json-v1.js +38 -294
- package/dist/runtime/messaging/packet-name.d.ts +1 -1
- package/dist/runtime/messaging/packet-name.js +8 -24
- package/dist/runtime/messaging/request-result.d.ts +2 -0
- package/dist/runtime/messaging/request-result.js +54 -0
- package/dist/runtime/messaging/submission-result.d.ts +1 -0
- package/dist/runtime/messaging/submission-result.js +5 -2
- package/dist/runtime/protocol/service_wire_codec.generated.d.ts +15 -0
- package/dist/runtime/protocol/service_wire_codec.generated.js +144 -0
- package/dist/runtime/protocol/service_wire_pilot_codec.generated.d.ts +9 -0
- package/dist/runtime/protocol/service_wire_pilot_codec.generated.js +14 -1
- package/dist/runtime/spots/formal-remote-actor-admission-registry.d.ts +7 -2
- package/dist/runtime/spots/formal-remote-actor-admission-registry.js +33 -19
- package/dist/runtime/spots/index.d.ts +3 -0
- package/dist/runtime/spots/index.js +9 -2
- package/dist/runtime/spots/spot-activation.d.ts +1 -0
- package/dist/runtime/spots/spot-activation.js +17 -8
- package/dist/runtime/spots/spot-entry-activation.js +6 -2
- package/dist/runtime/spots/spot-handler-registry.d.ts +3 -1
- package/dist/runtime/spots/spot-handler-registry.js +18 -8
- package/dist/runtime/spots/spot-node-autoconnect.js +4 -3
- package/dist/runtime/spots/spot-outbound.d.ts +3 -0
- package/dist/runtime/spots/spot-outbound.js +24 -15
- package/dist/runtime/spots/spot-routed-spot-packet-dispatch.d.ts +1 -0
- package/dist/runtime/spots/spot-routed-spot-packet-dispatch.js +1 -1
- package/dist/runtime/spots/spot-runtime-ports.d.ts +1 -0
- package/dist/runtime/spots/spot-subscription-dispatch.js +0 -3
- package/dist/runtime/spots/spot-timer.js +1 -0
- package/dist/runtime/streams/stream-session-runtime.d.ts +1 -1
- package/dist/runtime/streams/stream-session-runtime.js +13 -27
- package/package.json +2 -2
- package/src/contracts/Actors/ZLinkActorClient.ts +2 -1
- package/src/contracts/Channels/Calls.ts +2 -1
- package/src/contracts/Common/ZLinkMessageMetadata.ts +13 -8
- package/src/contracts/Handlers/Attributes.ts +14 -21
- package/src/contracts/Handlers/JsonContract.ts +8 -216
- package/src/contracts/Spots/Contracts.ts +2 -1
- package/src/nest-integration.ts +1 -0
- package/src/runtime/actors/actor-client.ts +40 -16
- package/src/runtime/actors/actor-handoff.ts +9 -4
- package/src/runtime/actors/actor-join-relocation.ts +1 -0
- package/src/runtime/actors/actor-local-native-join.ts +17 -26
- package/src/runtime/actors/actor-remote-wire.ts +17 -0
- package/src/runtime/actors/actor-routed-join-transport.ts +14 -4
- package/src/runtime/actors/actor-routed-json-request.ts +10 -2
- package/src/runtime/actors/index.ts +19 -5
- package/src/runtime/actors/spot-actor-dispatch.ts +4 -2
- package/src/runtime/admission.ts +18 -5
- package/src/runtime/application-jobs/application-ingress-record-owner.ts +41 -25
- package/src/runtime/application-jobs/application-job-queue-scope.ts +51 -22
- package/src/runtime/application-jobs/contracts.ts +14 -1
- package/src/runtime/backend/contracts/index.ts +53 -14
- package/src/runtime/backend/mesh-completion-table.ts +156 -57
- package/src/runtime/backend/mesh-dispatch-pump.ts +40 -10
- package/src/runtime/backend/node/node-backend-adapter-support.ts +17 -8
- package/src/runtime/backend/node/node-dealer-channel-client-transport.ts +12 -2
- package/src/runtime/backend/node/node-raw-binding-port.ts +57 -30
- package/src/runtime/backend/node/node-raw-mesh-backend.ts +128 -65
- package/src/runtime/backend/node/node-socket-backend-adapter.ts +21 -6
- package/src/runtime/backend/raw-binding-port.ts +7 -2
- package/src/runtime/backend/runtime-message.ts +6 -0
- package/src/runtime/backend/runtime-values.ts +1 -0
- package/src/runtime/channels/channel-clients.ts +90 -41
- package/src/runtime/channels/channel-dispatch-pipeline.ts +69 -34
- package/src/runtime/channels/channel-dispatch-services.ts +69 -1
- package/src/runtime/channels/channel-dispatchers.ts +29 -27
- package/src/runtime/channels/channel-envelope-inspection.ts +2 -16
- package/src/runtime/channels/channel-envelope.ts +133 -8
- package/src/runtime/channels/channel-outbound-operations.ts +22 -8
- package/src/runtime/channels/channel-runtime-manager.ts +29 -12
- package/src/runtime/channels/channel-transports.ts +128 -60
- package/src/runtime/channels/local-spot-route-transport.ts +3 -1
- package/src/runtime/channels/router-socket-spot-route-transport.ts +13 -4
- package/src/runtime/channels/source-spot-router.ts +8 -4
- package/src/runtime/channels/spot-node-route-transport.ts +8 -4
- package/src/runtime/channels/spot-route-bridge-transport.ts +6 -4
- package/src/runtime/channels/spot-route-dispatch-strategy.ts +47 -18
- package/src/runtime/execution/serial-execution-queue.ts +2 -0
- package/src/runtime/foundation/actor-join-recovery-codec.ts +2 -2
- package/src/runtime/foundation/durable-service-request.ts +137 -0
- package/src/runtime/foundation/operation-identity.ts +5 -1
- package/src/runtime/foundation/raw-service-mesh-runtime.ts +484 -293
- package/src/runtime/foundation/service-relocation-object-owner.ts +2 -0
- package/src/runtime/foundation/service-relocation-runtime.ts +18 -1
- package/src/runtime/foundation/service-runtime-contracts.ts +7 -1
- package/src/runtime/foundation/service-stateful-runtime.ts +67 -125
- package/src/runtime/foundation/service-stateful-wire-codec.ts +0 -5
- package/src/runtime/foundation/service-topology-registry.ts +91 -36
- package/src/runtime/foundation/service-wire-m6a-codec.ts +57 -38
- package/src/runtime/handlers/handler-instance-scope.ts +67 -16
- package/src/runtime/host/actor-runtime-options-factory.ts +13 -2
- package/src/runtime/host/application-job-queue.ts +175 -41
- package/src/runtime/host/entry-actor-runtime.ts +4 -0
- package/src/runtime/host/index.ts +201 -156
- package/src/runtime/host/remote-bound-session-relay.ts +2 -23
- package/src/runtime/host/route-mesh-runtime.ts +1 -1
- package/src/runtime/host/service-relocation-control.ts +11 -5
- package/src/runtime/host/service-relocation-host-runtime.ts +315 -291
- package/src/runtime/host/spot-address-transport.ts +4 -2
- package/src/runtime/host/spot-runtime-options-factory.ts +2 -0
- package/src/runtime/locations/actor-location-claims.ts +52 -30
- package/src/runtime/locations/lifecycle.ts +8 -26
- package/src/runtime/locations/runtime.ts +28 -24
- package/src/runtime/messaging/framework-json-v1.ts +5 -325
- package/src/runtime/messaging/packet-name.ts +5 -25
- package/src/runtime/messaging/request-result.ts +58 -0
- package/src/runtime/messaging/submission-result.ts +5 -1
- package/src/runtime/protocol/service_wire_codec.generated.ts +131 -1
- package/src/runtime/protocol/service_wire_pilot_codec.generated.ts +4 -4
- package/src/runtime/spots/formal-remote-actor-admission-registry.ts +43 -23
- package/src/runtime/spots/index.ts +14 -2
- package/src/runtime/spots/spot-activation.ts +28 -10
- package/src/runtime/spots/spot-entry-activation.ts +9 -2
- package/src/runtime/spots/spot-handler-registry.ts +41 -8
- package/src/runtime/spots/spot-node-autoconnect.ts +6 -2
- package/src/runtime/spots/spot-outbound.ts +36 -16
- package/src/runtime/spots/spot-routed-spot-packet-dispatch.ts +2 -1
- package/src/runtime/spots/spot-runtime-ports.ts +1 -0
- package/src/runtime/spots/spot-subscription-dispatch.ts +0 -3
- package/src/runtime/spots/spot-timer.ts +5 -1
- package/src/runtime/streams/stream-session-runtime.ts +12 -29
|
@@ -2,6 +2,7 @@ import { constants as osConstants } from 'node:os';
|
|
|
2
2
|
import { ApplicationIngressRecordOwner } from '../application-jobs/application-ingress-record-owner';
|
|
3
3
|
import type {
|
|
4
4
|
ApplicationJobPermitPort,
|
|
5
|
+
ApplicationJobReceiveRotationPort,
|
|
5
6
|
ApplicationJobQueuePort
|
|
6
7
|
} from '../application-jobs/contracts';
|
|
7
8
|
import type {
|
|
@@ -13,11 +14,11 @@ import type {
|
|
|
13
14
|
import { submitToRequestResult } from '../messaging/submission-result';
|
|
14
15
|
import { RequestResult, SubmitResult, isZLinkBackendResultError } from '../backend/runtime-values';
|
|
15
16
|
import { enumWireRejectReason } from '../protocol/service_wire_codec.generated';
|
|
17
|
+
import { SERVICE_WIRE_PREFIX_SIZE } from './service-wire-binary-primitives';
|
|
16
18
|
import { OperationRegistry, type PendingOperation } from './operation-registry';
|
|
17
19
|
import { ServiceLivenessRegistry, type ServiceLivenessTick } from './service-liveness-registry';
|
|
18
20
|
import { ServiceMailbox, type ServiceMailboxRecord } from './service-mailbox';
|
|
19
21
|
import {
|
|
20
|
-
sameServiceNodeDescriptor,
|
|
21
22
|
ServiceTopologyRegistry,
|
|
22
23
|
validateDescriptor,
|
|
23
24
|
type AdmittedServicePeer,
|
|
@@ -84,6 +85,7 @@ type RawServiceChannelTargetSelection =
|
|
|
84
85
|
type ServiceApplicationPayloadInput = ServiceApplicationPayload | Buffer;
|
|
85
86
|
|
|
86
87
|
export interface RawServiceIngressRecord {
|
|
88
|
+
readonly onAdmitted?: () => void;
|
|
87
89
|
readonly command: number;
|
|
88
90
|
readonly flags: number;
|
|
89
91
|
readonly sourceRoutingId: string;
|
|
@@ -129,6 +131,7 @@ export interface RawServiceMeshRuntimeOptions {
|
|
|
129
131
|
// Framework error code 13 (RequestTargetNotFound) is encoded as 14 on a
|
|
130
132
|
// RequestResult.NotFound reply. Boundary transport results keep failureCode 0.
|
|
131
133
|
const RAW_MESH_RECEIVE_RECORD_BUDGET = 64;
|
|
134
|
+
const NODE_SEND_HEADER = encodeNodeSendHeader();
|
|
132
135
|
const RAW_MESH_RECEIVE_BYTE_BUDGET = 4 * 1024 * 1024;
|
|
133
136
|
export const RAW_MESH_RECEIVE_TIME_BUDGET_MS = 2;
|
|
134
137
|
|
|
@@ -140,6 +143,14 @@ const livenessCodec = createServiceWireCodec({
|
|
|
140
143
|
commands: M6aServiceWireCommand
|
|
141
144
|
});
|
|
142
145
|
|
|
146
|
+
interface ControlSendState {
|
|
147
|
+
task?: Promise<boolean>;
|
|
148
|
+
next?: () => boolean | Promise<boolean>;
|
|
149
|
+
current?: () => boolean;
|
|
150
|
+
// Admission owns the first HELLO until ADMIT or route termination.
|
|
151
|
+
hello?: Uint8Array;
|
|
152
|
+
}
|
|
153
|
+
|
|
143
154
|
/**
|
|
144
155
|
* RouteMesh M6A runtime built only on the public raw binding package.
|
|
145
156
|
* It owns protocol, admission, mailbox, request and liveness state.
|
|
@@ -158,11 +169,11 @@ export class RawServiceMeshRuntime {
|
|
|
158
169
|
readonly endpoint?: string;
|
|
159
170
|
readonly securityIdentity?: string;
|
|
160
171
|
readonly lifecycleGeneration?: bigint;
|
|
161
|
-
helloSubmittedGeneration?: bigint;
|
|
162
172
|
}
|
|
163
173
|
>();
|
|
164
174
|
private readonly endpointOnlyPeers = new Set<string>();
|
|
165
175
|
private readonly peerConnectionIntentRemoved = new Set<(nodeRoutingId: string) => void>();
|
|
176
|
+
private readonly peerRouteChanged = new Set<(nodeRoutingId: string) => void>();
|
|
166
177
|
/**
|
|
167
178
|
* The last observed Core selected route of each RID (Core ROUTER §10.1):
|
|
168
179
|
* RID -> route generation. Only `observeSelectedRoutes` replaces it.
|
|
@@ -170,18 +181,13 @@ export class RawServiceMeshRuntime {
|
|
|
170
181
|
private selectedRoutes = new Map<string, bigint>();
|
|
171
182
|
private readonly bindingPort: ZLinkRawBindingPort;
|
|
172
183
|
private readonly applicationJobQueue: ApplicationJobQueuePort;
|
|
184
|
+
private readonly nodeMailboxOwner: string;
|
|
173
185
|
private readonly peerAdmissionSealed?: () => boolean;
|
|
174
186
|
private readonly applicationJobStop = new AbortController();
|
|
175
187
|
private receiveAcquisition?: Promise<ApplicationJobPermitPort>;
|
|
176
188
|
private receivePermit?: ApplicationJobPermitPort;
|
|
177
|
-
private
|
|
178
|
-
|
|
179
|
-
{
|
|
180
|
-
readonly task: Promise<boolean>;
|
|
181
|
-
next?: () => boolean | Promise<boolean>;
|
|
182
|
-
current?: () => boolean;
|
|
183
|
-
}
|
|
184
|
-
>();
|
|
189
|
+
private receiveRotation?: ApplicationJobReceiveRotationPort;
|
|
190
|
+
private readonly controlSends = new Map<string, ControlSendState>();
|
|
185
191
|
private infrastructureFailure?: { readonly error: unknown };
|
|
186
192
|
private readonly onReceiveReady?: () => void;
|
|
187
193
|
private readonly infrastructureTaskRunner?: RawServiceMeshRuntimeOptions['infrastructureTaskRunner'];
|
|
@@ -195,6 +201,7 @@ export class RawServiceMeshRuntime {
|
|
|
195
201
|
private router?: ZLinkRawRouterPort;
|
|
196
202
|
private nextCorrelation = 1n;
|
|
197
203
|
private serviceIngress?: RawServiceIngressHandler;
|
|
204
|
+
private serviceIngressAccepts?: (command: number) => boolean;
|
|
198
205
|
private closed = false;
|
|
199
206
|
|
|
200
207
|
constructor(options: RawServiceMeshRuntimeOptions) {
|
|
@@ -202,6 +209,7 @@ export class RawServiceMeshRuntime {
|
|
|
202
209
|
this.infrastructureTaskRunner = options.infrastructureTaskRunner;
|
|
203
210
|
this.operations = new OperationRegistry(undefined, options.onPendingOperationsChanged);
|
|
204
211
|
this.descriptor = options.descriptor;
|
|
212
|
+
this.nodeMailboxOwner = `node:${options.descriptor.nodeRoutingId}`;
|
|
205
213
|
this.topology = new ServiceTopologyRegistry(options.descriptor);
|
|
206
214
|
this.mailbox = new ServiceMailbox(options.onMailboxReady);
|
|
207
215
|
this.liveness = new ServiceLivenessRegistry(options.probeIntervalMs, options.peerTimeoutMs);
|
|
@@ -380,6 +388,36 @@ export class RawServiceMeshRuntime {
|
|
|
380
388
|
};
|
|
381
389
|
}
|
|
382
390
|
|
|
391
|
+
observePeerRoute(
|
|
392
|
+
nodeRoutingId: string
|
|
393
|
+
): import('./durable-service-request').PeerRouteObservation {
|
|
394
|
+
const selected = this.selectedRoutes.get(nodeRoutingId);
|
|
395
|
+
const admitted = this.topology.admittedPeer(nodeRoutingId)?.liveness?.connectionId;
|
|
396
|
+
return {
|
|
397
|
+
generation: selected ?? (typeof admitted === 'bigint' ? admitted : undefined),
|
|
398
|
+
ready: admitted !== undefined && (selected === undefined || admitted === selected)
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
get requestStopSignal(): AbortSignal {
|
|
403
|
+
return this.applicationJobStop.signal;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
observePeerRouteChanged(listener: (nodeRoutingId: string) => void): () => void {
|
|
407
|
+
this.peerRouteChanged.add(listener);
|
|
408
|
+
return () => {
|
|
409
|
+
this.peerRouteChanged.delete(listener);
|
|
410
|
+
};
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
hasSelectedPeerRoute(nodeRoutingId: string): boolean {
|
|
414
|
+
return this.selectedRoutes.has(nodeRoutingId);
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
private notifyPeerRouteChanged(nodeRoutingId: string): void {
|
|
418
|
+
for (const listener of this.peerRouteChanged) listener(nodeRoutingId);
|
|
419
|
+
}
|
|
420
|
+
|
|
383
421
|
private notifyPeerConnectionIntentRemoved(nodeRoutingId: string): void {
|
|
384
422
|
// Only the logical owner's removal publishes lifecycle termination.
|
|
385
423
|
// A physical disconnect alone retains the connection expectation.
|
|
@@ -389,33 +427,36 @@ export class RawServiceMeshRuntime {
|
|
|
389
427
|
}
|
|
390
428
|
|
|
391
429
|
announcePeer(nodeRoutingId: string): Promise<boolean> {
|
|
392
|
-
const expected = this.expectedPeers.get(nodeRoutingId);
|
|
393
430
|
const generation = this.selectedRoutes.get(nodeRoutingId);
|
|
394
|
-
if (
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
431
|
+
if (generation === undefined) return Promise.resolve(false);
|
|
432
|
+
const current = () => {
|
|
433
|
+
const admission = this.topology.routeAdmission(nodeRoutingId);
|
|
434
|
+
return (
|
|
435
|
+
this.selectedRoutes.get(nodeRoutingId) === generation &&
|
|
436
|
+
!(
|
|
437
|
+
admission?.connectionId === routeConnectionId(generation) &&
|
|
438
|
+
admission.remoteAdmitReceived === true
|
|
439
|
+
) &&
|
|
440
|
+
this.peerAdmissionSealed?.() !== true
|
|
441
|
+
);
|
|
442
|
+
};
|
|
443
|
+
const key = controlSendKey(nodeRoutingId, M6aServiceWireCommand.hello);
|
|
444
|
+
if (!current()) {
|
|
445
|
+
this.controlSends.delete(key);
|
|
400
446
|
return Promise.resolve(false);
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
447
|
+
}
|
|
448
|
+
const existing = this.controlSends.get(key);
|
|
449
|
+
const hello =
|
|
450
|
+
existing?.current?.() === true
|
|
451
|
+
? existing.hello!
|
|
452
|
+
: encodeRouteMeshAdmission(M6aServiceWireCommand.hello, this.topology.localDescriptor());
|
|
404
453
|
return Promise.resolve(
|
|
405
454
|
this.startControlSend(
|
|
406
455
|
nodeRoutingId,
|
|
407
456
|
M6aServiceWireCommand.hello,
|
|
408
|
-
() =>
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
[
|
|
412
|
-
encodeRouteMeshAdmission(M6aServiceWireCommand.hello, this.topology.localDescriptor())
|
|
413
|
-
],
|
|
414
|
-
() => {
|
|
415
|
-
if (current()) expected.helloSubmittedGeneration = generation;
|
|
416
|
-
}
|
|
417
|
-
),
|
|
418
|
-
current
|
|
457
|
+
() => this.submitControl(nodeRoutingId, [hello]),
|
|
458
|
+
current,
|
|
459
|
+
hello
|
|
419
460
|
)
|
|
420
461
|
);
|
|
421
462
|
}
|
|
@@ -423,7 +464,7 @@ export class RawServiceMeshRuntime {
|
|
|
423
464
|
isPeerRouteReady(nodeRoutingId: string, lifecycleGeneration?: bigint): boolean {
|
|
424
465
|
// Admission is bound to the observed selected route: the route observer
|
|
425
466
|
// removes the peer when that route ends or is replaced.
|
|
426
|
-
const peer = this.topology.
|
|
467
|
+
const peer = this.topology.admittedPeer(nodeRoutingId);
|
|
427
468
|
return (
|
|
428
469
|
peer !== undefined &&
|
|
429
470
|
(lifecycleGeneration === undefined ||
|
|
@@ -470,12 +511,18 @@ export class RawServiceMeshRuntime {
|
|
|
470
511
|
// placement and channel selectors therefore need the new mutable fields
|
|
471
512
|
// on that same connection before the next request is selected; waiting for
|
|
472
513
|
// a reconnect would leave a race where a drained node is selected again.
|
|
514
|
+
await this.publishLocalDescriptor(
|
|
515
|
+
this.topology.peers().map((peer) => peer.descriptor.nodeRoutingId)
|
|
516
|
+
);
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
private async publishLocalDescriptor(targets: readonly string[]): Promise<void> {
|
|
473
520
|
const update = encodeRouteMeshAdmission(
|
|
474
521
|
M6aServiceWireCommand.update,
|
|
475
522
|
this.topology.localDescriptor()
|
|
476
523
|
);
|
|
477
|
-
for (const
|
|
478
|
-
await this.
|
|
524
|
+
for (const target of targets) {
|
|
525
|
+
await this.sendService(target, [update]);
|
|
479
526
|
}
|
|
480
527
|
}
|
|
481
528
|
|
|
@@ -484,18 +531,14 @@ export class RawServiceMeshRuntime {
|
|
|
484
531
|
}
|
|
485
532
|
|
|
486
533
|
isObjectClientNodeDirectTarget(nodeRoutingId: string): boolean {
|
|
487
|
-
|
|
488
|
-
nodeRoutingId === this.descriptor.nodeRoutingId
|
|
489
|
-
? this.topology.localDescriptor()
|
|
490
|
-
: this.topology.knownDescriptor(nodeRoutingId);
|
|
491
|
-
return descriptor?.objectRole === 'client';
|
|
534
|
+
return this.topology.isObjectClientNodeDirectTarget(nodeRoutingId);
|
|
492
535
|
}
|
|
493
536
|
|
|
494
|
-
|
|
537
|
+
sendToNode(
|
|
495
538
|
targetNodeRoutingId: string,
|
|
496
539
|
payload: ServiceApplicationPayloadInput
|
|
497
|
-
): Promise<boolean> {
|
|
498
|
-
return this.send(targetNodeRoutingId, [
|
|
540
|
+
): boolean | Promise<boolean> {
|
|
541
|
+
return this.send(targetNodeRoutingId, [NODE_SEND_HEADER, this.applicationFrame(payload)]);
|
|
499
542
|
}
|
|
500
543
|
|
|
501
544
|
async sendToChannel(
|
|
@@ -517,15 +560,17 @@ export class RawServiceMeshRuntime {
|
|
|
517
560
|
requestToNode(
|
|
518
561
|
targetNodeRoutingId: string,
|
|
519
562
|
payload: ServiceApplicationPayloadInput,
|
|
520
|
-
timeoutMs: number
|
|
563
|
+
timeoutMs: number,
|
|
564
|
+
onAdmitted?: () => void
|
|
521
565
|
): PendingOperation<RawServiceRequestResult> {
|
|
522
|
-
return this.requestToTarget(targetNodeRoutingId, payload, timeoutMs);
|
|
566
|
+
return this.requestToTarget(targetNodeRoutingId, payload, timeoutMs, undefined, onAdmitted);
|
|
523
567
|
}
|
|
524
568
|
|
|
525
569
|
requestToChannel(
|
|
526
570
|
channelName: string,
|
|
527
571
|
payload: ServiceApplicationPayloadInput,
|
|
528
|
-
timeoutMs: number
|
|
572
|
+
timeoutMs: number,
|
|
573
|
+
onAdmitted?: () => void
|
|
529
574
|
): PendingOperation<RawServiceRequestResult> {
|
|
530
575
|
const selection = this.selectChannelTarget(channelName);
|
|
531
576
|
if (selection.kind !== 'selected') {
|
|
@@ -537,7 +582,8 @@ export class RawServiceMeshRuntime {
|
|
|
537
582
|
selection.peer.descriptor.nodeRoutingId,
|
|
538
583
|
payload,
|
|
539
584
|
timeoutMs,
|
|
540
|
-
channelName
|
|
585
|
+
channelName,
|
|
586
|
+
onAdmitted
|
|
541
587
|
);
|
|
542
588
|
}
|
|
543
589
|
|
|
@@ -562,11 +608,15 @@ export class RawServiceMeshRuntime {
|
|
|
562
608
|
};
|
|
563
609
|
}
|
|
564
610
|
|
|
565
|
-
setServiceIngress(
|
|
611
|
+
setServiceIngress(
|
|
612
|
+
handler: RawServiceIngressHandler,
|
|
613
|
+
accepts?: (command: number) => boolean
|
|
614
|
+
): void {
|
|
566
615
|
if (this.serviceIngress !== undefined && this.serviceIngress !== handler) {
|
|
567
616
|
throw new Error('Raw service ingress is already registered.');
|
|
568
617
|
}
|
|
569
618
|
this.serviceIngress = handler;
|
|
619
|
+
this.serviceIngressAccepts = accepts;
|
|
570
620
|
}
|
|
571
621
|
|
|
572
622
|
async reserveLocalIngress(signal?: AbortSignal): Promise<ApplicationIngressRecordOwner> {
|
|
@@ -579,20 +629,27 @@ export class RawServiceMeshRuntime {
|
|
|
579
629
|
}
|
|
580
630
|
|
|
581
631
|
async sendService(targetNodeRoutingId: string, parts: readonly Uint8Array[]): Promise<boolean> {
|
|
582
|
-
|
|
632
|
+
try {
|
|
633
|
+
await this.requireStarted().send(targetNodeRoutingId, parts);
|
|
634
|
+
return true;
|
|
635
|
+
} catch (error) {
|
|
636
|
+
return this.sendFailure(error);
|
|
637
|
+
}
|
|
583
638
|
}
|
|
584
639
|
|
|
585
640
|
requestService(
|
|
586
641
|
targetNodeRoutingId: string,
|
|
587
642
|
parts: readonly Uint8Array[],
|
|
588
|
-
timeoutMs: number
|
|
643
|
+
timeoutMs: number,
|
|
644
|
+
onAdmitted?: () => void
|
|
589
645
|
): Promise<readonly Buffer[]> {
|
|
590
|
-
const admission = this.topology.
|
|
646
|
+
const admission = this.topology.admittedPeer(targetNodeRoutingId)?.liveness;
|
|
591
647
|
return this.requireStarted().request(
|
|
592
648
|
targetNodeRoutingId,
|
|
593
649
|
parts,
|
|
594
650
|
timeoutMs,
|
|
595
|
-
admission?.recordReceived
|
|
651
|
+
admission?.recordReceived,
|
|
652
|
+
onAdmitted
|
|
596
653
|
);
|
|
597
654
|
}
|
|
598
655
|
|
|
@@ -668,59 +725,71 @@ export class RawServiceMeshRuntime {
|
|
|
668
725
|
const observe: RawServicePumpObserver = (_source, byteCount) => {
|
|
669
726
|
bytes += byteCount;
|
|
670
727
|
};
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
728
|
+
const rotation = (this.receiveRotation ??= this.applicationJobQueue.createReceiveRotation(
|
|
729
|
+
RAW_MESH_RECEIVE_RECORD_BUDGET,
|
|
730
|
+
'remote'
|
|
731
|
+
));
|
|
732
|
+
if (receiveReady) rotation.reserve();
|
|
733
|
+
try {
|
|
734
|
+
while (receiveReady && messages < RAW_MESH_RECEIVE_RECORD_BUDGET && !this.closed) {
|
|
735
|
+
const received = this.receiveOne(performance.now(), observe, rotation);
|
|
736
|
+
const result = received instanceof Promise ? await received : received;
|
|
737
|
+
if (result === 'noData') {
|
|
738
|
+
receiveReady = false;
|
|
739
|
+
break;
|
|
740
|
+
}
|
|
741
|
+
messages += 1;
|
|
742
|
+
// Core owns the per-peer fair-queue cursor. The Framework limit bounds
|
|
743
|
+
// the entire round, so changing peers does not renew its byte/count budget.
|
|
744
|
+
if (
|
|
745
|
+
bytes >= RAW_MESH_RECEIVE_BYTE_BUDGET ||
|
|
746
|
+
performance.now() - startedAtMs >= RAW_MESH_RECEIVE_TIME_BUDGET_MS
|
|
747
|
+
)
|
|
748
|
+
break;
|
|
676
749
|
}
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
// the entire round, so changing peers does not renew its byte/count budget.
|
|
680
|
-
if (
|
|
681
|
-
bytes >= RAW_MESH_RECEIVE_BYTE_BUDGET ||
|
|
682
|
-
performance.now() - startedAtMs >= RAW_MESH_RECEIVE_TIME_BUDGET_MS
|
|
683
|
-
)
|
|
684
|
-
break;
|
|
750
|
+
} finally {
|
|
751
|
+
rotation.releaseUnused();
|
|
685
752
|
}
|
|
686
753
|
if (!this.closed) {
|
|
687
|
-
for (const nodeRoutingId of this.expectedPeers.keys()) {
|
|
688
|
-
if (this.topology.peer(nodeRoutingId) === undefined) await this.announcePeer(nodeRoutingId);
|
|
689
|
-
}
|
|
690
754
|
this.tickLiveness();
|
|
691
755
|
}
|
|
692
756
|
return receiveReady && messages > 0;
|
|
693
757
|
}
|
|
694
758
|
|
|
695
|
-
private
|
|
759
|
+
private receiveOne(
|
|
696
760
|
nowMs: number,
|
|
697
|
-
observe?: RawServicePumpObserver
|
|
698
|
-
|
|
761
|
+
observe?: RawServicePumpObserver,
|
|
762
|
+
rotation?: ApplicationJobReceiveRotationPort
|
|
763
|
+
): RawServicePumpResult | Promise<RawServicePumpResult> {
|
|
699
764
|
const router = this.requireStarted();
|
|
700
765
|
this.observeInfrastructureFailure();
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
const permit = this.applicationJobQueue.acquireOrResume(
|
|
766
|
+
let permit = this.receivePermit;
|
|
767
|
+
this.receivePermit = undefined;
|
|
768
|
+
permit ??= rotation?.take();
|
|
769
|
+
if (permit === undefined && this.receiveAcquisition === undefined) {
|
|
770
|
+
let resolveAcquisition: ((permit: ApplicationJobPermitPort) => void) | undefined;
|
|
771
|
+
let rejectAcquisition: ((error: unknown) => void) | undefined;
|
|
772
|
+
permit = this.applicationJobQueue.acquireOrResume(
|
|
709
773
|
(permit) => {
|
|
710
774
|
this.receiveAcquisition = undefined;
|
|
711
|
-
if (this.closed)
|
|
712
|
-
|
|
775
|
+
if (this.closed) {
|
|
776
|
+
permit.releaseAfterInternalProcessing();
|
|
777
|
+
permit.releaseStorage?.();
|
|
778
|
+
} else {
|
|
713
779
|
this.receivePermit = permit;
|
|
714
780
|
this.onReceiveReady?.();
|
|
715
781
|
}
|
|
716
|
-
resolveAcquisition(permit);
|
|
782
|
+
resolveAcquisition?.(permit);
|
|
717
783
|
},
|
|
718
|
-
rejectAcquisition,
|
|
784
|
+
(error) => rejectAcquisition?.(error),
|
|
719
785
|
this.applicationJobStop.signal,
|
|
720
786
|
'remote'
|
|
721
787
|
);
|
|
722
|
-
if (permit
|
|
723
|
-
|
|
788
|
+
if (permit === undefined) {
|
|
789
|
+
const acquisition = new Promise<ApplicationJobPermitPort>((resolve, reject) => {
|
|
790
|
+
resolveAcquisition = resolve;
|
|
791
|
+
rejectAcquisition = reject;
|
|
792
|
+
});
|
|
724
793
|
this.receiveAcquisition = acquisition;
|
|
725
794
|
acquisition.catch((error) => {
|
|
726
795
|
this.receiveAcquisition = undefined;
|
|
@@ -728,11 +797,10 @@ export class RawServiceMeshRuntime {
|
|
|
728
797
|
});
|
|
729
798
|
}
|
|
730
799
|
}
|
|
731
|
-
const permit = this.receivePermit;
|
|
732
|
-
this.receivePermit = undefined;
|
|
733
800
|
if (permit === undefined) return 'noData';
|
|
734
801
|
if (this.closed) {
|
|
735
802
|
permit.releaseAfterInternalProcessing();
|
|
803
|
+
permit.releaseStorage?.();
|
|
736
804
|
return 'noData';
|
|
737
805
|
}
|
|
738
806
|
let received: ZLinkRawReceivedRecord | undefined;
|
|
@@ -740,10 +808,12 @@ export class RawServiceMeshRuntime {
|
|
|
740
808
|
received = router.receive(true);
|
|
741
809
|
} catch (error) {
|
|
742
810
|
permit.releaseAfterInternalProcessing();
|
|
811
|
+
permit.releaseStorage?.();
|
|
743
812
|
throw error;
|
|
744
813
|
}
|
|
745
814
|
if (received === undefined) {
|
|
746
815
|
permit.releaseAfterInternalProcessing();
|
|
816
|
+
permit.releaseStorage?.();
|
|
747
817
|
return 'noData';
|
|
748
818
|
}
|
|
749
819
|
const applicationJobOwner = ApplicationIngressRecordOwner.create(
|
|
@@ -751,18 +821,40 @@ export class RawServiceMeshRuntime {
|
|
|
751
821
|
permit,
|
|
752
822
|
received
|
|
753
823
|
);
|
|
824
|
+
let result: RawServicePumpResult | Promise<RawServicePumpResult>;
|
|
825
|
+
try {
|
|
826
|
+
result = this.processReceived(received, nowMs, applicationJobOwner);
|
|
827
|
+
} catch (error) {
|
|
828
|
+
applicationJobOwner.close();
|
|
829
|
+
throw error;
|
|
830
|
+
}
|
|
831
|
+
return result instanceof Promise
|
|
832
|
+
? result.then(
|
|
833
|
+
(value) => this.finishReceive(received, applicationJobOwner, observe, value),
|
|
834
|
+
(error) => {
|
|
835
|
+
applicationJobOwner.close();
|
|
836
|
+
throw error;
|
|
837
|
+
}
|
|
838
|
+
)
|
|
839
|
+
: this.finishReceive(received, applicationJobOwner, observe, result);
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
private finishReceive(
|
|
843
|
+
received: ZLinkRawReceivedRecord,
|
|
844
|
+
owner: ApplicationIngressRecordOwner,
|
|
845
|
+
observe: RawServicePumpObserver | undefined,
|
|
846
|
+
result: RawServicePumpResult
|
|
847
|
+
): RawServicePumpResult {
|
|
754
848
|
try {
|
|
755
|
-
|
|
756
|
-
if (
|
|
757
|
-
|
|
849
|
+
if (result === 'protocolError') this.reportProtocolError(received);
|
|
850
|
+
if (observe !== undefined) {
|
|
851
|
+
let bytes = 0;
|
|
852
|
+
for (const part of received.parts) bytes += part.byteLength;
|
|
853
|
+
observe(received.sourceRid, bytes);
|
|
758
854
|
}
|
|
759
|
-
observe?.(
|
|
760
|
-
received.sourceRid,
|
|
761
|
-
received.parts.reduce((sum, part) => sum + part.byteLength, 0)
|
|
762
|
-
);
|
|
763
855
|
return result;
|
|
764
856
|
} finally {
|
|
765
|
-
|
|
857
|
+
owner.close();
|
|
766
858
|
}
|
|
767
859
|
}
|
|
768
860
|
|
|
@@ -808,22 +900,15 @@ export class RawServiceMeshRuntime {
|
|
|
808
900
|
}
|
|
809
901
|
}
|
|
810
902
|
|
|
811
|
-
private
|
|
903
|
+
private processReceived(
|
|
812
904
|
received: ZLinkRawReceivedRecord,
|
|
813
905
|
nowMs: number,
|
|
814
906
|
applicationJobOwner: ApplicationIngressRecordOwner
|
|
815
|
-
): Promise<RawServicePumpResult> {
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
)
|
|
820
|
-
if (this.peerAdmissionSealed?.() === true) return 'dropped';
|
|
821
|
-
return (await this.sendControl(received.sourceRid, [
|
|
822
|
-
encodeRouteMeshAdmission(M6aServiceWireCommand.hello, this.topology.localDescriptor())
|
|
823
|
-
]))
|
|
824
|
-
? 'infrastructure'
|
|
825
|
-
: 'dropped';
|
|
826
|
-
}
|
|
907
|
+
): RawServicePumpResult | Promise<RawServicePumpResult> {
|
|
908
|
+
const peer = this.topology.admittedPeer(received.sourceRid);
|
|
909
|
+
const currentSource =
|
|
910
|
+
peer?.liveness !== undefined && peer.liveness.connectionId === received.routeGeneration;
|
|
911
|
+
if (received.parts.length === 0) return currentSource ? 'protocolError' : 'dropped';
|
|
827
912
|
try {
|
|
828
913
|
const header = decodeHeader(received.parts[0]!);
|
|
829
914
|
if (header.command === M6aServiceWireCommand.hello && this.peerAdmissionSealed?.() === true) {
|
|
@@ -831,107 +916,186 @@ export class RawServiceMeshRuntime {
|
|
|
831
916
|
}
|
|
832
917
|
if (
|
|
833
918
|
header.command === M6aServiceWireCommand.hello ||
|
|
834
|
-
header.command === M6aServiceWireCommand.admit
|
|
835
|
-
header.command === M6aServiceWireCommand.update
|
|
919
|
+
header.command === M6aServiceWireCommand.admit
|
|
836
920
|
) {
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
received.parts[0]!,
|
|
840
|
-
header.command,
|
|
841
|
-
received.sourceRid
|
|
921
|
+
return this.processAdmission(received, header, nowMs).catch((error) =>
|
|
922
|
+
this.receiveProtocolFailure(error)
|
|
842
923
|
);
|
|
843
|
-
const expected = this.expectedPeers.get(received.sourceRid);
|
|
844
|
-
if (
|
|
845
|
-
expected !== undefined &&
|
|
846
|
-
(expected.meshName !== descriptor.meshName ||
|
|
847
|
-
expected.nodeRoutingId !== descriptor.nodeRoutingId)
|
|
848
|
-
) {
|
|
849
|
-
await this.sendControl(received.sourceRid, [
|
|
850
|
-
encodeReject(enumWireRejectReason('identityMismatch'))
|
|
851
|
-
]);
|
|
852
|
-
return 'infrastructure';
|
|
853
|
-
}
|
|
854
|
-
const result = this.admitPeer(descriptor, received.routeGeneration, nowMs, expected);
|
|
855
|
-
if (result !== 'admitted' && result !== 'notRequired') {
|
|
856
|
-
const detail =
|
|
857
|
-
result === 'invalidDescriptor'
|
|
858
|
-
? describeInvalidAdmissionDescriptor(
|
|
859
|
-
descriptor,
|
|
860
|
-
expected,
|
|
861
|
-
this.topology.peer(descriptor.nodeRoutingId)?.descriptor
|
|
862
|
-
)
|
|
863
|
-
: undefined;
|
|
864
|
-
console.warn(
|
|
865
|
-
`RouteMesh admission rejected peer '${received.sourceRid}' as ${result}` +
|
|
866
|
-
(detail === undefined ? '.' : ` (${detail}).`)
|
|
867
|
-
);
|
|
868
|
-
await this.sendControl(received.sourceRid, [encodeReject(admissionReason(result))]);
|
|
869
|
-
return 'infrastructure';
|
|
870
|
-
}
|
|
871
|
-
if (result === 'admitted') this.selectBilateralConnection(descriptor);
|
|
872
|
-
if (header.command === M6aServiceWireCommand.hello) {
|
|
873
|
-
// Hello supplies only the sender's descriptor. Even NotRequired
|
|
874
|
-
// peers must receive ours before the intent owner closes transport;
|
|
875
|
-
// submitting a response does not guarantee delivery before close.
|
|
876
|
-
await this.sendControl(received.sourceRid, [
|
|
877
|
-
encodeRouteMeshAdmission(M6aServiceWireCommand.admit, this.topology.localDescriptor())
|
|
878
|
-
]);
|
|
879
|
-
} else if (result === 'notRequired') {
|
|
880
|
-
this.retireNotRequiredExpectedPeer(received.sourceRid, descriptor.advertisedEndpoint);
|
|
881
|
-
}
|
|
882
|
-
return 'infrastructure';
|
|
883
924
|
}
|
|
884
925
|
if (header.command === M6aServiceWireCommand.reject) {
|
|
885
926
|
if (received.parts.length !== 1) return 'protocolError';
|
|
886
927
|
decodeReject(received.parts[0]!);
|
|
887
928
|
return 'infrastructure';
|
|
888
929
|
}
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
if (
|
|
892
|
-
this.
|
|
930
|
+
if (!currentSource) return 'dropped';
|
|
931
|
+
this.liveness.recordReceived(peer.liveness, nowMs);
|
|
932
|
+
if (header.command === M6aServiceWireCommand.update) {
|
|
933
|
+
return this.processAdmission(received, header, nowMs).catch((error) =>
|
|
934
|
+
this.receiveProtocolFailure(error)
|
|
935
|
+
);
|
|
936
|
+
}
|
|
893
937
|
if (
|
|
894
938
|
header.command === M6aServiceWireCommand.livenessProbe ||
|
|
895
939
|
header.command === M6aServiceWireCommand.livenessAck
|
|
896
940
|
) {
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
const ack = this.liveness.acknowledgeProbe(
|
|
901
|
-
received.sourceRid,
|
|
902
|
-
received.routeGeneration,
|
|
903
|
-
record.probeId
|
|
904
|
-
);
|
|
905
|
-
if (ack === undefined) return 'protocolError';
|
|
906
|
-
const sent = await this.sendControl(received.sourceRid, [
|
|
907
|
-
livenessCodec.encodeLivenessRecord({
|
|
908
|
-
command: M6aServiceWireCommand.livenessAck,
|
|
909
|
-
probeId: record.probeId
|
|
910
|
-
})
|
|
911
|
-
]);
|
|
912
|
-
if (!sent) return 'dropped';
|
|
913
|
-
} else {
|
|
914
|
-
this.liveness.acknowledge(
|
|
915
|
-
received.sourceRid,
|
|
916
|
-
received.routeGeneration,
|
|
917
|
-
record.probeId,
|
|
918
|
-
nowMs
|
|
919
|
-
);
|
|
920
|
-
}
|
|
921
|
-
return 'infrastructure';
|
|
941
|
+
return this.processLiveness(received, nowMs).catch((error) =>
|
|
942
|
+
this.receiveProtocolFailure(error)
|
|
943
|
+
);
|
|
922
944
|
}
|
|
923
|
-
const stateful =
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
945
|
+
const stateful =
|
|
946
|
+
this.serviceIngress !== undefined && this.serviceIngressAccepts?.(header.command) !== false
|
|
947
|
+
? this.serviceIngress({
|
|
948
|
+
command: header.command,
|
|
949
|
+
flags: header.flags,
|
|
950
|
+
sourceRoutingId: received.sourceRid,
|
|
951
|
+
sourceNodeGeneration: peer.descriptor.lifecycleGeneration,
|
|
952
|
+
sourceRoute: received.sourceRoute,
|
|
953
|
+
...(received.reply === undefined ? {} : { reply: received.reply }),
|
|
954
|
+
...(received.requestSeq === undefined
|
|
955
|
+
? {}
|
|
956
|
+
: { requestSequence: received.requestSeq }),
|
|
957
|
+
parts: received.parts,
|
|
958
|
+
applicationJobOwner
|
|
959
|
+
})
|
|
960
|
+
: undefined;
|
|
961
|
+
if (stateful instanceof Promise)
|
|
962
|
+
return stateful.then(
|
|
963
|
+
(result) =>
|
|
964
|
+
result ?? this.processApplicationReceived(received, header, applicationJobOwner),
|
|
965
|
+
(error) => this.receiveProtocolFailure(error)
|
|
966
|
+
);
|
|
934
967
|
if (stateful !== undefined) return stateful;
|
|
968
|
+
return this.processApplicationReceived(received, header, applicationJobOwner);
|
|
969
|
+
} catch (error) {
|
|
970
|
+
if (!currentSource && error instanceof ServiceWireProtocolError) return 'dropped';
|
|
971
|
+
return this.receiveProtocolFailure(error);
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
private receiveProtocolFailure(error: unknown): RawServicePumpResult {
|
|
976
|
+
if (error instanceof ServiceWireProtocolError) return 'protocolError';
|
|
977
|
+
throw error;
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
private async processAdmission(
|
|
981
|
+
received: ZLinkRawReceivedRecord,
|
|
982
|
+
header: ReturnType<typeof decodeHeader>,
|
|
983
|
+
nowMs: number
|
|
984
|
+
): Promise<RawServicePumpResult> {
|
|
985
|
+
if (received.parts.length !== 1) return 'protocolError';
|
|
986
|
+
const descriptor = decodeRouteMeshAdmission(
|
|
987
|
+
received.parts[0]!,
|
|
988
|
+
header.command,
|
|
989
|
+
received.sourceRid
|
|
990
|
+
);
|
|
991
|
+
const expected = this.expectedPeers.get(received.sourceRid);
|
|
992
|
+
if (
|
|
993
|
+
expected !== undefined &&
|
|
994
|
+
(expected.meshName !== descriptor.meshName ||
|
|
995
|
+
expected.nodeRoutingId !== descriptor.nodeRoutingId)
|
|
996
|
+
) {
|
|
997
|
+
await this.sendControl(received.sourceRid, [
|
|
998
|
+
encodeReject(enumWireRejectReason('identityMismatch'))
|
|
999
|
+
]);
|
|
1000
|
+
return 'infrastructure';
|
|
1001
|
+
}
|
|
1002
|
+
const helloKey = controlSendKey(received.sourceRid, M6aServiceWireCommand.hello);
|
|
1003
|
+
const exchange = this.controlSends.get(helloKey);
|
|
1004
|
+
const result = this.admitPeer(
|
|
1005
|
+
descriptor,
|
|
1006
|
+
received.routeGeneration,
|
|
1007
|
+
nowMs,
|
|
1008
|
+
expected,
|
|
1009
|
+
header.command,
|
|
1010
|
+
received.parts[0]!.subarray(SERVICE_WIRE_PREFIX_SIZE)
|
|
1011
|
+
);
|
|
1012
|
+
if (result !== 'admitted' && result !== 'notRequired') {
|
|
1013
|
+
const detail =
|
|
1014
|
+
result === 'invalidDescriptor'
|
|
1015
|
+
? describeInvalidAdmissionDescriptor(
|
|
1016
|
+
descriptor,
|
|
1017
|
+
expected,
|
|
1018
|
+
this.topology.peer(descriptor.nodeRoutingId)?.descriptor
|
|
1019
|
+
)
|
|
1020
|
+
: undefined;
|
|
1021
|
+
console.warn(
|
|
1022
|
+
`RouteMesh admission rejected peer '${received.sourceRid}' as ${result}` +
|
|
1023
|
+
(detail === undefined ? '.' : ` (${detail}).`)
|
|
1024
|
+
);
|
|
1025
|
+
await this.sendControl(received.sourceRid, [encodeReject(admissionReason(result))]);
|
|
1026
|
+
return 'infrastructure';
|
|
1027
|
+
}
|
|
1028
|
+
if (result === 'admitted') this.selectBilateralConnection(descriptor);
|
|
1029
|
+
if (header.command === M6aServiceWireCommand.hello) {
|
|
1030
|
+
// Hello supplies only the sender's descriptor. Even NotRequired
|
|
1031
|
+
// peers must receive ours before the intent owner closes transport;
|
|
1032
|
+
// submitting a response does not guarantee delivery before close.
|
|
1033
|
+
const local =
|
|
1034
|
+
exchange?.current?.() === true && exchange.hello !== undefined
|
|
1035
|
+
? decodeRouteMeshAdmission(
|
|
1036
|
+
exchange.hello,
|
|
1037
|
+
M6aServiceWireCommand.hello,
|
|
1038
|
+
this.descriptor.nodeRoutingId
|
|
1039
|
+
)
|
|
1040
|
+
: this.topology.localDescriptor();
|
|
1041
|
+
await this.sendControl(received.sourceRid, [
|
|
1042
|
+
encodeRouteMeshAdmission(M6aServiceWireCommand.admit, local)
|
|
1043
|
+
]);
|
|
1044
|
+
} else if (header.command === M6aServiceWireCommand.admit) {
|
|
1045
|
+
this.controlSends.delete(helloKey);
|
|
1046
|
+
if (result === 'notRequired') {
|
|
1047
|
+
this.retireNotRequiredExpectedPeer(received.sourceRid, descriptor.advertisedEndpoint);
|
|
1048
|
+
} else if (
|
|
1049
|
+
exchange?.hello !== undefined &&
|
|
1050
|
+
decodeRouteMeshAdmission(
|
|
1051
|
+
exchange.hello,
|
|
1052
|
+
M6aServiceWireCommand.hello,
|
|
1053
|
+
this.descriptor.nodeRoutingId
|
|
1054
|
+
).descriptorRevision < this.descriptor.descriptorRevision
|
|
1055
|
+
) {
|
|
1056
|
+
await this.publishLocalDescriptor([received.sourceRid]);
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1059
|
+
return 'infrastructure';
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
private async processLiveness(
|
|
1063
|
+
received: ZLinkRawReceivedRecord,
|
|
1064
|
+
nowMs: number
|
|
1065
|
+
): Promise<RawServicePumpResult> {
|
|
1066
|
+
if (received.parts.length !== 1) return 'protocolError';
|
|
1067
|
+
const record = livenessCodec.decodeLivenessRecord(received.parts[0]!);
|
|
1068
|
+
if (record.command === M6aServiceWireCommand.livenessProbe) {
|
|
1069
|
+
const ack = this.liveness.acknowledgeProbe(
|
|
1070
|
+
received.sourceRid,
|
|
1071
|
+
received.routeGeneration,
|
|
1072
|
+
record.probeId
|
|
1073
|
+
);
|
|
1074
|
+
if (ack === undefined) return 'protocolError';
|
|
1075
|
+
const sent = await this.sendControl(received.sourceRid, [
|
|
1076
|
+
livenessCodec.encodeLivenessRecord({
|
|
1077
|
+
command: M6aServiceWireCommand.livenessAck,
|
|
1078
|
+
probeId: record.probeId
|
|
1079
|
+
})
|
|
1080
|
+
]);
|
|
1081
|
+
if (!sent) return 'dropped';
|
|
1082
|
+
} else {
|
|
1083
|
+
this.liveness.acknowledge(
|
|
1084
|
+
received.sourceRid,
|
|
1085
|
+
received.routeGeneration,
|
|
1086
|
+
record.probeId,
|
|
1087
|
+
nowMs
|
|
1088
|
+
);
|
|
1089
|
+
}
|
|
1090
|
+
return 'infrastructure';
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
private processApplicationReceived(
|
|
1094
|
+
received: ZLinkRawReceivedRecord,
|
|
1095
|
+
header: ReturnType<typeof decodeHeader>,
|
|
1096
|
+
applicationJobOwner: ApplicationIngressRecordOwner
|
|
1097
|
+
): RawServicePumpResult | Promise<RawServicePumpResult> {
|
|
1098
|
+
try {
|
|
935
1099
|
if (
|
|
936
1100
|
header.flags === 0 &&
|
|
937
1101
|
// A bare infrastructure control is normally exactly one frame, but a
|
|
@@ -949,7 +1113,7 @@ export class RawServiceMeshRuntime {
|
|
|
949
1113
|
isBareInfrastructureControl(header.command)
|
|
950
1114
|
) {
|
|
951
1115
|
const accepted = this.mailbox.tryEnqueue({
|
|
952
|
-
owner:
|
|
1116
|
+
owner: this.nodeMailboxOwner,
|
|
953
1117
|
domain: 'infrastructure',
|
|
954
1118
|
parts: received.parts,
|
|
955
1119
|
sourceRoutingId: received.sourceRid,
|
|
@@ -974,50 +1138,60 @@ export class RawServiceMeshRuntime {
|
|
|
974
1138
|
let owner: string;
|
|
975
1139
|
let correlation: bigint | undefined;
|
|
976
1140
|
if (header.command === M6aServiceWireCommand.nodeSend) {
|
|
977
|
-
owner =
|
|
1141
|
+
owner = this.nodeMailboxOwner;
|
|
978
1142
|
} else if (header.command === M6aServiceWireCommand.nodeRequest) {
|
|
979
|
-
owner =
|
|
980
|
-
correlation = decodeNodeRequestHeader(received.parts[0]
|
|
1143
|
+
owner = this.nodeMailboxOwner;
|
|
1144
|
+
correlation = decodeNodeRequestHeader(received.parts[0]!, header);
|
|
981
1145
|
} else if (header.command === M6aServiceWireCommand.channelSend) {
|
|
982
|
-
owner = `channel:${decodeChannelSendHeader(received.parts[0]
|
|
1146
|
+
owner = `channel:${decodeChannelSendHeader(received.parts[0]!, header)}`;
|
|
983
1147
|
} else {
|
|
984
|
-
const channel = decodeChannelRequestHeader(received.parts[0]
|
|
1148
|
+
const channel = decodeChannelRequestHeader(received.parts[0]!, header);
|
|
985
1149
|
owner = `channel:${channel.channelName}`;
|
|
986
1150
|
correlation = channel.correlation;
|
|
987
1151
|
}
|
|
988
|
-
const
|
|
989
|
-
|
|
990
|
-
owner,
|
|
991
|
-
|
|
992
|
-
parts: received.parts,
|
|
993
|
-
sourceRoutingId: received.sourceRid,
|
|
994
|
-
sourceRoute: received.sourceRoute,
|
|
995
|
-
...(received.reply === undefined ? {} : { reply: received.reply }),
|
|
996
|
-
requestSequence: received.requestSeq,
|
|
997
|
-
...(correlation === undefined ? {} : { correlation }),
|
|
998
|
-
applicationJob
|
|
999
|
-
});
|
|
1000
|
-
if (accepted) return 'application';
|
|
1001
|
-
applicationJob.close();
|
|
1002
|
-
if (correlation !== undefined && received.requestSeq !== undefined) {
|
|
1003
|
-
this.replyService(
|
|
1004
|
-
{
|
|
1005
|
-
sourceRoutingId: received.sourceRid,
|
|
1006
|
-
sourceRoute: received.sourceRoute,
|
|
1007
|
-
requestSequence: received.requestSeq,
|
|
1008
|
-
...(received.reply === undefined ? {} : { reply: received.reply })
|
|
1009
|
-
},
|
|
1010
|
-
[encodeReplyHeader(correlation, RequestResult.NotConnected, 0)]
|
|
1011
|
-
);
|
|
1012
|
-
return 'infrastructure';
|
|
1013
|
-
}
|
|
1014
|
-
return 'dropped';
|
|
1152
|
+
const job = applicationJobOwner.acquire('application');
|
|
1153
|
+
return job instanceof Promise
|
|
1154
|
+
? job.then((permit) => this.enqueueOrdinaryRecord(received, owner, correlation, permit))
|
|
1155
|
+
: this.enqueueOrdinaryRecord(received, owner, correlation, job);
|
|
1015
1156
|
} catch (error) {
|
|
1016
|
-
|
|
1017
|
-
throw error;
|
|
1157
|
+
return this.receiveProtocolFailure(error);
|
|
1018
1158
|
}
|
|
1019
1159
|
}
|
|
1020
1160
|
|
|
1161
|
+
private enqueueOrdinaryRecord(
|
|
1162
|
+
received: ZLinkRawReceivedRecord,
|
|
1163
|
+
owner: string,
|
|
1164
|
+
correlation: bigint | undefined,
|
|
1165
|
+
applicationJob: import('../application-jobs/application-ingress-record-owner').ApplicationJobRecordLease
|
|
1166
|
+
): RawServicePumpResult {
|
|
1167
|
+
const accepted = this.mailbox.tryEnqueue({
|
|
1168
|
+
owner,
|
|
1169
|
+
domain: 'application',
|
|
1170
|
+
parts: received.parts,
|
|
1171
|
+
sourceRoutingId: received.sourceRid,
|
|
1172
|
+
sourceRoute: received.sourceRoute,
|
|
1173
|
+
...(received.reply === undefined ? {} : { reply: received.reply }),
|
|
1174
|
+
requestSequence: received.requestSeq,
|
|
1175
|
+
...(correlation === undefined ? {} : { correlation }),
|
|
1176
|
+
applicationJob
|
|
1177
|
+
});
|
|
1178
|
+
if (accepted) return 'application';
|
|
1179
|
+
applicationJob.close();
|
|
1180
|
+
if (correlation !== undefined && received.requestSeq !== undefined) {
|
|
1181
|
+
this.replyService(
|
|
1182
|
+
{
|
|
1183
|
+
sourceRoutingId: received.sourceRid,
|
|
1184
|
+
sourceRoute: received.sourceRoute,
|
|
1185
|
+
requestSequence: received.requestSeq,
|
|
1186
|
+
...(received.reply === undefined ? {} : { reply: received.reply })
|
|
1187
|
+
},
|
|
1188
|
+
[encodeReplyHeader(correlation, RequestResult.NotConnected, 0)]
|
|
1189
|
+
);
|
|
1190
|
+
return 'infrastructure';
|
|
1191
|
+
}
|
|
1192
|
+
return 'dropped';
|
|
1193
|
+
}
|
|
1194
|
+
|
|
1021
1195
|
expireOperations(nowMs: number, turnDeadlineMs: number): number {
|
|
1022
1196
|
return this.operations.expire(nowMs, turnDeadlineMs);
|
|
1023
1197
|
}
|
|
@@ -1029,6 +1203,7 @@ export class RawServiceMeshRuntime {
|
|
|
1029
1203
|
tickLiveness(nowMs = performance.now()): ServiceLivenessTick {
|
|
1030
1204
|
const result = this.liveness.tick(nowMs);
|
|
1031
1205
|
this.requireStarted();
|
|
1206
|
+
for (const nodeRoutingId of this.selectedRoutes.keys()) void this.announcePeer(nodeRoutingId);
|
|
1032
1207
|
for (const probe of result.probes) {
|
|
1033
1208
|
const parts = [
|
|
1034
1209
|
livenessCodec.encodeLivenessRecord({
|
|
@@ -1065,9 +1240,8 @@ export class RawServiceMeshRuntime {
|
|
|
1065
1240
|
let changes = 0;
|
|
1066
1241
|
for (const [nodeRoutingId, generation] of this.selectedRoutes) {
|
|
1067
1242
|
if (observed.get(nodeRoutingId) === generation) continue;
|
|
1243
|
+
this.controlSends.delete(controlSendKey(nodeRoutingId, M6aServiceWireCommand.hello));
|
|
1068
1244
|
changes++;
|
|
1069
|
-
const expected = this.expectedPeers.get(nodeRoutingId);
|
|
1070
|
-
if (expected !== undefined) delete expected.helloSubmittedGeneration;
|
|
1071
1245
|
const peer = this.topology.peer(nodeRoutingId);
|
|
1072
1246
|
if (peer?.liveness?.connectionId === generation) {
|
|
1073
1247
|
this.removePeer(peer);
|
|
@@ -1080,6 +1254,7 @@ export class RawServiceMeshRuntime {
|
|
|
1080
1254
|
selected.push(nodeRoutingId);
|
|
1081
1255
|
}
|
|
1082
1256
|
this.selectedRoutes = observed;
|
|
1257
|
+
for (const nodeRoutingId of selected) this.notifyPeerRouteChanged(nodeRoutingId);
|
|
1083
1258
|
// A new selected route admits only through a new handshake.
|
|
1084
1259
|
for (const nodeRoutingId of selected) await this.announcePeer(nodeRoutingId);
|
|
1085
1260
|
return changes;
|
|
@@ -1092,8 +1267,10 @@ export class RawServiceMeshRuntime {
|
|
|
1092
1267
|
new Error('Raw service runtime application job admission stopped.')
|
|
1093
1268
|
);
|
|
1094
1269
|
this.receivePermit?.releaseAfterInternalProcessing();
|
|
1270
|
+
this.receivePermit?.releaseStorage?.();
|
|
1095
1271
|
this.receivePermit = undefined;
|
|
1096
1272
|
for (const send of this.controlSends.values()) delete send.next;
|
|
1273
|
+
this.controlSends.clear();
|
|
1097
1274
|
this.mailbox.close();
|
|
1098
1275
|
this.operations.close('Raw service runtime closed.');
|
|
1099
1276
|
this.serviceIngress = undefined;
|
|
@@ -1111,7 +1288,8 @@ export class RawServiceMeshRuntime {
|
|
|
1111
1288
|
targetNodeRoutingId: string,
|
|
1112
1289
|
payload: ServiceApplicationPayloadInput,
|
|
1113
1290
|
timeoutMs: number,
|
|
1114
|
-
channelName?: string
|
|
1291
|
+
channelName?: string,
|
|
1292
|
+
onAdmitted?: () => void
|
|
1115
1293
|
): PendingOperation<RawServiceRequestResult> {
|
|
1116
1294
|
const correlation = this.nextCorrelation++;
|
|
1117
1295
|
const header =
|
|
@@ -1182,6 +1360,8 @@ export class RawServiceMeshRuntime {
|
|
|
1182
1360
|
terminalResult: RequestResult.NotConnected,
|
|
1183
1361
|
failureCode: 0
|
|
1184
1362
|
});
|
|
1363
|
+
} else {
|
|
1364
|
+
onAdmitted?.();
|
|
1185
1365
|
}
|
|
1186
1366
|
} finally {
|
|
1187
1367
|
applicationJobOwner.close();
|
|
@@ -1189,7 +1369,7 @@ export class RawServiceMeshRuntime {
|
|
|
1189
1369
|
})().catch((error) => this.operations.fail(pending.id, error));
|
|
1190
1370
|
return pending;
|
|
1191
1371
|
}
|
|
1192
|
-
const selectedPeer = this.topology.
|
|
1372
|
+
const selectedPeer = this.topology.admittedPeer(selectedTargetNodeRoutingId);
|
|
1193
1373
|
if (selectedPeer === undefined) {
|
|
1194
1374
|
// Logical target selection is the requester's decision: an RID that is
|
|
1195
1375
|
// not an admitted peer is not a target. Whether an admitted target's
|
|
@@ -1207,7 +1387,8 @@ export class RawServiceMeshRuntime {
|
|
|
1207
1387
|
selectedTargetNodeRoutingId,
|
|
1208
1388
|
parts,
|
|
1209
1389
|
timeoutMs,
|
|
1210
|
-
selectedPeer.liveness?.recordReceived
|
|
1390
|
+
selectedPeer.liveness?.recordReceived,
|
|
1391
|
+
onAdmitted
|
|
1211
1392
|
);
|
|
1212
1393
|
} catch (error) {
|
|
1213
1394
|
this.operations.fail(pending.id, error);
|
|
@@ -1274,33 +1455,37 @@ export class RawServiceMeshRuntime {
|
|
|
1274
1455
|
readonly endpoint?: string;
|
|
1275
1456
|
readonly securityIdentity?: string;
|
|
1276
1457
|
readonly lifecycleGeneration?: bigint;
|
|
1277
|
-
}
|
|
1458
|
+
},
|
|
1459
|
+
command: number = M6aServiceWireCommand.hello,
|
|
1460
|
+
descriptorBytes?: Uint8Array
|
|
1278
1461
|
): PeerAdmissionResult {
|
|
1279
1462
|
const previous = this.topology.peer(descriptor.nodeRoutingId);
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1463
|
+
const topologyConnectionId =
|
|
1464
|
+
typeof connectionId === 'bigint' ? routeConnectionId(connectionId) : connectionId;
|
|
1465
|
+
const result = this.topology.admit(
|
|
1466
|
+
descriptor,
|
|
1467
|
+
topologyConnectionId,
|
|
1468
|
+
expected,
|
|
1469
|
+
() => {
|
|
1470
|
+
const admission = this.liveness.admit(descriptor.nodeRoutingId, connectionId, nowMs);
|
|
1471
|
+
this.liveness.recordReceived(admission, nowMs);
|
|
1472
|
+
return admission;
|
|
1473
|
+
},
|
|
1474
|
+
command,
|
|
1475
|
+
descriptorBytes
|
|
1476
|
+
);
|
|
1477
|
+
if (result === 'staleDescriptor') {
|
|
1287
1478
|
throw new ServiceWireProtocolError(
|
|
1288
1479
|
`RouteMesh peer '${descriptor.nodeRoutingId}' descriptor revision is stale or conflicting.`
|
|
1289
1480
|
);
|
|
1290
1481
|
}
|
|
1291
|
-
const topologyConnectionId =
|
|
1292
|
-
typeof connectionId === 'bigint' ? `route:${connectionId}` : connectionId;
|
|
1293
|
-
const result = this.topology.admit(descriptor, topologyConnectionId, expected, () => {
|
|
1294
|
-
const admission = this.liveness.admit(descriptor.nodeRoutingId, connectionId, nowMs);
|
|
1295
|
-
this.liveness.recordReceived(admission, nowMs);
|
|
1296
|
-
return admission;
|
|
1297
|
-
});
|
|
1298
1482
|
if (result === 'notRequired' && previous !== undefined) {
|
|
1299
1483
|
this.liveness.disconnect(
|
|
1300
1484
|
descriptor.nodeRoutingId,
|
|
1301
1485
|
previous.liveness?.connectionId ?? previous.connectionId
|
|
1302
1486
|
);
|
|
1303
1487
|
}
|
|
1488
|
+
if (result === 'admitted') this.notifyPeerRouteChanged(descriptor.nodeRoutingId);
|
|
1304
1489
|
return result;
|
|
1305
1490
|
}
|
|
1306
1491
|
|
|
@@ -1361,26 +1546,16 @@ export class RawServiceMeshRuntime {
|
|
|
1361
1546
|
);
|
|
1362
1547
|
}
|
|
1363
1548
|
|
|
1364
|
-
private submitControl(
|
|
1365
|
-
target: string,
|
|
1366
|
-
parts: readonly Uint8Array[],
|
|
1367
|
-
accepted?: () => void
|
|
1368
|
-
): boolean | Promise<boolean> {
|
|
1549
|
+
private submitControl(target: string, parts: readonly Uint8Array[]): boolean | Promise<boolean> {
|
|
1369
1550
|
let submission;
|
|
1370
1551
|
try {
|
|
1371
1552
|
submission = this.requireStarted().submitSend(target, parts);
|
|
1372
1553
|
} catch (error) {
|
|
1373
1554
|
return this.sendFailure(error);
|
|
1374
1555
|
}
|
|
1375
|
-
if (submission.result === SubmitResult.Ok)
|
|
1376
|
-
accepted?.();
|
|
1377
|
-
return true;
|
|
1378
|
-
}
|
|
1556
|
+
if (submission.result === SubmitResult.Ok) return true;
|
|
1379
1557
|
return submission.admitted.then(
|
|
1380
|
-
() =>
|
|
1381
|
-
accepted?.();
|
|
1382
|
-
return true;
|
|
1383
|
-
},
|
|
1558
|
+
() => true,
|
|
1384
1559
|
(error) => this.sendFailure(error)
|
|
1385
1560
|
);
|
|
1386
1561
|
}
|
|
@@ -1389,12 +1564,14 @@ export class RawServiceMeshRuntime {
|
|
|
1389
1564
|
target: string,
|
|
1390
1565
|
command: number,
|
|
1391
1566
|
attempt: () => boolean | Promise<boolean>,
|
|
1392
|
-
current?: () => boolean
|
|
1567
|
+
current?: () => boolean,
|
|
1568
|
+
hello?: Uint8Array
|
|
1393
1569
|
): boolean {
|
|
1394
|
-
const key =
|
|
1570
|
+
const key = controlSendKey(target, command);
|
|
1395
1571
|
const existing = this.controlSends.get(key);
|
|
1396
1572
|
if (
|
|
1397
1573
|
existing !== undefined &&
|
|
1574
|
+
existing.task !== undefined &&
|
|
1398
1575
|
(command !== M6aServiceWireCommand.hello || existing.current?.() === true)
|
|
1399
1576
|
) {
|
|
1400
1577
|
if (command !== M6aServiceWireCommand.hello) {
|
|
@@ -1404,34 +1581,37 @@ export class RawServiceMeshRuntime {
|
|
|
1404
1581
|
return true;
|
|
1405
1582
|
}
|
|
1406
1583
|
const first = attempt();
|
|
1407
|
-
if (typeof first === 'boolean')
|
|
1584
|
+
if (typeof first === 'boolean') {
|
|
1585
|
+
if (hello !== undefined) this.controlSends.set(key, { current, hello });
|
|
1586
|
+
return first;
|
|
1587
|
+
}
|
|
1408
1588
|
const owner = this;
|
|
1409
|
-
const state: {
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
current,
|
|
1415
|
-
task: first.then(function drain(accepted): boolean | Promise<boolean> {
|
|
1416
|
-
if (!owner.closed && state.next !== undefined) {
|
|
1417
|
-
const next = state.next;
|
|
1418
|
-
delete state.next;
|
|
1419
|
-
return Promise.resolve(next()).then(drain);
|
|
1420
|
-
}
|
|
1421
|
-
if (owner.controlSends.get(key) === state) owner.controlSends.delete(key);
|
|
1422
|
-
return accepted;
|
|
1423
|
-
})
|
|
1589
|
+
const state: ControlSendState = { current, hello };
|
|
1590
|
+
const completeSend = () => {
|
|
1591
|
+
if (owner.controlSends.get(key) !== state) return;
|
|
1592
|
+
if (hello !== undefined && current?.() === true) delete state.task;
|
|
1593
|
+
else owner.controlSends.delete(key);
|
|
1424
1594
|
};
|
|
1595
|
+
const task = first.then(function drain(accepted): boolean | Promise<boolean> {
|
|
1596
|
+
if (!owner.closed && state.next !== undefined) {
|
|
1597
|
+
const next = state.next;
|
|
1598
|
+
delete state.next;
|
|
1599
|
+
return Promise.resolve(next()).then(drain);
|
|
1600
|
+
}
|
|
1601
|
+
completeSend();
|
|
1602
|
+
return accepted;
|
|
1603
|
+
});
|
|
1604
|
+
state.task = task;
|
|
1425
1605
|
this.controlSends.set(key, state);
|
|
1426
|
-
|
|
1606
|
+
task.then(
|
|
1427
1607
|
() => {},
|
|
1428
1608
|
(error) => {
|
|
1429
|
-
|
|
1609
|
+
completeSend();
|
|
1430
1610
|
if (this.infrastructureTaskRunner === undefined) this.reportInfrastructureFailure(error);
|
|
1431
1611
|
}
|
|
1432
1612
|
);
|
|
1433
1613
|
this.infrastructureTaskRunner?.runDetached('RouteMesh control SEND', async () => {
|
|
1434
|
-
await
|
|
1614
|
+
await task;
|
|
1435
1615
|
});
|
|
1436
1616
|
return true;
|
|
1437
1617
|
}
|
|
@@ -1455,17 +1635,20 @@ export class RawServiceMeshRuntime {
|
|
|
1455
1635
|
if (failure !== undefined) throw failure.error;
|
|
1456
1636
|
}
|
|
1457
1637
|
|
|
1458
|
-
private send(
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1638
|
+
private send(
|
|
1639
|
+
targetNodeRoutingId: string,
|
|
1640
|
+
parts: readonly Uint8Array[]
|
|
1641
|
+
): boolean | Promise<boolean> {
|
|
1642
|
+
try {
|
|
1643
|
+
const submission = this.requireStarted().submitSend(targetNodeRoutingId, parts);
|
|
1644
|
+
if (submission.result === SubmitResult.Ok) return true;
|
|
1645
|
+
return submission.admitted.then(
|
|
1646
|
+
() => true,
|
|
1647
|
+
(error) => this.sendFailure(error)
|
|
1468
1648
|
);
|
|
1649
|
+
} catch (error) {
|
|
1650
|
+
return this.sendFailure(error);
|
|
1651
|
+
}
|
|
1469
1652
|
}
|
|
1470
1653
|
|
|
1471
1654
|
private sendFailure(error: unknown): false {
|
|
@@ -1478,6 +1661,14 @@ export class RawServiceMeshRuntime {
|
|
|
1478
1661
|
}
|
|
1479
1662
|
}
|
|
1480
1663
|
|
|
1664
|
+
function controlSendKey(target: string, command: number): string {
|
|
1665
|
+
return JSON.stringify([target, command]);
|
|
1666
|
+
}
|
|
1667
|
+
|
|
1668
|
+
function routeConnectionId(generation: bigint): string {
|
|
1669
|
+
return `route:${generation}`;
|
|
1670
|
+
}
|
|
1671
|
+
|
|
1481
1672
|
/**
|
|
1482
1673
|
* Relocation and bound-Session controls are service-wire records themselves,
|
|
1483
1674
|
* not application NodeSend(16) envelopes. Keep this list closed: a bare
|