@zlink-systems/framework 0.28.0 → 0.29.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/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/actors/actor-runtime-contracts.d.ts +1 -0
- package/dist/runtime/actors/index.d.ts +2 -0
- package/dist/runtime/actors/index.js +21 -6
- 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 +105 -145
- 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/handlers/handler-instance-scope.js +1 -1
- package/dist/runtime/host/actor-placement-coordinator.d.ts +2 -1
- package/dist/runtime/host/actor-placement-coordinator.js +2 -1
- package/dist/runtime/host/actor-runtime-options-factory.d.ts +2 -1
- package/dist/runtime/host/actor-runtime-options-factory.js +1 -0
- package/dist/runtime/host/index.d.ts +1 -1
- package/dist/runtime/host/index.js +3 -2
- 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 +3 -1
- package/dist/runtime/locations/in-memory-authority-store.d.ts +3 -0
- package/dist/runtime/locations/in-memory-authority-store.js +47 -12
- package/dist/runtime/locations/in-memory-location-store.d.ts +2 -1
- package/dist/runtime/locations/in-memory-location-store.js +15 -10
- package/dist/runtime/locations/internal-location-contracts.d.ts +5 -0
- package/dist/runtime/locations/internal-store-contracts.d.ts +1 -1
- package/dist/runtime/locations/location-store-repository.d.ts +2 -0
- package/dist/runtime/locations/location-store-repository.js +130 -54
- package/dist/runtime/spots/spot-entry-activation.d.ts +1 -0
- package/dist/runtime/spots/spot-entry-activation.js +3 -0
- package/dist/runtime/spots/spot-node-runtime-manager.d.ts +1 -0
- package/dist/runtime/spots/spot-node-runtime-manager.js +6 -0
- package/dist/runtime/spots/spot-serial-executor.d.ts +1 -0
- package/dist/runtime/spots/spot-serial-executor.js +14 -2
- 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/actors/actor-runtime-contracts.ts +4 -0
- package/src/runtime/actors/index.ts +77 -42
- 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 +152 -224
- package/src/runtime/foundation/service-stateful-wire-codec.ts +0 -25
- package/src/runtime/foundation/service-topology-registry.ts +30 -20
- package/src/runtime/handlers/handler-instance-scope.ts +2 -1
- package/src/runtime/host/actor-placement-coordinator.ts +8 -2
- package/src/runtime/host/actor-runtime-options-factory.ts +6 -0
- package/src/runtime/host/index.ts +6 -2
- 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 +6 -1
- package/src/runtime/locations/in-memory-authority-store.ts +83 -12
- package/src/runtime/locations/in-memory-location-store.ts +22 -10
- package/src/runtime/locations/internal-location-contracts.ts +6 -0
- package/src/runtime/locations/internal-store-contracts.ts +1 -0
- package/src/runtime/locations/location-store-repository.ts +182 -69
- package/src/runtime/protocol/service_wire_pilot_codec.generated.ts +1 -1
- package/src/runtime/spots/spot-entry-activation.ts +4 -0
- package/src/runtime/spots/spot-node-runtime-manager.ts +7 -0
- package/src/runtime/spots/spot-serial-executor.ts +14 -5
- 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
|
@@ -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.1",
|
|
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.1",
|
|
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> {
|
|
@@ -49,6 +49,10 @@ export interface ZLinkActorManagerOptions {
|
|
|
49
49
|
signal?: AbortSignal
|
|
50
50
|
) => Promise<ZLinkActorCreateResponse | undefined>;
|
|
51
51
|
readonly actorDestroyedCleanup?: (actorId: string) => void;
|
|
52
|
+
readonly terminateActorActivation?: (
|
|
53
|
+
actor: ZLinkActor,
|
|
54
|
+
terminal: () => Promise<void>
|
|
55
|
+
) => Promise<void>;
|
|
52
56
|
readonly publishActorAuthority?: (
|
|
53
57
|
actorType: string,
|
|
54
58
|
actorRef: ActorRef,
|
|
@@ -29,6 +29,7 @@ import {
|
|
|
29
29
|
wireReplyFailureException
|
|
30
30
|
} from '../framework-errors-internal';
|
|
31
31
|
import { disposeLifecycleHandlers } from '../handlers/handler-instance-scope';
|
|
32
|
+
import { currentZLinkActorExecution } from './actor-execution-context';
|
|
32
33
|
import { encodeFrameworkPayloadMessage } from '../messaging/payload-codec';
|
|
33
34
|
import { ZLinkActorCreationCoordinator, type ZLinkActorCreateRequest } from './actor-creation';
|
|
34
35
|
import type { ZLinkActorManagerOptions } from './actor-runtime-contracts';
|
|
@@ -217,34 +218,43 @@ export class DefaultZLinkActorManager implements ZLinkActorManager {
|
|
|
217
218
|
const completions = this.options.nativeActorCompletionTableProvider?.();
|
|
218
219
|
const entryNodeRid = state.entryNodeRid ?? current.nodeRid;
|
|
219
220
|
if (node === undefined || completions === undefined) return false;
|
|
220
|
-
const destroyTask = state.getOrStartDestroy(entryNodeRid,
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
221
|
+
const destroyTask = state.getOrStartDestroy(entryNodeRid, (nativeRef) =>
|
|
222
|
+
this.terminateActor(state, async () => {
|
|
223
|
+
if (nativeRef !== undefined) {
|
|
224
|
+
const completion = await completions.submit(
|
|
225
|
+
() => node.destroyActor(nativeRef, 0),
|
|
226
|
+
signal
|
|
227
|
+
);
|
|
228
|
+
try {
|
|
229
|
+
if (completion.terminalResult !== 0 || completion.failureErrno !== 0) {
|
|
230
|
+
// Classify the (terminal, fine) pair via the shared translator
|
|
231
|
+
// (spec 32-framework-error-model:83-118, 99-108) instead of
|
|
232
|
+
// collapsing every destroy failure to NotFound; genuine
|
|
233
|
+
// route-not-found fine codes still yield NotFound through the
|
|
234
|
+
// shared table.
|
|
235
|
+
throw wireReplyFailureException(
|
|
236
|
+
completion.terminalResult,
|
|
237
|
+
completion.failureErrno,
|
|
238
|
+
`Actor '${actor.actorId}' destroy failed with result '${completion.terminalResult}' and errno '${completion.failureErrno}'.`
|
|
239
|
+
);
|
|
240
|
+
}
|
|
241
|
+
} finally {
|
|
242
|
+
closeMeshCompletion(completion);
|
|
235
243
|
}
|
|
236
|
-
|
|
237
|
-
closeMeshCompletion(completion);
|
|
244
|
+
state.markNativeActorDestroyed(nativeRef);
|
|
238
245
|
}
|
|
239
|
-
state.
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
+
if (state.actorType !== undefined && state.ownsLocation) {
|
|
247
|
+
await this.options.locationLifecycle?.releaseActor(
|
|
248
|
+
state.actorType,
|
|
249
|
+
actor.actorId,
|
|
250
|
+
current
|
|
251
|
+
);
|
|
252
|
+
}
|
|
253
|
+
await this.finalizeDestroyedActor(actor.actorId, state);
|
|
254
|
+
})
|
|
255
|
+
);
|
|
246
256
|
try {
|
|
247
|
-
await destroyTask;
|
|
257
|
+
await this.awaitActorDestroy(state, destroyTask);
|
|
248
258
|
} catch (error) {
|
|
249
259
|
state.clearFailedDestroy(destroyTask);
|
|
250
260
|
throw error;
|
|
@@ -548,6 +558,29 @@ export class DefaultZLinkActorManager implements ZLinkActorManager {
|
|
|
548
558
|
this.options.metrics?.change(METRIC_NAMES.ActorCount, -1);
|
|
549
559
|
}
|
|
550
560
|
}
|
|
561
|
+
|
|
562
|
+
private terminateActor(
|
|
563
|
+
state: ZLinkActorRuntimeState,
|
|
564
|
+
terminal: () => Promise<void>
|
|
565
|
+
): Promise<void> {
|
|
566
|
+
const actor = state.actor;
|
|
567
|
+
const completion =
|
|
568
|
+
actor === undefined || this.options.terminateActorActivation === undefined
|
|
569
|
+
? terminal()
|
|
570
|
+
: this.options.terminateActorActivation(actor, terminal);
|
|
571
|
+
return completion;
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
private awaitActorDestroy(
|
|
575
|
+
state: ZLinkActorRuntimeState,
|
|
576
|
+
completion: Promise<void>
|
|
577
|
+
): Promise<void> {
|
|
578
|
+
if (currentZLinkActorExecution()?.actorId === state.actorId) {
|
|
579
|
+
this.detachedTaskRunner.runDetached('Actor terminal cleanup', () => completion);
|
|
580
|
+
return Promise.resolve();
|
|
581
|
+
}
|
|
582
|
+
return completion;
|
|
583
|
+
}
|
|
551
584
|
async getOrCreateActor(
|
|
552
585
|
actorId: string,
|
|
553
586
|
actorType: string,
|
|
@@ -648,25 +681,27 @@ export class DefaultZLinkActorManager implements ZLinkActorManager {
|
|
|
648
681
|
`Actor '${actor.context.actorId}' must leave its current SPOT before destroy.`
|
|
649
682
|
);
|
|
650
683
|
}
|
|
651
|
-
const destroyTask = state.getOrStartDestroy(entryNodeRid,
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
684
|
+
const destroyTask = state.getOrStartDestroy(entryNodeRid, (actorRef) =>
|
|
685
|
+
this.terminateActor(state, async () => {
|
|
686
|
+
const destroyedActorRef =
|
|
687
|
+
actorRef === undefined ? undefined : toFrameworkActorRef(actorRef, state.meshName ?? '');
|
|
688
|
+
if (actorRef !== undefined) {
|
|
689
|
+
await node.destroyActor(actorRef, 0, destroySignal);
|
|
690
|
+
state.markNativeActorDestroyed(actorRef);
|
|
691
|
+
}
|
|
692
|
+
if (state.actorType !== undefined && state.ownsLocation) {
|
|
693
|
+
await this.options.locationLifecycle?.releaseActor(
|
|
694
|
+
state.actorType,
|
|
695
|
+
actor.context.actorId,
|
|
696
|
+
destroyedActorRef
|
|
697
|
+
);
|
|
698
|
+
}
|
|
699
|
+
await this.finalizeDestroyedActor(actor.context.actorId, state);
|
|
700
|
+
})
|
|
701
|
+
);
|
|
667
702
|
|
|
668
703
|
try {
|
|
669
|
-
await destroyTask;
|
|
704
|
+
await this.awaitActorDestroy(state, destroyTask);
|
|
670
705
|
} catch (error) {
|
|
671
706
|
state.clearFailedDestroy(destroyTask);
|
|
672
707
|
throw error;
|
|
@@ -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
|
: {
|