@zlink-systems/framework 0.25.0 → 0.26.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/.tsbuildinfo +1 -1
- package/dist/contracts/Channels/IZLinkFanoutClient.d.ts +0 -6
- package/dist/contracts/RouteMesh/RuntimeTopology.d.ts +8 -0
- package/dist/internal.d.ts +1 -0
- package/dist/internal.js +1 -0
- package/dist/runtime/activation-admission.d.ts +29 -0
- package/dist/runtime/activation-admission.js +61 -0
- package/dist/runtime/actors/actor-context.js +6 -6
- package/dist/runtime/actors/actor-handoff.d.ts +9 -3
- package/dist/runtime/actors/actor-handoff.js +28 -17
- package/dist/runtime/actors/actor-local-native-join.js +2 -18
- package/dist/runtime/actors/actor-remote-joiner.d.ts +2 -2
- package/dist/runtime/actors/actor-remote-joiner.js +6 -8
- package/dist/runtime/actors/actor-runtime-contracts.d.ts +4 -2
- package/dist/runtime/actors/actor-source-transfer.d.ts +5 -5
- package/dist/runtime/actors/deferred-join-completion.d.ts +14 -0
- package/dist/runtime/actors/deferred-join-completion.js +20 -0
- package/dist/runtime/actors/index.d.ts +1 -1
- package/dist/runtime/actors/index.js +15 -5
- package/dist/runtime/admission.d.ts +5 -1
- package/dist/runtime/admission.js +4 -0
- package/dist/runtime/backend/contracts/index.d.ts +2 -10
- package/dist/runtime/backend/mesh-actor-session-node-adapter.js +2 -9
- package/dist/runtime/backend/node/node-backend-adapter-factory.d.ts +1 -2
- package/dist/runtime/backend/node/node-backend-adapter-factory.js +4 -18
- package/dist/runtime/backend/node/node-backend-adapter-support.d.ts +6 -3
- package/dist/runtime/backend/node/node-backend-adapter-support.js +12 -28
- package/dist/runtime/backend/node/node-event-loop-poller.d.ts +1 -1
- package/dist/runtime/backend/node/node-event-loop-poller.js +8 -2
- package/dist/runtime/backend/node/node-raw-binding-port.d.ts +1 -1
- package/dist/runtime/backend/node/node-raw-binding-port.js +11 -3
- package/dist/runtime/backend/node/node-raw-mesh-backend.d.ts +12 -9
- package/dist/runtime/backend/node/node-raw-mesh-backend.js +61 -29
- package/dist/runtime/backend/node/node-socket-backend-adapter.js +1 -1
- package/dist/runtime/backend/raw-binding-port.d.ts +12 -0
- package/dist/runtime/channels/channel-clients.d.ts +0 -1
- package/dist/runtime/channels/channel-clients.js +0 -8
- package/dist/runtime/channels/channel-outbound-operations.js +2 -10
- package/dist/runtime/channels/channel-runtime-manager.d.ts +2 -5
- package/dist/runtime/channels/channel-runtime-manager.js +1 -12
- package/dist/runtime/channels/channel-socket-registry.d.ts +17 -4
- package/dist/runtime/channels/channel-socket-registry.js +49 -62
- package/dist/runtime/channels/channel-transports.d.ts +0 -3
- package/dist/runtime/channels/fanout-location-runtime.js +3 -9
- package/dist/runtime/execution/index.d.ts +18 -8
- package/dist/runtime/execution/index.js +55 -29
- package/dist/runtime/execution/serial-execution-queue.d.ts +5 -6
- package/dist/runtime/execution/serial-execution-queue.js +49 -15
- package/dist/runtime/foundation/listener-records.d.ts +11 -0
- package/dist/runtime/foundation/listener-records.js +20 -0
- package/dist/runtime/foundation/raw-service-mesh-runtime.d.ts +10 -21
- package/dist/runtime/foundation/raw-service-mesh-runtime.js +66 -392
- package/dist/runtime/foundation/service-authority-payload-codec.d.ts +1 -0
- package/dist/runtime/foundation/service-authority-payload-codec.js +8 -0
- package/dist/runtime/foundation/service-maintenance-runtime.js +23 -2
- package/dist/runtime/foundation/service-relocation-coordinator.d.ts +9 -0
- package/dist/runtime/foundation/service-relocation-coordinator.js +14 -1
- package/dist/runtime/foundation/service-relocation-object-owner.d.ts +8 -0
- package/dist/runtime/foundation/service-relocation-object-owner.js +16 -4
- package/dist/runtime/foundation/service-session-binding-ingress-port.d.ts +6 -13
- package/dist/runtime/foundation/service-stateful-runtime.d.ts +10 -7
- package/dist/runtime/foundation/service-stateful-runtime.js +100 -60
- package/dist/runtime/foundation/service-stateful-wire-codec.d.ts +9 -4
- package/dist/runtime/foundation/service-stateful-wire-codec.js +153 -90
- package/dist/runtime/foundation/service-topology-registry.d.ts +1 -2
- package/dist/runtime/foundation/service-topology-registry.js +6 -30
- package/dist/runtime/host/actor-transfer-runtime.d.ts +17 -15
- package/dist/runtime/host/actor-transfer-runtime.js +73 -86
- package/dist/runtime/host/index.d.ts +6 -2
- package/dist/runtime/host/index.js +63 -37
- package/dist/runtime/host/remote-bound-session-relay.js +4 -3
- package/dist/runtime/host/route-mesh-runtime.d.ts +2 -0
- package/dist/runtime/host/route-mesh-runtime.js +7 -5
- package/dist/runtime/host/service-relocation-host-runtime.d.ts +50 -8
- package/dist/runtime/host/service-relocation-host-runtime.js +343 -270
- package/dist/runtime/host/spot-runtime-options-factory.d.ts +7 -4
- package/dist/runtime/host/spot-runtime-options-factory.js +4 -8
- package/dist/runtime/host/stateful-authority-route-runtime.js +6 -2
- package/dist/runtime/host/user-spot-creation-coordinator.d.ts +11 -3
- package/dist/runtime/host/user-spot-creation-coordinator.js +85 -87
- package/dist/runtime/locations/in-memory-provider-location-store.js +8 -6
- package/dist/runtime/locations/lifecycle.d.ts +1 -1
- package/dist/runtime/locations/lifecycle.js +2 -2
- package/dist/runtime/locations/resolvers.js +7 -2
- package/dist/runtime/locations/spot-location-claims.d.ts +2 -2
- package/dist/runtime/locations/spot-location-claims.js +3 -27
- package/dist/runtime/protocol/service_wire_codec.generated.d.ts +1 -0
- package/dist/runtime/protocol/service_wire_codec.generated.js +5 -1
- package/dist/runtime/protocol/service_wire_pilot_codec.generated.d.ts +1 -0
- package/dist/runtime/protocol/service_wire_pilot_codec.generated.js +3 -2
- package/dist/runtime/spots/formal-remote-actor-admission-registry.d.ts +3 -3
- package/dist/runtime/spots/formal-remote-actor-admission-registry.js +4 -4
- package/dist/runtime/spots/formal-remote-actor-transfer-registry.d.ts +3 -3
- package/dist/runtime/spots/index.d.ts +25 -19
- package/dist/runtime/spots/index.js +207 -205
- package/dist/runtime/spots/spot-activation-registry.d.ts +4 -2
- package/dist/runtime/spots/spot-activation-registry.js +34 -31
- package/dist/runtime/spots/spot-activation-state.d.ts +2 -11
- package/dist/runtime/spots/spot-activation-state.js +4 -17
- package/dist/runtime/spots/spot-activation.d.ts +8 -6
- package/dist/runtime/spots/spot-activation.js +54 -49
- package/dist/runtime/spots/spot-actor-admission-coordinator.d.ts +1 -1
- package/dist/runtime/spots/spot-actor-admission-coordinator.js +1 -1
- package/dist/runtime/spots/spot-actor-join-dispatch.d.ts +2 -2
- package/dist/runtime/spots/spot-actor-join-dispatch.js +3 -5
- package/dist/runtime/spots/spot-actor-lifecycle-drain.js +2 -2
- package/dist/runtime/spots/spot-actor-membership.d.ts +4 -0
- package/dist/runtime/spots/spot-actor-membership.js +26 -12
- package/dist/runtime/spots/spot-entry-activation.d.ts +1 -2
- package/dist/runtime/spots/spot-entry-activation.js +0 -1
- package/dist/runtime/spots/spot-manager-public.js +1 -1
- package/dist/runtime/spots/spot-native-actor-join-admission.d.ts +1 -1
- package/dist/runtime/spots/spot-native-actor-join-admission.js +2 -2
- package/dist/runtime/spots/spot-node-runtime-manager.d.ts +5 -5
- package/dist/runtime/spots/spot-node-runtime-manager.js +7 -11
- package/dist/runtime/spots/spot-outbound.js +1 -29
- package/dist/runtime/spots/spot-routed-actor-admission.d.ts +1 -0
- package/dist/runtime/spots/spot-routed-actor-admission.js +10 -0
- package/dist/runtime/spots/spot-routed-frame-dispatch.d.ts +2 -6
- package/dist/runtime/spots/spot-routed-frame-dispatch.js +2 -70
- package/dist/runtime/spots/spot-runtime-ports.d.ts +3 -5
- package/dist/runtime/spots/spot-serial-executor.d.ts +5 -0
- package/dist/runtime/spots/spot-serial-executor.js +8 -1
- package/dist/runtime/spots/spot-serial-turn-executor.d.ts +3 -0
- package/dist/runtime/spots/spot-serial-turn-executor.js +8 -4
- package/dist/runtime/streams/actor-session-binding-registry.d.ts +13 -15
- package/dist/runtime/streams/actor-session-binding-registry.js +19 -171
- package/dist/runtime/streams/actor-session-binding-runtime-owner.d.ts +3 -4
- package/dist/runtime/streams/bound-session-service.js +1 -18
- package/dist/runtime/streams/index.d.ts +2 -0
- package/dist/runtime/streams/index.js +19 -19
- package/dist/runtime/streams/session-serial-executor.js +2 -2
- package/package.json +3 -3
- package/src/contracts/Channels/IZLinkFanoutClient.ts +0 -7
- package/src/contracts/RouteMesh/RuntimeTopology.ts +10 -0
- package/src/internal.ts +1 -0
- package/src/runtime/activation-admission.ts +78 -0
- package/src/runtime/actors/actor-context.ts +6 -6
- package/src/runtime/actors/actor-handoff.ts +31 -23
- package/src/runtime/actors/actor-local-native-join.ts +3 -42
- package/src/runtime/actors/actor-remote-joiner.ts +6 -25
- package/src/runtime/actors/actor-runtime-contracts.ts +4 -10
- package/src/runtime/actors/actor-source-transfer.ts +5 -18
- package/src/runtime/actors/deferred-join-completion.ts +40 -0
- package/src/runtime/actors/index.ts +22 -9
- package/src/runtime/admission.ts +5 -1
- package/src/runtime/backend/contracts/index.ts +2 -13
- package/src/runtime/backend/mesh-actor-session-node-adapter.ts +2 -10
- package/src/runtime/backend/node/node-backend-adapter-factory.ts +5 -25
- package/src/runtime/backend/node/node-backend-adapter-support.ts +9 -29
- package/src/runtime/backend/node/node-event-loop-poller.ts +14 -2
- package/src/runtime/backend/node/node-raw-binding-port.ts +17 -3
- package/src/runtime/backend/node/node-raw-mesh-backend.ts +65 -32
- package/src/runtime/backend/node/node-socket-backend-adapter.ts +2 -2
- package/src/runtime/backend/raw-binding-port.ts +13 -0
- package/src/runtime/channels/channel-clients.ts +0 -11
- package/src/runtime/channels/channel-outbound-operations.ts +3 -11
- package/src/runtime/channels/channel-runtime-manager.ts +4 -15
- package/src/runtime/channels/channel-socket-registry.ts +69 -71
- package/src/runtime/channels/channel-transports.ts +0 -3
- package/src/runtime/channels/fanout-location-runtime.ts +4 -16
- package/src/runtime/execution/index.ts +68 -30
- package/src/runtime/execution/serial-execution-queue.ts +52 -20
- package/src/runtime/foundation/listener-records.ts +21 -0
- package/src/runtime/foundation/raw-service-mesh-runtime.ts +66 -466
- package/src/runtime/foundation/service-authority-payload-codec.ts +10 -0
- package/src/runtime/foundation/service-maintenance-runtime.ts +30 -9
- package/src/runtime/foundation/service-relocation-coordinator.ts +13 -0
- package/src/runtime/foundation/service-relocation-object-owner.ts +18 -5
- package/src/runtime/foundation/service-session-binding-ingress-port.ts +6 -14
- package/src/runtime/foundation/service-stateful-runtime.ts +151 -89
- package/src/runtime/foundation/service-stateful-wire-codec.ts +203 -121
- package/src/runtime/foundation/service-topology-registry.ts +8 -48
- package/src/runtime/host/actor-transfer-runtime.ts +94 -166
- package/src/runtime/host/index.ts +119 -54
- package/src/runtime/host/remote-bound-session-relay.ts +4 -3
- package/src/runtime/host/route-mesh-runtime.ts +9 -5
- package/src/runtime/host/service-relocation-host-runtime.ts +495 -376
- package/src/runtime/host/spot-runtime-options-factory.ts +24 -13
- package/src/runtime/host/stateful-authority-route-runtime.ts +8 -4
- package/src/runtime/host/user-spot-creation-coordinator.ts +147 -142
- package/src/runtime/locations/in-memory-provider-location-store.ts +11 -7
- package/src/runtime/locations/lifecycle.ts +3 -2
- package/src/runtime/locations/resolvers.ts +19 -13
- package/src/runtime/locations/spot-location-claims.ts +5 -34
- package/src/runtime/protocol/service_wire_codec.generated.ts +9 -2
- package/src/runtime/protocol/service_wire_pilot_codec.generated.ts +4 -4
- package/src/runtime/spots/formal-remote-actor-admission-registry.ts +6 -6
- package/src/runtime/spots/formal-remote-actor-transfer-registry.ts +3 -3
- package/src/runtime/spots/index.ts +341 -295
- package/src/runtime/spots/spot-activation-registry.ts +41 -31
- package/src/runtime/spots/spot-activation-state.ts +4 -16
- package/src/runtime/spots/spot-activation.ts +102 -80
- package/src/runtime/spots/spot-actor-admission-coordinator.ts +2 -2
- package/src/runtime/spots/spot-actor-join-dispatch.ts +6 -7
- package/src/runtime/spots/spot-actor-lifecycle-drain.ts +20 -18
- package/src/runtime/spots/spot-actor-membership.ts +81 -17
- package/src/runtime/spots/spot-entry-activation.ts +0 -3
- package/src/runtime/spots/spot-manager-public.ts +8 -4
- package/src/runtime/spots/spot-native-actor-join-admission.ts +2 -2
- package/src/runtime/spots/spot-node-runtime-manager.ts +12 -18
- package/src/runtime/spots/spot-outbound.ts +4 -47
- package/src/runtime/spots/spot-routed-actor-admission.ts +20 -0
- package/src/runtime/spots/spot-routed-frame-dispatch.ts +6 -76
- package/src/runtime/spots/spot-runtime-ports.ts +6 -17
- package/src/runtime/spots/spot-serial-executor.ts +9 -1
- package/src/runtime/spots/spot-serial-turn-executor.ts +10 -4
- package/src/runtime/streams/actor-session-binding-registry.ts +20 -308
- package/src/runtime/streams/actor-session-binding-runtime-owner.ts +2 -12
- package/src/runtime/streams/bound-session-service.ts +2 -36
- package/src/runtime/streams/index.ts +29 -27
- package/src/runtime/streams/session-serial-executor.ts +2 -2
- package/dist/runtime/actors/deferred-join-accepted-journal.d.ts +0 -47
- package/dist/runtime/actors/deferred-join-accepted-journal.js +0 -992
- package/src/runtime/actors/deferred-join-accepted-journal.ts +0 -1346
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ZLinkListenerRecords } from '../foundation/listener-records';
|
|
1
2
|
import {
|
|
2
3
|
ZLinkFrameworkRuntimeState,
|
|
3
4
|
type RoutingId,
|
|
@@ -85,7 +86,6 @@ interface ClientServerPhysicalConnection {
|
|
|
85
86
|
readonly aliases: Set<string>;
|
|
86
87
|
readonly callbacksByAlias: Map<string, ZLinkClientServerConnectionCallbacks>;
|
|
87
88
|
physicalConnectionId: symbol;
|
|
88
|
-
terminationRequested: boolean;
|
|
89
89
|
readyConnectionId?: string;
|
|
90
90
|
admittedDescriptor?: ClientServerDiscoveryDescriptor;
|
|
91
91
|
nextProbeAt?: number;
|
|
@@ -170,14 +170,19 @@ export class ZLinkChannelSocketRegistry {
|
|
|
170
170
|
>();
|
|
171
171
|
private readonly fanoutTopologyHandlers = new Map<string, Set<() => void>>();
|
|
172
172
|
|
|
173
|
+
private readonly listenerRecords: ZLinkListenerRecords;
|
|
174
|
+
|
|
173
175
|
constructor(
|
|
174
176
|
private readonly registration: ZLinkFrameworkRegistration,
|
|
175
177
|
private readonly adapter: ZLinkChannelBackendAdapter,
|
|
176
178
|
private readonly context: ZLinkBackendContext,
|
|
177
179
|
private readonly monitoringAdapter?: ZLinkMonitoringBackendAdapter,
|
|
178
180
|
private readonly oneWayFailureSink?: (error: unknown) => void,
|
|
179
|
-
private readonly applicationJobQueue?: ApplicationJobQueue
|
|
180
|
-
|
|
181
|
+
private readonly applicationJobQueue?: ApplicationJobQueue,
|
|
182
|
+
listenerRecords?: ZLinkListenerRecords
|
|
183
|
+
) {
|
|
184
|
+
this.listenerRecords = listenerRecords ?? new ZLinkListenerRecords();
|
|
185
|
+
}
|
|
181
186
|
|
|
182
187
|
async dispose(): Promise<void> {
|
|
183
188
|
const clientServerConnections = [...new Set(this.clientServerConnections.values())];
|
|
@@ -306,14 +311,18 @@ export class ZLinkChannelSocketRegistry {
|
|
|
306
311
|
});
|
|
307
312
|
}
|
|
308
313
|
this.channelRouters.set(channelName, router);
|
|
314
|
+
const endpoint = advertisedEndpoint(
|
|
315
|
+
router.lastEndpoint ?? channel.server.bind,
|
|
316
|
+
channel.server.advertiseHost
|
|
317
|
+
);
|
|
318
|
+
this.recordListener('clientServer', channelName, endpoint);
|
|
309
319
|
if (!this.clientServerServerDescriptors.has(channelName)) {
|
|
310
|
-
const boundEndpoint = router.lastEndpoint ?? channel.server.bind;
|
|
311
320
|
this.clientServerServerDescriptors.set(channelName, {
|
|
312
321
|
channelName,
|
|
313
322
|
serverRid: identity.serverRid,
|
|
314
323
|
lifecycleGeneration: identity.lifecycleGeneration,
|
|
315
324
|
descriptorRevision: 1n,
|
|
316
|
-
endpoint
|
|
325
|
+
endpoint,
|
|
317
326
|
weight: publicWeight,
|
|
318
327
|
state: ZLinkFrameworkRuntimeState.Serving,
|
|
319
328
|
securityIdentity: 'default',
|
|
@@ -326,22 +335,21 @@ export class ZLinkChannelSocketRegistry {
|
|
|
326
335
|
}
|
|
327
336
|
|
|
328
337
|
clientServerServerIdentity(channelName: string): ZLinkClientServerServerSocketIdentity {
|
|
329
|
-
|
|
338
|
+
this.channelRouter(channelName);
|
|
330
339
|
const identity = this.clientServerIdentities.get(channelName);
|
|
331
|
-
const
|
|
332
|
-
if (identity === undefined ||
|
|
340
|
+
const endpoint = this.listenerRecords.endpoint('clientServer', channelName);
|
|
341
|
+
if (identity === undefined || endpoint === undefined) {
|
|
333
342
|
throw new ZLinkConfigurationException(`Channel server '${channelName}' is not registered.`);
|
|
334
343
|
}
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
};
|
|
344
|
+
return { ...identity, endpoint };
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
fanoutPublisherEndpoint(channelName: string): string | undefined {
|
|
348
|
+
return this.listenerRecords.endpoint('fanout', channelName);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
private recordListener(kind: 'clientServer' | 'fanout', name: string, endpoint: string): void {
|
|
352
|
+
this.listenerRecords.record(kind, name, endpoint);
|
|
345
353
|
}
|
|
346
354
|
|
|
347
355
|
clientServerServerSocket(channelName: string): ZLinkBackendRouterSocket {
|
|
@@ -417,8 +425,7 @@ export class ZLinkChannelSocketRegistry {
|
|
|
417
425
|
monitor,
|
|
418
426
|
aliases: new Set([connectionId]),
|
|
419
427
|
callbacksByAlias: new Map([[connectionId, callbacks]]),
|
|
420
|
-
physicalConnectionId: Symbol(connectionId)
|
|
421
|
-
terminationRequested: false
|
|
428
|
+
physicalConnectionId: Symbol(connectionId)
|
|
422
429
|
};
|
|
423
430
|
this.clientServerConnections.set(connectionId, connection);
|
|
424
431
|
this.ensureClientServerLivenessTimer();
|
|
@@ -436,7 +443,7 @@ export class ZLinkChannelSocketRegistry {
|
|
|
436
443
|
const routingId = event.routingId === undefined ? undefined : String(event.routingId);
|
|
437
444
|
if (event.nativeEvent === ZLinkSocketNativeEventType.ConnectionReady) {
|
|
438
445
|
// This single-endpoint DEALER also receives its disconnected ready-count snapshot.
|
|
439
|
-
if (event.value === 0n
|
|
446
|
+
if (event.value === 0n) return;
|
|
440
447
|
connection.physicalConnectionId = Symbol(connectionId);
|
|
441
448
|
connection.admissionAttempt = undefined;
|
|
442
449
|
for (const currentCallbacks of [...connection.callbacksByAlias.values()]) {
|
|
@@ -451,26 +458,9 @@ export class ZLinkChannelSocketRegistry {
|
|
|
451
458
|
event.nativeEvent === ZLinkSocketNativeEventType.HandshakeFailedProtocol ||
|
|
452
459
|
event.nativeEvent === ZLinkSocketNativeEventType.HandshakeFailedAuth
|
|
453
460
|
) {
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
(event.nativeEvent !== ZLinkSocketNativeEventType.Disconnected &&
|
|
458
|
-
event.nativeEvent !== ZLinkSocketNativeEventType.Closed))
|
|
459
|
-
)
|
|
460
|
-
return;
|
|
461
|
-
connection.physicalConnectionId = Symbol(connectionId);
|
|
462
|
-
connection.admissionAttempt = undefined;
|
|
463
|
-
if (connection.readyConnectionId !== undefined) {
|
|
464
|
-
this.removeReadyConnection(connection.readyConnectionId);
|
|
465
|
-
}
|
|
466
|
-
for (const currentCallbacks of [...connection.callbacksByAlias.values()]) {
|
|
467
|
-
currentCallbacks.onTerminated(routingId, event.remoteAddr);
|
|
468
|
-
}
|
|
469
|
-
if (connection.terminationRequested) {
|
|
470
|
-
connection.terminationRequested = false;
|
|
471
|
-
// Explicit disconnect removes intent; restore it once after this endpoint closes.
|
|
472
|
-
connection.dealer.connect(connection.endpoint);
|
|
473
|
-
}
|
|
461
|
+
// Core owns the endpoint reconnect (transport liveness §6); the
|
|
462
|
+
// connect intent stays and the next READY starts a new admission.
|
|
463
|
+
this.endClientServerAdmission(connectionId, connection, routingId, event.remoteAddr);
|
|
474
464
|
}
|
|
475
465
|
});
|
|
476
466
|
dealer.connect(endpoint);
|
|
@@ -790,10 +780,6 @@ export class ZLinkChannelSocketRegistry {
|
|
|
790
780
|
);
|
|
791
781
|
}
|
|
792
782
|
|
|
793
|
-
hasKnownClientServerTargets(channelName: string): boolean {
|
|
794
|
-
return this.clientServerDiscovery.clientServerDescriptors(channelName).length > 0;
|
|
795
|
-
}
|
|
796
|
-
|
|
797
783
|
fanoutActiveTargets(channelName: string) {
|
|
798
784
|
return this.clientServerDiscovery.fanoutEndpoints(channelName);
|
|
799
785
|
}
|
|
@@ -929,7 +915,7 @@ export class ZLinkChannelSocketRegistry {
|
|
|
929
915
|
this.drainClientServerControl(connectionId, connection);
|
|
930
916
|
if (connection.deadlineAt === undefined) continue;
|
|
931
917
|
if (nowMs >= connection.deadlineAt) {
|
|
932
|
-
this.
|
|
918
|
+
this.restartClientServerAdmission(connectionId, connection);
|
|
933
919
|
continue;
|
|
934
920
|
}
|
|
935
921
|
if (connection.nextProbeAt === undefined || nowMs < connection.nextProbeAt) continue;
|
|
@@ -1298,7 +1284,6 @@ export class ZLinkChannelSocketRegistry {
|
|
|
1298
1284
|
connectionId: string,
|
|
1299
1285
|
connection: ClientServerPhysicalConnection
|
|
1300
1286
|
): void {
|
|
1301
|
-
if (connection.terminationRequested) return;
|
|
1302
1287
|
for (;;) {
|
|
1303
1288
|
if (!connection.readablePoller.wait(0)) return;
|
|
1304
1289
|
const received = connection.dealer.recv(1);
|
|
@@ -1331,7 +1316,7 @@ export class ZLinkChannelSocketRegistry {
|
|
|
1331
1316
|
}
|
|
1332
1317
|
this.applyClientServerDescriptorUpdate(connectionId, connection, record.admission);
|
|
1333
1318
|
} catch (error) {
|
|
1334
|
-
this.
|
|
1319
|
+
this.restartClientServerAdmission(connectionId, connection);
|
|
1335
1320
|
this.oneWayFailureSink?.(error);
|
|
1336
1321
|
return;
|
|
1337
1322
|
} finally {
|
|
@@ -1340,19 +1325,39 @@ export class ZLinkChannelSocketRegistry {
|
|
|
1340
1325
|
}
|
|
1341
1326
|
}
|
|
1342
1327
|
|
|
1343
|
-
|
|
1328
|
+
/**
|
|
1329
|
+
* Ends the current logical admission of this connection: later replies of
|
|
1330
|
+
* the fenced attempt no longer apply and its ready target is withdrawn.
|
|
1331
|
+
*/
|
|
1332
|
+
private endClientServerAdmission(
|
|
1344
1333
|
connectionId: string,
|
|
1345
|
-
connection: ClientServerPhysicalConnection
|
|
1334
|
+
connection: ClientServerPhysicalConnection,
|
|
1335
|
+
routingId: string | undefined,
|
|
1336
|
+
endpoint: string
|
|
1346
1337
|
): void {
|
|
1347
|
-
if (connection.terminationRequested) return;
|
|
1348
|
-
connection.terminationRequested = true;
|
|
1349
1338
|
connection.physicalConnectionId = Symbol(connectionId);
|
|
1350
1339
|
connection.admissionAttempt = undefined;
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1340
|
+
if (connection.readyConnectionId !== undefined) {
|
|
1341
|
+
this.removeReadyConnection(connection.readyConnectionId);
|
|
1342
|
+
}
|
|
1343
|
+
for (const callbacks of [...connection.callbacksByAlias.values()]) {
|
|
1344
|
+
callbacks.onTerminated(routingId, endpoint);
|
|
1345
|
+
}
|
|
1346
|
+
}
|
|
1347
|
+
|
|
1348
|
+
/**
|
|
1349
|
+
* A peer deadline or an invalid pushed control ends only the current logical
|
|
1350
|
+
* admission. The connect intent stays with Core, which owns the endpoint
|
|
1351
|
+
* reconnect (transport liveness §6); the service handshake starts again on
|
|
1352
|
+
* the existing admission path.
|
|
1353
|
+
*/
|
|
1354
|
+
private restartClientServerAdmission(
|
|
1355
|
+
connectionId: string,
|
|
1356
|
+
connection: ClientServerPhysicalConnection
|
|
1357
|
+
): void {
|
|
1358
|
+
this.endClientServerAdmission(connectionId, connection, undefined, connection.endpoint);
|
|
1359
|
+
for (const callbacks of [...connection.callbacksByAlias.values()]) {
|
|
1360
|
+
callbacks.onTransportReady('', connection.endpoint);
|
|
1356
1361
|
}
|
|
1357
1362
|
}
|
|
1358
1363
|
|
|
@@ -1612,6 +1617,13 @@ export class ZLinkChannelSocketRegistry {
|
|
|
1612
1617
|
applyFanoutPublisherSocketOptions(publisher, channel);
|
|
1613
1618
|
publisher.bind(channel.publisher.bind);
|
|
1614
1619
|
this.publishers.set(channelName, publisher);
|
|
1620
|
+
const endpoint = buildAdvertisedEndpoint(
|
|
1621
|
+
publisher.lastEndpoint ?? channel.publisher.bind,
|
|
1622
|
+
channel.publisher.advertiseHost
|
|
1623
|
+
);
|
|
1624
|
+
if (endpoint !== undefined && endpoint.length > 0) {
|
|
1625
|
+
this.recordListener('fanout', channelName, endpoint);
|
|
1626
|
+
}
|
|
1615
1627
|
this.fanoutPublisherNextBeacon.set(
|
|
1616
1628
|
channelName,
|
|
1617
1629
|
performance.now() + CLIENT_SERVER_PROBE_INTERVAL_MS
|
|
@@ -1620,20 +1632,6 @@ export class ZLinkChannelSocketRegistry {
|
|
|
1620
1632
|
return publisher;
|
|
1621
1633
|
}
|
|
1622
1634
|
|
|
1623
|
-
fanoutPublisherEndpoint(channelName: string): string | undefined {
|
|
1624
|
-
const channel = this.registration.channels.get(channelName);
|
|
1625
|
-
const publisher = this.publishers.get(channelName);
|
|
1626
|
-
const boundEndpoint = publisher?.lastEndpoint;
|
|
1627
|
-
if (
|
|
1628
|
-
channel?.publisher === undefined ||
|
|
1629
|
-
boundEndpoint === undefined ||
|
|
1630
|
-
boundEndpoint.length === 0
|
|
1631
|
-
) {
|
|
1632
|
-
return undefined;
|
|
1633
|
-
}
|
|
1634
|
-
return advertisedEndpoint(boundEndpoint, channel.publisher.advertiseHost);
|
|
1635
|
-
}
|
|
1636
|
-
|
|
1637
1635
|
routeRouter(routerChannelId: string): ZLinkBackendRouterSocket {
|
|
1638
1636
|
const existing = this.routeRouters.get(routerChannelId);
|
|
1639
1637
|
if (existing !== undefined) {
|
|
@@ -15,7 +15,6 @@ import {
|
|
|
15
15
|
import type { ZLinkBackendMeshNode, ZLinkBackendSpot } from '../backend/contracts';
|
|
16
16
|
import type { ZLinkFrameworkInternalErrorKind as ZLinkFrameworkInternalErrorKindType } from '../framework-errors-internal';
|
|
17
17
|
import { ZLinkFrameworkException } from '../../contracts';
|
|
18
|
-
import type { ZLinkFanoutListenerStatus } from '../../contracts';
|
|
19
18
|
import type { Message } from '../../contracts/Common/Message';
|
|
20
19
|
import { ZLinkSubmitStatus, type ZLinkSubmitResult } from '../messaging/submission-result';
|
|
21
20
|
import { ZLinkConfigurationException } from '../configuration';
|
|
@@ -73,7 +72,6 @@ export interface ZLinkChannelClientTransport {
|
|
|
73
72
|
signal?: AbortSignal,
|
|
74
73
|
metadata?: ReadonlyMap<string, string>
|
|
75
74
|
): ZLinkSubmitResult | Promise<ZLinkSubmitResult>;
|
|
76
|
-
getFanoutListenerStatus?(channelName: string): ZLinkFanoutListenerStatus;
|
|
77
75
|
}
|
|
78
76
|
|
|
79
77
|
export type ZLinkChannelClientTransportSource =
|
|
@@ -187,7 +185,6 @@ interface ZLinkChannelTransportRuntime {
|
|
|
187
185
|
signal?: AbortSignal,
|
|
188
186
|
metadata?: ReadonlyMap<string, string>
|
|
189
187
|
): Promise<ZLinkSubmitResult>;
|
|
190
|
-
getFanoutListenerStatus(channelName: string): ZLinkFanoutListenerStatus;
|
|
191
188
|
canRouteChannel(routerChannelId: string): boolean;
|
|
192
189
|
canRoutePacketChannel(routerChannelId: string): boolean;
|
|
193
190
|
routeSubmit(
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
type ZLinkLocationOptionOverrides
|
|
13
13
|
} from '../../contracts/Locations/Options';
|
|
14
14
|
import type { ZLinkFrameworkRegistration } from '../configuration';
|
|
15
|
-
import {
|
|
15
|
+
import { ZLinkConfigurationException } from '../configuration';
|
|
16
16
|
import type { ZLinkLocationRuntime, ZLinkLocationRuntimeStores } from '../locations';
|
|
17
17
|
import type { ZLinkBackendSubscriberSocket } from '../backend/contracts';
|
|
18
18
|
import { ZLinkChannelSocketRegistry } from './channel-socket-registry';
|
|
@@ -142,11 +142,9 @@ export class ZLinkFanoutLocationRuntime {
|
|
|
142
142
|
const owner = this.requireOwnerToken();
|
|
143
143
|
for (const [channelName, channel] of this.registration.channels) {
|
|
144
144
|
if (channel.publisher === undefined) continue;
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
channel.publisher.advertiseHost
|
|
149
|
-
);
|
|
145
|
+
// Opening the publisher binds it and fixes its advertised endpoint.
|
|
146
|
+
this.sockets.publisher(channelName);
|
|
147
|
+
const endpoint = this.sockets.fanoutPublisherEndpoint(channelName) ?? '';
|
|
150
148
|
if (endpoint.length === 0) {
|
|
151
149
|
throw new ZLinkConfigurationException(
|
|
152
150
|
`Fanout publisher '${channelName}' did not report a bound endpoint.`
|
|
@@ -412,16 +410,6 @@ export class ZLinkFanoutLocationRuntime {
|
|
|
412
410
|
}
|
|
413
411
|
}
|
|
414
412
|
|
|
415
|
-
function advertisedEndpoint(boundEndpoint: string, advertiseHost: string | undefined): string {
|
|
416
|
-
const result = buildAdvertisedEndpoint(boundEndpoint, advertiseHost);
|
|
417
|
-
if (result === undefined) {
|
|
418
|
-
throw new ZLinkConfigurationException(
|
|
419
|
-
`Fanout publisher bound endpoint '${boundEndpoint}' cannot be advertised.`
|
|
420
|
-
);
|
|
421
|
-
}
|
|
422
|
-
return result;
|
|
423
|
-
}
|
|
424
|
-
|
|
425
413
|
function fanoutConnectionId(descriptor: ZLinkFanoutPublisherDescriptor): string {
|
|
426
414
|
return (
|
|
427
415
|
`${descriptor.channelName}\0${String(descriptor.publisherRid)}\0` +
|
|
@@ -66,17 +66,15 @@ export class ZLinkSpotSerialTurn {
|
|
|
66
66
|
readonly yieldAllowed: boolean
|
|
67
67
|
) {}
|
|
68
68
|
|
|
69
|
-
bindExecutionClaim(claim: ZLinkExecutionBarrierClaim | undefined): void {
|
|
70
|
-
if (this.executionClaim !== undefined) {
|
|
71
|
-
throw new Error('ZLink Spot serial turn already owns an execution claim.');
|
|
72
|
-
}
|
|
73
|
-
this.executionClaim = claim;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
69
|
get isSuspended(): boolean {
|
|
77
70
|
return this.suspendSignaled;
|
|
78
71
|
}
|
|
79
72
|
|
|
73
|
+
/** False when a seal that does not drain yielded turns won before this resume. */
|
|
74
|
+
resumeExecutionClaim(): boolean {
|
|
75
|
+
return this.executionClaim?.resume() ?? true;
|
|
76
|
+
}
|
|
77
|
+
|
|
80
78
|
releaseBoundExecutionClaim(): void {
|
|
81
79
|
this.executionClaim?.release();
|
|
82
80
|
this.executionClaim = undefined;
|
|
@@ -96,6 +94,14 @@ export class ZLinkSpotSerialTurn {
|
|
|
96
94
|
this.owner = owner;
|
|
97
95
|
}
|
|
98
96
|
|
|
97
|
+
/** Settles when the whole turn ends by any path, including yielded continuations. */
|
|
98
|
+
get completion(): Promise<void> {
|
|
99
|
+
return (this.owner ?? Promise.resolve()).then(
|
|
100
|
+
() => undefined,
|
|
101
|
+
() => undefined
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
99
105
|
blockFrameworkOperations(): void {
|
|
100
106
|
this.frameworkOperationsBlocked = true;
|
|
101
107
|
}
|
|
@@ -150,7 +156,7 @@ export class ZLinkSpotSerialTurn {
|
|
|
150
156
|
return;
|
|
151
157
|
}
|
|
152
158
|
this.suspendSignaled = true;
|
|
153
|
-
this.
|
|
159
|
+
this.executionClaim?.suspend();
|
|
154
160
|
this.suspendedResolve?.();
|
|
155
161
|
}
|
|
156
162
|
|
|
@@ -235,10 +241,19 @@ export function isCurrentZLinkSpotSerialTurn(executor: ZLinkSpotSerialTurnExecut
|
|
|
235
241
|
|
|
236
242
|
export interface ZLinkExecutionBarrierSeal {
|
|
237
243
|
readonly generation: bigint;
|
|
244
|
+
/**
|
|
245
|
+
* A Spot Close seal waits for yielded turns accepted before it and lets them
|
|
246
|
+
* resume (Spot address messaging §7 step 2); a relocation seal does not, and
|
|
247
|
+
* their continuation fails (execution gate §4).
|
|
248
|
+
*/
|
|
249
|
+
readonly drainsYieldedTurns: boolean;
|
|
238
250
|
}
|
|
239
251
|
|
|
240
252
|
export interface ZLinkExecutionBarrierClaim {
|
|
241
253
|
release(): void;
|
|
254
|
+
/** The turn yielded; it no longer runs until resume() admits it again. */
|
|
255
|
+
suspend(): void;
|
|
256
|
+
resume(): boolean;
|
|
242
257
|
}
|
|
243
258
|
|
|
244
259
|
interface ZLinkExecutionBarrierWaiter {
|
|
@@ -254,11 +269,16 @@ interface ZLinkExecutionBarrierWaiter {
|
|
|
254
269
|
export class ZLinkExecutionBarrier {
|
|
255
270
|
private generation = 0n;
|
|
256
271
|
private activeClaims = 0;
|
|
272
|
+
private suspendedClaims = 0;
|
|
257
273
|
private currentSeal: ZLinkExecutionBarrierSeal | undefined;
|
|
258
274
|
private readonly admissionWaiters: ZLinkExecutionBarrierWaiter[] = [];
|
|
259
275
|
private readonly quiescenceWaiters = new Set<() => void>();
|
|
260
276
|
private committed = false;
|
|
261
277
|
|
|
278
|
+
get isSealed(): boolean {
|
|
279
|
+
return this.currentSeal !== undefined || this.committed;
|
|
280
|
+
}
|
|
281
|
+
|
|
262
282
|
async enter(): Promise<ZLinkExecutionBarrierClaim> {
|
|
263
283
|
if (this.committed) {
|
|
264
284
|
throw createInternalFrameworkException(
|
|
@@ -274,19 +294,7 @@ export class ZLinkExecutionBarrier {
|
|
|
274
294
|
return this.createClaim();
|
|
275
295
|
}
|
|
276
296
|
|
|
277
|
-
|
|
278
|
-
* Re-enters a previously yielded application turn only while admission is
|
|
279
|
-
* open. A continuation must fail instead of waiting behind a relocation
|
|
280
|
-
* seal that already won the turn-boundary race.
|
|
281
|
-
*/
|
|
282
|
-
tryEnter(): ZLinkExecutionBarrierClaim | undefined {
|
|
283
|
-
if (this.committed || this.currentSeal !== undefined) {
|
|
284
|
-
return undefined;
|
|
285
|
-
}
|
|
286
|
-
return this.createClaim();
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
seal(): ZLinkExecutionBarrierSeal {
|
|
297
|
+
seal(drainsYieldedTurns = false): ZLinkExecutionBarrierSeal {
|
|
290
298
|
if (this.committed) {
|
|
291
299
|
throw createInternalFrameworkException(
|
|
292
300
|
ZLinkFrameworkInternalErrorKind.SpotMoving,
|
|
@@ -296,17 +304,19 @@ export class ZLinkExecutionBarrier {
|
|
|
296
304
|
if (this.currentSeal !== undefined) {
|
|
297
305
|
throw new Error('ZLink execution barrier is already sealed.');
|
|
298
306
|
}
|
|
299
|
-
const seal = Object.freeze({ generation: ++this.generation });
|
|
307
|
+
const seal = Object.freeze({ generation: ++this.generation, drainsYieldedTurns });
|
|
300
308
|
this.currentSeal = seal;
|
|
301
309
|
return seal;
|
|
302
310
|
}
|
|
303
311
|
|
|
304
312
|
async waitForQuiescence(seal: ZLinkExecutionBarrierSeal, signal?: AbortSignal): Promise<void> {
|
|
305
313
|
this.requireCurrent(seal);
|
|
306
|
-
if (this.
|
|
314
|
+
if (this.isQuiescent(seal)) return;
|
|
307
315
|
if (signal?.aborted === true) throw signal.reason;
|
|
308
316
|
await new Promise<void>((resolve, reject) => {
|
|
309
317
|
const complete = () => {
|
|
318
|
+
if (!this.isQuiescent(seal)) return;
|
|
319
|
+
this.quiescenceWaiters.delete(complete);
|
|
310
320
|
signal?.removeEventListener('abort', abort);
|
|
311
321
|
resolve();
|
|
312
322
|
};
|
|
@@ -345,18 +355,46 @@ export class ZLinkExecutionBarrier {
|
|
|
345
355
|
return this.currentSeal?.generation === seal.generation;
|
|
346
356
|
}
|
|
347
357
|
|
|
358
|
+
private isQuiescent(seal: ZLinkExecutionBarrierSeal): boolean {
|
|
359
|
+
return this.activeClaims === 0 && (!seal.drainsYieldedTurns || this.suspendedClaims === 0);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
private notifyQuiescence(): void {
|
|
363
|
+
for (const waiter of [...this.quiescenceWaiters]) waiter();
|
|
364
|
+
}
|
|
365
|
+
|
|
348
366
|
private createClaim(): ZLinkExecutionBarrierClaim {
|
|
349
367
|
this.activeClaims++;
|
|
350
|
-
let released =
|
|
368
|
+
let state: 'active' | 'suspended' | 'released' = 'active';
|
|
369
|
+
const release = () => {
|
|
370
|
+
if (state === 'released') return;
|
|
371
|
+
if (state === 'active') this.activeClaims--;
|
|
372
|
+
else this.suspendedClaims--;
|
|
373
|
+
state = 'released';
|
|
374
|
+
this.notifyQuiescence();
|
|
375
|
+
};
|
|
351
376
|
return {
|
|
352
|
-
release
|
|
353
|
-
|
|
354
|
-
|
|
377
|
+
release,
|
|
378
|
+
suspend: () => {
|
|
379
|
+
if (state !== 'active') return;
|
|
380
|
+
state = 'suspended';
|
|
355
381
|
this.activeClaims--;
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
382
|
+
this.suspendedClaims++;
|
|
383
|
+
this.notifyQuiescence();
|
|
384
|
+
},
|
|
385
|
+
resume: () => {
|
|
386
|
+
if (state !== 'suspended') return state === 'active';
|
|
387
|
+
if (
|
|
388
|
+
this.committed ||
|
|
389
|
+
(this.currentSeal !== undefined && !this.currentSeal.drainsYieldedTurns)
|
|
390
|
+
) {
|
|
391
|
+
release();
|
|
392
|
+
return false;
|
|
359
393
|
}
|
|
394
|
+
state = 'active';
|
|
395
|
+
this.suspendedClaims--;
|
|
396
|
+
this.activeClaims++;
|
|
397
|
+
return true;
|
|
360
398
|
}
|
|
361
399
|
};
|
|
362
400
|
}
|
|
@@ -38,6 +38,7 @@ export interface ZLinkSerialWorkPreparation {
|
|
|
38
38
|
|
|
39
39
|
type SerialWorkRecord<T> = Omit<ZLinkSerialWorkRecord<T>, 'acceptedSequence'> & {
|
|
40
40
|
acceptedSequence: bigint;
|
|
41
|
+
readonly operationEnvelope: boolean;
|
|
41
42
|
readonly preparation?: ZLinkSerialWorkPreparation;
|
|
42
43
|
preparationState: 'idle' | 'pending' | 'canceling' | 'ready' | 'failed';
|
|
43
44
|
preparationController?: AbortController;
|
|
@@ -55,6 +56,7 @@ interface ZLinkSerialAdmissionLane {
|
|
|
55
56
|
export class ZLinkSerialExecutionQueue {
|
|
56
57
|
private readonly application: ZLinkSerialAdmissionLane;
|
|
57
58
|
private readonly lifecycle: ZLinkSerialAdmissionLane;
|
|
59
|
+
private activeLifecycle?: ZLinkSerialWorkRecord<unknown>;
|
|
58
60
|
private readonly ownerTimeBudget: number;
|
|
59
61
|
private readonly lifecycleBurstLimit: number;
|
|
60
62
|
private nextAcceptedSequence = 1n;
|
|
@@ -138,23 +140,17 @@ export class ZLinkSerialExecutionQueue {
|
|
|
138
140
|
return this.admit(operation, options, context);
|
|
139
141
|
}
|
|
140
142
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
readonly lifecycleMessages: number;
|
|
145
|
-
readonly lifecycleBytes: number;
|
|
146
|
-
} {
|
|
147
|
-
return {
|
|
148
|
-
applicationMessages: this.application.records.length,
|
|
149
|
-
applicationBytes: 0,
|
|
150
|
-
lifecycleMessages: this.lifecycle.records.length,
|
|
151
|
-
lifecycleBytes: 0
|
|
152
|
-
};
|
|
143
|
+
/** Holds lifecycle FIFO through an operation result while its application turns can run. */
|
|
144
|
+
submitLifecycleOperation<T>(operation: () => Promise<T> | T): Promise<T> {
|
|
145
|
+
return this.admit(operation, { lane: 'lifecycle' }, undefined, undefined, true);
|
|
153
146
|
}
|
|
154
147
|
|
|
155
148
|
get hasPendingWork(): boolean {
|
|
156
149
|
return (
|
|
157
|
-
this.draining ||
|
|
150
|
+
this.draining ||
|
|
151
|
+
this.activeLifecycle !== undefined ||
|
|
152
|
+
this.application.records.length > 0 ||
|
|
153
|
+
this.lifecycle.records.length > 0
|
|
158
154
|
);
|
|
159
155
|
}
|
|
160
156
|
|
|
@@ -165,15 +161,21 @@ export class ZLinkSerialExecutionQueue {
|
|
|
165
161
|
|
|
166
162
|
/** Stops new submissions while allowing the accepted FIFO to finish. */
|
|
167
163
|
close(): Promise<void> {
|
|
168
|
-
this.
|
|
164
|
+
this.closeAdmission();
|
|
169
165
|
return this.whenIdle();
|
|
170
166
|
}
|
|
171
167
|
|
|
168
|
+
/** Stops admission within the lifecycle operation that owns this queue's close. */
|
|
169
|
+
closeAdmission(): void {
|
|
170
|
+
this.closed = true;
|
|
171
|
+
}
|
|
172
|
+
|
|
172
173
|
private admit<T>(
|
|
173
174
|
operation: () => Promise<T> | T,
|
|
174
175
|
options: ZLinkSerialWorkOptions,
|
|
175
176
|
context?: unknown,
|
|
176
|
-
preparation?: ZLinkSerialWorkPreparation
|
|
177
|
+
preparation?: ZLinkSerialWorkPreparation,
|
|
178
|
+
operationEnvelope = false
|
|
177
179
|
): Promise<T> {
|
|
178
180
|
if (this.closed) throw new Error('The serial execution queue is closed.');
|
|
179
181
|
const lane = options.lane ?? 'application';
|
|
@@ -189,6 +191,14 @@ export class ZLinkSerialExecutionQueue {
|
|
|
189
191
|
const release = (): void => {
|
|
190
192
|
if (released) return;
|
|
191
193
|
released = true;
|
|
194
|
+
if (this.activeLifecycle === record) {
|
|
195
|
+
this.activeLifecycle = undefined;
|
|
196
|
+
if (this.application.records.length > 0 || this.lifecycle.records.length > 0) {
|
|
197
|
+
this.scheduleDrain();
|
|
198
|
+
} else if (!this.draining) {
|
|
199
|
+
this.resolveIdleWaiters();
|
|
200
|
+
}
|
|
201
|
+
}
|
|
192
202
|
};
|
|
193
203
|
const resolve = (value: T): void => {
|
|
194
204
|
if (settled) return;
|
|
@@ -204,6 +214,7 @@ export class ZLinkSerialExecutionQueue {
|
|
|
204
214
|
acceptedSequence: 0n,
|
|
205
215
|
lane,
|
|
206
216
|
operation,
|
|
217
|
+
operationEnvelope,
|
|
207
218
|
context,
|
|
208
219
|
preparation,
|
|
209
220
|
preparationState: preparation === undefined ? 'ready' : 'idle',
|
|
@@ -275,7 +286,25 @@ export class ZLinkSerialExecutionQueue {
|
|
|
275
286
|
}
|
|
276
287
|
this.claimStartedAt ??= performance.now();
|
|
277
288
|
try {
|
|
278
|
-
|
|
289
|
+
if (record.operationEnvelope) {
|
|
290
|
+
try {
|
|
291
|
+
const outcome = record.operation();
|
|
292
|
+
void Promise.resolve(outcome).then(
|
|
293
|
+
(value) => {
|
|
294
|
+
record.resolve(value);
|
|
295
|
+
record.release();
|
|
296
|
+
},
|
|
297
|
+
(error) => {
|
|
298
|
+
record.reject(error);
|
|
299
|
+
record.release();
|
|
300
|
+
}
|
|
301
|
+
);
|
|
302
|
+
} catch (error) {
|
|
303
|
+
record.fail(error);
|
|
304
|
+
}
|
|
305
|
+
} else {
|
|
306
|
+
await this.executeRecord(record);
|
|
307
|
+
}
|
|
279
308
|
} catch (error) {
|
|
280
309
|
record.fail(error);
|
|
281
310
|
} finally {
|
|
@@ -291,11 +320,12 @@ export class ZLinkSerialExecutionQueue {
|
|
|
291
320
|
this.claimStartedAt = undefined;
|
|
292
321
|
if (
|
|
293
322
|
!waitingForPreparation &&
|
|
294
|
-
(this.application.records.length > 0 ||
|
|
323
|
+
(this.application.records.length > 0 ||
|
|
324
|
+
(this.activeLifecycle === undefined && this.lifecycle.records.length > 0))
|
|
295
325
|
) {
|
|
296
326
|
this.scheduleDrain();
|
|
297
327
|
} else {
|
|
298
|
-
if (this.
|
|
328
|
+
if (!this.hasPendingWork) {
|
|
299
329
|
this.resolveIdleWaiters();
|
|
300
330
|
}
|
|
301
331
|
}
|
|
@@ -309,7 +339,7 @@ export class ZLinkSerialExecutionQueue {
|
|
|
309
339
|
}
|
|
310
340
|
| undefined {
|
|
311
341
|
const applicationReady = this.application.records.length > 0;
|
|
312
|
-
const lifecycleReady = this.lifecycle.records.length > 0;
|
|
342
|
+
const lifecycleReady = this.activeLifecycle === undefined && this.lifecycle.records.length > 0;
|
|
313
343
|
if (!applicationReady && !lifecycleReady) return undefined;
|
|
314
344
|
if (!applicationReady) {
|
|
315
345
|
return { lane: 'lifecycle', record: this.lifecycle.records[0]! };
|
|
@@ -339,7 +369,9 @@ export class ZLinkSerialExecutionQueue {
|
|
|
339
369
|
if (this.application.records.length > 0 && this.lifecycleStreak >= this.lifecycleBurstLimit) {
|
|
340
370
|
this.lifecycleDebt = true;
|
|
341
371
|
}
|
|
342
|
-
|
|
372
|
+
const record = this.lifecycle.records.shift()!;
|
|
373
|
+
this.activeLifecycle = record;
|
|
374
|
+
return record;
|
|
343
375
|
}
|
|
344
376
|
|
|
345
377
|
private takeApplication(): ZLinkSerialWorkRecord<unknown> {
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { ZLinkListenerKind } from '../../contracts/RouteMesh/RuntimeTopology';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Bound listener records of one runtime generation (spec 04 §3.1). A listener writes its
|
|
5
|
+
* record when it finishes binding. The listener status query reads only this object.
|
|
6
|
+
*/
|
|
7
|
+
export class ZLinkListenerRecords {
|
|
8
|
+
private readonly endpoints = new Map<string, string>();
|
|
9
|
+
|
|
10
|
+
record(kind: ZLinkListenerKind, name: string, endpoint: string): void {
|
|
11
|
+
this.endpoints.set(`${kind}:${name}`, endpoint);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
clear(): void {
|
|
15
|
+
this.endpoints.clear();
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
endpoint(kind: ZLinkListenerKind, name: string): string | undefined {
|
|
19
|
+
return this.endpoints.get(`${kind}:${name}`);
|
|
20
|
+
}
|
|
21
|
+
}
|