@zlink-systems/framework 0.29.1 → 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/Configuration/Builders.d.ts +1 -2
- package/dist/contracts/Configuration/Configs.d.ts +0 -5
- package/dist/contracts/Configuration/RegistrationBuilders.js +6 -4
- package/dist/contracts/Configuration/RegistrationTypes.d.ts +1 -6
- package/dist/contracts/Configuration/RegistrationValidators.js +4 -4
- package/dist/contracts/Configuration/index.d.ts +1 -1
- package/dist/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/internal.d.ts +1 -0
- package/dist/internal.js +3 -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 +3 -2
- package/dist/runtime/actors/actor-handoff.js +15 -7
- 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 +9 -9
- package/dist/runtime/application-jobs/application-ingress-record-owner.js +45 -23
- package/dist/runtime/application-jobs/application-job-queue-scope.d.ts +5 -2
- package/dist/runtime/application-jobs/application-job-queue-scope.js +41 -21
- package/dist/runtime/application-jobs/contracts.d.ts +15 -2
- package/dist/runtime/backend/contracts/index.d.ts +27 -27
- package/dist/runtime/backend/mesh-completion-table.d.ts +15 -5
- package/dist/runtime/backend/mesh-completion-table.js +160 -50
- package/dist/runtime/backend/mesh-dispatch-pump.d.ts +3 -0
- package/dist/runtime/backend/mesh-dispatch-pump.js +53 -36
- package/dist/runtime/backend/node/node-backend-adapter-factory.js +6 -1
- package/dist/runtime/backend/node/node-backend-adapter-support.d.ts +3 -3
- package/dist/runtime/backend/node/node-backend-adapter-support.js +33 -31
- 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-mesh-backend-adapter.d.ts +1 -9
- package/dist/runtime/backend/node/node-mesh-backend-adapter.js +1 -1
- package/dist/runtime/backend/node/node-raw-binding-port.js +50 -25
- package/dist/runtime/backend/node/node-raw-mesh-backend.d.ts +22 -16
- package/dist/runtime/backend/node/node-raw-mesh-backend.js +133 -149
- package/dist/runtime/backend/node/node-socket-backend-adapter.js +15 -10
- package/dist/runtime/backend/raw-binding-port.d.ts +10 -3
- 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-receive-loops.js +3 -3
- package/dist/runtime/channels/channel-runtime-lifecycle.js +2 -1
- 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-socket-registry.d.ts +10 -3
- package/dist/runtime/channels/channel-socket-registry.js +59 -82
- 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/diagnostics/message-flow.d.ts +1 -0
- package/dist/runtime/diagnostics/message-flow.js +3 -3
- 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 +37 -6
- package/dist/runtime/foundation/raw-service-mesh-runtime.js +473 -220
- package/dist/runtime/foundation/service-liveness-registry.d.ts +20 -8
- package/dist/runtime/foundation/service-liveness-registry.js +57 -44
- package/dist/runtime/foundation/service-mailbox.d.ts +3 -0
- package/dist/runtime/foundation/service-mailbox.js +2 -1
- package/dist/runtime/foundation/service-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 +10 -9
- package/dist/runtime/foundation/service-stateful-runtime.js +78 -126
- package/dist/runtime/foundation/service-stateful-wire-codec.d.ts +5 -7
- package/dist/runtime/foundation/service-stateful-wire-codec.js +43 -61
- package/dist/runtime/foundation/service-topology-registry.d.ts +16 -4
- package/dist/runtime/foundation/service-topology-registry.js +69 -30
- 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-packet-relay.js +7 -7
- package/dist/runtime/host/actor-runtime-options-factory.js +2 -2
- package/dist/runtime/host/application-job-queue.d.ts +28 -9
- package/dist/runtime/host/application-job-queue.js +214 -50
- package/dist/runtime/host/entry-actor-runtime.d.ts +1 -0
- package/dist/runtime/host/entry-actor-runtime.js +4 -1
- package/dist/runtime/host/index.d.ts +7 -1
- package/dist/runtime/host/index.js +154 -126
- package/dist/runtime/host/instance-activation-authority.d.ts +2 -1
- package/dist/runtime/host/instance-activation-authority.js +89 -25
- package/dist/runtime/host/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 +215 -243
- package/dist/runtime/host/spot-address-transport.d.ts +1 -0
- package/dist/runtime/host/spot-address-transport.js +28 -14
- package/dist/runtime/host/spot-runtime-options-factory.js +3 -2
- package/dist/runtime/host/user-spot-creation-coordinator.d.ts +1 -0
- package/dist/runtime/host/user-spot-creation-coordinator.js +1 -0
- package/dist/runtime/locations/actor-location-claims.d.ts +2 -2
- package/dist/runtime/locations/actor-location-claims.js +34 -19
- package/dist/runtime/locations/domain-store-contract.d.ts +2 -2
- package/dist/runtime/locations/in-memory-authority-store.d.ts +4 -2
- package/dist/runtime/locations/in-memory-authority-store.js +80 -8
- package/dist/runtime/locations/in-memory-location-store.d.ts +2 -2
- package/dist/runtime/locations/in-memory-location-store.js +19 -3
- package/dist/runtime/locations/internal-location-contracts.d.ts +3 -0
- package/dist/runtime/locations/lifecycle.d.ts +1 -2
- package/dist/runtime/locations/lifecycle.js +2 -5
- package/dist/runtime/locations/location-store-repository.d.ts +3 -2
- package/dist/runtime/locations/location-store-repository.js +165 -22
- package/dist/runtime/locations/resolvers.d.ts +2 -1
- package/dist/runtime/locations/resolvers.js +10 -1
- 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 +2 -1
- package/dist/runtime/messaging/submission-result.js +7 -4
- 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 +9 -2
- package/dist/runtime/spots/formal-remote-actor-admission-registry.js +33 -19
- package/dist/runtime/spots/index.d.ts +7 -3
- package/dist/runtime/spots/index.js +23 -16
- package/dist/runtime/spots/spot-activation.d.ts +3 -1
- package/dist/runtime/spots/spot-activation.js +21 -12
- package/dist/runtime/spots/spot-actor-admission-coordinator.d.ts +2 -1
- package/dist/runtime/spots/spot-actor-admission-coordinator.js +11 -10
- package/dist/runtime/spots/spot-actor-packet-dispatch.d.ts +4 -1
- package/dist/runtime/spots/spot-actor-packet-dispatch.js +7 -5
- package/dist/runtime/spots/spot-actor-packet-drain.js +2 -0
- package/dist/runtime/spots/spot-actor-packet-relay-dispatch.js +1 -0
- package/dist/runtime/spots/spot-entry-activation.js +9 -3
- 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-node-runtime-manager.d.ts +2 -1
- package/dist/runtime/spots/spot-node-runtime-manager.js +4 -11
- 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-routing-internal.d.ts +6 -0
- package/dist/runtime/spots/spot-runtime-ports.d.ts +3 -1
- 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 +14 -28
- package/package.json +3 -3
- 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/Configuration/Builders.ts +1 -2
- package/src/contracts/Configuration/Configs.ts +0 -6
- package/src/contracts/Configuration/RegistrationBuilders.ts +8 -12
- package/src/contracts/Configuration/RegistrationTypes.ts +1 -6
- package/src/contracts/Configuration/RegistrationValidators.ts +9 -10
- package/src/contracts/Configuration/index.ts +1 -6
- package/src/contracts/Handlers/Attributes.ts +14 -21
- package/src/contracts/Handlers/JsonContract.ts +8 -216
- package/src/contracts/Spots/Contracts.ts +2 -1
- package/src/internal.ts +1 -0
- package/src/nest-integration.ts +1 -0
- package/src/runtime/actors/actor-client.ts +40 -16
- package/src/runtime/actors/actor-handoff.ts +22 -8
- 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 +53 -27
- package/src/runtime/application-jobs/application-job-queue-scope.ts +56 -22
- package/src/runtime/application-jobs/contracts.ts +28 -2
- package/src/runtime/backend/contracts/index.ts +63 -32
- package/src/runtime/backend/mesh-completion-table.ts +175 -59
- package/src/runtime/backend/mesh-dispatch-pump.ts +59 -32
- package/src/runtime/backend/node/node-backend-adapter-factory.ts +12 -5
- package/src/runtime/backend/node/node-backend-adapter-support.ts +42 -31
- package/src/runtime/backend/node/node-dealer-channel-client-transport.ts +12 -2
- package/src/runtime/backend/node/node-mesh-backend-adapter.ts +3 -10
- package/src/runtime/backend/node/node-raw-binding-port.ts +73 -37
- package/src/runtime/backend/node/node-raw-mesh-backend.ts +209 -176
- package/src/runtime/backend/node/node-socket-backend-adapter.ts +32 -13
- package/src/runtime/backend/raw-binding-port.ts +18 -3
- 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-receive-loops.ts +3 -3
- package/src/runtime/channels/channel-runtime-lifecycle.ts +7 -1
- package/src/runtime/channels/channel-runtime-manager.ts +29 -12
- package/src/runtime/channels/channel-socket-registry.ts +84 -130
- 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/diagnostics/message-flow.ts +1 -1
- 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 +642 -253
- package/src/runtime/foundation/service-liveness-registry.ts +68 -59
- package/src/runtime/foundation/service-mailbox.ts +7 -1
- 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 +128 -160
- package/src/runtime/foundation/service-stateful-wire-codec.ts +55 -84
- package/src/runtime/foundation/service-topology-registry.ts +103 -42
- 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-packet-relay.ts +7 -0
- package/src/runtime/host/actor-runtime-options-factory.ts +14 -2
- package/src/runtime/host/application-job-queue.ts +249 -48
- package/src/runtime/host/entry-actor-runtime.ts +5 -0
- package/src/runtime/host/index.ts +224 -160
- package/src/runtime/host/instance-activation-authority.ts +132 -27
- 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 +317 -291
- package/src/runtime/host/spot-address-transport.ts +49 -24
- package/src/runtime/host/spot-runtime-options-factory.ts +4 -0
- package/src/runtime/host/user-spot-creation-coordinator.ts +8 -0
- package/src/runtime/locations/actor-location-claims.ts +52 -30
- package/src/runtime/locations/domain-store-contract.ts +6 -2
- package/src/runtime/locations/in-memory-authority-store.ts +113 -9
- package/src/runtime/locations/in-memory-location-store.ts +25 -3
- package/src/runtime/locations/internal-location-contracts.ts +4 -0
- package/src/runtime/locations/lifecycle.ts +8 -26
- package/src/runtime/locations/location-store-repository.ts +220 -15
- package/src/runtime/locations/resolvers.ts +25 -2
- 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 +7 -3
- 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 +45 -23
- package/src/runtime/spots/index.ts +30 -2
- package/src/runtime/spots/spot-activation.ts +33 -10
- package/src/runtime/spots/spot-actor-admission-coordinator.ts +13 -0
- package/src/runtime/spots/spot-actor-packet-dispatch.ts +10 -0
- package/src/runtime/spots/spot-actor-packet-drain.ts +2 -0
- package/src/runtime/spots/spot-actor-packet-relay-dispatch.ts +1 -0
- package/src/runtime/spots/spot-entry-activation.ts +12 -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-node-runtime-manager.ts +7 -12
- 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-routing-internal.ts +14 -0
- package/src/runtime/spots/spot-runtime-ports.ts +3 -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 +13 -30
|
@@ -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;
|
|
@@ -100,6 +102,8 @@ export type RawServiceIngressHandler = (
|
|
|
100
102
|
) => RawServicePumpResult | undefined | Promise<RawServicePumpResult | undefined>;
|
|
101
103
|
|
|
102
104
|
export interface RawServiceMeshRuntimeOptions {
|
|
105
|
+
readonly onReceiveReady?: () => void;
|
|
106
|
+
readonly infrastructureTaskRunner?: import('../spots/spot-actor-join-dispatch').ZLinkDetachedTaskRunner;
|
|
103
107
|
readonly onPendingOperationsChanged?: () => void;
|
|
104
108
|
readonly descriptor: ServiceNodeDescriptor;
|
|
105
109
|
readonly resolveAdvertisedEndpoint?: (boundEndpoint: string) => string;
|
|
@@ -127,6 +131,7 @@ export interface RawServiceMeshRuntimeOptions {
|
|
|
127
131
|
// Framework error code 13 (RequestTargetNotFound) is encoded as 14 on a
|
|
128
132
|
// RequestResult.NotFound reply. Boundary transport results keep failureCode 0.
|
|
129
133
|
const RAW_MESH_RECEIVE_RECORD_BUDGET = 64;
|
|
134
|
+
const NODE_SEND_HEADER = encodeNodeSendHeader();
|
|
130
135
|
const RAW_MESH_RECEIVE_BYTE_BUDGET = 4 * 1024 * 1024;
|
|
131
136
|
export const RAW_MESH_RECEIVE_TIME_BUDGET_MS = 2;
|
|
132
137
|
|
|
@@ -138,6 +143,14 @@ const livenessCodec = createServiceWireCodec({
|
|
|
138
143
|
commands: M6aServiceWireCommand
|
|
139
144
|
});
|
|
140
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
|
+
|
|
141
154
|
/**
|
|
142
155
|
* RouteMesh M6A runtime built only on the public raw binding package.
|
|
143
156
|
* It owns protocol, admission, mailbox, request and liveness state.
|
|
@@ -156,11 +169,11 @@ export class RawServiceMeshRuntime {
|
|
|
156
169
|
readonly endpoint?: string;
|
|
157
170
|
readonly securityIdentity?: string;
|
|
158
171
|
readonly lifecycleGeneration?: bigint;
|
|
159
|
-
helloSubmittedGeneration?: bigint;
|
|
160
172
|
}
|
|
161
173
|
>();
|
|
162
174
|
private readonly endpointOnlyPeers = new Set<string>();
|
|
163
175
|
private readonly peerConnectionIntentRemoved = new Set<(nodeRoutingId: string) => void>();
|
|
176
|
+
private readonly peerRouteChanged = new Set<(nodeRoutingId: string) => void>();
|
|
164
177
|
/**
|
|
165
178
|
* The last observed Core selected route of each RID (Core ROUTER §10.1):
|
|
166
179
|
* RID -> route generation. Only `observeSelectedRoutes` replaces it.
|
|
@@ -168,8 +181,16 @@ export class RawServiceMeshRuntime {
|
|
|
168
181
|
private selectedRoutes = new Map<string, bigint>();
|
|
169
182
|
private readonly bindingPort: ZLinkRawBindingPort;
|
|
170
183
|
private readonly applicationJobQueue: ApplicationJobQueuePort;
|
|
184
|
+
private readonly nodeMailboxOwner: string;
|
|
171
185
|
private readonly peerAdmissionSealed?: () => boolean;
|
|
172
186
|
private readonly applicationJobStop = new AbortController();
|
|
187
|
+
private receiveAcquisition?: Promise<ApplicationJobPermitPort>;
|
|
188
|
+
private receivePermit?: ApplicationJobPermitPort;
|
|
189
|
+
private receiveRotation?: ApplicationJobReceiveRotationPort;
|
|
190
|
+
private readonly controlSends = new Map<string, ControlSendState>();
|
|
191
|
+
private infrastructureFailure?: { readonly error: unknown };
|
|
192
|
+
private readonly onReceiveReady?: () => void;
|
|
193
|
+
private readonly infrastructureTaskRunner?: RawServiceMeshRuntimeOptions['infrastructureTaskRunner'];
|
|
173
194
|
private readonly onPeerNotRequired?: RawServiceMeshRuntimeOptions['onPeerNotRequired'];
|
|
174
195
|
private readonly onPeerDisconnected?: RawServiceMeshRuntimeOptions['onPeerDisconnected'];
|
|
175
196
|
private readonly onProtocolError?: RawServiceMeshRuntimeOptions['onProtocolError'];
|
|
@@ -180,11 +201,15 @@ export class RawServiceMeshRuntime {
|
|
|
180
201
|
private router?: ZLinkRawRouterPort;
|
|
181
202
|
private nextCorrelation = 1n;
|
|
182
203
|
private serviceIngress?: RawServiceIngressHandler;
|
|
204
|
+
private serviceIngressAccepts?: (command: number) => boolean;
|
|
183
205
|
private closed = false;
|
|
184
206
|
|
|
185
207
|
constructor(options: RawServiceMeshRuntimeOptions) {
|
|
208
|
+
this.onReceiveReady = options.onReceiveReady;
|
|
209
|
+
this.infrastructureTaskRunner = options.infrastructureTaskRunner;
|
|
186
210
|
this.operations = new OperationRegistry(undefined, options.onPendingOperationsChanged);
|
|
187
211
|
this.descriptor = options.descriptor;
|
|
212
|
+
this.nodeMailboxOwner = `node:${options.descriptor.nodeRoutingId}`;
|
|
188
213
|
this.topology = new ServiceTopologyRegistry(options.descriptor);
|
|
189
214
|
this.mailbox = new ServiceMailbox(options.onMailboxReady);
|
|
190
215
|
this.liveness = new ServiceLivenessRegistry(options.probeIntervalMs, options.peerTimeoutMs);
|
|
@@ -363,6 +388,36 @@ export class RawServiceMeshRuntime {
|
|
|
363
388
|
};
|
|
364
389
|
}
|
|
365
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
|
+
|
|
366
421
|
private notifyPeerConnectionIntentRemoved(nodeRoutingId: string): void {
|
|
367
422
|
// Only the logical owner's removal publishes lifecycle termination.
|
|
368
423
|
// A physical disconnect alone retains the connection expectation.
|
|
@@ -371,37 +426,49 @@ export class RawServiceMeshRuntime {
|
|
|
371
426
|
for (const listener of this.peerConnectionIntentRemoved) listener(nodeRoutingId);
|
|
372
427
|
}
|
|
373
428
|
|
|
374
|
-
|
|
375
|
-
const expected = this.expectedPeers.get(nodeRoutingId);
|
|
429
|
+
announcePeer(nodeRoutingId: string): Promise<boolean> {
|
|
376
430
|
const generation = this.selectedRoutes.get(nodeRoutingId);
|
|
377
|
-
if (
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
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);
|
|
446
|
+
return Promise.resolve(false);
|
|
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());
|
|
453
|
+
return Promise.resolve(
|
|
454
|
+
this.startControlSend(
|
|
455
|
+
nodeRoutingId,
|
|
456
|
+
M6aServiceWireCommand.hello,
|
|
457
|
+
() => this.submitControl(nodeRoutingId, [hello]),
|
|
458
|
+
current,
|
|
459
|
+
hello
|
|
460
|
+
)
|
|
461
|
+
);
|
|
394
462
|
}
|
|
395
463
|
|
|
396
464
|
isPeerRouteReady(nodeRoutingId: string, lifecycleGeneration?: bigint): boolean {
|
|
397
465
|
// Admission is bound to the observed selected route: the route observer
|
|
398
466
|
// removes the peer when that route ends or is replaced.
|
|
399
|
-
const peer = this.topology.
|
|
467
|
+
const peer = this.topology.admittedPeer(nodeRoutingId);
|
|
400
468
|
return (
|
|
401
469
|
peer !== undefined &&
|
|
402
470
|
(lifecycleGeneration === undefined ||
|
|
403
|
-
peer.descriptor.lifecycleGeneration === lifecycleGeneration)
|
|
404
|
-
this.liveness.isReady(nodeRoutingId, peer.connectionId)
|
|
471
|
+
peer.descriptor.lifecycleGeneration === lifecycleGeneration)
|
|
405
472
|
);
|
|
406
473
|
}
|
|
407
474
|
|
|
@@ -444,12 +511,18 @@ export class RawServiceMeshRuntime {
|
|
|
444
511
|
// placement and channel selectors therefore need the new mutable fields
|
|
445
512
|
// on that same connection before the next request is selected; waiting for
|
|
446
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> {
|
|
447
520
|
const update = encodeRouteMeshAdmission(
|
|
448
521
|
M6aServiceWireCommand.update,
|
|
449
522
|
this.topology.localDescriptor()
|
|
450
523
|
);
|
|
451
|
-
for (const
|
|
452
|
-
await this.
|
|
524
|
+
for (const target of targets) {
|
|
525
|
+
await this.sendService(target, [update]);
|
|
453
526
|
}
|
|
454
527
|
}
|
|
455
528
|
|
|
@@ -458,18 +531,14 @@ export class RawServiceMeshRuntime {
|
|
|
458
531
|
}
|
|
459
532
|
|
|
460
533
|
isObjectClientNodeDirectTarget(nodeRoutingId: string): boolean {
|
|
461
|
-
|
|
462
|
-
nodeRoutingId === this.descriptor.nodeRoutingId
|
|
463
|
-
? this.topology.localDescriptor()
|
|
464
|
-
: this.topology.knownDescriptor(nodeRoutingId);
|
|
465
|
-
return descriptor?.objectRole === 'client';
|
|
534
|
+
return this.topology.isObjectClientNodeDirectTarget(nodeRoutingId);
|
|
466
535
|
}
|
|
467
536
|
|
|
468
|
-
|
|
537
|
+
sendToNode(
|
|
469
538
|
targetNodeRoutingId: string,
|
|
470
539
|
payload: ServiceApplicationPayloadInput
|
|
471
|
-
): Promise<boolean> {
|
|
472
|
-
return this.send(targetNodeRoutingId, [
|
|
540
|
+
): boolean | Promise<boolean> {
|
|
541
|
+
return this.send(targetNodeRoutingId, [NODE_SEND_HEADER, this.applicationFrame(payload)]);
|
|
473
542
|
}
|
|
474
543
|
|
|
475
544
|
async sendToChannel(
|
|
@@ -491,15 +560,17 @@ export class RawServiceMeshRuntime {
|
|
|
491
560
|
requestToNode(
|
|
492
561
|
targetNodeRoutingId: string,
|
|
493
562
|
payload: ServiceApplicationPayloadInput,
|
|
494
|
-
timeoutMs: number
|
|
563
|
+
timeoutMs: number,
|
|
564
|
+
onAdmitted?: () => void
|
|
495
565
|
): PendingOperation<RawServiceRequestResult> {
|
|
496
|
-
return this.requestToTarget(targetNodeRoutingId, payload, timeoutMs);
|
|
566
|
+
return this.requestToTarget(targetNodeRoutingId, payload, timeoutMs, undefined, onAdmitted);
|
|
497
567
|
}
|
|
498
568
|
|
|
499
569
|
requestToChannel(
|
|
500
570
|
channelName: string,
|
|
501
571
|
payload: ServiceApplicationPayloadInput,
|
|
502
|
-
timeoutMs: number
|
|
572
|
+
timeoutMs: number,
|
|
573
|
+
onAdmitted?: () => void
|
|
503
574
|
): PendingOperation<RawServiceRequestResult> {
|
|
504
575
|
const selection = this.selectChannelTarget(channelName);
|
|
505
576
|
if (selection.kind !== 'selected') {
|
|
@@ -511,7 +582,8 @@ export class RawServiceMeshRuntime {
|
|
|
511
582
|
selection.peer.descriptor.nodeRoutingId,
|
|
512
583
|
payload,
|
|
513
584
|
timeoutMs,
|
|
514
|
-
channelName
|
|
585
|
+
channelName,
|
|
586
|
+
onAdmitted
|
|
515
587
|
);
|
|
516
588
|
}
|
|
517
589
|
|
|
@@ -536,11 +608,15 @@ export class RawServiceMeshRuntime {
|
|
|
536
608
|
};
|
|
537
609
|
}
|
|
538
610
|
|
|
539
|
-
setServiceIngress(
|
|
611
|
+
setServiceIngress(
|
|
612
|
+
handler: RawServiceIngressHandler,
|
|
613
|
+
accepts?: (command: number) => boolean
|
|
614
|
+
): void {
|
|
540
615
|
if (this.serviceIngress !== undefined && this.serviceIngress !== handler) {
|
|
541
616
|
throw new Error('Raw service ingress is already registered.');
|
|
542
617
|
}
|
|
543
618
|
this.serviceIngress = handler;
|
|
619
|
+
this.serviceIngressAccepts = accepts;
|
|
544
620
|
}
|
|
545
621
|
|
|
546
622
|
async reserveLocalIngress(signal?: AbortSignal): Promise<ApplicationIngressRecordOwner> {
|
|
@@ -548,20 +624,33 @@ export class RawServiceMeshRuntime {
|
|
|
548
624
|
signal === undefined
|
|
549
625
|
? this.applicationJobStop.signal
|
|
550
626
|
: AbortSignal.any([this.applicationJobStop.signal, signal]);
|
|
551
|
-
const permit = await this.applicationJobQueue.acquire(waitSignal);
|
|
627
|
+
const permit = await this.applicationJobQueue.acquire(waitSignal, 'local');
|
|
552
628
|
return ApplicationIngressRecordOwner.create(this.applicationJobQueue, permit, { close() {} });
|
|
553
629
|
}
|
|
554
630
|
|
|
555
631
|
async sendService(targetNodeRoutingId: string, parts: readonly Uint8Array[]): Promise<boolean> {
|
|
556
|
-
|
|
632
|
+
try {
|
|
633
|
+
await this.requireStarted().send(targetNodeRoutingId, parts);
|
|
634
|
+
return true;
|
|
635
|
+
} catch (error) {
|
|
636
|
+
return this.sendFailure(error);
|
|
637
|
+
}
|
|
557
638
|
}
|
|
558
639
|
|
|
559
640
|
requestService(
|
|
560
641
|
targetNodeRoutingId: string,
|
|
561
642
|
parts: readonly Uint8Array[],
|
|
562
|
-
timeoutMs: number
|
|
643
|
+
timeoutMs: number,
|
|
644
|
+
onAdmitted?: () => void
|
|
563
645
|
): Promise<readonly Buffer[]> {
|
|
564
|
-
|
|
646
|
+
const admission = this.topology.admittedPeer(targetNodeRoutingId)?.liveness;
|
|
647
|
+
return this.requireStarted().request(
|
|
648
|
+
targetNodeRoutingId,
|
|
649
|
+
parts,
|
|
650
|
+
timeoutMs,
|
|
651
|
+
admission?.recordReceived,
|
|
652
|
+
onAdmitted
|
|
653
|
+
);
|
|
565
654
|
}
|
|
566
655
|
|
|
567
656
|
replyService(
|
|
@@ -607,6 +696,18 @@ export class RawServiceMeshRuntime {
|
|
|
607
696
|
nowMs = performance.now(),
|
|
608
697
|
observe?: RawServicePumpObserver
|
|
609
698
|
): Promise<RawServicePumpResult> {
|
|
699
|
+
const result = await this.receiveOne(nowMs, observe);
|
|
700
|
+
const acquisition = this.receiveAcquisition;
|
|
701
|
+
if (acquisition === undefined) return result;
|
|
702
|
+
// A single receive operation may await its FIFO grant. Infrastructure
|
|
703
|
+
// rounds use receiveOne directly and end while that grant is pending.
|
|
704
|
+
try {
|
|
705
|
+
await acquisition;
|
|
706
|
+
} catch (error) {
|
|
707
|
+
if (this.closed) return 'noData';
|
|
708
|
+
throw error;
|
|
709
|
+
}
|
|
710
|
+
if (this.closed) return 'noData';
|
|
610
711
|
return this.receiveOne(nowMs, observe);
|
|
611
712
|
}
|
|
612
713
|
|
|
@@ -616,6 +717,7 @@ export class RawServiceMeshRuntime {
|
|
|
616
717
|
|
|
617
718
|
/** Returns whether a receive budget ended before no-data; idle ticks only maintain peers. */
|
|
618
719
|
async pumpBatch(receiveReady = true, routeReady = false): Promise<boolean> {
|
|
720
|
+
this.observeInfrastructureFailure();
|
|
619
721
|
if (routeReady) await this.observeSelectedRoutes();
|
|
620
722
|
const startedAtMs = performance.now();
|
|
621
723
|
let messages = 0;
|
|
@@ -623,42 +725,82 @@ export class RawServiceMeshRuntime {
|
|
|
623
725
|
const observe: RawServicePumpObserver = (_source, byteCount) => {
|
|
624
726
|
bytes += byteCount;
|
|
625
727
|
};
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
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;
|
|
631
749
|
}
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
// the entire round, so changing peers does not renew its byte/count budget.
|
|
635
|
-
if (
|
|
636
|
-
bytes >= RAW_MESH_RECEIVE_BYTE_BUDGET ||
|
|
637
|
-
performance.now() - startedAtMs >= RAW_MESH_RECEIVE_TIME_BUDGET_MS
|
|
638
|
-
)
|
|
639
|
-
break;
|
|
750
|
+
} finally {
|
|
751
|
+
rotation.releaseUnused();
|
|
640
752
|
}
|
|
641
753
|
if (!this.closed) {
|
|
642
|
-
|
|
643
|
-
await this.tickLiveness();
|
|
754
|
+
this.tickLiveness();
|
|
644
755
|
}
|
|
645
756
|
return receiveReady && messages > 0;
|
|
646
757
|
}
|
|
647
758
|
|
|
648
|
-
private
|
|
759
|
+
private receiveOne(
|
|
649
760
|
nowMs: number,
|
|
650
|
-
observe?: RawServicePumpObserver
|
|
651
|
-
|
|
761
|
+
observe?: RawServicePumpObserver,
|
|
762
|
+
rotation?: ApplicationJobReceiveRotationPort
|
|
763
|
+
): RawServicePumpResult | Promise<RawServicePumpResult> {
|
|
652
764
|
const router = this.requireStarted();
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
765
|
+
this.observeInfrastructureFailure();
|
|
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(
|
|
773
|
+
(permit) => {
|
|
774
|
+
this.receiveAcquisition = undefined;
|
|
775
|
+
if (this.closed) {
|
|
776
|
+
permit.releaseAfterInternalProcessing();
|
|
777
|
+
permit.releaseStorage?.();
|
|
778
|
+
} else {
|
|
779
|
+
this.receivePermit = permit;
|
|
780
|
+
this.onReceiveReady?.();
|
|
781
|
+
}
|
|
782
|
+
resolveAcquisition?.(permit);
|
|
783
|
+
},
|
|
784
|
+
(error) => rejectAcquisition?.(error),
|
|
785
|
+
this.applicationJobStop.signal,
|
|
786
|
+
'remote'
|
|
787
|
+
);
|
|
788
|
+
if (permit === undefined) {
|
|
789
|
+
const acquisition = new Promise<ApplicationJobPermitPort>((resolve, reject) => {
|
|
790
|
+
resolveAcquisition = resolve;
|
|
791
|
+
rejectAcquisition = reject;
|
|
792
|
+
});
|
|
793
|
+
this.receiveAcquisition = acquisition;
|
|
794
|
+
acquisition.catch((error) => {
|
|
795
|
+
this.receiveAcquisition = undefined;
|
|
796
|
+
if (!this.applicationJobStop.signal.aborted) this.reportInfrastructureFailure(error);
|
|
797
|
+
});
|
|
798
|
+
}
|
|
659
799
|
}
|
|
800
|
+
if (permit === undefined) return 'noData';
|
|
660
801
|
if (this.closed) {
|
|
661
802
|
permit.releaseAfterInternalProcessing();
|
|
803
|
+
permit.releaseStorage?.();
|
|
662
804
|
return 'noData';
|
|
663
805
|
}
|
|
664
806
|
let received: ZLinkRawReceivedRecord | undefined;
|
|
@@ -666,10 +808,12 @@ export class RawServiceMeshRuntime {
|
|
|
666
808
|
received = router.receive(true);
|
|
667
809
|
} catch (error) {
|
|
668
810
|
permit.releaseAfterInternalProcessing();
|
|
811
|
+
permit.releaseStorage?.();
|
|
669
812
|
throw error;
|
|
670
813
|
}
|
|
671
814
|
if (received === undefined) {
|
|
672
815
|
permit.releaseAfterInternalProcessing();
|
|
816
|
+
permit.releaseStorage?.();
|
|
673
817
|
return 'noData';
|
|
674
818
|
}
|
|
675
819
|
const applicationJobOwner = ApplicationIngressRecordOwner.create(
|
|
@@ -677,18 +821,40 @@ export class RawServiceMeshRuntime {
|
|
|
677
821
|
permit,
|
|
678
822
|
received
|
|
679
823
|
);
|
|
824
|
+
let result: RawServicePumpResult | Promise<RawServicePumpResult>;
|
|
680
825
|
try {
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
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 {
|
|
848
|
+
try {
|
|
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);
|
|
684
854
|
}
|
|
685
|
-
observe?.(
|
|
686
|
-
received.sourceRid,
|
|
687
|
-
received.parts.reduce((sum, part) => sum + part.byteLength, 0)
|
|
688
|
-
);
|
|
689
855
|
return result;
|
|
690
856
|
} finally {
|
|
691
|
-
|
|
857
|
+
owner.close();
|
|
692
858
|
}
|
|
693
859
|
}
|
|
694
860
|
|
|
@@ -734,22 +900,15 @@ export class RawServiceMeshRuntime {
|
|
|
734
900
|
}
|
|
735
901
|
}
|
|
736
902
|
|
|
737
|
-
private
|
|
903
|
+
private processReceived(
|
|
738
904
|
received: ZLinkRawReceivedRecord,
|
|
739
905
|
nowMs: number,
|
|
740
906
|
applicationJobOwner: ApplicationIngressRecordOwner
|
|
741
|
-
): Promise<RawServicePumpResult> {
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
)
|
|
746
|
-
if (this.peerAdmissionSealed?.() === true) return 'dropped';
|
|
747
|
-
return (await this.send(received.sourceRid, [
|
|
748
|
-
encodeRouteMeshAdmission(M6aServiceWireCommand.hello, this.topology.localDescriptor())
|
|
749
|
-
]))
|
|
750
|
-
? 'infrastructure'
|
|
751
|
-
: 'dropped';
|
|
752
|
-
}
|
|
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';
|
|
753
912
|
try {
|
|
754
913
|
const header = decodeHeader(received.parts[0]!);
|
|
755
914
|
if (header.command === M6aServiceWireCommand.hello && this.peerAdmissionSealed?.() === true) {
|
|
@@ -757,105 +916,186 @@ export class RawServiceMeshRuntime {
|
|
|
757
916
|
}
|
|
758
917
|
if (
|
|
759
918
|
header.command === M6aServiceWireCommand.hello ||
|
|
760
|
-
header.command === M6aServiceWireCommand.admit
|
|
761
|
-
header.command === M6aServiceWireCommand.update
|
|
919
|
+
header.command === M6aServiceWireCommand.admit
|
|
762
920
|
) {
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
received.parts[0]!,
|
|
766
|
-
header.command,
|
|
767
|
-
received.sourceRid
|
|
921
|
+
return this.processAdmission(received, header, nowMs).catch((error) =>
|
|
922
|
+
this.receiveProtocolFailure(error)
|
|
768
923
|
);
|
|
769
|
-
const expected = this.expectedPeers.get(received.sourceRid);
|
|
770
|
-
if (
|
|
771
|
-
expected !== undefined &&
|
|
772
|
-
(expected.meshName !== descriptor.meshName ||
|
|
773
|
-
expected.nodeRoutingId !== descriptor.nodeRoutingId)
|
|
774
|
-
) {
|
|
775
|
-
await this.send(received.sourceRid, [
|
|
776
|
-
encodeReject(enumWireRejectReason('identityMismatch'))
|
|
777
|
-
]);
|
|
778
|
-
return 'infrastructure';
|
|
779
|
-
}
|
|
780
|
-
const result = this.admitPeer(
|
|
781
|
-
descriptor,
|
|
782
|
-
routeConnectionId(received.routeGeneration),
|
|
783
|
-
nowMs,
|
|
784
|
-
expected
|
|
785
|
-
);
|
|
786
|
-
if (result !== 'admitted' && result !== 'notRequired') {
|
|
787
|
-
const detail =
|
|
788
|
-
result === 'invalidDescriptor'
|
|
789
|
-
? describeInvalidAdmissionDescriptor(
|
|
790
|
-
descriptor,
|
|
791
|
-
expected,
|
|
792
|
-
this.topology.peer(descriptor.nodeRoutingId)?.descriptor
|
|
793
|
-
)
|
|
794
|
-
: undefined;
|
|
795
|
-
console.warn(
|
|
796
|
-
`RouteMesh admission rejected peer '${received.sourceRid}' as ${result}` +
|
|
797
|
-
(detail === undefined ? '.' : ` (${detail}).`)
|
|
798
|
-
);
|
|
799
|
-
await this.send(received.sourceRid, [encodeReject(admissionReason(result))]);
|
|
800
|
-
return 'infrastructure';
|
|
801
|
-
}
|
|
802
|
-
if (result === 'admitted') this.selectBilateralConnection(descriptor);
|
|
803
|
-
if (header.command === M6aServiceWireCommand.hello) {
|
|
804
|
-
// Hello supplies only the sender's descriptor. Even NotRequired
|
|
805
|
-
// peers must receive ours before the intent owner closes transport;
|
|
806
|
-
// submitting a response does not guarantee delivery before close.
|
|
807
|
-
await this.send(received.sourceRid, [
|
|
808
|
-
encodeRouteMeshAdmission(M6aServiceWireCommand.admit, this.topology.localDescriptor())
|
|
809
|
-
]);
|
|
810
|
-
} else if (result === 'notRequired') {
|
|
811
|
-
this.retireNotRequiredExpectedPeer(received.sourceRid, descriptor.advertisedEndpoint);
|
|
812
|
-
}
|
|
813
|
-
return 'infrastructure';
|
|
814
924
|
}
|
|
815
925
|
if (header.command === M6aServiceWireCommand.reject) {
|
|
816
926
|
if (received.parts.length !== 1) return 'protocolError';
|
|
817
927
|
decodeReject(received.parts[0]!);
|
|
818
928
|
return 'infrastructure';
|
|
819
929
|
}
|
|
820
|
-
|
|
821
|
-
|
|
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
|
+
}
|
|
822
937
|
if (
|
|
823
938
|
header.command === M6aServiceWireCommand.livenessProbe ||
|
|
824
939
|
header.command === M6aServiceWireCommand.livenessAck
|
|
825
940
|
) {
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
const ack = this.liveness.acknowledgeProbe(
|
|
830
|
-
received.sourceRid,
|
|
831
|
-
peer.connectionId,
|
|
832
|
-
record.probeId
|
|
833
|
-
);
|
|
834
|
-
if (ack === undefined) return 'protocolError';
|
|
835
|
-
const sent = await this.send(received.sourceRid, [
|
|
836
|
-
livenessCodec.encodeLivenessRecord({
|
|
837
|
-
command: M6aServiceWireCommand.livenessAck,
|
|
838
|
-
probeId: record.probeId
|
|
839
|
-
})
|
|
840
|
-
]);
|
|
841
|
-
if (!sent) return 'dropped';
|
|
842
|
-
} else {
|
|
843
|
-
this.liveness.acknowledge(received.sourceRid, peer.connectionId, record.probeId, nowMs);
|
|
844
|
-
}
|
|
845
|
-
return 'infrastructure';
|
|
941
|
+
return this.processLiveness(received, nowMs).catch((error) =>
|
|
942
|
+
this.receiveProtocolFailure(error)
|
|
943
|
+
);
|
|
846
944
|
}
|
|
847
|
-
const stateful =
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
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
|
+
);
|
|
858
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 {
|
|
859
1099
|
if (
|
|
860
1100
|
header.flags === 0 &&
|
|
861
1101
|
// A bare infrastructure control is normally exactly one frame, but a
|
|
@@ -873,7 +1113,7 @@ export class RawServiceMeshRuntime {
|
|
|
873
1113
|
isBareInfrastructureControl(header.command)
|
|
874
1114
|
) {
|
|
875
1115
|
const accepted = this.mailbox.tryEnqueue({
|
|
876
|
-
owner:
|
|
1116
|
+
owner: this.nodeMailboxOwner,
|
|
877
1117
|
domain: 'infrastructure',
|
|
878
1118
|
parts: received.parts,
|
|
879
1119
|
sourceRoutingId: received.sourceRid,
|
|
@@ -898,48 +1138,58 @@ export class RawServiceMeshRuntime {
|
|
|
898
1138
|
let owner: string;
|
|
899
1139
|
let correlation: bigint | undefined;
|
|
900
1140
|
if (header.command === M6aServiceWireCommand.nodeSend) {
|
|
901
|
-
owner =
|
|
1141
|
+
owner = this.nodeMailboxOwner;
|
|
902
1142
|
} else if (header.command === M6aServiceWireCommand.nodeRequest) {
|
|
903
|
-
owner =
|
|
904
|
-
correlation = decodeNodeRequestHeader(received.parts[0]
|
|
1143
|
+
owner = this.nodeMailboxOwner;
|
|
1144
|
+
correlation = decodeNodeRequestHeader(received.parts[0]!, header);
|
|
905
1145
|
} else if (header.command === M6aServiceWireCommand.channelSend) {
|
|
906
|
-
owner = `channel:${decodeChannelSendHeader(received.parts[0]
|
|
1146
|
+
owner = `channel:${decodeChannelSendHeader(received.parts[0]!, header)}`;
|
|
907
1147
|
} else {
|
|
908
|
-
const channel = decodeChannelRequestHeader(received.parts[0]
|
|
1148
|
+
const channel = decodeChannelRequestHeader(received.parts[0]!, header);
|
|
909
1149
|
owner = `channel:${channel.channelName}`;
|
|
910
1150
|
correlation = channel.correlation;
|
|
911
1151
|
}
|
|
912
|
-
const
|
|
913
|
-
|
|
914
|
-
owner,
|
|
915
|
-
|
|
916
|
-
parts: received.parts,
|
|
917
|
-
sourceRoutingId: received.sourceRid,
|
|
918
|
-
sourceRoute: received.sourceRoute,
|
|
919
|
-
...(received.reply === undefined ? {} : { reply: received.reply }),
|
|
920
|
-
requestSequence: received.requestSeq,
|
|
921
|
-
...(correlation === undefined ? {} : { correlation }),
|
|
922
|
-
applicationJob
|
|
923
|
-
});
|
|
924
|
-
if (accepted) return 'application';
|
|
925
|
-
applicationJob.close();
|
|
926
|
-
if (correlation !== undefined && received.requestSeq !== undefined) {
|
|
927
|
-
this.replyService(
|
|
928
|
-
{
|
|
929
|
-
sourceRoutingId: received.sourceRid,
|
|
930
|
-
sourceRoute: received.sourceRoute,
|
|
931
|
-
requestSequence: received.requestSeq,
|
|
932
|
-
...(received.reply === undefined ? {} : { reply: received.reply })
|
|
933
|
-
},
|
|
934
|
-
[encodeReplyHeader(correlation, RequestResult.NotConnected, 0)]
|
|
935
|
-
);
|
|
936
|
-
return 'infrastructure';
|
|
937
|
-
}
|
|
938
|
-
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);
|
|
939
1156
|
} catch (error) {
|
|
940
|
-
|
|
941
|
-
|
|
1157
|
+
return this.receiveProtocolFailure(error);
|
|
1158
|
+
}
|
|
1159
|
+
}
|
|
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';
|
|
942
1191
|
}
|
|
1192
|
+
return 'dropped';
|
|
943
1193
|
}
|
|
944
1194
|
|
|
945
1195
|
expireOperations(nowMs: number, turnDeadlineMs: number): number {
|
|
@@ -950,21 +1200,25 @@ export class RawServiceMeshRuntime {
|
|
|
950
1200
|
return this.operations.size;
|
|
951
1201
|
}
|
|
952
1202
|
|
|
953
|
-
|
|
1203
|
+
tickLiveness(nowMs = performance.now()): ServiceLivenessTick {
|
|
954
1204
|
const result = this.liveness.tick(nowMs);
|
|
955
1205
|
this.requireStarted();
|
|
1206
|
+
for (const nodeRoutingId of this.selectedRoutes.keys()) void this.announcePeer(nodeRoutingId);
|
|
956
1207
|
for (const probe of result.probes) {
|
|
957
|
-
|
|
1208
|
+
const parts = [
|
|
958
1209
|
livenessCodec.encodeLivenessRecord({
|
|
959
1210
|
command: M6aServiceWireCommand.livenessProbe,
|
|
960
1211
|
probeId: probe.probeId
|
|
961
1212
|
})
|
|
962
|
-
]
|
|
1213
|
+
];
|
|
1214
|
+
this.startControlSend(probe.nodeRoutingId, M6aServiceWireCommand.livenessProbe, () =>
|
|
1215
|
+
this.submitControl(probe.nodeRoutingId, parts)
|
|
1216
|
+
);
|
|
963
1217
|
}
|
|
964
1218
|
for (const nodeRoutingId of result.timedOutNodes) {
|
|
965
1219
|
const peer = this.topology.peer(nodeRoutingId);
|
|
966
1220
|
if (peer === undefined) continue;
|
|
967
|
-
|
|
1221
|
+
this.requireStarted().disconnectRid!(nodeRoutingId);
|
|
968
1222
|
this.removePeer(peer);
|
|
969
1223
|
}
|
|
970
1224
|
return result;
|
|
@@ -986,11 +1240,10 @@ export class RawServiceMeshRuntime {
|
|
|
986
1240
|
let changes = 0;
|
|
987
1241
|
for (const [nodeRoutingId, generation] of this.selectedRoutes) {
|
|
988
1242
|
if (observed.get(nodeRoutingId) === generation) continue;
|
|
1243
|
+
this.controlSends.delete(controlSendKey(nodeRoutingId, M6aServiceWireCommand.hello));
|
|
989
1244
|
changes++;
|
|
990
|
-
const expected = this.expectedPeers.get(nodeRoutingId);
|
|
991
|
-
if (expected !== undefined) delete expected.helloSubmittedGeneration;
|
|
992
1245
|
const peer = this.topology.peer(nodeRoutingId);
|
|
993
|
-
if (peer
|
|
1246
|
+
if (peer?.liveness?.connectionId === generation) {
|
|
994
1247
|
this.removePeer(peer);
|
|
995
1248
|
}
|
|
996
1249
|
}
|
|
@@ -1001,6 +1254,7 @@ export class RawServiceMeshRuntime {
|
|
|
1001
1254
|
selected.push(nodeRoutingId);
|
|
1002
1255
|
}
|
|
1003
1256
|
this.selectedRoutes = observed;
|
|
1257
|
+
for (const nodeRoutingId of selected) this.notifyPeerRouteChanged(nodeRoutingId);
|
|
1004
1258
|
// A new selected route admits only through a new handshake.
|
|
1005
1259
|
for (const nodeRoutingId of selected) await this.announcePeer(nodeRoutingId);
|
|
1006
1260
|
return changes;
|
|
@@ -1008,9 +1262,15 @@ export class RawServiceMeshRuntime {
|
|
|
1008
1262
|
|
|
1009
1263
|
close(): void {
|
|
1010
1264
|
if (this.closed) return;
|
|
1265
|
+
this.closed = true;
|
|
1011
1266
|
this.applicationJobStop.abort(
|
|
1012
1267
|
new Error('Raw service runtime application job admission stopped.')
|
|
1013
1268
|
);
|
|
1269
|
+
this.receivePermit?.releaseAfterInternalProcessing();
|
|
1270
|
+
this.receivePermit?.releaseStorage?.();
|
|
1271
|
+
this.receivePermit = undefined;
|
|
1272
|
+
for (const send of this.controlSends.values()) delete send.next;
|
|
1273
|
+
this.controlSends.clear();
|
|
1014
1274
|
this.mailbox.close();
|
|
1015
1275
|
this.operations.close('Raw service runtime closed.');
|
|
1016
1276
|
this.serviceIngress = undefined;
|
|
@@ -1022,14 +1282,14 @@ export class RawServiceMeshRuntime {
|
|
|
1022
1282
|
if (host !== undefined) host.close();
|
|
1023
1283
|
this.router = undefined;
|
|
1024
1284
|
this.host = undefined;
|
|
1025
|
-
this.closed = true;
|
|
1026
1285
|
}
|
|
1027
1286
|
|
|
1028
1287
|
private requestToTarget(
|
|
1029
1288
|
targetNodeRoutingId: string,
|
|
1030
1289
|
payload: ServiceApplicationPayloadInput,
|
|
1031
1290
|
timeoutMs: number,
|
|
1032
|
-
channelName?: string
|
|
1291
|
+
channelName?: string,
|
|
1292
|
+
onAdmitted?: () => void
|
|
1033
1293
|
): PendingOperation<RawServiceRequestResult> {
|
|
1034
1294
|
const correlation = this.nextCorrelation++;
|
|
1035
1295
|
const header =
|
|
@@ -1100,6 +1360,8 @@ export class RawServiceMeshRuntime {
|
|
|
1100
1360
|
terminalResult: RequestResult.NotConnected,
|
|
1101
1361
|
failureCode: 0
|
|
1102
1362
|
});
|
|
1363
|
+
} else {
|
|
1364
|
+
onAdmitted?.();
|
|
1103
1365
|
}
|
|
1104
1366
|
} finally {
|
|
1105
1367
|
applicationJobOwner.close();
|
|
@@ -1107,7 +1369,8 @@ export class RawServiceMeshRuntime {
|
|
|
1107
1369
|
})().catch((error) => this.operations.fail(pending.id, error));
|
|
1108
1370
|
return pending;
|
|
1109
1371
|
}
|
|
1110
|
-
|
|
1372
|
+
const selectedPeer = this.topology.admittedPeer(selectedTargetNodeRoutingId);
|
|
1373
|
+
if (selectedPeer === undefined) {
|
|
1111
1374
|
// Logical target selection is the requester's decision: an RID that is
|
|
1112
1375
|
// not an admitted peer is not a target. Whether an admitted target's
|
|
1113
1376
|
// route can carry the request is Core's REQUEST result.
|
|
@@ -1118,11 +1381,15 @@ export class RawServiceMeshRuntime {
|
|
|
1118
1381
|
return pending;
|
|
1119
1382
|
}
|
|
1120
1383
|
const parts = [header, encodedPayload];
|
|
1121
|
-
let router: ZLinkRawRouterPort;
|
|
1122
1384
|
let request: Promise<readonly Uint8Array[]>;
|
|
1123
1385
|
try {
|
|
1124
|
-
|
|
1125
|
-
|
|
1386
|
+
request = this.requireStarted().request(
|
|
1387
|
+
selectedTargetNodeRoutingId,
|
|
1388
|
+
parts,
|
|
1389
|
+
timeoutMs,
|
|
1390
|
+
selectedPeer.liveness?.recordReceived,
|
|
1391
|
+
onAdmitted
|
|
1392
|
+
);
|
|
1126
1393
|
} catch (error) {
|
|
1127
1394
|
this.operations.fail(pending.id, error);
|
|
1128
1395
|
return pending;
|
|
@@ -1182,33 +1449,43 @@ export class RawServiceMeshRuntime {
|
|
|
1182
1449
|
|
|
1183
1450
|
private admitPeer(
|
|
1184
1451
|
descriptor: ServiceNodeDescriptor,
|
|
1185
|
-
connectionId: string,
|
|
1452
|
+
connectionId: string | bigint,
|
|
1186
1453
|
nowMs: number,
|
|
1187
1454
|
expected?: {
|
|
1188
1455
|
readonly endpoint?: string;
|
|
1189
1456
|
readonly securityIdentity?: string;
|
|
1190
1457
|
readonly lifecycleGeneration?: bigint;
|
|
1191
|
-
}
|
|
1458
|
+
},
|
|
1459
|
+
command: number = M6aServiceWireCommand.hello,
|
|
1460
|
+
descriptorBytes?: Uint8Array
|
|
1192
1461
|
): PeerAdmissionResult {
|
|
1193
1462
|
const previous = this.topology.peer(descriptor.nodeRoutingId);
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
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') {
|
|
1201
1478
|
throw new ServiceWireProtocolError(
|
|
1202
1479
|
`RouteMesh peer '${descriptor.nodeRoutingId}' descriptor revision is stale or conflicting.`
|
|
1203
1480
|
);
|
|
1204
1481
|
}
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
this.liveness.disconnect(descriptor.nodeRoutingId, previous.connectionId);
|
|
1482
|
+
if (result === 'notRequired' && previous !== undefined) {
|
|
1483
|
+
this.liveness.disconnect(
|
|
1484
|
+
descriptor.nodeRoutingId,
|
|
1485
|
+
previous.liveness?.connectionId ?? previous.connectionId
|
|
1486
|
+
);
|
|
1211
1487
|
}
|
|
1488
|
+
if (result === 'admitted') this.notifyPeerRouteChanged(descriptor.nodeRoutingId);
|
|
1212
1489
|
return result;
|
|
1213
1490
|
}
|
|
1214
1491
|
|
|
@@ -1244,7 +1521,10 @@ export class RawServiceMeshRuntime {
|
|
|
1244
1521
|
|
|
1245
1522
|
private removePeer(peer: AdmittedServicePeer): void {
|
|
1246
1523
|
this.topology.disconnect(peer.descriptor.nodeRoutingId, peer.connectionId);
|
|
1247
|
-
this.liveness.disconnect(
|
|
1524
|
+
this.liveness.disconnect(
|
|
1525
|
+
peer.descriptor.nodeRoutingId,
|
|
1526
|
+
peer.liveness?.connectionId ?? peer.connectionId
|
|
1527
|
+
);
|
|
1248
1528
|
this.onPeerDisconnected?.(
|
|
1249
1529
|
peer.descriptor.nodeRoutingId,
|
|
1250
1530
|
peer.descriptor.advertisedEndpoint,
|
|
@@ -1257,19 +1537,136 @@ export class RawServiceMeshRuntime {
|
|
|
1257
1537
|
return this.router;
|
|
1258
1538
|
}
|
|
1259
1539
|
|
|
1260
|
-
private async
|
|
1540
|
+
private async sendControl(
|
|
1541
|
+
targetNodeRoutingId: string,
|
|
1542
|
+
parts: readonly Uint8Array[]
|
|
1543
|
+
): Promise<boolean> {
|
|
1544
|
+
return this.startControlSend(targetNodeRoutingId, decodeHeader(parts[0]!).command, () =>
|
|
1545
|
+
this.submitControl(targetNodeRoutingId, parts)
|
|
1546
|
+
);
|
|
1547
|
+
}
|
|
1548
|
+
|
|
1549
|
+
private submitControl(target: string, parts: readonly Uint8Array[]): boolean | Promise<boolean> {
|
|
1550
|
+
let submission;
|
|
1261
1551
|
try {
|
|
1262
|
-
|
|
1263
|
-
return true;
|
|
1552
|
+
submission = this.requireStarted().submitSend(target, parts);
|
|
1264
1553
|
} catch (error) {
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1554
|
+
return this.sendFailure(error);
|
|
1555
|
+
}
|
|
1556
|
+
if (submission.result === SubmitResult.Ok) return true;
|
|
1557
|
+
return submission.admitted.then(
|
|
1558
|
+
() => true,
|
|
1559
|
+
(error) => this.sendFailure(error)
|
|
1560
|
+
);
|
|
1561
|
+
}
|
|
1562
|
+
|
|
1563
|
+
private startControlSend(
|
|
1564
|
+
target: string,
|
|
1565
|
+
command: number,
|
|
1566
|
+
attempt: () => boolean | Promise<boolean>,
|
|
1567
|
+
current?: () => boolean,
|
|
1568
|
+
hello?: Uint8Array
|
|
1569
|
+
): boolean {
|
|
1570
|
+
const key = controlSendKey(target, command);
|
|
1571
|
+
const existing = this.controlSends.get(key);
|
|
1572
|
+
if (
|
|
1573
|
+
existing !== undefined &&
|
|
1574
|
+
existing.task !== undefined &&
|
|
1575
|
+
(command !== M6aServiceWireCommand.hello || existing.current?.() === true)
|
|
1576
|
+
) {
|
|
1577
|
+
if (command !== M6aServiceWireCommand.hello) {
|
|
1578
|
+
existing.next = attempt;
|
|
1579
|
+
existing.current = current;
|
|
1269
1580
|
}
|
|
1270
|
-
|
|
1581
|
+
return true;
|
|
1582
|
+
}
|
|
1583
|
+
const first = attempt();
|
|
1584
|
+
if (typeof first === 'boolean') {
|
|
1585
|
+
if (hello !== undefined) this.controlSends.set(key, { current, hello });
|
|
1586
|
+
return first;
|
|
1587
|
+
}
|
|
1588
|
+
const owner = this;
|
|
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);
|
|
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;
|
|
1605
|
+
this.controlSends.set(key, state);
|
|
1606
|
+
task.then(
|
|
1607
|
+
() => {},
|
|
1608
|
+
(error) => {
|
|
1609
|
+
completeSend();
|
|
1610
|
+
if (this.infrastructureTaskRunner === undefined) this.reportInfrastructureFailure(error);
|
|
1611
|
+
}
|
|
1612
|
+
);
|
|
1613
|
+
this.infrastructureTaskRunner?.runDetached('RouteMesh control SEND', async () => {
|
|
1614
|
+
await task;
|
|
1615
|
+
});
|
|
1616
|
+
return true;
|
|
1617
|
+
}
|
|
1618
|
+
|
|
1619
|
+
private reportInfrastructureFailure(error: unknown): void {
|
|
1620
|
+
this.infrastructureFailure = {
|
|
1621
|
+
error:
|
|
1622
|
+
this.infrastructureFailure === undefined
|
|
1623
|
+
? error
|
|
1624
|
+
: new AggregateError(
|
|
1625
|
+
[this.infrastructureFailure.error, error],
|
|
1626
|
+
'RouteMesh infrastructure failed.'
|
|
1627
|
+
)
|
|
1628
|
+
};
|
|
1629
|
+
this.onReceiveReady?.();
|
|
1630
|
+
}
|
|
1631
|
+
|
|
1632
|
+
private observeInfrastructureFailure(): void {
|
|
1633
|
+
const failure = this.infrastructureFailure;
|
|
1634
|
+
this.infrastructureFailure = undefined;
|
|
1635
|
+
if (failure !== undefined) throw failure.error;
|
|
1636
|
+
}
|
|
1637
|
+
|
|
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)
|
|
1648
|
+
);
|
|
1649
|
+
} catch (error) {
|
|
1650
|
+
return this.sendFailure(error);
|
|
1271
1651
|
}
|
|
1272
1652
|
}
|
|
1653
|
+
|
|
1654
|
+
private sendFailure(error: unknown): false {
|
|
1655
|
+
if (isZLinkBackendResultError(error) && error.operation === 'submit') {
|
|
1656
|
+
const terminal = submitToRequestResult(error.result, error.phase);
|
|
1657
|
+
if (terminal === RequestResult.NotConnected || terminal === RequestResult.NotFound)
|
|
1658
|
+
return false;
|
|
1659
|
+
}
|
|
1660
|
+
throw error;
|
|
1661
|
+
}
|
|
1662
|
+
}
|
|
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}`;
|
|
1273
1670
|
}
|
|
1274
1671
|
|
|
1275
1672
|
/**
|
|
@@ -1309,14 +1706,6 @@ function isAlreadyDisconnectedError(error: unknown): boolean {
|
|
|
1309
1706
|
return (error as { readonly nativeErrno?: unknown }).nativeErrno === nativeErrnoValues.ENOENT;
|
|
1310
1707
|
}
|
|
1311
1708
|
|
|
1312
|
-
/**
|
|
1313
|
-
* An admission belongs to one Core selected route. Its connection identity is
|
|
1314
|
-
* that route's generation, an opaque token compared only for equality.
|
|
1315
|
-
*/
|
|
1316
|
-
function routeConnectionId(routeGeneration: bigint): string {
|
|
1317
|
-
return `route:${routeGeneration.toString()}`;
|
|
1318
|
-
}
|
|
1319
|
-
|
|
1320
1709
|
function describeInvalidAdmissionDescriptor(
|
|
1321
1710
|
descriptor: ServiceNodeDescriptor,
|
|
1322
1711
|
expected: ServicePeerAdmissionExpectation | undefined,
|