@zlink-systems/framework 0.28.0 → 0.29.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/Configuration/Builders.d.ts +2 -0
- package/dist/contracts/Configuration/InternalDefaults.d.ts +3 -0
- package/dist/contracts/Configuration/InternalDefaults.js +4 -1
- package/dist/contracts/Configuration/RegistrationBuilders.js +9 -0
- package/dist/contracts/Configuration/RegistrationNormalizers.d.ts +1 -0
- package/dist/contracts/Configuration/RegistrationNormalizers.js +16 -0
- package/dist/contracts/Configuration/RegistrationTypes.d.ts +3 -0
- package/dist/runtime/actors/actor-mailbox.d.ts +2 -2
- package/dist/runtime/actors/actor-mailbox.js +2 -2
- package/dist/runtime/backend/node/node-raw-mesh-backend.js +1 -3
- package/dist/runtime/execution/serial-execution-queue.d.ts +12 -3
- package/dist/runtime/execution/serial-execution-queue.js +116 -28
- package/dist/runtime/foundation/operation-registry.d.ts +3 -2
- package/dist/runtime/foundation/operation-registry.js +5 -1
- package/dist/runtime/foundation/service-authority-payload-codec.d.ts +0 -2
- package/dist/runtime/foundation/service-authority-payload-codec.js +0 -43
- package/dist/runtime/foundation/service-stateful-registry.d.ts +4 -3
- package/dist/runtime/foundation/service-stateful-registry.js +5 -2
- package/dist/runtime/foundation/service-stateful-runtime.d.ts +1 -8
- package/dist/runtime/foundation/service-stateful-runtime.js +78 -126
- package/dist/runtime/foundation/service-stateful-wire-codec.d.ts +0 -11
- package/dist/runtime/foundation/service-stateful-wire-codec.js +1 -23
- package/dist/runtime/foundation/service-topology-registry.d.ts +5 -0
- package/dist/runtime/foundation/service-topology-registry.js +16 -17
- package/dist/runtime/host/instance-activation-authority.d.ts +0 -1
- package/dist/runtime/host/instance-activation-authority.js +46 -97
- package/dist/runtime/host/spot-address-transport.js +1 -1
- package/dist/runtime/host/stateful-authority-route-runtime.d.ts +1 -0
- package/dist/runtime/host/stateful-authority-route-runtime.js +17 -9
- package/dist/runtime/locations/domain-store-contract.d.ts +1 -0
- package/dist/runtime/locations/in-memory-authority-store.d.ts +2 -0
- package/dist/runtime/locations/in-memory-authority-store.js +22 -8
- package/dist/runtime/locations/in-memory-location-store.d.ts +1 -0
- package/dist/runtime/locations/in-memory-location-store.js +3 -0
- package/dist/runtime/locations/internal-store-contracts.d.ts +1 -1
- package/dist/runtime/locations/location-store-repository.d.ts +1 -0
- package/dist/runtime/locations/location-store-repository.js +92 -49
- package/dist/runtime/spots/spot-serial-executor.js +4 -1
- package/dist/runtime/spots/spot-serial-turn-executor.d.ts +3 -0
- package/dist/runtime/spots/spot-serial-turn-executor.js +5 -0
- package/dist/runtime/streams/index.d.ts +0 -1
- package/dist/runtime/streams/index.js +4 -3
- package/dist/runtime/streams/protocol.js +0 -9
- package/dist/runtime/streams/session-context.d.ts +0 -4
- package/dist/runtime/streams/session-context.js +0 -26
- package/dist/runtime/streams/session-serial-executor.d.ts +0 -1
- package/dist/runtime/streams/session-serial-executor.js +0 -3
- package/dist/runtime/streams/stream-message-utils.d.ts +0 -1
- package/dist/runtime/streams/stream-message-utils.js +0 -20
- package/dist/runtime/streams/stream-session-runtime.d.ts +6 -3
- package/dist/runtime/streams/stream-session-runtime.js +40 -85
- package/package.json +3 -3
- package/src/contracts/Configuration/Builders.ts +2 -0
- package/src/contracts/Configuration/InternalDefaults.ts +4 -0
- package/src/contracts/Configuration/RegistrationBuilders.ts +13 -0
- package/src/contracts/Configuration/RegistrationNormalizers.ts +25 -0
- package/src/contracts/Configuration/RegistrationTypes.ts +3 -0
- package/src/runtime/actors/actor-mailbox.ts +7 -2
- package/src/runtime/backend/node/node-raw-mesh-backend.ts +1 -7
- package/src/runtime/execution/serial-execution-queue.ts +117 -29
- package/src/runtime/foundation/operation-registry.ts +16 -9
- package/src/runtime/foundation/service-authority-payload-codec.ts +0 -46
- package/src/runtime/foundation/service-stateful-registry.ts +10 -5
- package/src/runtime/foundation/service-stateful-runtime.ts +103 -181
- package/src/runtime/foundation/service-stateful-wire-codec.ts +0 -25
- package/src/runtime/foundation/service-topology-registry.ts +30 -20
- package/src/runtime/host/instance-activation-authority.ts +58 -125
- package/src/runtime/host/spot-address-transport.ts +1 -1
- package/src/runtime/host/stateful-authority-route-runtime.ts +22 -9
- package/src/runtime/locations/domain-store-contract.ts +5 -0
- package/src/runtime/locations/in-memory-authority-store.ts +42 -8
- package/src/runtime/locations/in-memory-location-store.ts +8 -0
- package/src/runtime/locations/internal-store-contracts.ts +1 -0
- package/src/runtime/locations/location-store-repository.ts +123 -64
- package/src/runtime/protocol/service_wire_pilot_codec.generated.ts +1 -1
- package/src/runtime/spots/spot-serial-executor.ts +4 -1
- package/src/runtime/spots/spot-serial-turn-executor.ts +10 -0
- package/src/runtime/streams/index.ts +3 -1
- package/src/runtime/streams/protocol.ts +0 -13
- package/src/runtime/streams/session-context.ts +0 -32
- package/src/runtime/streams/session-serial-executor.ts +0 -8
- package/src/runtime/streams/stream-message-utils.ts +1 -27
- package/src/runtime/streams/stream-session-runtime.ts +54 -97
- package/dist/runtime/streams/session-requests.d.ts +0 -25
- package/dist/runtime/streams/session-requests.js +0 -101
- package/src/runtime/streams/session-requests.ts +0 -110
|
@@ -118,15 +118,6 @@ function decodeStreamHeader(header, flowEnabled = true) {
|
|
|
118
118
|
const decoded = (0, stream_wire_1.decodeStreamWireHeader)(header, undefined, flowEnabled);
|
|
119
119
|
const kind = decoded.kind;
|
|
120
120
|
const flags = decoded.flags;
|
|
121
|
-
const hasRequestSeq = (flags & stream_wire_1.ZlinkStreamHeaderFlags.HasRequestSeq) !== 0;
|
|
122
|
-
const hasMetadata = (flags & stream_wire_1.ZlinkStreamHeaderFlags.HasMetadata) !== 0;
|
|
123
|
-
const hasCorrelation = (flags & stream_wire_1.ZlinkStreamHeaderFlags.HasCorrelationId) !== 0;
|
|
124
|
-
const hasFlow = (flags & stream_wire_1.ZlinkStreamHeaderFlags.HasFlowId) !== 0;
|
|
125
|
-
const hasActorSlot = (flags & stream_wire_1.ZlinkStreamHeaderFlags.HasActorSlot) !== 0;
|
|
126
|
-
if (kind === stream_wire_1.ZlinkStreamMessageKind.Control &&
|
|
127
|
-
(hasCorrelation || hasRequestSeq || hasMetadata || hasFlow || hasActorSlot)) {
|
|
128
|
-
throw new Error('Control packet must not contain a request sequence, metadata, correlation id, or flow id.');
|
|
129
|
-
}
|
|
130
121
|
const metadata = publicStreamMetadata(decoded.metadata);
|
|
131
122
|
return {
|
|
132
123
|
kind,
|
|
@@ -6,7 +6,6 @@ import type { ServiceRetiredBoundSessionRouteFence } from '../foundation/service
|
|
|
6
6
|
import { type ZLinkSubmitResult } from '../messaging/submission-result';
|
|
7
7
|
import { ZLinkStreamMessageKind, type ZLinkStreamFrameHeader, type ZLinkStreamReplyMessageKind } from './protocol';
|
|
8
8
|
import { DefaultZLinkBoundSessionSendCall } from './session-calls';
|
|
9
|
-
import { type ZLinkPendingSessionRequest } from './session-requests';
|
|
10
9
|
export interface ZLinkSessionContextStream extends ZLinkStream {
|
|
11
10
|
writeRaw(payload: Message, flags?: number): boolean;
|
|
12
11
|
submitRaw(payload: Message): Promise<ZLinkSubmitResult>;
|
|
@@ -42,7 +41,6 @@ export declare class DefaultZLinkSessionContext implements ZLinkSessionContext {
|
|
|
42
41
|
private readonly handlerRegistry;
|
|
43
42
|
readonly handlers: ZLinkSessionHandlerRegistry;
|
|
44
43
|
private readonly localActors;
|
|
45
|
-
private readonly requests;
|
|
46
44
|
private currentDispatchHeader;
|
|
47
45
|
private currentReplyClaimed;
|
|
48
46
|
private actorBindingReplacementHandler?;
|
|
@@ -73,8 +71,6 @@ export declare class DefaultZLinkSessionContext implements ZLinkSessionContext {
|
|
|
73
71
|
/** True while the current request dispatch has already submitted its reply. */
|
|
74
72
|
get replyClaimed(): boolean;
|
|
75
73
|
claimReply(requestHeader: ZLinkStreamFrameHeader): void;
|
|
76
|
-
startRequest(timeoutMs?: number): ZLinkPendingSessionRequest;
|
|
77
|
-
tryCompleteResponse(header: ZLinkStreamFrameHeader, payload: Message): boolean;
|
|
78
74
|
payloadForHeader(header: ZLinkStreamFrameHeader, payload: Message): Message;
|
|
79
75
|
get boundActors(): readonly DefaultZLinkSessionActor[];
|
|
80
76
|
findBoundActor(actorId: string): DefaultZLinkSessionActor | undefined;
|
|
@@ -12,7 +12,6 @@ const submission_result_1 = require("../messaging/submission-result");
|
|
|
12
12
|
const protocol_1 = require("./protocol");
|
|
13
13
|
const session_calls_1 = require("./session-calls");
|
|
14
14
|
const session_local_actors_1 = require("./session-local-actors");
|
|
15
|
-
const session_requests_1 = require("./session-requests");
|
|
16
15
|
class DefaultZLinkSessionContext {
|
|
17
16
|
runtime;
|
|
18
17
|
stream;
|
|
@@ -22,7 +21,6 @@ class DefaultZLinkSessionContext {
|
|
|
22
21
|
handlerRegistry;
|
|
23
22
|
handlers;
|
|
24
23
|
localActors = new session_local_actors_1.ZLinkSessionLocalActorBindings();
|
|
25
|
-
requests = new session_requests_1.ZLinkSessionRequestTracker();
|
|
26
24
|
currentDispatchHeader;
|
|
27
25
|
currentReplyClaimed = false;
|
|
28
26
|
actorBindingReplacementHandler;
|
|
@@ -137,21 +135,6 @@ class DefaultZLinkSessionContext {
|
|
|
137
135
|
}
|
|
138
136
|
this.currentReplyClaimed = true;
|
|
139
137
|
}
|
|
140
|
-
startRequest(timeoutMs) {
|
|
141
|
-
return this.requests.start(timeoutMs);
|
|
142
|
-
}
|
|
143
|
-
tryCompleteResponse(header, payload) {
|
|
144
|
-
if (header.requestSeq === undefined) {
|
|
145
|
-
return false;
|
|
146
|
-
}
|
|
147
|
-
if (header.kind === protocol_1.ZLinkStreamMessageKind.Response) {
|
|
148
|
-
return this.requests.complete(header.requestSeq, payload);
|
|
149
|
-
}
|
|
150
|
-
if (header.kind === protocol_1.ZLinkStreamMessageKind.Error) {
|
|
151
|
-
return this.requests.fail(header.requestSeq, decodeStreamErrorPayload(payload));
|
|
152
|
-
}
|
|
153
|
-
return false;
|
|
154
|
-
}
|
|
155
138
|
payloadForHeader(header, payload) {
|
|
156
139
|
if ((header.flags & protocol_1.ZLinkStreamHeaderFlags.PayloadCompressed) === 0) {
|
|
157
140
|
return payload;
|
|
@@ -374,15 +357,6 @@ class DefaultZLinkBoundSessionFactory {
|
|
|
374
357
|
}
|
|
375
358
|
}
|
|
376
359
|
exports.DefaultZLinkBoundSessionFactory = DefaultZLinkBoundSessionFactory;
|
|
377
|
-
function decodeStreamErrorPayload(payload) {
|
|
378
|
-
try {
|
|
379
|
-
const value = JSON.parse(Buffer.from((0, protocol_1.messageToBytes)(payload)).toString());
|
|
380
|
-
return new Error(typeof value.message === 'string' ? value.message : 'Stream request failed.');
|
|
381
|
-
}
|
|
382
|
-
catch {
|
|
383
|
-
return new Error('Stream request failed.');
|
|
384
|
-
}
|
|
385
|
-
}
|
|
386
360
|
function sameRetiredSession(left, right) {
|
|
387
361
|
return (left.sessionOwnerNodeRid === right.sessionOwnerNodeRid &&
|
|
388
362
|
left.sessionOwnerNodeGeneration === right.sessionOwnerNodeGeneration &&
|
|
@@ -4,7 +4,6 @@ export declare class ZLinkSessionSerialExecutor {
|
|
|
4
4
|
private closed;
|
|
5
5
|
executeApplication(work: () => Promise<void>, options?: Omit<ZLinkSerialWorkOptions, 'lane'>, onRejected?: (error?: unknown) => void): boolean;
|
|
6
6
|
executeControl(work: () => Promise<void>, options?: Omit<ZLinkSerialWorkOptions, 'lane'>, onRejected?: (error?: unknown) => void): boolean;
|
|
7
|
-
executeInfrastructure(work: () => Promise<void>, options?: Omit<ZLinkSerialWorkOptions, 'lane'>, onRejected?: (error?: unknown) => void): boolean;
|
|
8
7
|
executeFinal(work: () => Promise<void>): Promise<void>;
|
|
9
8
|
private submit;
|
|
10
9
|
dispose(): Promise<void>;
|
|
@@ -13,9 +13,6 @@ class ZLinkSessionSerialExecutor {
|
|
|
13
13
|
executeControl(work, options = {}, onRejected) {
|
|
14
14
|
return this.submit(work, 'lifecycle', options, onRejected);
|
|
15
15
|
}
|
|
16
|
-
executeInfrastructure(work, options = {}, onRejected) {
|
|
17
|
-
return this.submit(work, 'lifecycle', options, onRejected);
|
|
18
|
-
}
|
|
19
16
|
executeFinal(work) {
|
|
20
17
|
if (this.closed) {
|
|
21
18
|
return Promise.reject((0, framework_errors_internal_1.createInternalFrameworkException)(framework_errors_internal_1.ZLinkFrameworkInternalErrorKind.RuntimeShutdown, 'Session execution queue is closed.'));
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import type { Message } from '../../contracts/Common/Message';
|
|
2
|
-
export declare function copyMessage(message: Message): Message;
|
|
3
2
|
export declare function simpleMessage(bytes: Uint8Array): unknown;
|
|
4
3
|
/** Create the structural Message used by the internal receive assembler. */
|
|
5
4
|
export declare function ownedMessage(bytes: Uint8Array): Message;
|
|
@@ -1,30 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.copyMessage = copyMessage;
|
|
4
3
|
exports.simpleMessage = simpleMessage;
|
|
5
4
|
exports.ownedMessage = ownedMessage;
|
|
6
5
|
const framework_json_v1_1 = require("../messaging/framework-json-v1");
|
|
7
6
|
const protocol_1 = require("./protocol");
|
|
8
7
|
const EMPTY_MESSAGE_BYTES = new Uint8Array(0);
|
|
9
|
-
function copyMessage(message) {
|
|
10
|
-
const value = message;
|
|
11
|
-
if (value.copy !== undefined) {
|
|
12
|
-
return value.copy();
|
|
13
|
-
}
|
|
14
|
-
if (value.toBytes !== undefined) {
|
|
15
|
-
return simpleMessage(value.toBytes());
|
|
16
|
-
}
|
|
17
|
-
if (value.data !== undefined) {
|
|
18
|
-
return simpleMessage(value.data());
|
|
19
|
-
}
|
|
20
|
-
if (value.bytes !== undefined) {
|
|
21
|
-
return simpleMessage(value.bytes);
|
|
22
|
-
}
|
|
23
|
-
if (value.getString !== undefined) {
|
|
24
|
-
return simpleMessage((0, protocol_1.utf8Encode)(value.getString()));
|
|
25
|
-
}
|
|
26
|
-
throw new Error('Stream response payload cannot be copied.');
|
|
27
|
-
}
|
|
28
8
|
function simpleMessage(bytes) {
|
|
29
9
|
return createSimpleMessage(new Uint8Array(bytes), false);
|
|
30
10
|
}
|
|
@@ -30,6 +30,9 @@ export interface ZLinkStreamSessionContextFactory {
|
|
|
30
30
|
createSessionContext(stream: ZLinkManagedStream, close?: (signal?: AbortSignal) => Promise<void>, providerResolver?: ZLinkProviderResolver): DefaultZLinkSessionContext;
|
|
31
31
|
}
|
|
32
32
|
export interface ZLinkStreamSessionRuntimeOptions {
|
|
33
|
+
readonly heartbeatIntervalMs?: number;
|
|
34
|
+
readonly heartbeatTimeoutMs?: number;
|
|
35
|
+
readonly idleTimeoutMs?: number;
|
|
33
36
|
readonly socket: ZLinkBackendStreamSocket;
|
|
34
37
|
readonly nativeSessionService?: StreamSessionService;
|
|
35
38
|
readonly meshCompletions?: ZLinkMeshCompletionTable;
|
|
@@ -66,8 +69,10 @@ export declare class ZLinkStreamSessionRuntime {
|
|
|
66
69
|
private closeReason;
|
|
67
70
|
private metricsClosed;
|
|
68
71
|
private readonly livenessClock;
|
|
72
|
+
private readonly liveness;
|
|
69
73
|
private lastApplicationActivityAt;
|
|
70
74
|
private lastInboundFrameAt;
|
|
75
|
+
private lastHeartbeatPingAt;
|
|
71
76
|
private livenessTimer;
|
|
72
77
|
constructor(options: ZLinkStreamSessionRuntimeOptions & ZLinkStreamLivenessOptions, routingId: unknown, removeSession?: (sessionId: string, session: ZLinkStreamSessionRuntime) => void);
|
|
73
78
|
get session(): Promise<ZLinkSession>;
|
|
@@ -79,8 +84,7 @@ export declare class ZLinkStreamSessionRuntime {
|
|
|
79
84
|
enqueuePacket(payload: Message, decodedHeader: ZLinkStreamFrameHeader, terminalOwner?: ZLinkRetainedStreamOwner, applicationJobPermit?: ApplicationJobPermitPort): void;
|
|
80
85
|
enqueueDisconnected(error?: unknown): void;
|
|
81
86
|
/**
|
|
82
|
-
*
|
|
83
|
-
* without retaining that lane while application code awaits unrelated work.
|
|
87
|
+
* Retains the session turn through the callback's terminal result.
|
|
84
88
|
*/
|
|
85
89
|
enqueueActorBindingReplaced(actor: ServiceActorRef, retiredSession: ServiceRetiredBoundSessionRouteFence): void;
|
|
86
90
|
close(signal?: AbortSignal): Promise<void>;
|
|
@@ -105,7 +109,6 @@ export declare class ZLinkStreamSessionRuntime {
|
|
|
105
109
|
private queueDisconnect;
|
|
106
110
|
private cleanup;
|
|
107
111
|
private enqueueControl;
|
|
108
|
-
private enqueueInfrastructure;
|
|
109
112
|
private enqueueApplication;
|
|
110
113
|
/**
|
|
111
114
|
* Spec 26 §2.1/§3.1: a frame discarded because the session serial lane is
|
|
@@ -19,19 +19,18 @@ const protocol_1 = require("./protocol");
|
|
|
19
19
|
const session_context_1 = require("./session-context");
|
|
20
20
|
const session_serial_executor_1 = require("./session-serial-executor");
|
|
21
21
|
const stream_message_utils_1 = require("./stream-message-utils");
|
|
22
|
+
const RegistrationNormalizers_1 = require("../../contracts/Configuration/RegistrationNormalizers");
|
|
22
23
|
const ZLINK_SEND_DONT_WAIT = 1;
|
|
23
24
|
const ZLINK_RECV_DONT_WAIT = 1;
|
|
24
|
-
const ZLINK_STREAM_HEARTBEAT_INTERVAL_MS = 1_000;
|
|
25
|
-
const ZLINK_STREAM_HEARTBEAT_TIMEOUT_MS = 5_000;
|
|
26
|
-
const ZLINK_STREAM_APPLICATION_IDLE_TIMEOUT_MS = 30_000;
|
|
27
25
|
const ZLINK_STREAM_RECEIVE_FRAME_BATCH_LIMIT = 64;
|
|
28
26
|
const ZLINK_STREAM_ACTOR_BINDING_REPLACEMENT_CLOSE_DELAY_MS = 100;
|
|
29
27
|
const ZLINK_STREAM_MONITOR_IDLE_MIN_DELAY_MS = 1;
|
|
30
28
|
const ZLINK_STREAM_MONITOR_IDLE_MAX_DELAY_MS = 20;
|
|
31
29
|
const ZLINK_STREAM_MONITOR_IDLE_MAX_STEPS = 7;
|
|
30
|
+
const MAX_TIMER_DELAY_MS = 2_147_483_647;
|
|
32
31
|
const systemLivenessClock = {
|
|
33
32
|
now: () => performance.now(),
|
|
34
|
-
setTimer: (callback, delayMs) => setTimeout(callback, delayMs),
|
|
33
|
+
setTimer: (callback, delayMs) => setTimeout(callback, Math.min(delayMs, MAX_TIMER_DELAY_MS)),
|
|
35
34
|
clearTimer: (timer) => clearTimeout(timer)
|
|
36
35
|
};
|
|
37
36
|
class ZLinkStreamReceiveWake {
|
|
@@ -69,12 +68,15 @@ class ZLinkStreamSessionRuntime {
|
|
|
69
68
|
closeReason = 'client_close';
|
|
70
69
|
metricsClosed = false;
|
|
71
70
|
livenessClock;
|
|
71
|
+
liveness;
|
|
72
72
|
lastApplicationActivityAt = 0;
|
|
73
73
|
lastInboundFrameAt = 0;
|
|
74
|
+
lastHeartbeatPingAt = 0;
|
|
74
75
|
livenessTimer;
|
|
75
76
|
constructor(options, routingId, removeSession = () => { }) {
|
|
76
77
|
this.options = options;
|
|
77
78
|
this.removeSession = removeSession;
|
|
79
|
+
this.liveness = (0, RegistrationNormalizers_1.normalizeStreamLiveness)(options);
|
|
78
80
|
this.livenessClock = options.livenessClock ?? systemLivenessClock;
|
|
79
81
|
this.stream = new managed_stream_1.ZLinkManagedStream(options.socket, routingId, options.messageSerializers, options.nativeSessionService, options.meshCompletions, undefined, options.nativeSessionRouteForMesh);
|
|
80
82
|
this.context = options.bindingRuntime.createSessionContext(this.stream, (signal) => this.close(signal), options.providerResolver);
|
|
@@ -112,7 +114,7 @@ class ZLinkStreamSessionRuntime {
|
|
|
112
114
|
return this.sessionReady;
|
|
113
115
|
}
|
|
114
116
|
enqueueConnected(localAddr, remoteAddr) {
|
|
115
|
-
this.serial.
|
|
117
|
+
this.serial.executeControl(async () => this.markConnected(localAddr, remoteAddr));
|
|
116
118
|
}
|
|
117
119
|
enqueuePacket(payload, decodedHeader, terminalOwner, applicationJobPermit) {
|
|
118
120
|
this.lastInboundFrameAt = Math.max(this.lastInboundFrameAt, this.livenessClock.now());
|
|
@@ -124,41 +126,20 @@ class ZLinkStreamSessionRuntime {
|
|
|
124
126
|
terminalOwner?.close();
|
|
125
127
|
};
|
|
126
128
|
if (decodedHeader.kind === protocol_1.ZLinkStreamMessageKind.Control) {
|
|
127
|
-
|
|
128
|
-
(
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
this.options.onError?.(error);
|
|
133
|
-
})
|
|
134
|
-
.finally(() => {
|
|
135
|
-
applicationJobPermit?.releaseAfterInternalProcessing();
|
|
136
|
-
payload.close();
|
|
137
|
-
releaseTerminal();
|
|
138
|
-
});
|
|
139
|
-
return;
|
|
140
|
-
}
|
|
141
|
-
this.enqueueControl(async () => {
|
|
142
|
-
try {
|
|
143
|
-
const dispatch = () => this.dispatchPacket(payload, decodedHeader);
|
|
144
|
-
if (applicationJobPermit === undefined)
|
|
145
|
-
await dispatch();
|
|
146
|
-
else
|
|
147
|
-
await (0, application_job_queue_scope_1.runWithApplicationJobPermit)(applicationJobPermit, dispatch);
|
|
148
|
-
}
|
|
149
|
-
finally {
|
|
150
|
-
releaseTerminal();
|
|
151
|
-
}
|
|
152
|
-
}, () => {
|
|
129
|
+
void this.handleControl(decodedHeader, payload)
|
|
130
|
+
.catch((error) => {
|
|
131
|
+
this.options.onError?.(error);
|
|
132
|
+
})
|
|
133
|
+
.finally(() => {
|
|
153
134
|
applicationJobPermit?.releaseAfterInternalProcessing();
|
|
154
135
|
payload.close();
|
|
155
136
|
releaseTerminal();
|
|
156
|
-
}
|
|
137
|
+
});
|
|
157
138
|
return;
|
|
158
139
|
}
|
|
159
140
|
if (decodedHeader.kind === protocol_1.ZLinkStreamMessageKind.Response ||
|
|
160
141
|
decodedHeader.kind === protocol_1.ZLinkStreamMessageKind.Error) {
|
|
161
|
-
this.
|
|
142
|
+
this.enqueueControl(async () => {
|
|
162
143
|
try {
|
|
163
144
|
await this.dispatchPacket(payload, decodedHeader);
|
|
164
145
|
}
|
|
@@ -198,8 +179,7 @@ class ZLinkStreamSessionRuntime {
|
|
|
198
179
|
this.queueDisconnect(error);
|
|
199
180
|
}
|
|
200
181
|
/**
|
|
201
|
-
*
|
|
202
|
-
* without retaining that lane while application code awaits unrelated work.
|
|
182
|
+
* Retains the session turn through the callback's terminal result.
|
|
203
183
|
*/
|
|
204
184
|
enqueueActorBindingReplaced(actor, retiredSession) {
|
|
205
185
|
this.serial.executeControl(async () => {
|
|
@@ -209,7 +189,6 @@ class ZLinkStreamSessionRuntime {
|
|
|
209
189
|
return;
|
|
210
190
|
const session = await this.requireSession();
|
|
211
191
|
let callbackDeadlineTimer;
|
|
212
|
-
let callbackTerminal = false;
|
|
213
192
|
let forcedClose = false;
|
|
214
193
|
const scheduleClose = () => {
|
|
215
194
|
if (forcedClose)
|
|
@@ -223,25 +202,6 @@ class ZLinkStreamSessionRuntime {
|
|
|
223
202
|
void this.close().catch((error) => this.options.onError?.(error));
|
|
224
203
|
}, ZLINK_STREAM_ACTOR_BINDING_REPLACEMENT_CLOSE_DELAY_MS);
|
|
225
204
|
};
|
|
226
|
-
const completeCallback = () => {
|
|
227
|
-
if (callbackTerminal)
|
|
228
|
-
return;
|
|
229
|
-
callbackTerminal = true;
|
|
230
|
-
if (callbackDeadlineTimer !== undefined) {
|
|
231
|
-
this.livenessClock.clearTimer(callbackDeadlineTimer);
|
|
232
|
-
callbackDeadlineTimer = undefined;
|
|
233
|
-
}
|
|
234
|
-
scheduleClose();
|
|
235
|
-
};
|
|
236
|
-
const reportCallbackFailure = (error) => {
|
|
237
|
-
try {
|
|
238
|
-
this.options.onError?.(error);
|
|
239
|
-
}
|
|
240
|
-
catch {
|
|
241
|
-
// Diagnostics must not prevent the lifecycle terminal transition.
|
|
242
|
-
}
|
|
243
|
-
completeCallback();
|
|
244
|
-
};
|
|
245
205
|
callbackDeadlineTimer = this.livenessClock.setTimer(() => {
|
|
246
206
|
if (this.disposed ||
|
|
247
207
|
this.disconnected ||
|
|
@@ -251,19 +211,17 @@ class ZLinkStreamSessionRuntime {
|
|
|
251
211
|
forcedClose = true;
|
|
252
212
|
void this.close().catch((error) => this.options.onError?.(error));
|
|
253
213
|
}, this.options.replacementCallbackTimeoutMs ?? Registration_1.DEFAULT_SESSION_REPLACEMENT_CALLBACK_TIMEOUT_MS);
|
|
254
|
-
const callback = session.onActorBindingReplaced;
|
|
255
|
-
if (callback === undefined) {
|
|
256
|
-
completeCallback();
|
|
257
|
-
return;
|
|
258
|
-
}
|
|
259
214
|
try {
|
|
260
|
-
|
|
261
|
-
// serial lane while application code awaits an unrelated operation.
|
|
262
|
-
const result = callback.call(session, this.context, actor.actorId);
|
|
263
|
-
void Promise.resolve(result).then(() => completeCallback(), reportCallbackFailure);
|
|
215
|
+
await session.onActorBindingReplaced?.(this.context, actor.actorId);
|
|
264
216
|
}
|
|
265
217
|
catch (error) {
|
|
266
|
-
|
|
218
|
+
this.options.onError?.(error);
|
|
219
|
+
}
|
|
220
|
+
finally {
|
|
221
|
+
if (callbackDeadlineTimer !== undefined) {
|
|
222
|
+
this.livenessClock.clearTimer(callbackDeadlineTimer);
|
|
223
|
+
}
|
|
224
|
+
scheduleClose();
|
|
267
225
|
}
|
|
268
226
|
});
|
|
269
227
|
}
|
|
@@ -313,6 +271,7 @@ class ZLinkStreamSessionRuntime {
|
|
|
313
271
|
this.connected = true;
|
|
314
272
|
const connectedAt = this.livenessClock.now();
|
|
315
273
|
this.lastApplicationActivityAt = connectedAt;
|
|
274
|
+
this.lastHeartbeatPingAt = connectedAt;
|
|
316
275
|
this.lastInboundFrameAt = Math.max(this.lastInboundFrameAt, connectedAt);
|
|
317
276
|
this.scheduleLivenessCheck();
|
|
318
277
|
this.options.metrics?.change(runtime_metrics_1.METRIC_NAMES.StreamConnectionsActive, 1, { transport: 'tcp' });
|
|
@@ -324,15 +283,8 @@ class ZLinkStreamSessionRuntime {
|
|
|
324
283
|
let dispatchPayload = payload;
|
|
325
284
|
let enteredDispatch = false;
|
|
326
285
|
try {
|
|
327
|
-
if (decodedHeader.kind === protocol_1.ZLinkStreamMessageKind.Control) {
|
|
328
|
-
await this.handleControl(decodedHeader, payload);
|
|
329
|
-
return;
|
|
330
|
-
}
|
|
331
286
|
this.lastApplicationActivityAt = this.livenessClock.now();
|
|
332
287
|
dispatchPayload = this.context.payloadForHeader(decodedHeader, payload);
|
|
333
|
-
if (this.context.tryCompleteResponse(decodedHeader, dispatchPayload)) {
|
|
334
|
-
return;
|
|
335
|
-
}
|
|
336
288
|
const dispatchActor = this.context.actorForSlot(decodedHeader.actorSlot);
|
|
337
289
|
if (decodedHeader.actorSlot !== undefined && dispatchActor === undefined) {
|
|
338
290
|
const staleFlowEnabled = this.options.dispatchErrors?.flow.flowCreationEnabled() ?? true;
|
|
@@ -466,25 +418,33 @@ class ZLinkStreamSessionRuntime {
|
|
|
466
418
|
if (this.disposed || this.disconnected || this.livenessTimer !== undefined) {
|
|
467
419
|
return;
|
|
468
420
|
}
|
|
421
|
+
const now = this.livenessClock.now();
|
|
422
|
+
const due = Math.min(this.lastHeartbeatPingAt + this.liveness.heartbeatIntervalMs, this.lastInboundFrameAt + this.liveness.heartbeatTimeoutMs, this.liveness.idleTimeoutMs > 0
|
|
423
|
+
? this.lastApplicationActivityAt + this.liveness.idleTimeoutMs
|
|
424
|
+
: Infinity);
|
|
469
425
|
this.livenessTimer = this.livenessClock.setTimer(() => {
|
|
470
426
|
this.livenessTimer = undefined;
|
|
471
|
-
|
|
472
|
-
},
|
|
427
|
+
void this.runLivenessCheck().catch((error) => this.options.onError?.(error));
|
|
428
|
+
}, Math.max(0, due - now));
|
|
473
429
|
}
|
|
474
430
|
async runLivenessCheck() {
|
|
475
431
|
if (this.disposed || this.disconnected || !this.connected) {
|
|
476
432
|
return;
|
|
477
433
|
}
|
|
478
434
|
const now = this.livenessClock.now();
|
|
479
|
-
if (
|
|
435
|
+
if (this.liveness.idleTimeoutMs > 0 &&
|
|
436
|
+
now - this.lastApplicationActivityAt >= this.liveness.idleTimeoutMs) {
|
|
480
437
|
await this.closeForLiveness(protocol_1.ZLinkStreamCloseReasonCode.IdleTimeout, 'idle_timeout', 'Application idle timeout.');
|
|
481
438
|
return;
|
|
482
439
|
}
|
|
483
|
-
if (now - this.lastInboundFrameAt >=
|
|
440
|
+
if (now - this.lastInboundFrameAt >= this.liveness.heartbeatTimeoutMs) {
|
|
484
441
|
await this.closeForLiveness(protocol_1.ZLinkStreamCloseReasonCode.HeartbeatTimeout, 'heartbeat_timeout', 'Heartbeat timeout.');
|
|
485
442
|
return;
|
|
486
443
|
}
|
|
487
|
-
|
|
444
|
+
if (now - this.lastHeartbeatPingAt >= this.liveness.heartbeatIntervalMs) {
|
|
445
|
+
this.lastHeartbeatPingAt = now;
|
|
446
|
+
await this.stream.writeControl(protocol_1.ZLINK_STREAM_HEARTBEAT_PING);
|
|
447
|
+
}
|
|
488
448
|
this.scheduleLivenessCheck();
|
|
489
449
|
}
|
|
490
450
|
async closeForLiveness(reason, closeReason, diagnostic) {
|
|
@@ -496,7 +456,9 @@ class ZLinkStreamSessionRuntime {
|
|
|
496
456
|
this.closeReason = closeReason;
|
|
497
457
|
this.stopLivenessChecks();
|
|
498
458
|
await this.stream.closeForReason(reason, diagnostic);
|
|
499
|
-
|
|
459
|
+
void this.serial
|
|
460
|
+
.executeFinal(async () => this.complete(undefined, true))
|
|
461
|
+
.catch((error) => this.options.onError?.(error));
|
|
500
462
|
}
|
|
501
463
|
stopLivenessChecks() {
|
|
502
464
|
if (this.livenessTimer !== undefined) {
|
|
@@ -609,13 +571,6 @@ class ZLinkStreamSessionRuntime {
|
|
|
609
571
|
onRejected?.();
|
|
610
572
|
}
|
|
611
573
|
}
|
|
612
|
-
enqueueInfrastructure(work, onRejected, onShutdownRejected) {
|
|
613
|
-
if (!this.serial.executeInfrastructure(work, {}, onRejected)) {
|
|
614
|
-
// The serial lane only refuses new work after dispose() closed it.
|
|
615
|
-
onShutdownRejected?.();
|
|
616
|
-
onRejected?.();
|
|
617
|
-
}
|
|
618
|
-
}
|
|
619
574
|
enqueueApplication(work, onRejected, onShutdownRejected) {
|
|
620
575
|
let claim;
|
|
621
576
|
try {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zlink-systems/framework",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.29.0",
|
|
4
4
|
"license": "SEE LICENSE IN LICENSE",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"dependencies": {
|
|
20
20
|
"@opentelemetry/api": "^1.9.1",
|
|
21
21
|
"@opentelemetry/api-logs": "^0.221.0",
|
|
22
|
-
"@zlink-systems/stream-wire": "0.
|
|
23
|
-
"@zlink-systems/zlink": "1.
|
|
22
|
+
"@zlink-systems/stream-wire": "0.29.0",
|
|
23
|
+
"@zlink-systems/zlink": "1.17.0"
|
|
24
24
|
}
|
|
25
25
|
}
|
|
@@ -187,6 +187,8 @@ export interface ZLinkFanoutChannelBuilder {
|
|
|
187
187
|
}
|
|
188
188
|
|
|
189
189
|
export interface ZLinkStreamNodeBuilder {
|
|
190
|
+
setHeartbeat(intervalMs: number, timeoutMs: number): this;
|
|
191
|
+
setIdleTimeout(timeoutMs: number): this;
|
|
190
192
|
bind(endpointOrPort?: string | number): this;
|
|
191
193
|
setBindHost(bindHost: string): this;
|
|
192
194
|
setAdvertiseHost(advertiseHost: string): this;
|
|
@@ -22,3 +22,7 @@ export const ZLINK_RELOCATION_MAX_BLOB_REFERENCE_BYTES = 4096;
|
|
|
22
22
|
export const AUTHORITY_ENVELOPE_MAX_BYTES = 1024 * 1024;
|
|
23
23
|
export const CREATION_INTENT_MAX_BYTES = 1024 * 1024;
|
|
24
24
|
export const CREATION_TERMINAL_ENVELOPE_MAX_BYTES = 1024 * 1024;
|
|
25
|
+
|
|
26
|
+
export const DEFAULT_STREAM_HEARTBEAT_INTERVAL_MS = 1_000;
|
|
27
|
+
export const DEFAULT_STREAM_HEARTBEAT_TIMEOUT_MS = 5_000;
|
|
28
|
+
export const DEFAULT_STREAM_IDLE_TIMEOUT_MS = 0;
|
|
@@ -689,6 +689,16 @@ class DefaultClientServerChannelServerBuilder implements ZLinkClientServerChanne
|
|
|
689
689
|
}
|
|
690
690
|
|
|
691
691
|
class DefaultStreamNodeBuilder implements ZLinkStreamNodeBuilder {
|
|
692
|
+
setHeartbeat(intervalMs: number, timeoutMs: number): this {
|
|
693
|
+
this.streamNode.heartbeatIntervalMs = intervalMs;
|
|
694
|
+
this.streamNode.heartbeatTimeoutMs = timeoutMs;
|
|
695
|
+
return this;
|
|
696
|
+
}
|
|
697
|
+
setIdleTimeout(timeoutMs: number): this {
|
|
698
|
+
this.streamNode.idleTimeoutMs = timeoutMs;
|
|
699
|
+
return this;
|
|
700
|
+
}
|
|
701
|
+
|
|
692
702
|
constructor(private readonly streamNode: MutableStreamNodeOptions) {}
|
|
693
703
|
|
|
694
704
|
bind(endpointOrPort: string | number = 0): this {
|
|
@@ -1605,6 +1615,9 @@ interface MutablePublisherCapabilityOptions {
|
|
|
1605
1615
|
}
|
|
1606
1616
|
|
|
1607
1617
|
interface MutableStreamNodeOptions {
|
|
1618
|
+
heartbeatIntervalMs?: number;
|
|
1619
|
+
heartbeatTimeoutMs?: number;
|
|
1620
|
+
idleTimeoutMs?: number;
|
|
1608
1621
|
bind?: string;
|
|
1609
1622
|
bindHost?: string;
|
|
1610
1623
|
advertiseHost?: string;
|
|
@@ -21,6 +21,9 @@ import { normalizeEndpoint, parseEndpointHostPort } from './EndpointNotation';
|
|
|
21
21
|
import {
|
|
22
22
|
defaultWorkerMaxThreads,
|
|
23
23
|
DEFAULT_STREAM_NODE_MAX_MESSAGE_SIZE,
|
|
24
|
+
DEFAULT_STREAM_HEARTBEAT_INTERVAL_MS,
|
|
25
|
+
DEFAULT_STREAM_HEARTBEAT_TIMEOUT_MS,
|
|
26
|
+
DEFAULT_STREAM_IDLE_TIMEOUT_MS,
|
|
24
27
|
DEFAULT_WORKER_IDLE_TIMEOUT_MS,
|
|
25
28
|
DEFAULT_WORKER_MIN_THREADS
|
|
26
29
|
} from './InternalDefaults';
|
|
@@ -77,6 +80,27 @@ export function toChannelMap(
|
|
|
77
80
|
);
|
|
78
81
|
}
|
|
79
82
|
|
|
83
|
+
export function normalizeStreamLiveness(
|
|
84
|
+
options: ZLinkStreamNodeOptions
|
|
85
|
+
): Required<
|
|
86
|
+
Pick<ZLinkStreamNodeOptions, 'heartbeatIntervalMs' | 'heartbeatTimeoutMs' | 'idleTimeoutMs'>
|
|
87
|
+
> {
|
|
88
|
+
const heartbeatIntervalMs = options.heartbeatIntervalMs ?? DEFAULT_STREAM_HEARTBEAT_INTERVAL_MS;
|
|
89
|
+
const heartbeatTimeoutMs = options.heartbeatTimeoutMs ?? DEFAULT_STREAM_HEARTBEAT_TIMEOUT_MS;
|
|
90
|
+
const idleTimeoutMs = options.idleTimeoutMs ?? DEFAULT_STREAM_IDLE_TIMEOUT_MS;
|
|
91
|
+
if (!Number.isFinite(heartbeatIntervalMs) || heartbeatIntervalMs <= 0)
|
|
92
|
+
throw new ZLinkConfigurationException('STREAM heartbeat interval must be positive.');
|
|
93
|
+
if (!Number.isFinite(heartbeatTimeoutMs) || heartbeatTimeoutMs <= 0)
|
|
94
|
+
throw new ZLinkConfigurationException('STREAM heartbeat timeout must be positive.');
|
|
95
|
+
if (heartbeatTimeoutMs <= heartbeatIntervalMs)
|
|
96
|
+
throw new ZLinkConfigurationException(
|
|
97
|
+
'STREAM heartbeat timeout must be greater than interval.'
|
|
98
|
+
);
|
|
99
|
+
if (!Number.isFinite(idleTimeoutMs) || idleTimeoutMs < 0)
|
|
100
|
+
throw new ZLinkConfigurationException('STREAM idle timeout must not be negative.');
|
|
101
|
+
return { heartbeatIntervalMs, heartbeatTimeoutMs, idleTimeoutMs };
|
|
102
|
+
}
|
|
103
|
+
|
|
80
104
|
export function toStreamNodeMap(
|
|
81
105
|
streamNodes: ZLinkFrameworkRegistrationOptions['streamNodes'],
|
|
82
106
|
network: ZLinkNetworkOptions
|
|
@@ -88,6 +112,7 @@ export function toStreamNodeMap(
|
|
|
88
112
|
name,
|
|
89
113
|
{
|
|
90
114
|
...normalized,
|
|
115
|
+
...normalizeStreamLiveness(streamNode),
|
|
91
116
|
maxMessageSize: streamNode.maxMessageSize ?? DEFAULT_STREAM_NODE_MAX_MESSAGE_SIZE
|
|
92
117
|
}
|
|
93
118
|
];
|
|
@@ -233,6 +233,9 @@ export interface ZLinkRouteChannelOptions {
|
|
|
233
233
|
}
|
|
234
234
|
|
|
235
235
|
export interface ZLinkStreamNodeOptions {
|
|
236
|
+
readonly heartbeatIntervalMs?: number;
|
|
237
|
+
readonly heartbeatTimeoutMs?: number;
|
|
238
|
+
readonly idleTimeoutMs?: number;
|
|
236
239
|
readonly bind?: string;
|
|
237
240
|
readonly bindHost?: string;
|
|
238
241
|
readonly advertiseHost?: string;
|
|
@@ -17,9 +17,14 @@ export class ZLinkActorSerialExecutor {
|
|
|
17
17
|
constructor(
|
|
18
18
|
private readonly actorId: string,
|
|
19
19
|
private readonly sourceSpotId: unknown,
|
|
20
|
-
options?: ZLinkSerialSchedulerOptions
|
|
20
|
+
options?: ZLinkSerialSchedulerOptions,
|
|
21
|
+
sharedOwner?: ZLinkSerialExecutionQueue
|
|
21
22
|
) {
|
|
22
|
-
this.scheduler = new ZLinkSerialExecutionQueue(
|
|
23
|
+
this.scheduler = new ZLinkSerialExecutionQueue(
|
|
24
|
+
(record) => this.runRecord(record),
|
|
25
|
+
options,
|
|
26
|
+
sharedOwner
|
|
27
|
+
);
|
|
23
28
|
}
|
|
24
29
|
|
|
25
30
|
execute<T>(operation: () => Promise<T> | T, workOptions?: ZLinkSerialWorkOptions): Promise<T> {
|
|
@@ -297,19 +297,13 @@ export class ZLinkNodeRawMeshBackend implements ZLinkBackendMeshNode {
|
|
|
297
297
|
selectObjectPlacement(stableType: string): ZLinkBackendObjectPlacement {
|
|
298
298
|
const runtime = this.requireRuntime();
|
|
299
299
|
const localNodeRid = runtime.topology.localDescriptor().nodeRoutingId;
|
|
300
|
-
const status = runtime.topology.
|
|
300
|
+
const { status, descriptor } = runtime.topology.selectObjectPlacementWithStatus(
|
|
301
301
|
stableType,
|
|
302
302
|
(candidate) =>
|
|
303
303
|
candidate.nodeRoutingId === localNodeRid ||
|
|
304
304
|
runtime.isPeerRouteReady(candidate.nodeRoutingId)
|
|
305
305
|
);
|
|
306
306
|
if (status !== 'available') return { kind: status };
|
|
307
|
-
const descriptor = runtime.topology.selectObjectPlacement(
|
|
308
|
-
stableType,
|
|
309
|
-
(candidate) =>
|
|
310
|
-
candidate.nodeRoutingId === localNodeRid ||
|
|
311
|
-
runtime.isPeerRouteReady(candidate.nodeRoutingId)
|
|
312
|
-
);
|
|
313
307
|
return descriptor === undefined
|
|
314
308
|
? { kind: 'unavailable' }
|
|
315
309
|
: {
|