@zlink-systems/framework 0.30.0 → 0.30.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/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/ZLinkEncodedPayload.d.ts +6 -0
- package/dist/contracts/Common/ZLinkEncodedPayload.js +18 -3
- package/dist/contracts/Common/ZLinkMessageMetadata.js +11 -5
- package/dist/contracts/Common/encoded-payload-storage.d.ts +1 -1
- package/dist/contracts/Common/encoded-payload-storage.js +5 -2
- package/dist/contracts/Configuration/Builders.d.ts +1 -1
- package/dist/contracts/Configuration/RegistrationBuilderPolicy.d.ts +2 -1
- package/dist/contracts/Configuration/RegistrationBuilderPolicy.js +5 -1
- package/dist/contracts/Configuration/RegistrationBuilders.js +2 -2
- package/dist/contracts/Configuration/RegistrationTypes.d.ts +1 -0
- package/dist/contracts/Dispatch/ZLinkDispatchOptions.d.ts +2 -2
- 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/Locations/Rows.d.ts +2 -0
- package/dist/contracts/Spots/Contracts.d.ts +6 -1
- package/dist/contracts/Spots/ZLinkSpot.d.ts +3 -3
- package/dist/nest-integration.d.ts +1 -1
- package/dist/nest-integration.js +2 -1
- package/dist/runtime/abort.d.ts +5 -0
- package/dist/runtime/abort.js +10 -0
- package/dist/runtime/actors/actor-authority-publication.js +3 -2
- package/dist/runtime/actors/actor-client.d.ts +1 -2
- package/dist/runtime/actors/actor-client.js +52 -57
- package/dist/runtime/actors/actor-context.d.ts +2 -0
- package/dist/runtime/actors/actor-context.js +46 -9
- package/dist/runtime/actors/actor-handoff.d.ts +8 -4
- package/dist/runtime/actors/actor-handoff.js +219 -92
- package/dist/runtime/actors/actor-join-relocation.d.ts +3 -0
- package/dist/runtime/actors/actor-local-native-join.js +13 -12
- package/dist/runtime/actors/actor-mailbox.js +27 -1
- package/dist/runtime/actors/actor-remote-joiner.js +3 -1
- package/dist/runtime/actors/actor-remote-wire.js +15 -0
- package/dist/runtime/actors/actor-reply.d.ts +4 -0
- package/dist/runtime/actors/actor-reply.js +29 -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/actor-runtime-contracts.d.ts +2 -0
- package/dist/runtime/actors/actor-runtime-state.js +2 -20
- package/dist/runtime/actors/actor-source-transfer.d.ts +1 -17
- package/dist/runtime/actors/index.d.ts +8 -3
- package/dist/runtime/actors/index.js +55 -27
- package/dist/runtime/actors/session-disconnected-wire.d.ts +8 -0
- package/dist/runtime/actors/session-disconnected-wire.js +48 -0
- package/dist/runtime/actors/spot-actor-dispatch.js +2 -2
- package/dist/runtime/admission.d.ts +9 -1
- package/dist/runtime/admission.js +56 -23
- 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 +15 -1
- package/dist/runtime/application-jobs/application-job-queue-scope.js +61 -20
- package/dist/runtime/application-jobs/contracts.d.ts +12 -1
- package/dist/runtime/backend/contracts/index.d.ts +37 -12
- package/dist/runtime/backend/mesh-actor-session-node-adapter.js +2 -2
- package/dist/runtime/backend/mesh-completion-table.d.ts +9 -1
- package/dist/runtime/backend/mesh-completion-table.js +154 -49
- package/dist/runtime/backend/mesh-dispatch-pump.d.ts +28 -1
- package/dist/runtime/backend/mesh-dispatch-pump.js +208 -101
- 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 +124 -72
- package/dist/runtime/backend/node/node-raw-mesh-backend.d.ts +46 -13
- package/dist/runtime/backend/node/node-raw-mesh-backend.js +181 -164
- package/dist/runtime/backend/node/node-socket-backend-adapter.d.ts +1 -1
- package/dist/runtime/backend/node/node-socket-backend-adapter.js +11 -11
- 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 +2 -0
- package/dist/runtime/channels/channel-clients.js +75 -23
- package/dist/runtime/channels/channel-dispatch-pipeline.d.ts +25 -5
- package/dist/runtime/channels/channel-dispatch-pipeline.js +111 -56
- 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 +4 -3
- package/dist/runtime/channels/channel-dispatchers.js +62 -54
- 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 +2 -0
- package/dist/runtime/channels/channel-envelope.js +146 -28
- 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-lifecycle.d.ts +4 -3
- package/dist/runtime/channels/channel-runtime-lifecycle.js +19 -24
- package/dist/runtime/channels/channel-runtime-manager.d.ts +9 -8
- package/dist/runtime/channels/channel-runtime-manager.js +16 -16
- package/dist/runtime/channels/channel-transports.d.ts +18 -16
- package/dist/runtime/channels/channel-transports.js +77 -58
- package/dist/runtime/channels/client-server-location-runtime.js +5 -4
- package/dist/runtime/channels/dispatch-error-reporter.d.ts +2 -1
- package/dist/runtime/channels/dispatch-error-reporter.js +2 -1
- package/dist/runtime/channels/fanout-location-runtime.d.ts +1 -1
- package/dist/runtime/channels/fanout-location-runtime.js +3 -3
- 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/flow-context.js +19 -7
- package/dist/runtime/diagnostics/index.js +4 -9
- package/dist/runtime/diagnostics/message-flow.d.ts +8 -0
- package/dist/runtime/diagnostics/message-flow.js +35 -5
- package/dist/runtime/diagnostics/runtime-metrics.d.ts +2 -2
- package/dist/runtime/diagnostics/runtime-metrics.js +10 -8
- package/dist/runtime/execution/index.d.ts +4 -2
- package/dist/runtime/execution/index.js +33 -10
- package/dist/runtime/execution/serial-execution-queue.d.ts +5 -0
- package/dist/runtime/execution/serial-execution-queue.js +22 -2
- package/dist/runtime/execution/state-lane.d.ts +2 -0
- package/dist/runtime/execution/state-lane.js +5 -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/operation-registry.d.ts +25 -1
- package/dist/runtime/foundation/operation-registry.js +86 -18
- package/dist/runtime/foundation/raw-service-mesh-runtime.d.ts +61 -8
- package/dist/runtime/foundation/raw-service-mesh-runtime.js +486 -266
- package/dist/runtime/foundation/service-liveness-registry.d.ts +12 -1
- package/dist/runtime/foundation/service-liveness-registry.js +38 -0
- package/dist/runtime/foundation/service-mailbox.d.ts +7 -0
- package/dist/runtime/foundation/service-maintenance-runtime.js +1 -6
- package/dist/runtime/foundation/service-relocation-object-owner.d.ts +3 -3
- package/dist/runtime/foundation/service-relocation-object-owner.js +48 -34
- package/dist/runtime/foundation/service-relocation-runtime.d.ts +2 -0
- package/dist/runtime/foundation/service-relocation-runtime.js +20 -4
- package/dist/runtime/foundation/service-runtime-contracts.d.ts +10 -1
- package/dist/runtime/foundation/service-session-binding-ingress-port.d.ts +2 -1
- package/dist/runtime/foundation/service-stateful-registry.d.ts +5 -4
- package/dist/runtime/foundation/service-stateful-registry.js +24 -26
- package/dist/runtime/foundation/service-stateful-runtime.d.ts +75 -9
- package/dist/runtime/foundation/service-stateful-runtime.js +486 -218
- package/dist/runtime/foundation/service-stateful-wire-codec.d.ts +14 -6
- package/dist/runtime/foundation/service-stateful-wire-codec.js +48 -27
- 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-binary-primitives.d.ts +6 -1
- package/dist/runtime/foundation/service-wire-binary-primitives.js +27 -11
- package/dist/runtime/foundation/service-wire-constants.generated.d.ts +1 -0
- package/dist/runtime/foundation/service-wire-constants.generated.js +1 -0
- package/dist/runtime/foundation/service-wire-m6a-codec.d.ts +13 -3
- package/dist/runtime/foundation/service-wire-m6a-codec.js +189 -78
- 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.d.ts +6 -4
- package/dist/runtime/host/actor-packet-relay.js +59 -72
- package/dist/runtime/host/actor-placement-coordinator.js +25 -8
- package/dist/runtime/host/actor-runtime-options-factory.js +2 -2
- package/dist/runtime/host/actor-transfer-runtime.d.ts +7 -31
- package/dist/runtime/host/actor-transfer-runtime.js +85 -331
- package/dist/runtime/host/application-job-queue.d.ts +40 -7
- package/dist/runtime/host/application-job-queue.js +137 -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 +12 -2
- package/dist/runtime/host/index.js +300 -202
- package/dist/runtime/host/location-runtime-owner.js +2 -6
- package/dist/runtime/host/mesh-router-resolver.d.ts +4 -0
- package/dist/runtime/host/mesh-router-resolver.js +14 -6
- package/dist/runtime/host/remote-bound-session-relay.d.ts +2 -4
- package/dist/runtime/host/remote-bound-session-relay.js +73 -208
- package/dist/runtime/host/route-mesh-runtime.d.ts +2 -2
- package/dist/runtime/host/route-mesh-runtime.js +6 -4
- package/dist/runtime/host/runtime-shutdown.d.ts +1 -0
- package/dist/runtime/host/runtime-shutdown.js +3 -2
- 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 +22 -14
- package/dist/runtime/host/service-relocation-host-runtime.js +754 -599
- package/dist/runtime/host/spot-address-transport.js +3 -2
- package/dist/runtime/host/spot-runtime-options-factory.js +16 -7
- package/dist/runtime/host/user-spot-creation-coordinator.js +33 -84
- 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/auto-connect-loop.d.ts +2 -0
- package/dist/runtime/locations/auto-connect-loop.js +22 -14
- package/dist/runtime/locations/auto-connect-reconciler.js +0 -6
- package/dist/runtime/locations/auto-connect-types.d.ts +0 -1
- package/dist/runtime/locations/descriptor-publication.d.ts +4 -0
- package/dist/runtime/locations/descriptor-publication.js +11 -0
- package/dist/runtime/locations/domain-store-contract.d.ts +1 -0
- package/dist/runtime/locations/in-memory-authority-store.d.ts +7 -1
- package/dist/runtime/locations/in-memory-authority-store.js +56 -19
- package/dist/runtime/locations/in-memory-location-store.js +1 -10
- package/dist/runtime/locations/index.d.ts +1 -0
- package/dist/runtime/locations/index.js +1 -0
- package/dist/runtime/locations/lease-tracker.js +1 -6
- 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 +51 -1
- package/dist/runtime/locations/location-store-repository.js +363 -139
- package/dist/runtime/locations/relocation-blob.d.ts +18 -1
- package/dist/runtime/locations/relocation-blob.js +52 -1
- package/dist/runtime/locations/request-source-resolver.d.ts +37 -0
- package/dist/runtime/locations/request-source-resolver.js +80 -0
- package/dist/runtime/locations/resolvers.js +1 -1
- package/dist/runtime/locations/runtime.d.ts +7 -3
- package/dist/runtime/locations/runtime.js +61 -53
- 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/payload-codec.d.ts +7 -1
- package/dist/runtime/messaging/payload-codec.js +7 -6
- package/dist/runtime/messaging/request-result.d.ts +2 -0
- package/dist/runtime/messaging/request-result.js +57 -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 +34 -1
- package/dist/runtime/protocol/service_wire_codec.generated.js +229 -0
- package/dist/runtime/protocol/service_wire_pilot_codec.generated.d.ts +16 -1
- package/dist/runtime/protocol/service_wire_pilot_codec.generated.js +37 -8
- package/dist/runtime/spots/formal-remote-actor-admission-registry.d.ts +7 -2
- package/dist/runtime/spots/formal-remote-actor-admission-registry.js +36 -21
- package/dist/runtime/spots/index.d.ts +21 -11
- package/dist/runtime/spots/index.js +243 -178
- package/dist/runtime/spots/spot-activation-registry.d.ts +4 -2
- package/dist/runtime/spots/spot-activation-registry.js +21 -11
- package/dist/runtime/spots/spot-activation-state.d.ts +1 -1
- package/dist/runtime/spots/spot-activation-state.js +7 -7
- package/dist/runtime/spots/spot-activation.d.ts +7 -3
- package/dist/runtime/spots/spot-activation.js +140 -52
- package/dist/runtime/spots/spot-actor-admission-coordinator.d.ts +1 -0
- package/dist/runtime/spots/spot-actor-admission-coordinator.js +13 -22
- package/dist/runtime/spots/spot-actor-membership.d.ts +0 -1
- package/dist/runtime/spots/spot-actor-membership.js +0 -8
- package/dist/runtime/spots/spot-actor-packet-dispatch.js +33 -8
- package/dist/runtime/spots/spot-actor-packet-drain.js +5 -33
- package/dist/runtime/spots/spot-entry-activation.d.ts +2 -0
- package/dist/runtime/spots/spot-entry-activation.js +20 -11
- package/dist/runtime/spots/spot-handler-registry.d.ts +7 -4
- package/dist/runtime/spots/spot-handler-registry.js +24 -10
- package/dist/runtime/spots/spot-manager-internal-contracts.d.ts +2 -2
- package/dist/runtime/spots/spot-manager-public.js +2 -2
- package/dist/runtime/spots/spot-node-autoconnect.d.ts +2 -1
- package/dist/runtime/spots/spot-node-autoconnect.js +22 -42
- package/dist/runtime/spots/spot-node-runtime-manager.d.ts +19 -2
- package/dist/runtime/spots/spot-node-runtime-manager.js +115 -50
- package/dist/runtime/spots/spot-outbound.d.ts +3 -0
- package/dist/runtime/spots/spot-outbound.js +27 -18
- package/dist/runtime/spots/spot-remote-codec.js +5 -1
- 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 -1
- package/dist/runtime/spots/spot-serial-executor.d.ts +10 -0
- package/dist/runtime/spots/spot-serial-executor.js +61 -8
- package/dist/runtime/spots/spot-serial-turn-executor.js +19 -2
- package/dist/runtime/spots/spot-subscription-dispatch.js +1 -4
- package/dist/runtime/spots/spot-timer.d.ts +57 -5
- package/dist/runtime/spots/spot-timer.js +288 -103
- package/dist/runtime/streams/actor-session-binding-registry.d.ts +42 -11
- package/dist/runtime/streams/actor-session-binding-registry.js +165 -48
- package/dist/runtime/streams/actor-session-binding-runtime-owner.d.ts +8 -4
- package/dist/runtime/streams/bound-actor-relay-sender.d.ts +2 -1
- package/dist/runtime/streams/bound-actor-relay-sender.js +15 -8
- package/dist/runtime/streams/bound-session-service.d.ts +1 -1
- package/dist/runtime/streams/bound-session-service.js +6 -11
- package/dist/runtime/streams/index.d.ts +3 -2
- package/dist/runtime/streams/index.js +6 -4
- package/dist/runtime/streams/managed-stream.js +3 -2
- package/dist/runtime/streams/native-fallback-bound-session.js +8 -73
- package/dist/runtime/streams/session-actor-coordinator.d.ts +2 -8
- package/dist/runtime/streams/session-actor-coordinator.js +15 -32
- package/dist/runtime/streams/stream-binding-runtime-ports.d.ts +3 -10
- package/dist/runtime/streams/stream-session-runtime.d.ts +1 -1
- package/dist/runtime/streams/stream-session-runtime.js +14 -31
- package/dist/runtime/workers/index.js +1 -1
- 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/ZLinkEncodedPayload.ts +24 -4
- package/src/contracts/Common/ZLinkMessageMetadata.ts +13 -8
- package/src/contracts/Common/encoded-payload-storage.ts +6 -3
- package/src/contracts/Configuration/Builders.ts +4 -1
- package/src/contracts/Configuration/RegistrationBuilderPolicy.ts +7 -2
- package/src/contracts/Configuration/RegistrationBuilders.ts +6 -2
- package/src/contracts/Configuration/RegistrationTypes.ts +1 -0
- package/src/contracts/Dispatch/ZLinkDispatchOptions.ts +2 -1
- package/src/contracts/Handlers/Attributes.ts +14 -21
- package/src/contracts/Handlers/JsonContract.ts +8 -216
- package/src/contracts/Locations/Rows.ts +2 -0
- package/src/contracts/Spots/Contracts.ts +10 -1
- package/src/contracts/Spots/ZLinkSpot.ts +3 -3
- package/src/nest-integration.ts +1 -0
- package/src/runtime/abort.ts +12 -0
- package/src/runtime/actors/actor-authority-publication.ts +3 -2
- package/src/runtime/actors/actor-client.ts +100 -85
- package/src/runtime/actors/actor-context.ts +49 -9
- package/src/runtime/actors/actor-handoff.ts +291 -110
- package/src/runtime/actors/actor-join-relocation.ts +3 -0
- package/src/runtime/actors/actor-local-native-join.ts +21 -26
- package/src/runtime/actors/actor-mailbox.ts +40 -2
- package/src/runtime/actors/actor-remote-joiner.ts +6 -1
- package/src/runtime/actors/actor-remote-wire.ts +17 -0
- package/src/runtime/actors/actor-reply.ts +42 -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/actor-runtime-contracts.ts +2 -0
- package/src/runtime/actors/actor-runtime-state.ts +2 -20
- package/src/runtime/actors/actor-source-transfer.ts +1 -34
- package/src/runtime/actors/index.ts +60 -32
- package/src/runtime/actors/session-disconnected-wire.ts +66 -0
- package/src/runtime/actors/spot-actor-dispatch.ts +4 -2
- package/src/runtime/admission.ts +69 -21
- package/src/runtime/application-jobs/application-ingress-record-owner.ts +41 -25
- package/src/runtime/application-jobs/application-job-queue-scope.ts +83 -22
- package/src/runtime/application-jobs/contracts.ts +16 -1
- package/src/runtime/backend/contracts/index.ts +92 -18
- package/src/runtime/backend/mesh-actor-session-node-adapter.ts +5 -3
- package/src/runtime/backend/mesh-completion-table.ts +160 -57
- package/src/runtime/backend/mesh-dispatch-pump.ts +237 -108
- 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 +148 -85
- package/src/runtime/backend/node/node-raw-mesh-backend.ts +315 -201
- package/src/runtime/backend/node/node-socket-backend-adapter.ts +26 -11
- 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 +132 -58
- package/src/runtime/channels/channel-dispatch-pipeline.ts +198 -114
- package/src/runtime/channels/channel-dispatch-services.ts +69 -1
- package/src/runtime/channels/channel-dispatchers.ts +136 -90
- package/src/runtime/channels/channel-envelope-inspection.ts +2 -16
- package/src/runtime/channels/channel-envelope.ts +225 -31
- package/src/runtime/channels/channel-outbound-operations.ts +22 -8
- package/src/runtime/channels/channel-runtime-lifecycle.ts +13 -13
- package/src/runtime/channels/channel-runtime-manager.ts +41 -17
- package/src/runtime/channels/channel-transports.ts +171 -112
- package/src/runtime/channels/client-server-location-runtime.ts +11 -10
- package/src/runtime/channels/dispatch-error-reporter.ts +4 -2
- package/src/runtime/channels/fanout-location-runtime.ts +17 -9
- 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/flow-context.ts +20 -7
- package/src/runtime/diagnostics/index.ts +1 -7
- package/src/runtime/diagnostics/message-flow.ts +47 -9
- package/src/runtime/diagnostics/runtime-metrics.ts +12 -10
- package/src/runtime/execution/index.ts +40 -14
- package/src/runtime/execution/serial-execution-queue.ts +33 -4
- package/src/runtime/execution/state-lane.ts +7 -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/operation-registry.ts +98 -20
- package/src/runtime/foundation/raw-service-mesh-runtime.ts +704 -336
- package/src/runtime/foundation/service-liveness-registry.ts +39 -0
- package/src/runtime/foundation/service-mailbox.ts +8 -0
- package/src/runtime/foundation/service-maintenance-runtime.ts +1 -8
- package/src/runtime/foundation/service-relocation-object-owner.ts +55 -36
- package/src/runtime/foundation/service-relocation-runtime.ts +25 -4
- package/src/runtime/foundation/service-runtime-contracts.ts +19 -2
- package/src/runtime/foundation/service-session-binding-ingress-port.ts +2 -1
- package/src/runtime/foundation/service-stateful-registry.ts +38 -35
- package/src/runtime/foundation/service-stateful-runtime.ts +732 -275
- package/src/runtime/foundation/service-stateful-wire-codec.ts +49 -16
- package/src/runtime/foundation/service-topology-registry.ts +91 -36
- package/src/runtime/foundation/service-wire-binary-primitives.ts +35 -12
- package/src/runtime/foundation/service-wire-constants.generated.ts +1 -0
- package/src/runtime/foundation/service-wire-m6a-codec.ts +231 -76
- package/src/runtime/handlers/handler-instance-scope.ts +67 -16
- package/src/runtime/host/actor-packet-relay.ts +113 -94
- package/src/runtime/host/actor-placement-coordinator.ts +31 -8
- package/src/runtime/host/actor-runtime-options-factory.ts +13 -2
- package/src/runtime/host/actor-transfer-runtime.ts +104 -430
- package/src/runtime/host/application-job-queue.ts +160 -41
- package/src/runtime/host/entry-actor-runtime.ts +4 -0
- package/src/runtime/host/index.ts +473 -271
- package/src/runtime/host/location-runtime-owner.ts +2 -6
- package/src/runtime/host/mesh-router-resolver.ts +13 -5
- package/src/runtime/host/remote-bound-session-relay.ts +88 -271
- package/src/runtime/host/route-mesh-runtime.ts +12 -6
- package/src/runtime/host/runtime-shutdown.ts +15 -4
- package/src/runtime/host/service-relocation-control.ts +11 -5
- package/src/runtime/host/service-relocation-host-runtime.ts +1092 -759
- package/src/runtime/host/spot-address-transport.ts +4 -2
- package/src/runtime/host/spot-runtime-options-factory.ts +20 -7
- package/src/runtime/host/user-spot-creation-coordinator.ts +41 -100
- package/src/runtime/locations/actor-location-claims.ts +52 -30
- package/src/runtime/locations/auto-connect-loop.ts +22 -14
- package/src/runtime/locations/auto-connect-reconciler.ts +0 -6
- package/src/runtime/locations/auto-connect-types.ts +0 -1
- package/src/runtime/locations/descriptor-publication.ts +7 -0
- package/src/runtime/locations/domain-store-contract.ts +5 -0
- package/src/runtime/locations/in-memory-authority-store.ts +85 -23
- package/src/runtime/locations/in-memory-location-store.ts +1 -12
- package/src/runtime/locations/index.ts +1 -0
- package/src/runtime/locations/lease-tracker.ts +2 -9
- package/src/runtime/locations/lifecycle.ts +8 -26
- package/src/runtime/locations/location-store-repository.ts +628 -152
- package/src/runtime/locations/relocation-blob.ts +75 -1
- package/src/runtime/locations/request-source-resolver.ts +138 -0
- package/src/runtime/locations/resolvers.ts +2 -2
- package/src/runtime/locations/runtime.ts +98 -64
- package/src/runtime/messaging/framework-json-v1.ts +5 -325
- package/src/runtime/messaging/packet-name.ts +5 -25
- package/src/runtime/messaging/payload-codec.ts +8 -11
- package/src/runtime/messaging/request-result.ts +62 -0
- package/src/runtime/messaging/submission-result.ts +5 -1
- package/src/runtime/protocol/service_wire_codec.generated.ts +218 -5
- package/src/runtime/protocol/service_wire_pilot_codec.generated.ts +16 -13
- package/src/runtime/spots/formal-remote-actor-admission-registry.ts +46 -25
- package/src/runtime/spots/index.ts +389 -283
- package/src/runtime/spots/spot-activation-registry.ts +25 -13
- package/src/runtime/spots/spot-activation-state.ts +12 -19
- package/src/runtime/spots/spot-activation.ts +198 -96
- package/src/runtime/spots/spot-actor-admission-coordinator.ts +25 -55
- package/src/runtime/spots/spot-actor-membership.ts +0 -15
- package/src/runtime/spots/spot-actor-packet-dispatch.ts +56 -11
- package/src/runtime/spots/spot-actor-packet-drain.ts +9 -52
- package/src/runtime/spots/spot-entry-activation.ts +33 -21
- package/src/runtime/spots/spot-handler-registry.ts +56 -12
- package/src/runtime/spots/spot-manager-internal-contracts.ts +2 -2
- package/src/runtime/spots/spot-manager-public.ts +2 -2
- package/src/runtime/spots/spot-node-autoconnect.ts +28 -47
- package/src/runtime/spots/spot-node-runtime-manager.ts +197 -67
- package/src/runtime/spots/spot-outbound.ts +39 -19
- package/src/runtime/spots/spot-remote-codec.ts +9 -2
- package/src/runtime/spots/spot-routed-spot-packet-dispatch.ts +2 -1
- package/src/runtime/spots/spot-runtime-ports.ts +1 -1
- package/src/runtime/spots/spot-serial-executor.ts +97 -25
- package/src/runtime/spots/spot-serial-turn-executor.ts +22 -3
- package/src/runtime/spots/spot-subscription-dispatch.ts +1 -4
- package/src/runtime/spots/spot-timer.ts +418 -158
- package/src/runtime/streams/actor-session-binding-registry.ts +236 -64
- package/src/runtime/streams/actor-session-binding-runtime-owner.ts +16 -8
- package/src/runtime/streams/bound-actor-relay-sender.ts +23 -9
- package/src/runtime/streams/bound-session-service.ts +7 -10
- package/src/runtime/streams/index.ts +12 -6
- package/src/runtime/streams/managed-stream.ts +4 -2
- package/src/runtime/streams/native-fallback-bound-session.ts +14 -103
- package/src/runtime/streams/session-actor-coordinator.ts +39 -88
- package/src/runtime/streams/stream-binding-runtime-ports.ts +3 -9
- package/src/runtime/streams/stream-session-runtime.ts +13 -34
- package/src/runtime/workers/index.ts +1 -1
|
@@ -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,16 @@ 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';
|
|
16
|
-
import {
|
|
17
|
+
import { SERVICE_WIRE_PREFIX_SIZE } from './service-wire-binary-primitives';
|
|
18
|
+
import {
|
|
19
|
+
OperationRegistry,
|
|
20
|
+
OperationTimeoutError,
|
|
21
|
+
type OperationObserver,
|
|
22
|
+
type PendingOperation
|
|
23
|
+
} from './operation-registry';
|
|
17
24
|
import { ServiceLivenessRegistry, type ServiceLivenessTick } from './service-liveness-registry';
|
|
18
25
|
import { ServiceMailbox, type ServiceMailboxRecord } from './service-mailbox';
|
|
19
26
|
import {
|
|
20
|
-
sameServiceNodeDescriptor,
|
|
21
27
|
ServiceTopologyRegistry,
|
|
22
28
|
validateDescriptor,
|
|
23
29
|
type AdmittedServicePeer,
|
|
@@ -67,6 +73,19 @@ export interface RawServiceRequestResult {
|
|
|
67
73
|
readonly payload?: ServiceApplicationPayload;
|
|
68
74
|
}
|
|
69
75
|
|
|
76
|
+
/** Receives observed request terminals; the context is the caller's operation kind. */
|
|
77
|
+
export type RawServiceRequestObserver = OperationObserver<RawServiceRequestResult, number>;
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* A registered request. An observed remote request has no Promise; its
|
|
81
|
+
* terminal reaches the observer. Local and Promise-contract requests carry
|
|
82
|
+
* the Promise.
|
|
83
|
+
*/
|
|
84
|
+
export interface RawServiceObservedRequest {
|
|
85
|
+
readonly id: bigint;
|
|
86
|
+
readonly promise?: Promise<RawServiceRequestResult>;
|
|
87
|
+
}
|
|
88
|
+
|
|
70
89
|
type RawServiceChannelTargetSelection =
|
|
71
90
|
| { readonly kind: 'selected'; readonly peer: AdmittedServicePeer }
|
|
72
91
|
| {
|
|
@@ -84,6 +103,9 @@ type RawServiceChannelTargetSelection =
|
|
|
84
103
|
type ServiceApplicationPayloadInput = ServiceApplicationPayload | Buffer;
|
|
85
104
|
|
|
86
105
|
export interface RawServiceIngressRecord {
|
|
106
|
+
/** Source identity recorded once by the first accepting owner (Location runtime §9.3). */
|
|
107
|
+
requestSource?: import('../locations/request-source-resolver').ZLinkRequestSourceFence;
|
|
108
|
+
readonly onAdmitted?: () => void;
|
|
87
109
|
readonly command: number;
|
|
88
110
|
readonly flags: number;
|
|
89
111
|
readonly sourceRoutingId: string;
|
|
@@ -129,6 +151,7 @@ export interface RawServiceMeshRuntimeOptions {
|
|
|
129
151
|
// Framework error code 13 (RequestTargetNotFound) is encoded as 14 on a
|
|
130
152
|
// RequestResult.NotFound reply. Boundary transport results keep failureCode 0.
|
|
131
153
|
const RAW_MESH_RECEIVE_RECORD_BUDGET = 64;
|
|
154
|
+
const NODE_SEND_HEADER = encodeNodeSendHeader();
|
|
132
155
|
const RAW_MESH_RECEIVE_BYTE_BUDGET = 4 * 1024 * 1024;
|
|
133
156
|
export const RAW_MESH_RECEIVE_TIME_BUDGET_MS = 2;
|
|
134
157
|
|
|
@@ -140,6 +163,14 @@ const livenessCodec = createServiceWireCodec({
|
|
|
140
163
|
commands: M6aServiceWireCommand
|
|
141
164
|
});
|
|
142
165
|
|
|
166
|
+
interface ControlSendState {
|
|
167
|
+
task?: Promise<boolean>;
|
|
168
|
+
next?: () => boolean | Promise<boolean>;
|
|
169
|
+
current?: () => boolean;
|
|
170
|
+
// Admission owns the first HELLO until ADMIT or route termination.
|
|
171
|
+
hello?: Uint8Array;
|
|
172
|
+
}
|
|
173
|
+
|
|
143
174
|
/**
|
|
144
175
|
* RouteMesh M6A runtime built only on the public raw binding package.
|
|
145
176
|
* It owns protocol, admission, mailbox, request and liveness state.
|
|
@@ -149,7 +180,7 @@ export class RawServiceMeshRuntime {
|
|
|
149
180
|
readonly mailbox: ServiceMailbox;
|
|
150
181
|
readonly liveness: ServiceLivenessRegistry;
|
|
151
182
|
|
|
152
|
-
private readonly operations: OperationRegistry<RawServiceRequestResult>;
|
|
183
|
+
private readonly operations: OperationRegistry<RawServiceRequestResult, number>;
|
|
153
184
|
private readonly expectedPeers = new Map<
|
|
154
185
|
string,
|
|
155
186
|
{
|
|
@@ -158,11 +189,11 @@ export class RawServiceMeshRuntime {
|
|
|
158
189
|
readonly endpoint?: string;
|
|
159
190
|
readonly securityIdentity?: string;
|
|
160
191
|
readonly lifecycleGeneration?: bigint;
|
|
161
|
-
helloSubmittedGeneration?: bigint;
|
|
162
192
|
}
|
|
163
193
|
>();
|
|
164
194
|
private readonly endpointOnlyPeers = new Set<string>();
|
|
165
195
|
private readonly peerConnectionIntentRemoved = new Set<(nodeRoutingId: string) => void>();
|
|
196
|
+
private readonly peerRouteChanged = new Set<(nodeRoutingId: string) => void>();
|
|
166
197
|
/**
|
|
167
198
|
* The last observed Core selected route of each RID (Core ROUTER §10.1):
|
|
168
199
|
* RID -> route generation. Only `observeSelectedRoutes` replaces it.
|
|
@@ -170,18 +201,13 @@ export class RawServiceMeshRuntime {
|
|
|
170
201
|
private selectedRoutes = new Map<string, bigint>();
|
|
171
202
|
private readonly bindingPort: ZLinkRawBindingPort;
|
|
172
203
|
private readonly applicationJobQueue: ApplicationJobQueuePort;
|
|
204
|
+
private readonly nodeMailboxOwner: string;
|
|
173
205
|
private readonly peerAdmissionSealed?: () => boolean;
|
|
174
206
|
private readonly applicationJobStop = new AbortController();
|
|
175
207
|
private receiveAcquisition?: Promise<ApplicationJobPermitPort>;
|
|
176
208
|
private receivePermit?: ApplicationJobPermitPort;
|
|
177
|
-
private
|
|
178
|
-
|
|
179
|
-
{
|
|
180
|
-
readonly task: Promise<boolean>;
|
|
181
|
-
next?: () => boolean | Promise<boolean>;
|
|
182
|
-
current?: () => boolean;
|
|
183
|
-
}
|
|
184
|
-
>();
|
|
209
|
+
private receiveRotation?: ApplicationJobReceiveRotationPort;
|
|
210
|
+
private readonly controlSends = new Map<string, ControlSendState>();
|
|
185
211
|
private infrastructureFailure?: { readonly error: unknown };
|
|
186
212
|
private readonly onReceiveReady?: () => void;
|
|
187
213
|
private readonly infrastructureTaskRunner?: RawServiceMeshRuntimeOptions['infrastructureTaskRunner'];
|
|
@@ -195,6 +221,7 @@ export class RawServiceMeshRuntime {
|
|
|
195
221
|
private router?: ZLinkRawRouterPort;
|
|
196
222
|
private nextCorrelation = 1n;
|
|
197
223
|
private serviceIngress?: RawServiceIngressHandler;
|
|
224
|
+
private serviceIngressAccepts?: (command: number) => boolean;
|
|
198
225
|
private closed = false;
|
|
199
226
|
|
|
200
227
|
constructor(options: RawServiceMeshRuntimeOptions) {
|
|
@@ -202,6 +229,7 @@ export class RawServiceMeshRuntime {
|
|
|
202
229
|
this.infrastructureTaskRunner = options.infrastructureTaskRunner;
|
|
203
230
|
this.operations = new OperationRegistry(undefined, options.onPendingOperationsChanged);
|
|
204
231
|
this.descriptor = options.descriptor;
|
|
232
|
+
this.nodeMailboxOwner = `node:${options.descriptor.nodeRoutingId}`;
|
|
205
233
|
this.topology = new ServiceTopologyRegistry(options.descriptor);
|
|
206
234
|
this.mailbox = new ServiceMailbox(options.onMailboxReady);
|
|
207
235
|
this.liveness = new ServiceLivenessRegistry(options.probeIntervalMs, options.peerTimeoutMs);
|
|
@@ -331,10 +359,12 @@ export class RawServiceMeshRuntime {
|
|
|
331
359
|
if (router.disconnectRid !== undefined) {
|
|
332
360
|
router.disconnectRid(nodeRoutingId);
|
|
333
361
|
} else {
|
|
334
|
-
|
|
362
|
+
try {
|
|
363
|
+
router.disconnect(endpoint);
|
|
364
|
+
} catch (error) {
|
|
365
|
+
if (!isAlreadyDisconnectedError(error)) throw error;
|
|
366
|
+
}
|
|
335
367
|
}
|
|
336
|
-
} catch (error) {
|
|
337
|
-
if (!isAlreadyDisconnectedError(error)) throw error;
|
|
338
368
|
} finally {
|
|
339
369
|
if (current !== undefined) this.removePeer(current);
|
|
340
370
|
this.topology.forgetNotRequired(nodeRoutingId);
|
|
@@ -380,6 +410,65 @@ export class RawServiceMeshRuntime {
|
|
|
380
410
|
};
|
|
381
411
|
}
|
|
382
412
|
|
|
413
|
+
observePeerRoute(
|
|
414
|
+
nodeRoutingId: string
|
|
415
|
+
): import('./durable-service-request').PeerRouteObservation {
|
|
416
|
+
const selected = this.selectedRoutes.get(nodeRoutingId);
|
|
417
|
+
const admitted = this.topology.admittedPeer(nodeRoutingId)?.liveness?.connectionId;
|
|
418
|
+
return {
|
|
419
|
+
generation: selected ?? (typeof admitted === 'bigint' ? admitted : undefined),
|
|
420
|
+
ready: admitted !== undefined && (selected === undefined || admitted === selected)
|
|
421
|
+
};
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
async waitForPeerRoute(
|
|
425
|
+
nodeRoutingId: string,
|
|
426
|
+
lifecycleGeneration: bigint,
|
|
427
|
+
deadlineUnixMs: bigint,
|
|
428
|
+
signal?: AbortSignal
|
|
429
|
+
): Promise<void> {
|
|
430
|
+
const remaining = deadlineUnixMs - BigInt(Date.now());
|
|
431
|
+
if (remaining <= 0n) throw new OperationTimeoutError(0n);
|
|
432
|
+
const pending = this.operations.register(Number(remaining));
|
|
433
|
+
const check = () => {
|
|
434
|
+
if (this.isPeerRouteReady(nodeRoutingId, lifecycleGeneration)) {
|
|
435
|
+
this.operations.complete(pending.id, { terminalResult: RequestResult.Ok, failureCode: 0 });
|
|
436
|
+
}
|
|
437
|
+
};
|
|
438
|
+
const unsubscribe = this.observePeerRouteChanged((rid) => {
|
|
439
|
+
if (rid === nodeRoutingId) check();
|
|
440
|
+
});
|
|
441
|
+
const abort = () => this.operations.fail(pending.id, signal?.reason);
|
|
442
|
+
signal?.addEventListener('abort', abort, { once: true });
|
|
443
|
+
try {
|
|
444
|
+
if (signal?.aborted === true) abort();
|
|
445
|
+
else check();
|
|
446
|
+
await pending.promise;
|
|
447
|
+
} finally {
|
|
448
|
+
unsubscribe();
|
|
449
|
+
signal?.removeEventListener('abort', abort);
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
get requestStopSignal(): AbortSignal {
|
|
454
|
+
return this.applicationJobStop.signal;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
observePeerRouteChanged(listener: (nodeRoutingId: string) => void): () => void {
|
|
458
|
+
this.peerRouteChanged.add(listener);
|
|
459
|
+
return () => {
|
|
460
|
+
this.peerRouteChanged.delete(listener);
|
|
461
|
+
};
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
hasSelectedPeerRoute(nodeRoutingId: string): boolean {
|
|
465
|
+
return this.selectedRoutes.has(nodeRoutingId);
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
private notifyPeerRouteChanged(nodeRoutingId: string): void {
|
|
469
|
+
for (const listener of this.peerRouteChanged) listener(nodeRoutingId);
|
|
470
|
+
}
|
|
471
|
+
|
|
383
472
|
private notifyPeerConnectionIntentRemoved(nodeRoutingId: string): void {
|
|
384
473
|
// Only the logical owner's removal publishes lifecycle termination.
|
|
385
474
|
// A physical disconnect alone retains the connection expectation.
|
|
@@ -388,42 +477,46 @@ export class RawServiceMeshRuntime {
|
|
|
388
477
|
for (const listener of this.peerConnectionIntentRemoved) listener(nodeRoutingId);
|
|
389
478
|
}
|
|
390
479
|
|
|
391
|
-
announcePeer(nodeRoutingId: string):
|
|
392
|
-
|
|
480
|
+
announcePeer(nodeRoutingId: string): boolean {
|
|
481
|
+
if (this.closed) return false;
|
|
393
482
|
const generation = this.selectedRoutes.get(nodeRoutingId);
|
|
394
|
-
if (
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
const
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
483
|
+
if (generation === undefined) return false;
|
|
484
|
+
const key = controlSendKey(nodeRoutingId, M6aServiceWireCommand.hello);
|
|
485
|
+
if (!this.isPeerAnnouncementCurrent(nodeRoutingId, generation)) {
|
|
486
|
+
this.controlSends.delete(key);
|
|
487
|
+
return false;
|
|
488
|
+
}
|
|
489
|
+
const current = () => this.isPeerAnnouncementCurrent(nodeRoutingId, generation);
|
|
490
|
+
const existing = this.controlSends.get(key);
|
|
491
|
+
const hello =
|
|
492
|
+
existing?.current?.() === true
|
|
493
|
+
? existing.hello!
|
|
494
|
+
: encodeRouteMeshAdmission(M6aServiceWireCommand.hello, this.topology.localDescriptor());
|
|
495
|
+
return this.startControlSend(
|
|
496
|
+
nodeRoutingId,
|
|
497
|
+
M6aServiceWireCommand.hello,
|
|
498
|
+
() => this.submitControl(nodeRoutingId, [hello]),
|
|
499
|
+
current,
|
|
500
|
+
hello
|
|
501
|
+
);
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
private isPeerAnnouncementCurrent(nodeRoutingId: string, generation: bigint): boolean {
|
|
505
|
+
const admission = this.topology.routeAdmission(nodeRoutingId);
|
|
506
|
+
return (
|
|
507
|
+
this.selectedRoutes.get(nodeRoutingId) === generation &&
|
|
508
|
+
!(
|
|
509
|
+
admission?.connectionId === routeConnectionId(generation) &&
|
|
510
|
+
admission.remoteAdmitReceived === true
|
|
511
|
+
) &&
|
|
512
|
+
this.peerAdmissionSealed?.() !== true
|
|
420
513
|
);
|
|
421
514
|
}
|
|
422
515
|
|
|
423
516
|
isPeerRouteReady(nodeRoutingId: string, lifecycleGeneration?: bigint): boolean {
|
|
424
517
|
// Admission is bound to the observed selected route: the route observer
|
|
425
518
|
// removes the peer when that route ends or is replaced.
|
|
426
|
-
const peer = this.topology.
|
|
519
|
+
const peer = this.topology.admittedPeer(nodeRoutingId);
|
|
427
520
|
return (
|
|
428
521
|
peer !== undefined &&
|
|
429
522
|
(lifecycleGeneration === undefined ||
|
|
@@ -470,12 +563,18 @@ export class RawServiceMeshRuntime {
|
|
|
470
563
|
// placement and channel selectors therefore need the new mutable fields
|
|
471
564
|
// on that same connection before the next request is selected; waiting for
|
|
472
565
|
// a reconnect would leave a race where a drained node is selected again.
|
|
566
|
+
await this.publishLocalDescriptor(
|
|
567
|
+
this.topology.peers().map((peer) => peer.descriptor.nodeRoutingId)
|
|
568
|
+
);
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
private async publishLocalDescriptor(targets: readonly string[]): Promise<void> {
|
|
473
572
|
const update = encodeRouteMeshAdmission(
|
|
474
573
|
M6aServiceWireCommand.update,
|
|
475
574
|
this.topology.localDescriptor()
|
|
476
575
|
);
|
|
477
|
-
for (const
|
|
478
|
-
await this.
|
|
576
|
+
for (const target of targets) {
|
|
577
|
+
await this.sendService(target, [update]);
|
|
479
578
|
}
|
|
480
579
|
}
|
|
481
580
|
|
|
@@ -484,18 +583,14 @@ export class RawServiceMeshRuntime {
|
|
|
484
583
|
}
|
|
485
584
|
|
|
486
585
|
isObjectClientNodeDirectTarget(nodeRoutingId: string): boolean {
|
|
487
|
-
|
|
488
|
-
nodeRoutingId === this.descriptor.nodeRoutingId
|
|
489
|
-
? this.topology.localDescriptor()
|
|
490
|
-
: this.topology.knownDescriptor(nodeRoutingId);
|
|
491
|
-
return descriptor?.objectRole === 'client';
|
|
586
|
+
return this.topology.isObjectClientNodeDirectTarget(nodeRoutingId);
|
|
492
587
|
}
|
|
493
588
|
|
|
494
|
-
|
|
589
|
+
sendToNode(
|
|
495
590
|
targetNodeRoutingId: string,
|
|
496
591
|
payload: ServiceApplicationPayloadInput
|
|
497
|
-
): Promise<boolean> {
|
|
498
|
-
return this.send(targetNodeRoutingId, [
|
|
592
|
+
): boolean | Promise<boolean> {
|
|
593
|
+
return this.send(targetNodeRoutingId, [NODE_SEND_HEADER, this.applicationFrame(payload)]);
|
|
499
594
|
}
|
|
500
595
|
|
|
501
596
|
async sendToChannel(
|
|
@@ -517,30 +612,108 @@ export class RawServiceMeshRuntime {
|
|
|
517
612
|
requestToNode(
|
|
518
613
|
targetNodeRoutingId: string,
|
|
519
614
|
payload: ServiceApplicationPayloadInput,
|
|
520
|
-
timeoutMs: number
|
|
521
|
-
|
|
522
|
-
|
|
615
|
+
timeoutMs: number,
|
|
616
|
+
onAdmitted?: () => void
|
|
617
|
+
): PendingOperation<RawServiceRequestResult>;
|
|
618
|
+
requestToNode(
|
|
619
|
+
targetNodeRoutingId: string,
|
|
620
|
+
payload: ServiceApplicationPayloadInput,
|
|
621
|
+
timeoutMs: number,
|
|
622
|
+
onAdmitted: (() => void) | undefined,
|
|
623
|
+
observer: RawServiceRequestObserver,
|
|
624
|
+
operationKind: number
|
|
625
|
+
): RawServiceObservedRequest;
|
|
626
|
+
requestToNode(
|
|
627
|
+
targetNodeRoutingId: string,
|
|
628
|
+
payload: ServiceApplicationPayloadInput,
|
|
629
|
+
timeoutMs: number,
|
|
630
|
+
onAdmitted?: () => void,
|
|
631
|
+
observer?: RawServiceRequestObserver,
|
|
632
|
+
operationKind?: number
|
|
633
|
+
): RawServiceObservedRequest {
|
|
634
|
+
return this.requestToTarget(
|
|
635
|
+
targetNodeRoutingId,
|
|
636
|
+
payload,
|
|
637
|
+
timeoutMs,
|
|
638
|
+
undefined,
|
|
639
|
+
onAdmitted,
|
|
640
|
+
observer,
|
|
641
|
+
operationKind
|
|
642
|
+
);
|
|
523
643
|
}
|
|
524
644
|
|
|
525
645
|
requestToChannel(
|
|
526
646
|
channelName: string,
|
|
527
647
|
payload: ServiceApplicationPayloadInput,
|
|
528
|
-
timeoutMs: number
|
|
529
|
-
|
|
648
|
+
timeoutMs: number,
|
|
649
|
+
onAdmitted?: () => void
|
|
650
|
+
): PendingOperation<RawServiceRequestResult>;
|
|
651
|
+
requestToChannel(
|
|
652
|
+
channelName: string,
|
|
653
|
+
payload: ServiceApplicationPayloadInput,
|
|
654
|
+
timeoutMs: number,
|
|
655
|
+
onAdmitted: (() => void) | undefined,
|
|
656
|
+
observer: RawServiceRequestObserver,
|
|
657
|
+
operationKind: number
|
|
658
|
+
): RawServiceObservedRequest;
|
|
659
|
+
requestToChannel(
|
|
660
|
+
channelName: string,
|
|
661
|
+
payload: ServiceApplicationPayloadInput,
|
|
662
|
+
timeoutMs: number,
|
|
663
|
+
onAdmitted?: () => void,
|
|
664
|
+
observer?: RawServiceRequestObserver,
|
|
665
|
+
operationKind?: number
|
|
666
|
+
): RawServiceObservedRequest {
|
|
530
667
|
const selection = this.selectChannelTarget(channelName);
|
|
531
668
|
if (selection.kind !== 'selected') {
|
|
532
|
-
const pending = this.
|
|
533
|
-
this.
|
|
669
|
+
const pending = this.registerRequest(timeoutMs, observer, operationKind);
|
|
670
|
+
this.completeRequest(pending, selection.requestResult);
|
|
534
671
|
return pending;
|
|
535
672
|
}
|
|
536
673
|
return this.requestToTarget(
|
|
537
674
|
selection.peer.descriptor.nodeRoutingId,
|
|
538
675
|
payload,
|
|
539
676
|
timeoutMs,
|
|
540
|
-
channelName
|
|
677
|
+
channelName,
|
|
678
|
+
onAdmitted,
|
|
679
|
+
observer,
|
|
680
|
+
operationKind
|
|
541
681
|
);
|
|
542
682
|
}
|
|
543
683
|
|
|
684
|
+
/**
|
|
685
|
+
* One pending entry per request: an observed request reports its terminal
|
|
686
|
+
* to the caller's observer; any other request keeps the Promise contract.
|
|
687
|
+
*/
|
|
688
|
+
private registerRequest(
|
|
689
|
+
timeoutMs: number,
|
|
690
|
+
observer: RawServiceRequestObserver | undefined,
|
|
691
|
+
operationKind: number | undefined
|
|
692
|
+
): RawServiceObservedRequest {
|
|
693
|
+
return observer === undefined
|
|
694
|
+
? this.operations.register(timeoutMs)
|
|
695
|
+
: { id: this.operations.registerObserved(timeoutMs, observer, operationKind) };
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
/**
|
|
699
|
+
* An observed caller learns the operation id only when the request call
|
|
700
|
+
* returns, so a terminal decided inside that call is delivered after it.
|
|
701
|
+
*/
|
|
702
|
+
private completeRequest(
|
|
703
|
+
pending: RawServiceObservedRequest,
|
|
704
|
+
result: RawServiceRequestResult
|
|
705
|
+
): void {
|
|
706
|
+
if (pending.promise === undefined)
|
|
707
|
+
queueMicrotask(() => this.operations.complete(pending.id, result));
|
|
708
|
+
else this.operations.complete(pending.id, result);
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
private failRequest(pending: RawServiceObservedRequest, error: unknown): void {
|
|
712
|
+
if (pending.promise === undefined)
|
|
713
|
+
queueMicrotask(() => this.operations.fail(pending.id, error));
|
|
714
|
+
else this.operations.fail(pending.id, error);
|
|
715
|
+
}
|
|
716
|
+
|
|
544
717
|
private selectChannelTarget(channelName: string): RawServiceChannelTargetSelection {
|
|
545
718
|
const selected = this.topology.selectChannel(channelName, (peer) =>
|
|
546
719
|
this.isPeerRouteReady(peer.descriptor.nodeRoutingId)
|
|
@@ -562,11 +735,15 @@ export class RawServiceMeshRuntime {
|
|
|
562
735
|
};
|
|
563
736
|
}
|
|
564
737
|
|
|
565
|
-
setServiceIngress(
|
|
738
|
+
setServiceIngress(
|
|
739
|
+
handler: RawServiceIngressHandler,
|
|
740
|
+
accepts?: (command: number) => boolean
|
|
741
|
+
): void {
|
|
566
742
|
if (this.serviceIngress !== undefined && this.serviceIngress !== handler) {
|
|
567
743
|
throw new Error('Raw service ingress is already registered.');
|
|
568
744
|
}
|
|
569
745
|
this.serviceIngress = handler;
|
|
746
|
+
this.serviceIngressAccepts = accepts;
|
|
570
747
|
}
|
|
571
748
|
|
|
572
749
|
async reserveLocalIngress(signal?: AbortSignal): Promise<ApplicationIngressRecordOwner> {
|
|
@@ -579,20 +756,27 @@ export class RawServiceMeshRuntime {
|
|
|
579
756
|
}
|
|
580
757
|
|
|
581
758
|
async sendService(targetNodeRoutingId: string, parts: readonly Uint8Array[]): Promise<boolean> {
|
|
582
|
-
|
|
759
|
+
try {
|
|
760
|
+
await this.requireStarted().send(targetNodeRoutingId, parts);
|
|
761
|
+
return true;
|
|
762
|
+
} catch (error) {
|
|
763
|
+
return this.sendFailure(error);
|
|
764
|
+
}
|
|
583
765
|
}
|
|
584
766
|
|
|
585
767
|
requestService(
|
|
586
768
|
targetNodeRoutingId: string,
|
|
587
769
|
parts: readonly Uint8Array[],
|
|
588
|
-
timeoutMs: number
|
|
770
|
+
timeoutMs: number,
|
|
771
|
+
onAdmitted?: () => void
|
|
589
772
|
): Promise<readonly Buffer[]> {
|
|
590
|
-
const admission = this.topology.
|
|
773
|
+
const admission = this.topology.admittedPeer(targetNodeRoutingId)?.liveness;
|
|
591
774
|
return this.requireStarted().request(
|
|
592
775
|
targetNodeRoutingId,
|
|
593
776
|
parts,
|
|
594
777
|
timeoutMs,
|
|
595
|
-
admission?.recordReceived
|
|
778
|
+
admission?.recordReceived,
|
|
779
|
+
onAdmitted
|
|
596
780
|
);
|
|
597
781
|
}
|
|
598
782
|
|
|
@@ -668,59 +852,71 @@ export class RawServiceMeshRuntime {
|
|
|
668
852
|
const observe: RawServicePumpObserver = (_source, byteCount) => {
|
|
669
853
|
bytes += byteCount;
|
|
670
854
|
};
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
855
|
+
const rotation = (this.receiveRotation ??= this.applicationJobQueue.createReceiveRotation(
|
|
856
|
+
RAW_MESH_RECEIVE_RECORD_BUDGET,
|
|
857
|
+
'remote'
|
|
858
|
+
));
|
|
859
|
+
if (receiveReady) rotation.reserve();
|
|
860
|
+
try {
|
|
861
|
+
while (receiveReady && messages < RAW_MESH_RECEIVE_RECORD_BUDGET && !this.closed) {
|
|
862
|
+
const received = this.receiveOne(performance.now(), observe, rotation);
|
|
863
|
+
const result = received instanceof Promise ? await received : received;
|
|
864
|
+
if (result === 'noData') {
|
|
865
|
+
receiveReady = false;
|
|
866
|
+
break;
|
|
867
|
+
}
|
|
868
|
+
messages += 1;
|
|
869
|
+
// Core owns the per-peer fair-queue cursor. The Framework limit bounds
|
|
870
|
+
// the entire round, so changing peers does not renew its byte/count budget.
|
|
871
|
+
if (
|
|
872
|
+
bytes >= RAW_MESH_RECEIVE_BYTE_BUDGET ||
|
|
873
|
+
performance.now() - startedAtMs >= RAW_MESH_RECEIVE_TIME_BUDGET_MS
|
|
874
|
+
)
|
|
875
|
+
break;
|
|
676
876
|
}
|
|
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;
|
|
877
|
+
} finally {
|
|
878
|
+
rotation.releaseUnused();
|
|
685
879
|
}
|
|
686
880
|
if (!this.closed) {
|
|
687
|
-
for (const nodeRoutingId of this.expectedPeers.keys()) {
|
|
688
|
-
if (this.topology.peer(nodeRoutingId) === undefined) await this.announcePeer(nodeRoutingId);
|
|
689
|
-
}
|
|
690
881
|
this.tickLiveness();
|
|
691
882
|
}
|
|
692
883
|
return receiveReady && messages > 0;
|
|
693
884
|
}
|
|
694
885
|
|
|
695
|
-
private
|
|
886
|
+
private receiveOne(
|
|
696
887
|
nowMs: number,
|
|
697
|
-
observe?: RawServicePumpObserver
|
|
698
|
-
|
|
888
|
+
observe?: RawServicePumpObserver,
|
|
889
|
+
rotation?: ApplicationJobReceiveRotationPort
|
|
890
|
+
): RawServicePumpResult | Promise<RawServicePumpResult> {
|
|
699
891
|
const router = this.requireStarted();
|
|
700
892
|
this.observeInfrastructureFailure();
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
const permit = this.applicationJobQueue.acquireOrResume(
|
|
893
|
+
let permit = this.receivePermit;
|
|
894
|
+
this.receivePermit = undefined;
|
|
895
|
+
permit ??= rotation?.take();
|
|
896
|
+
if (permit === undefined && this.receiveAcquisition === undefined) {
|
|
897
|
+
let resolveAcquisition: ((permit: ApplicationJobPermitPort) => void) | undefined;
|
|
898
|
+
let rejectAcquisition: ((error: unknown) => void) | undefined;
|
|
899
|
+
permit = this.applicationJobQueue.acquireOrResume(
|
|
709
900
|
(permit) => {
|
|
710
901
|
this.receiveAcquisition = undefined;
|
|
711
|
-
if (this.closed)
|
|
712
|
-
|
|
902
|
+
if (this.closed) {
|
|
903
|
+
permit.releaseAfterInternalProcessing();
|
|
904
|
+
permit.releaseStorage?.();
|
|
905
|
+
} else {
|
|
713
906
|
this.receivePermit = permit;
|
|
714
907
|
this.onReceiveReady?.();
|
|
715
908
|
}
|
|
716
|
-
resolveAcquisition(permit);
|
|
909
|
+
resolveAcquisition?.(permit);
|
|
717
910
|
},
|
|
718
|
-
rejectAcquisition,
|
|
911
|
+
(error) => rejectAcquisition?.(error),
|
|
719
912
|
this.applicationJobStop.signal,
|
|
720
913
|
'remote'
|
|
721
914
|
);
|
|
722
|
-
if (permit
|
|
723
|
-
|
|
915
|
+
if (permit === undefined) {
|
|
916
|
+
const acquisition = new Promise<ApplicationJobPermitPort>((resolve, reject) => {
|
|
917
|
+
resolveAcquisition = resolve;
|
|
918
|
+
rejectAcquisition = reject;
|
|
919
|
+
});
|
|
724
920
|
this.receiveAcquisition = acquisition;
|
|
725
921
|
acquisition.catch((error) => {
|
|
726
922
|
this.receiveAcquisition = undefined;
|
|
@@ -728,11 +924,10 @@ export class RawServiceMeshRuntime {
|
|
|
728
924
|
});
|
|
729
925
|
}
|
|
730
926
|
}
|
|
731
|
-
const permit = this.receivePermit;
|
|
732
|
-
this.receivePermit = undefined;
|
|
733
927
|
if (permit === undefined) return 'noData';
|
|
734
928
|
if (this.closed) {
|
|
735
929
|
permit.releaseAfterInternalProcessing();
|
|
930
|
+
permit.releaseStorage?.();
|
|
736
931
|
return 'noData';
|
|
737
932
|
}
|
|
738
933
|
let received: ZLinkRawReceivedRecord | undefined;
|
|
@@ -740,10 +935,12 @@ export class RawServiceMeshRuntime {
|
|
|
740
935
|
received = router.receive(true);
|
|
741
936
|
} catch (error) {
|
|
742
937
|
permit.releaseAfterInternalProcessing();
|
|
938
|
+
permit.releaseStorage?.();
|
|
743
939
|
throw error;
|
|
744
940
|
}
|
|
745
941
|
if (received === undefined) {
|
|
746
942
|
permit.releaseAfterInternalProcessing();
|
|
943
|
+
permit.releaseStorage?.();
|
|
747
944
|
return 'noData';
|
|
748
945
|
}
|
|
749
946
|
const applicationJobOwner = ApplicationIngressRecordOwner.create(
|
|
@@ -751,18 +948,40 @@ export class RawServiceMeshRuntime {
|
|
|
751
948
|
permit,
|
|
752
949
|
received
|
|
753
950
|
);
|
|
951
|
+
let result: RawServicePumpResult | Promise<RawServicePumpResult>;
|
|
754
952
|
try {
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
953
|
+
result = this.processReceived(received, nowMs, applicationJobOwner);
|
|
954
|
+
} catch (error) {
|
|
955
|
+
applicationJobOwner.close();
|
|
956
|
+
throw error;
|
|
957
|
+
}
|
|
958
|
+
return result instanceof Promise
|
|
959
|
+
? result.then(
|
|
960
|
+
(value) => this.finishReceive(received, applicationJobOwner, observe, value),
|
|
961
|
+
(error) => {
|
|
962
|
+
applicationJobOwner.close();
|
|
963
|
+
throw error;
|
|
964
|
+
}
|
|
965
|
+
)
|
|
966
|
+
: this.finishReceive(received, applicationJobOwner, observe, result);
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
private finishReceive(
|
|
970
|
+
received: ZLinkRawReceivedRecord,
|
|
971
|
+
owner: ApplicationIngressRecordOwner,
|
|
972
|
+
observe: RawServicePumpObserver | undefined,
|
|
973
|
+
result: RawServicePumpResult
|
|
974
|
+
): RawServicePumpResult {
|
|
975
|
+
try {
|
|
976
|
+
if (result === 'protocolError') this.reportProtocolError(received);
|
|
977
|
+
if (observe !== undefined) {
|
|
978
|
+
let bytes = 0;
|
|
979
|
+
for (const part of received.parts) bytes += part.byteLength;
|
|
980
|
+
observe(received.sourceRid, bytes);
|
|
758
981
|
}
|
|
759
|
-
observe?.(
|
|
760
|
-
received.sourceRid,
|
|
761
|
-
received.parts.reduce((sum, part) => sum + part.byteLength, 0)
|
|
762
|
-
);
|
|
763
982
|
return result;
|
|
764
983
|
} finally {
|
|
765
|
-
|
|
984
|
+
owner.close();
|
|
766
985
|
}
|
|
767
986
|
}
|
|
768
987
|
|
|
@@ -808,22 +1027,15 @@ export class RawServiceMeshRuntime {
|
|
|
808
1027
|
}
|
|
809
1028
|
}
|
|
810
1029
|
|
|
811
|
-
private
|
|
1030
|
+
private processReceived(
|
|
812
1031
|
received: ZLinkRawReceivedRecord,
|
|
813
1032
|
nowMs: number,
|
|
814
1033
|
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
|
-
}
|
|
1034
|
+
): RawServicePumpResult | Promise<RawServicePumpResult> {
|
|
1035
|
+
const peer = this.topology.admittedPeer(received.sourceRid);
|
|
1036
|
+
const currentSource =
|
|
1037
|
+
peer?.liveness !== undefined && peer.liveness.connectionId === received.routeGeneration;
|
|
1038
|
+
if (received.parts.length === 0) return currentSource ? 'protocolError' : 'dropped';
|
|
827
1039
|
try {
|
|
828
1040
|
const header = decodeHeader(received.parts[0]!);
|
|
829
1041
|
if (header.command === M6aServiceWireCommand.hello && this.peerAdmissionSealed?.() === true) {
|
|
@@ -831,107 +1043,187 @@ export class RawServiceMeshRuntime {
|
|
|
831
1043
|
}
|
|
832
1044
|
if (
|
|
833
1045
|
header.command === M6aServiceWireCommand.hello ||
|
|
834
|
-
header.command === M6aServiceWireCommand.admit
|
|
835
|
-
header.command === M6aServiceWireCommand.update
|
|
1046
|
+
header.command === M6aServiceWireCommand.admit
|
|
836
1047
|
) {
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
received.parts[0]!,
|
|
840
|
-
header.command,
|
|
841
|
-
received.sourceRid
|
|
1048
|
+
return this.processAdmission(received, header, nowMs).catch((error) =>
|
|
1049
|
+
this.receiveProtocolFailure(error)
|
|
842
1050
|
);
|
|
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
1051
|
}
|
|
884
1052
|
if (header.command === M6aServiceWireCommand.reject) {
|
|
885
1053
|
if (received.parts.length !== 1) return 'protocolError';
|
|
886
1054
|
decodeReject(received.parts[0]!);
|
|
887
1055
|
return 'infrastructure';
|
|
888
1056
|
}
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
if (
|
|
892
|
-
this.
|
|
1057
|
+
if (!currentSource) return 'dropped';
|
|
1058
|
+
this.liveness.recordReceived(peer.liveness, nowMs);
|
|
1059
|
+
if (header.command === M6aServiceWireCommand.update) {
|
|
1060
|
+
return this.processAdmission(received, header, nowMs).catch((error) =>
|
|
1061
|
+
this.receiveProtocolFailure(error)
|
|
1062
|
+
);
|
|
1063
|
+
}
|
|
893
1064
|
if (
|
|
894
1065
|
header.command === M6aServiceWireCommand.livenessProbe ||
|
|
895
1066
|
header.command === M6aServiceWireCommand.livenessAck
|
|
896
1067
|
) {
|
|
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';
|
|
1068
|
+
return this.processLiveness(received, nowMs).catch((error) =>
|
|
1069
|
+
this.receiveProtocolFailure(error)
|
|
1070
|
+
);
|
|
922
1071
|
}
|
|
923
|
-
const stateful =
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
1072
|
+
const stateful =
|
|
1073
|
+
this.serviceIngress !== undefined && this.serviceIngressAccepts?.(header.command) !== false
|
|
1074
|
+
? this.serviceIngress({
|
|
1075
|
+
command: header.command,
|
|
1076
|
+
flags: header.flags,
|
|
1077
|
+
sourceRoutingId: received.sourceRid,
|
|
1078
|
+
sourceNodeGeneration: peer.descriptor.lifecycleGeneration,
|
|
1079
|
+
sourceRoute: received.sourceRoute,
|
|
1080
|
+
...(received.reply === undefined ? {} : { reply: received.reply }),
|
|
1081
|
+
...(received.requestSeq === undefined
|
|
1082
|
+
? {}
|
|
1083
|
+
: { requestSequence: received.requestSeq }),
|
|
1084
|
+
parts: received.parts,
|
|
1085
|
+
applicationJobOwner
|
|
1086
|
+
})
|
|
1087
|
+
: undefined;
|
|
1088
|
+
if (stateful instanceof Promise)
|
|
1089
|
+
return stateful.then(
|
|
1090
|
+
(result) =>
|
|
1091
|
+
result ?? this.processApplicationReceived(received, header, applicationJobOwner),
|
|
1092
|
+
(error) => this.receiveProtocolFailure(error)
|
|
1093
|
+
);
|
|
934
1094
|
if (stateful !== undefined) return stateful;
|
|
1095
|
+
return this.processApplicationReceived(received, header, applicationJobOwner);
|
|
1096
|
+
} catch (error) {
|
|
1097
|
+
if (!currentSource && error instanceof ServiceWireProtocolError) return 'dropped';
|
|
1098
|
+
return this.receiveProtocolFailure(error);
|
|
1099
|
+
}
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1102
|
+
private receiveProtocolFailure(error: unknown): RawServicePumpResult {
|
|
1103
|
+
if (error instanceof ServiceWireProtocolError) return 'protocolError';
|
|
1104
|
+
throw error;
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1107
|
+
private async processAdmission(
|
|
1108
|
+
received: ZLinkRawReceivedRecord,
|
|
1109
|
+
header: ReturnType<typeof decodeHeader>,
|
|
1110
|
+
nowMs: number
|
|
1111
|
+
): Promise<RawServicePumpResult> {
|
|
1112
|
+
if (received.parts.length !== 1) return 'protocolError';
|
|
1113
|
+
const descriptor = decodeRouteMeshAdmission(
|
|
1114
|
+
received.parts[0]!,
|
|
1115
|
+
header.command,
|
|
1116
|
+
received.sourceRid
|
|
1117
|
+
);
|
|
1118
|
+
const expected = this.expectedPeers.get(received.sourceRid);
|
|
1119
|
+
if (
|
|
1120
|
+
expected !== undefined &&
|
|
1121
|
+
(expected.meshName !== descriptor.meshName ||
|
|
1122
|
+
expected.nodeRoutingId !== descriptor.nodeRoutingId)
|
|
1123
|
+
) {
|
|
1124
|
+
await this.sendControl(received.sourceRid, [
|
|
1125
|
+
encodeReject(enumWireRejectReason('identityMismatch'))
|
|
1126
|
+
]);
|
|
1127
|
+
return 'infrastructure';
|
|
1128
|
+
}
|
|
1129
|
+
const helloKey = controlSendKey(received.sourceRid, M6aServiceWireCommand.hello);
|
|
1130
|
+
const exchange = this.controlSends.get(helloKey);
|
|
1131
|
+
const result = this.admitPeer(
|
|
1132
|
+
descriptor,
|
|
1133
|
+
received.routeGeneration,
|
|
1134
|
+
nowMs,
|
|
1135
|
+
expected,
|
|
1136
|
+
header.command,
|
|
1137
|
+
received.parts[0]!.subarray(SERVICE_WIRE_PREFIX_SIZE)
|
|
1138
|
+
);
|
|
1139
|
+
if (result !== 'admitted' && result !== 'notRequired') {
|
|
1140
|
+
const detail =
|
|
1141
|
+
result === 'invalidDescriptor'
|
|
1142
|
+
? describeInvalidAdmissionDescriptor(
|
|
1143
|
+
descriptor,
|
|
1144
|
+
expected,
|
|
1145
|
+
this.topology.peer(descriptor.nodeRoutingId)?.descriptor
|
|
1146
|
+
)
|
|
1147
|
+
: undefined;
|
|
1148
|
+
console.warn(
|
|
1149
|
+
`RouteMesh admission rejected peer '${received.sourceRid}' as ${result}` +
|
|
1150
|
+
(detail === undefined ? '.' : ` (${detail}).`)
|
|
1151
|
+
);
|
|
1152
|
+
await this.sendControl(received.sourceRid, [encodeReject(admissionReason(result))]);
|
|
1153
|
+
return 'infrastructure';
|
|
1154
|
+
}
|
|
1155
|
+
if (result === 'admitted') this.selectBilateralConnection(descriptor);
|
|
1156
|
+
if (header.command === M6aServiceWireCommand.hello) {
|
|
1157
|
+
// Hello supplies only the sender's descriptor. Even NotRequired
|
|
1158
|
+
// peers must receive ours before the intent owner closes transport;
|
|
1159
|
+
// submitting a response does not guarantee delivery before close.
|
|
1160
|
+
const local =
|
|
1161
|
+
exchange?.current?.() === true && exchange.hello !== undefined
|
|
1162
|
+
? decodeRouteMeshAdmission(
|
|
1163
|
+
exchange.hello,
|
|
1164
|
+
M6aServiceWireCommand.hello,
|
|
1165
|
+
this.descriptor.nodeRoutingId
|
|
1166
|
+
)
|
|
1167
|
+
: this.topology.localDescriptor();
|
|
1168
|
+
await this.sendControl(received.sourceRid, [
|
|
1169
|
+
encodeRouteMeshAdmission(M6aServiceWireCommand.admit, local)
|
|
1170
|
+
]);
|
|
1171
|
+
} else if (header.command === M6aServiceWireCommand.admit) {
|
|
1172
|
+
this.controlSends.delete(helloKey);
|
|
1173
|
+
this.refreshAdmissionPending(nowMs);
|
|
1174
|
+
if (result === 'notRequired') {
|
|
1175
|
+
this.retireNotRequiredExpectedPeer(received.sourceRid, descriptor.advertisedEndpoint);
|
|
1176
|
+
} else if (
|
|
1177
|
+
exchange?.hello !== undefined &&
|
|
1178
|
+
decodeRouteMeshAdmission(
|
|
1179
|
+
exchange.hello,
|
|
1180
|
+
M6aServiceWireCommand.hello,
|
|
1181
|
+
this.descriptor.nodeRoutingId
|
|
1182
|
+
).descriptorRevision < this.descriptor.descriptorRevision
|
|
1183
|
+
) {
|
|
1184
|
+
await this.publishLocalDescriptor([received.sourceRid]);
|
|
1185
|
+
}
|
|
1186
|
+
}
|
|
1187
|
+
return 'infrastructure';
|
|
1188
|
+
}
|
|
1189
|
+
|
|
1190
|
+
private async processLiveness(
|
|
1191
|
+
received: ZLinkRawReceivedRecord,
|
|
1192
|
+
nowMs: number
|
|
1193
|
+
): Promise<RawServicePumpResult> {
|
|
1194
|
+
if (received.parts.length !== 1) return 'protocolError';
|
|
1195
|
+
const record = livenessCodec.decodeLivenessRecord(received.parts[0]!);
|
|
1196
|
+
if (record.command === M6aServiceWireCommand.livenessProbe) {
|
|
1197
|
+
const ack = this.liveness.acknowledgeProbe(
|
|
1198
|
+
received.sourceRid,
|
|
1199
|
+
received.routeGeneration,
|
|
1200
|
+
record.probeId
|
|
1201
|
+
);
|
|
1202
|
+
if (ack === undefined) return 'protocolError';
|
|
1203
|
+
const sent = await this.sendControl(received.sourceRid, [
|
|
1204
|
+
livenessCodec.encodeLivenessRecord({
|
|
1205
|
+
command: M6aServiceWireCommand.livenessAck,
|
|
1206
|
+
probeId: record.probeId
|
|
1207
|
+
})
|
|
1208
|
+
]);
|
|
1209
|
+
if (!sent) return 'dropped';
|
|
1210
|
+
} else {
|
|
1211
|
+
this.liveness.acknowledge(
|
|
1212
|
+
received.sourceRid,
|
|
1213
|
+
received.routeGeneration,
|
|
1214
|
+
record.probeId,
|
|
1215
|
+
nowMs
|
|
1216
|
+
);
|
|
1217
|
+
}
|
|
1218
|
+
return 'infrastructure';
|
|
1219
|
+
}
|
|
1220
|
+
|
|
1221
|
+
private processApplicationReceived(
|
|
1222
|
+
received: ZLinkRawReceivedRecord,
|
|
1223
|
+
header: ReturnType<typeof decodeHeader>,
|
|
1224
|
+
applicationJobOwner: ApplicationIngressRecordOwner
|
|
1225
|
+
): RawServicePumpResult | Promise<RawServicePumpResult> {
|
|
1226
|
+
try {
|
|
935
1227
|
if (
|
|
936
1228
|
header.flags === 0 &&
|
|
937
1229
|
// A bare infrastructure control is normally exactly one frame, but a
|
|
@@ -949,7 +1241,7 @@ export class RawServiceMeshRuntime {
|
|
|
949
1241
|
isBareInfrastructureControl(header.command)
|
|
950
1242
|
) {
|
|
951
1243
|
const accepted = this.mailbox.tryEnqueue({
|
|
952
|
-
owner:
|
|
1244
|
+
owner: this.nodeMailboxOwner,
|
|
953
1245
|
domain: 'infrastructure',
|
|
954
1246
|
parts: received.parts,
|
|
955
1247
|
sourceRoutingId: received.sourceRid,
|
|
@@ -962,62 +1254,67 @@ export class RawServiceMeshRuntime {
|
|
|
962
1254
|
if (
|
|
963
1255
|
header.flags !== 0 ||
|
|
964
1256
|
received.parts.length !== 2 ||
|
|
965
|
-
!
|
|
966
|
-
M6aServiceWireCommand.nodeSend,
|
|
967
|
-
M6aServiceWireCommand.nodeRequest,
|
|
968
|
-
M6aServiceWireCommand.channelSend,
|
|
969
|
-
M6aServiceWireCommand.channelRequest
|
|
970
|
-
].includes(header.command as never)
|
|
1257
|
+
!isApplicationEnvelopeCommand(header.command)
|
|
971
1258
|
) {
|
|
972
1259
|
return 'protocolError';
|
|
973
1260
|
}
|
|
974
1261
|
let owner: string;
|
|
975
1262
|
let correlation: bigint | undefined;
|
|
976
1263
|
if (header.command === M6aServiceWireCommand.nodeSend) {
|
|
977
|
-
owner =
|
|
1264
|
+
owner = this.nodeMailboxOwner;
|
|
978
1265
|
} else if (header.command === M6aServiceWireCommand.nodeRequest) {
|
|
979
|
-
owner =
|
|
980
|
-
correlation = decodeNodeRequestHeader(received.parts[0]
|
|
1266
|
+
owner = this.nodeMailboxOwner;
|
|
1267
|
+
correlation = decodeNodeRequestHeader(received.parts[0]!, header);
|
|
981
1268
|
} else if (header.command === M6aServiceWireCommand.channelSend) {
|
|
982
|
-
owner = `channel:${decodeChannelSendHeader(received.parts[0]
|
|
1269
|
+
owner = `channel:${decodeChannelSendHeader(received.parts[0]!, header)}`;
|
|
983
1270
|
} else {
|
|
984
|
-
const channel = decodeChannelRequestHeader(received.parts[0]
|
|
1271
|
+
const channel = decodeChannelRequestHeader(received.parts[0]!, header);
|
|
985
1272
|
owner = `channel:${channel.channelName}`;
|
|
986
1273
|
correlation = channel.correlation;
|
|
987
1274
|
}
|
|
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';
|
|
1275
|
+
const job = applicationJobOwner.acquire('application');
|
|
1276
|
+
return job instanceof Promise
|
|
1277
|
+
? job.then((permit) => this.enqueueOrdinaryRecord(received, owner, correlation, permit))
|
|
1278
|
+
: this.enqueueOrdinaryRecord(received, owner, correlation, job);
|
|
1015
1279
|
} catch (error) {
|
|
1016
|
-
|
|
1017
|
-
throw error;
|
|
1280
|
+
return this.receiveProtocolFailure(error);
|
|
1018
1281
|
}
|
|
1019
1282
|
}
|
|
1020
1283
|
|
|
1284
|
+
private enqueueOrdinaryRecord(
|
|
1285
|
+
received: ZLinkRawReceivedRecord,
|
|
1286
|
+
owner: string,
|
|
1287
|
+
correlation: bigint | undefined,
|
|
1288
|
+
applicationJob: import('../application-jobs/application-ingress-record-owner').ApplicationJobRecordLease
|
|
1289
|
+
): RawServicePumpResult {
|
|
1290
|
+
const accepted = this.mailbox.tryEnqueue({
|
|
1291
|
+
owner,
|
|
1292
|
+
domain: 'application',
|
|
1293
|
+
parts: received.parts,
|
|
1294
|
+
sourceRoutingId: received.sourceRid,
|
|
1295
|
+
sourceRoute: received.sourceRoute,
|
|
1296
|
+
reply: received.reply,
|
|
1297
|
+
requestSequence: received.requestSeq,
|
|
1298
|
+
correlation,
|
|
1299
|
+
applicationJob
|
|
1300
|
+
});
|
|
1301
|
+
if (accepted) return 'application';
|
|
1302
|
+
applicationJob.close();
|
|
1303
|
+
if (correlation !== undefined && received.requestSeq !== undefined) {
|
|
1304
|
+
this.replyService(
|
|
1305
|
+
{
|
|
1306
|
+
sourceRoutingId: received.sourceRid,
|
|
1307
|
+
sourceRoute: received.sourceRoute,
|
|
1308
|
+
requestSequence: received.requestSeq,
|
|
1309
|
+
...(received.reply === undefined ? {} : { reply: received.reply })
|
|
1310
|
+
},
|
|
1311
|
+
[encodeReplyHeader(correlation, RequestResult.NotConnected, 0)]
|
|
1312
|
+
);
|
|
1313
|
+
return 'infrastructure';
|
|
1314
|
+
}
|
|
1315
|
+
return 'dropped';
|
|
1316
|
+
}
|
|
1317
|
+
|
|
1021
1318
|
expireOperations(nowMs: number, turnDeadlineMs: number): number {
|
|
1022
1319
|
return this.operations.expire(nowMs, turnDeadlineMs);
|
|
1023
1320
|
}
|
|
@@ -1026,9 +1323,42 @@ export class RawServiceMeshRuntime {
|
|
|
1026
1323
|
return this.operations.size;
|
|
1027
1324
|
}
|
|
1028
1325
|
|
|
1326
|
+
cancelPendingOperations(reason: unknown): void {
|
|
1327
|
+
this.operations.failAll(reason);
|
|
1328
|
+
}
|
|
1329
|
+
|
|
1330
|
+
/**
|
|
1331
|
+
* The next management deadline of this node (spec 08 §4.2): request expiry,
|
|
1332
|
+
* liveness probe or timeout, or an owned HELLO exchange still awaiting
|
|
1333
|
+
* ADMIT, which keeps the existing announce cadence.
|
|
1334
|
+
*/
|
|
1335
|
+
nextMaintenanceDeadlineMs(nowMs: number): number {
|
|
1336
|
+
this.refreshAdmissionPending(nowMs);
|
|
1337
|
+
return Math.min(this.operations.nextDeadlineMs(), this.liveness.nextDeadlineMs());
|
|
1338
|
+
}
|
|
1339
|
+
|
|
1340
|
+
private refreshAdmissionPending(nowMs: number): void {
|
|
1341
|
+
let pending = false;
|
|
1342
|
+
if (!this.closed && this.controlSends.size !== 0) {
|
|
1343
|
+
for (const nodeRoutingId of this.selectedRoutes.keys()) {
|
|
1344
|
+
const exchange = this.controlSends.get(
|
|
1345
|
+
controlSendKey(nodeRoutingId, M6aServiceWireCommand.hello)
|
|
1346
|
+
);
|
|
1347
|
+
if (exchange?.current?.() === true) {
|
|
1348
|
+
pending = true;
|
|
1349
|
+
break;
|
|
1350
|
+
}
|
|
1351
|
+
}
|
|
1352
|
+
}
|
|
1353
|
+
this.liveness.setAdmissionPending(pending, nowMs);
|
|
1354
|
+
}
|
|
1355
|
+
|
|
1029
1356
|
tickLiveness(nowMs = performance.now()): ServiceLivenessTick {
|
|
1357
|
+
const admissionDue = this.liveness.tickAdmission(nowMs);
|
|
1030
1358
|
const result = this.liveness.tick(nowMs);
|
|
1031
1359
|
this.requireStarted();
|
|
1360
|
+
if (admissionDue)
|
|
1361
|
+
for (const nodeRoutingId of this.selectedRoutes.keys()) void this.announcePeer(nodeRoutingId);
|
|
1032
1362
|
for (const probe of result.probes) {
|
|
1033
1363
|
const parts = [
|
|
1034
1364
|
livenessCodec.encodeLivenessRecord({
|
|
@@ -1065,9 +1395,8 @@ export class RawServiceMeshRuntime {
|
|
|
1065
1395
|
let changes = 0;
|
|
1066
1396
|
for (const [nodeRoutingId, generation] of this.selectedRoutes) {
|
|
1067
1397
|
if (observed.get(nodeRoutingId) === generation) continue;
|
|
1398
|
+
this.controlSends.delete(controlSendKey(nodeRoutingId, M6aServiceWireCommand.hello));
|
|
1068
1399
|
changes++;
|
|
1069
|
-
const expected = this.expectedPeers.get(nodeRoutingId);
|
|
1070
|
-
if (expected !== undefined) delete expected.helloSubmittedGeneration;
|
|
1071
1400
|
const peer = this.topology.peer(nodeRoutingId);
|
|
1072
1401
|
if (peer?.liveness?.connectionId === generation) {
|
|
1073
1402
|
this.removePeer(peer);
|
|
@@ -1080,8 +1409,10 @@ export class RawServiceMeshRuntime {
|
|
|
1080
1409
|
selected.push(nodeRoutingId);
|
|
1081
1410
|
}
|
|
1082
1411
|
this.selectedRoutes = observed;
|
|
1412
|
+
for (const nodeRoutingId of selected) this.notifyPeerRouteChanged(nodeRoutingId);
|
|
1083
1413
|
// A new selected route admits only through a new handshake.
|
|
1084
1414
|
for (const nodeRoutingId of selected) await this.announcePeer(nodeRoutingId);
|
|
1415
|
+
this.refreshAdmissionPending(performance.now());
|
|
1085
1416
|
return changes;
|
|
1086
1417
|
}
|
|
1087
1418
|
|
|
@@ -1092,8 +1423,10 @@ export class RawServiceMeshRuntime {
|
|
|
1092
1423
|
new Error('Raw service runtime application job admission stopped.')
|
|
1093
1424
|
);
|
|
1094
1425
|
this.receivePermit?.releaseAfterInternalProcessing();
|
|
1426
|
+
this.receivePermit?.releaseStorage?.();
|
|
1095
1427
|
this.receivePermit = undefined;
|
|
1096
1428
|
for (const send of this.controlSends.values()) delete send.next;
|
|
1429
|
+
this.controlSends.clear();
|
|
1097
1430
|
this.mailbox.close();
|
|
1098
1431
|
this.operations.close('Raw service runtime closed.');
|
|
1099
1432
|
this.serviceIngress = undefined;
|
|
@@ -1111,15 +1444,17 @@ export class RawServiceMeshRuntime {
|
|
|
1111
1444
|
targetNodeRoutingId: string,
|
|
1112
1445
|
payload: ServiceApplicationPayloadInput,
|
|
1113
1446
|
timeoutMs: number,
|
|
1114
|
-
channelName?: string
|
|
1115
|
-
|
|
1447
|
+
channelName?: string,
|
|
1448
|
+
onAdmitted?: () => void,
|
|
1449
|
+
observer?: RawServiceRequestObserver,
|
|
1450
|
+
operationKind?: number
|
|
1451
|
+
): RawServiceObservedRequest {
|
|
1116
1452
|
const correlation = this.nextCorrelation++;
|
|
1117
1453
|
const header =
|
|
1118
1454
|
channelName === undefined
|
|
1119
1455
|
? encodeNodeRequestHeader(correlation)
|
|
1120
1456
|
: encodeChannelRequestHeader(correlation, channelName);
|
|
1121
1457
|
const encodedPayload = this.applicationFrame(payload);
|
|
1122
|
-
const pending = this.operations.register(timeoutMs);
|
|
1123
1458
|
let selectedTargetNodeRoutingId = targetNodeRoutingId;
|
|
1124
1459
|
if (
|
|
1125
1460
|
selectedTargetNodeRoutingId !== this.descriptor.nodeRoutingId &&
|
|
@@ -1147,6 +1482,9 @@ export class RawServiceMeshRuntime {
|
|
|
1147
1482
|
}
|
|
1148
1483
|
}
|
|
1149
1484
|
if (selectedTargetNodeRoutingId === this.descriptor.nodeRoutingId) {
|
|
1485
|
+
// The local admission path aborts its capacity wait on the terminal
|
|
1486
|
+
// Promise, so a local request always keeps the Promise entry.
|
|
1487
|
+
const pending = this.operations.register(timeoutMs);
|
|
1150
1488
|
const parts = [header, encodedPayload];
|
|
1151
1489
|
const capacityStop = new AbortController();
|
|
1152
1490
|
void pending.promise.finally(() => capacityStop.abort()).catch(() => undefined);
|
|
@@ -1182,6 +1520,8 @@ export class RawServiceMeshRuntime {
|
|
|
1182
1520
|
terminalResult: RequestResult.NotConnected,
|
|
1183
1521
|
failureCode: 0
|
|
1184
1522
|
});
|
|
1523
|
+
} else {
|
|
1524
|
+
onAdmitted?.();
|
|
1185
1525
|
}
|
|
1186
1526
|
} finally {
|
|
1187
1527
|
applicationJobOwner.close();
|
|
@@ -1189,12 +1529,13 @@ export class RawServiceMeshRuntime {
|
|
|
1189
1529
|
})().catch((error) => this.operations.fail(pending.id, error));
|
|
1190
1530
|
return pending;
|
|
1191
1531
|
}
|
|
1192
|
-
const
|
|
1532
|
+
const pending = this.registerRequest(timeoutMs, observer, operationKind);
|
|
1533
|
+
const selectedPeer = this.topology.admittedPeer(selectedTargetNodeRoutingId);
|
|
1193
1534
|
if (selectedPeer === undefined) {
|
|
1194
1535
|
// Logical target selection is the requester's decision: an RID that is
|
|
1195
1536
|
// not an admitted peer is not a target. Whether an admitted target's
|
|
1196
1537
|
// route can carry the request is Core's REQUEST result.
|
|
1197
|
-
this.
|
|
1538
|
+
this.completeRequest(pending, {
|
|
1198
1539
|
terminalResult: RequestResult.NotFound,
|
|
1199
1540
|
failureCode: REQUEST_TARGET_NOT_FOUND_FAILURE_CODE
|
|
1200
1541
|
});
|
|
@@ -1207,10 +1548,11 @@ export class RawServiceMeshRuntime {
|
|
|
1207
1548
|
selectedTargetNodeRoutingId,
|
|
1208
1549
|
parts,
|
|
1209
1550
|
timeoutMs,
|
|
1210
|
-
selectedPeer.liveness?.recordReceived
|
|
1551
|
+
selectedPeer.liveness?.recordReceived,
|
|
1552
|
+
onAdmitted
|
|
1211
1553
|
);
|
|
1212
1554
|
} catch (error) {
|
|
1213
|
-
this.
|
|
1555
|
+
this.failRequest(pending, error);
|
|
1214
1556
|
return pending;
|
|
1215
1557
|
}
|
|
1216
1558
|
void request.then(
|
|
@@ -1274,33 +1616,37 @@ export class RawServiceMeshRuntime {
|
|
|
1274
1616
|
readonly endpoint?: string;
|
|
1275
1617
|
readonly securityIdentity?: string;
|
|
1276
1618
|
readonly lifecycleGeneration?: bigint;
|
|
1277
|
-
}
|
|
1619
|
+
},
|
|
1620
|
+
command: number = M6aServiceWireCommand.hello,
|
|
1621
|
+
descriptorBytes?: Uint8Array
|
|
1278
1622
|
): PeerAdmissionResult {
|
|
1279
1623
|
const previous = this.topology.peer(descriptor.nodeRoutingId);
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1624
|
+
const topologyConnectionId =
|
|
1625
|
+
typeof connectionId === 'bigint' ? routeConnectionId(connectionId) : connectionId;
|
|
1626
|
+
const result = this.topology.admit(
|
|
1627
|
+
descriptor,
|
|
1628
|
+
topologyConnectionId,
|
|
1629
|
+
expected,
|
|
1630
|
+
() => {
|
|
1631
|
+
const admission = this.liveness.admit(descriptor.nodeRoutingId, connectionId, nowMs);
|
|
1632
|
+
this.liveness.recordReceived(admission, nowMs);
|
|
1633
|
+
return admission;
|
|
1634
|
+
},
|
|
1635
|
+
command,
|
|
1636
|
+
descriptorBytes
|
|
1637
|
+
);
|
|
1638
|
+
if (result === 'staleDescriptor') {
|
|
1287
1639
|
throw new ServiceWireProtocolError(
|
|
1288
1640
|
`RouteMesh peer '${descriptor.nodeRoutingId}' descriptor revision is stale or conflicting.`
|
|
1289
1641
|
);
|
|
1290
1642
|
}
|
|
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
1643
|
if (result === 'notRequired' && previous !== undefined) {
|
|
1299
1644
|
this.liveness.disconnect(
|
|
1300
1645
|
descriptor.nodeRoutingId,
|
|
1301
1646
|
previous.liveness?.connectionId ?? previous.connectionId
|
|
1302
1647
|
);
|
|
1303
1648
|
}
|
|
1649
|
+
if (result === 'admitted') this.notifyPeerRouteChanged(descriptor.nodeRoutingId);
|
|
1304
1650
|
return result;
|
|
1305
1651
|
}
|
|
1306
1652
|
|
|
@@ -1361,26 +1707,16 @@ export class RawServiceMeshRuntime {
|
|
|
1361
1707
|
);
|
|
1362
1708
|
}
|
|
1363
1709
|
|
|
1364
|
-
private submitControl(
|
|
1365
|
-
target: string,
|
|
1366
|
-
parts: readonly Uint8Array[],
|
|
1367
|
-
accepted?: () => void
|
|
1368
|
-
): boolean | Promise<boolean> {
|
|
1710
|
+
private submitControl(target: string, parts: readonly Uint8Array[]): boolean | Promise<boolean> {
|
|
1369
1711
|
let submission;
|
|
1370
1712
|
try {
|
|
1371
1713
|
submission = this.requireStarted().submitSend(target, parts);
|
|
1372
1714
|
} catch (error) {
|
|
1373
1715
|
return this.sendFailure(error);
|
|
1374
1716
|
}
|
|
1375
|
-
if (submission.result === SubmitResult.Ok)
|
|
1376
|
-
accepted?.();
|
|
1377
|
-
return true;
|
|
1378
|
-
}
|
|
1717
|
+
if (submission.result === SubmitResult.Ok) return true;
|
|
1379
1718
|
return submission.admitted.then(
|
|
1380
|
-
() =>
|
|
1381
|
-
accepted?.();
|
|
1382
|
-
return true;
|
|
1383
|
-
},
|
|
1719
|
+
() => true,
|
|
1384
1720
|
(error) => this.sendFailure(error)
|
|
1385
1721
|
);
|
|
1386
1722
|
}
|
|
@@ -1389,12 +1725,16 @@ export class RawServiceMeshRuntime {
|
|
|
1389
1725
|
target: string,
|
|
1390
1726
|
command: number,
|
|
1391
1727
|
attempt: () => boolean | Promise<boolean>,
|
|
1392
|
-
current?: () => boolean
|
|
1728
|
+
current?: () => boolean,
|
|
1729
|
+
hello?: Uint8Array
|
|
1393
1730
|
): boolean {
|
|
1394
|
-
const key =
|
|
1731
|
+
const key = controlSendKey(target, command);
|
|
1732
|
+
if (command === M6aServiceWireCommand.hello)
|
|
1733
|
+
this.liveness.setAdmissionPending(true, performance.now());
|
|
1395
1734
|
const existing = this.controlSends.get(key);
|
|
1396
1735
|
if (
|
|
1397
1736
|
existing !== undefined &&
|
|
1737
|
+
existing.task !== undefined &&
|
|
1398
1738
|
(command !== M6aServiceWireCommand.hello || existing.current?.() === true)
|
|
1399
1739
|
) {
|
|
1400
1740
|
if (command !== M6aServiceWireCommand.hello) {
|
|
@@ -1404,39 +1744,45 @@ export class RawServiceMeshRuntime {
|
|
|
1404
1744
|
return true;
|
|
1405
1745
|
}
|
|
1406
1746
|
const first = attempt();
|
|
1407
|
-
if (typeof first === 'boolean')
|
|
1747
|
+
if (typeof first === 'boolean') {
|
|
1748
|
+
if (hello !== undefined) this.controlSends.set(key, { current, hello });
|
|
1749
|
+
return first;
|
|
1750
|
+
}
|
|
1408
1751
|
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
|
-
})
|
|
1752
|
+
const state: ControlSendState = { current, hello };
|
|
1753
|
+
const completeSend = () => {
|
|
1754
|
+
if (owner.controlSends.get(key) !== state) return;
|
|
1755
|
+
if (hello !== undefined && current?.() === true) delete state.task;
|
|
1756
|
+
else owner.controlSends.delete(key);
|
|
1424
1757
|
};
|
|
1758
|
+
const task = first.then(function drain(accepted): boolean | Promise<boolean> {
|
|
1759
|
+
if (!owner.closed && state.next !== undefined) {
|
|
1760
|
+
const next = state.next;
|
|
1761
|
+
delete state.next;
|
|
1762
|
+
return Promise.resolve(next()).then(drain);
|
|
1763
|
+
}
|
|
1764
|
+
completeSend();
|
|
1765
|
+
return accepted;
|
|
1766
|
+
});
|
|
1767
|
+
state.task = task;
|
|
1425
1768
|
this.controlSends.set(key, state);
|
|
1426
|
-
|
|
1769
|
+
task.then(
|
|
1427
1770
|
() => {},
|
|
1428
1771
|
(error) => {
|
|
1429
|
-
|
|
1430
|
-
|
|
1772
|
+
completeSend();
|
|
1773
|
+
this.reportInfrastructureFailure(error);
|
|
1431
1774
|
}
|
|
1432
1775
|
);
|
|
1433
|
-
this.infrastructureTaskRunner?.runDetached('RouteMesh control SEND', async () => {
|
|
1434
|
-
await state.task;
|
|
1435
|
-
});
|
|
1436
1776
|
return true;
|
|
1437
1777
|
}
|
|
1438
1778
|
|
|
1439
|
-
|
|
1779
|
+
reportInfrastructureFailure(error: unknown): void {
|
|
1780
|
+
if (this.infrastructureTaskRunner !== undefined) {
|
|
1781
|
+
this.infrastructureTaskRunner.runDetached('RouteMesh infrastructure', async () => {
|
|
1782
|
+
throw error;
|
|
1783
|
+
});
|
|
1784
|
+
return;
|
|
1785
|
+
}
|
|
1440
1786
|
this.infrastructureFailure = {
|
|
1441
1787
|
error:
|
|
1442
1788
|
this.infrastructureFailure === undefined
|
|
@@ -1455,17 +1801,20 @@ export class RawServiceMeshRuntime {
|
|
|
1455
1801
|
if (failure !== undefined) throw failure.error;
|
|
1456
1802
|
}
|
|
1457
1803
|
|
|
1458
|
-
private send(
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1804
|
+
private send(
|
|
1805
|
+
targetNodeRoutingId: string,
|
|
1806
|
+
parts: readonly Uint8Array[]
|
|
1807
|
+
): boolean | Promise<boolean> {
|
|
1808
|
+
try {
|
|
1809
|
+
const submission = this.requireStarted().submitSend(targetNodeRoutingId, parts);
|
|
1810
|
+
if (submission.result === SubmitResult.Ok) return true;
|
|
1811
|
+
return submission.admitted.then(
|
|
1812
|
+
() => true,
|
|
1813
|
+
(error) => this.sendFailure(error)
|
|
1468
1814
|
);
|
|
1815
|
+
} catch (error) {
|
|
1816
|
+
return this.sendFailure(error);
|
|
1817
|
+
}
|
|
1469
1818
|
}
|
|
1470
1819
|
|
|
1471
1820
|
private sendFailure(error: unknown): false {
|
|
@@ -1478,25 +1827,44 @@ export class RawServiceMeshRuntime {
|
|
|
1478
1827
|
}
|
|
1479
1828
|
}
|
|
1480
1829
|
|
|
1830
|
+
function controlSendKey(target: string, command: number): string {
|
|
1831
|
+
return `${command}:${target}`;
|
|
1832
|
+
}
|
|
1833
|
+
|
|
1834
|
+
function routeConnectionId(generation: bigint): string {
|
|
1835
|
+
return `route:${generation}`;
|
|
1836
|
+
}
|
|
1837
|
+
|
|
1481
1838
|
/**
|
|
1482
1839
|
* Relocation and bound-Session controls are service-wire records themselves,
|
|
1483
1840
|
* not application NodeSend(16) envelopes. Keep this list closed: a bare
|
|
1484
1841
|
* record outside the frozen control vocabulary remains a protocol error.
|
|
1485
1842
|
*/
|
|
1843
|
+
const BARE_INFRASTRUCTURE_CONTROLS: ReadonlySet<number> = new Set<number>([
|
|
1844
|
+
ServiceWireCommand.relocationReady,
|
|
1845
|
+
ServiceWireCommand.relocationData,
|
|
1846
|
+
ServiceWireCommand.replyRelay,
|
|
1847
|
+
ServiceWireCommand.relocationCutover,
|
|
1848
|
+
ServiceWireCommand.relocationPrepare,
|
|
1849
|
+
ServiceWireCommand.sessionRelocationSeal,
|
|
1850
|
+
ServiceWireCommand.sessionRelocationSealed,
|
|
1851
|
+
ServiceWireCommand.sessionRelocationRoute,
|
|
1852
|
+
ServiceWireCommand.replyRelayAck,
|
|
1853
|
+
ServiceWireCommand.relocationState,
|
|
1854
|
+
ServiceWireCommand.relocationFailed
|
|
1855
|
+
]);
|
|
1856
|
+
|
|
1486
1857
|
function isBareInfrastructureControl(command: number): boolean {
|
|
1487
|
-
return
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
ServiceWireCommand.relocationState,
|
|
1498
|
-
ServiceWireCommand.relocationFailed
|
|
1499
|
-
].includes(command as never);
|
|
1858
|
+
return BARE_INFRASTRUCTURE_CONTROLS.has(command);
|
|
1859
|
+
}
|
|
1860
|
+
|
|
1861
|
+
function isApplicationEnvelopeCommand(command: number): boolean {
|
|
1862
|
+
return (
|
|
1863
|
+
command === M6aServiceWireCommand.nodeSend ||
|
|
1864
|
+
command === M6aServiceWireCommand.nodeRequest ||
|
|
1865
|
+
command === M6aServiceWireCommand.channelSend ||
|
|
1866
|
+
command === M6aServiceWireCommand.channelRequest
|
|
1867
|
+
);
|
|
1500
1868
|
}
|
|
1501
1869
|
|
|
1502
1870
|
function protocolCommand(parts: readonly Uint8Array[]): { readonly command?: number } {
|