@zlink-systems/framework 0.10.0 → 0.12.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/EndpointNotation.d.ts +5 -4
- package/dist/contracts/Configuration/EndpointNotation.js +5 -4
- package/dist/contracts/Configuration/InternalDefaults.d.ts +0 -1
- package/dist/contracts/Configuration/InternalDefaults.js +1 -2
- package/dist/contracts/Configuration/RegistrationNormalizers.js +1 -2
- package/dist/contracts/Configuration/RegistrationTypes.d.ts +2 -5
- package/dist/contracts/Configuration/RegistrationValidators.js +0 -1
- package/dist/contracts/Errors/ZLinkFrameworkException.d.ts +6 -7
- package/dist/contracts/Errors/ZLinkFrameworkException.js +6 -7
- package/dist/runtime/actors/actor-client.js +1 -1
- package/dist/runtime/actors/actor-mailbox.js +1 -5
- package/dist/runtime/backend/contracts/index.d.ts +2 -0
- package/dist/runtime/backend/mesh-completion-table.d.ts +1 -3
- package/dist/runtime/backend/mesh-completion-table.js +11 -15
- package/dist/runtime/backend/mesh-dispatch-pump.d.ts +11 -4
- package/dist/runtime/backend/mesh-dispatch-pump.js +201 -134
- package/dist/runtime/backend/node/node-backend-adapter-factory.d.ts +3 -4
- package/dist/runtime/backend/node/node-backend-adapter-factory.js +51 -37
- package/dist/runtime/backend/node/node-backend-adapter-support.d.ts +2 -2
- package/dist/runtime/backend/node/node-backend-adapter-support.js +2 -2
- package/dist/runtime/backend/node/node-dealer-channel-client-transport.js +2 -2
- package/dist/runtime/backend/node/node-event-loop-poller.d.ts +16 -0
- package/dist/runtime/backend/node/node-event-loop-poller.js +49 -0
- package/dist/runtime/backend/node/node-raw-binding-port.js +24 -41
- package/dist/runtime/backend/node/node-raw-mesh-backend.d.ts +8 -8
- package/dist/runtime/backend/node/node-raw-mesh-backend.js +100 -107
- package/dist/runtime/backend/node/node-socket-backend-adapter.d.ts +3 -2
- package/dist/runtime/backend/node/node-socket-backend-adapter.js +18 -67
- package/dist/runtime/backend/raw-binding-port.d.ts +1 -0
- package/dist/runtime/backend/runtime-values.d.ts +6 -1
- package/dist/runtime/backend/runtime-values.js +6 -0
- package/dist/runtime/channels/channel-dispatchers.js +2 -2
- package/dist/runtime/channels/channel-envelope.js +324 -121
- package/dist/runtime/channels/channel-multipart.d.ts +8 -1
- package/dist/runtime/channels/channel-outbound-operations.js +0 -2
- package/dist/runtime/channels/channel-receive-loops.d.ts +1 -3
- package/dist/runtime/channels/channel-receive-loops.js +25 -65
- package/dist/runtime/channels/channel-socket-registry.js +14 -1
- package/dist/runtime/channels/channel-transports.d.ts +2 -1
- package/dist/runtime/channels/channel-transports.js +12 -10
- package/dist/runtime/channels/client-server-location-runtime.js +15 -3
- package/dist/runtime/execution/serial-execution-queue.d.ts +5 -18
- package/dist/runtime/execution/serial-execution-queue.js +16 -80
- package/dist/runtime/foundation/event-loop-resources.d.ts +0 -3
- package/dist/runtime/foundation/event-loop-resources.js +2 -11
- package/dist/runtime/foundation/operation-registry.d.ts +2 -8
- package/dist/runtime/foundation/operation-registry.js +3 -21
- package/dist/runtime/foundation/raw-service-mesh-runtime.d.ts +19 -7
- package/dist/runtime/foundation/raw-service-mesh-runtime.js +56 -10
- package/dist/runtime/foundation/service-mailbox.d.ts +1 -7
- package/dist/runtime/foundation/service-mailbox.js +3 -2
- package/dist/runtime/foundation/service-runtime-contracts.d.ts +2 -1
- package/dist/runtime/foundation/service-stateful-registry.d.ts +1 -1
- package/dist/runtime/foundation/service-stateful-registry.js +2 -2
- package/dist/runtime/foundation/service-stateful-runtime.js +14 -14
- package/dist/runtime/framework-errors-internal.d.ts +4 -4
- package/dist/runtime/framework-errors-internal.js +8 -10
- package/dist/runtime/host/actor-placement-coordinator.js +1 -1
- package/dist/runtime/host/index.js +1 -1
- package/dist/runtime/host/service-relocation-host-runtime.js +0 -4
- package/dist/runtime/host/spot-address-transport.js +1 -1
- package/dist/runtime/host/user-spot-creation-coordinator.js +1 -1
- package/dist/runtime/locations/location-store-repository.d.ts +1 -0
- package/dist/runtime/locations/location-store-repository.js +64 -75
- package/dist/runtime/spots/spot-node-runtime-manager.d.ts +0 -12
- package/dist/runtime/spots/spot-node-runtime-manager.js +6 -166
- package/dist/runtime/spots/spot-serial-turn-executor.js +1 -4
- package/dist/runtime/spots/spot-timer.d.ts +3 -0
- package/dist/runtime/spots/spot-timer.js +87 -18
- package/dist/runtime/streams/session-serial-executor.js +1 -3
- package/dist/runtime/streams/stream-session-runtime.d.ts +2 -2
- package/dist/runtime/streams/stream-session-runtime.js +13 -19
- package/dist/runtime/workers/index.d.ts +0 -1
- package/dist/runtime/workers/index.js +1 -9
- package/package.json +9 -3
- package/src/contracts/Configuration/EndpointNotation.ts +5 -4
- package/src/contracts/Configuration/InternalDefaults.ts +0 -1
- package/src/contracts/Configuration/RegistrationNormalizers.ts +2 -4
- package/src/contracts/Configuration/RegistrationTypes.ts +2 -5
- package/src/contracts/Configuration/RegistrationValidators.ts +0 -1
- package/src/contracts/Errors/ZLinkFrameworkException.ts +6 -7
- package/src/runtime/actors/actor-client.ts +1 -1
- package/src/runtime/actors/actor-mailbox.ts +1 -12
- package/src/runtime/backend/contracts/index.ts +2 -0
- package/src/runtime/backend/mesh-completion-table.ts +12 -23
- package/src/runtime/backend/mesh-dispatch-pump.ts +206 -138
- package/src/runtime/backend/node/node-backend-adapter-factory.ts +66 -35
- package/src/runtime/backend/node/node-backend-adapter-support.ts +4 -4
- package/src/runtime/backend/node/node-dealer-channel-client-transport.ts +2 -2
- package/src/runtime/backend/node/node-event-loop-poller.ts +55 -0
- package/src/runtime/backend/node/node-raw-binding-port.ts +30 -45
- package/src/runtime/backend/node/node-raw-mesh-backend.ts +97 -110
- package/src/runtime/backend/node/node-socket-backend-adapter.ts +23 -69
- package/src/runtime/backend/raw-binding-port.ts +1 -0
- package/src/runtime/backend/runtime-values.ts +13 -1
- package/src/runtime/channels/channel-dispatchers.ts +2 -2
- package/src/runtime/channels/channel-envelope.ts +379 -109
- package/src/runtime/channels/channel-multipart.ts +9 -1
- package/src/runtime/channels/channel-outbound-operations.ts +0 -1
- package/src/runtime/channels/channel-receive-loops.ts +24 -75
- package/src/runtime/channels/channel-socket-registry.ts +17 -1
- package/src/runtime/channels/channel-transports.ts +20 -10
- package/src/runtime/channels/client-server-location-runtime.ts +16 -2
- package/src/runtime/execution/serial-execution-queue.ts +16 -108
- package/src/runtime/foundation/event-loop-resources.ts +2 -11
- package/src/runtime/foundation/operation-registry.ts +2 -22
- package/src/runtime/foundation/raw-service-mesh-runtime.ts +74 -19
- package/src/runtime/foundation/service-mailbox.ts +3 -13
- package/src/runtime/foundation/service-runtime-contracts.ts +2 -1
- package/src/runtime/foundation/service-stateful-registry.ts +2 -2
- package/src/runtime/foundation/service-stateful-runtime.ts +14 -14
- package/src/runtime/framework-errors-internal.ts +8 -10
- package/src/runtime/host/actor-placement-coordinator.ts +1 -1
- package/src/runtime/host/index.ts +2 -1
- package/src/runtime/host/service-relocation-host-runtime.ts +0 -4
- package/src/runtime/host/spot-address-transport.ts +1 -1
- package/src/runtime/host/user-spot-creation-coordinator.ts +1 -1
- package/src/runtime/locations/location-store-repository.ts +118 -80
- package/src/runtime/spots/spot-node-runtime-manager.ts +13 -233
- package/src/runtime/spots/spot-serial-turn-executor.ts +1 -8
- package/src/runtime/spots/spot-timer.ts +109 -18
- package/src/runtime/streams/session-serial-executor.ts +1 -7
- package/src/runtime/streams/stream-session-runtime.ts +13 -17
- package/src/runtime/workers/index.ts +2 -17
|
@@ -54,7 +54,7 @@ export class ZLinkDealerChannelClientTransport implements ZLinkChannelClientTran
|
|
|
54
54
|
true,
|
|
55
55
|
metadata
|
|
56
56
|
)
|
|
57
|
-
).submit());
|
|
57
|
+
).submit().admitted);
|
|
58
58
|
return { status: ZLinkSubmitStatus.Submitted };
|
|
59
59
|
}
|
|
60
60
|
|
|
@@ -83,7 +83,7 @@ export class ZLinkDealerChannelClientTransport implements ZLinkChannelClientTran
|
|
|
83
83
|
)
|
|
84
84
|
));
|
|
85
85
|
if (timeoutMs !== undefined) operation.timeout(timeoutMs);
|
|
86
|
-
const reply = await submitRequestOperation(operation, 'channel request');
|
|
86
|
+
const reply = await submitRequestOperation({ submit: () => operation.submit().reply }, 'channel request');
|
|
87
87
|
try {
|
|
88
88
|
return decodeChannelReply<TReply>(reply);
|
|
89
89
|
} finally {
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
BaseSocket,
|
|
3
|
+
PollEventFlagValue,
|
|
4
|
+
Socket
|
|
5
|
+
} from '@zlink-systems/zlink';
|
|
6
|
+
import { zlink } from './node-backend-adapter-support';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Owns one binding public Poller and drives it from the socket's libuv
|
|
10
|
+
* readiness callback. PollCompletion is drained only by Poller.wait; the
|
|
11
|
+
* socket callback itself is only the event-loop wakeup.
|
|
12
|
+
*/
|
|
13
|
+
export class ZLinkNodeEventLoopPoller {
|
|
14
|
+
private readonly poller = zlink.createPoller();
|
|
15
|
+
private readonly events = zlink.createPollEvents(1);
|
|
16
|
+
private readableHandler: () => void;
|
|
17
|
+
private disposed = false;
|
|
18
|
+
|
|
19
|
+
constructor(
|
|
20
|
+
socket: Socket,
|
|
21
|
+
pollCompletion: boolean,
|
|
22
|
+
readableHandler: () => void
|
|
23
|
+
) {
|
|
24
|
+
this.readableHandler = readableHandler;
|
|
25
|
+
try {
|
|
26
|
+
const pollEvents: PollEventFlagValue[] = [zlink.PollEventFlag.PollIn];
|
|
27
|
+
if (pollCompletion) pollEvents.push(zlink.PollEventFlag.PollCompletion);
|
|
28
|
+
this.poller.add(socket as BaseSocket, pollEvents, 0);
|
|
29
|
+
socket.setReadableHandler(this.onReady);
|
|
30
|
+
} catch (error) {
|
|
31
|
+
this.events.close();
|
|
32
|
+
this.poller.close();
|
|
33
|
+
throw error;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
setReadableHandler(handler: () => void): void {
|
|
38
|
+
this.readableHandler = handler;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
dispose(): void {
|
|
42
|
+
if (this.disposed) return;
|
|
43
|
+
this.disposed = true;
|
|
44
|
+
this.events.close();
|
|
45
|
+
this.poller.close();
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
private readonly onReady = (): void => {
|
|
49
|
+
if (this.disposed) return;
|
|
50
|
+
const ready = this.poller.wait(this.events, 0);
|
|
51
|
+
if (ready === 0 || this.events.hasEvent(0, zlink.PollEventFlag.PollIn)) {
|
|
52
|
+
this.readableHandler();
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
}
|
|
@@ -5,11 +5,9 @@ import {
|
|
|
5
5
|
RoutingId as BindingRoutingId,
|
|
6
6
|
createContext,
|
|
7
7
|
createDealerSocket,
|
|
8
|
-
createPollEvents,
|
|
9
|
-
createPoller,
|
|
10
8
|
createRouterSocket,
|
|
11
|
-
PollEventFlag,
|
|
12
9
|
ReceiveFlowState,
|
|
10
|
+
SubmitResult,
|
|
13
11
|
type Context,
|
|
14
12
|
type DealerSocket,
|
|
15
13
|
type MonitorEvent,
|
|
@@ -24,8 +22,9 @@ import {
|
|
|
24
22
|
type SendSubmitOperation,
|
|
25
23
|
type Socket
|
|
26
24
|
} from '@zlink-systems/zlink';
|
|
27
|
-
import {
|
|
25
|
+
import { translateBindingResultError } from './node-backend-adapter-support';
|
|
28
26
|
import { isEndpointCloseIgnorableError } from './node-socket-backend-adapter';
|
|
27
|
+
import { ZLinkNodeEventLoopPoller } from './node-event-loop-poller';
|
|
29
28
|
import type {
|
|
30
29
|
ZLinkRawBindingPort,
|
|
31
30
|
ZLinkRawDealerPort,
|
|
@@ -122,12 +121,20 @@ class NodeRawHostPort implements ZLinkRawHostPort {
|
|
|
122
121
|
abstract class NodeRawSocketPort<TSocket extends Socket> implements ZLinkRawSocketPort {
|
|
123
122
|
private readonly endpoints = new Set<string>();
|
|
124
123
|
private readonly monitors = new Set<NodeRawMonitorPort>();
|
|
125
|
-
private readonly
|
|
126
|
-
private readonly readableEvents = createPollEvents(1);
|
|
124
|
+
private readonly eventLoopPoller: ZLinkNodeEventLoopPoller;
|
|
127
125
|
private closed = false;
|
|
128
126
|
|
|
129
127
|
protected constructor(protected readonly socket: TSocket) {
|
|
130
|
-
this.
|
|
128
|
+
this.eventLoopPoller = new ZLinkNodeEventLoopPoller(
|
|
129
|
+
socket,
|
|
130
|
+
true,
|
|
131
|
+
() => {}
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
setReadableHandler(handler: () => void): void {
|
|
136
|
+
this.requireOpen();
|
|
137
|
+
this.eventLoopPoller.setReadableHandler(handler);
|
|
131
138
|
}
|
|
132
139
|
|
|
133
140
|
bind(endpoint: string): void {
|
|
@@ -172,6 +179,11 @@ abstract class NodeRawSocketPort<TSocket extends Socket> implements ZLinkRawSock
|
|
|
172
179
|
close(): void {
|
|
173
180
|
if (this.closed) return;
|
|
174
181
|
const failures: unknown[] = [];
|
|
182
|
+
try {
|
|
183
|
+
this.eventLoopPoller.dispose();
|
|
184
|
+
} catch (error) {
|
|
185
|
+
failures.push(error);
|
|
186
|
+
}
|
|
175
187
|
for (const monitor of [...this.monitors].reverse()) {
|
|
176
188
|
try {
|
|
177
189
|
monitor.close();
|
|
@@ -192,21 +204,6 @@ abstract class NodeRawSocketPort<TSocket extends Socket> implements ZLinkRawSock
|
|
|
192
204
|
}
|
|
193
205
|
}
|
|
194
206
|
this.endpoints.clear();
|
|
195
|
-
try {
|
|
196
|
-
this.readablePoller.remove(this.socket as never);
|
|
197
|
-
} catch (error) {
|
|
198
|
-
failures.push(error);
|
|
199
|
-
}
|
|
200
|
-
try {
|
|
201
|
-
this.readableEvents.close();
|
|
202
|
-
} catch (error) {
|
|
203
|
-
failures.push(error);
|
|
204
|
-
}
|
|
205
|
-
try {
|
|
206
|
-
this.readablePoller.close();
|
|
207
|
-
} catch (error) {
|
|
208
|
-
failures.push(error);
|
|
209
|
-
}
|
|
210
207
|
try {
|
|
211
208
|
this.socket.close();
|
|
212
209
|
} catch (error) {
|
|
@@ -222,24 +219,6 @@ abstract class NodeRawSocketPort<TSocket extends Socket> implements ZLinkRawSock
|
|
|
222
219
|
protected requireOpen(): void {
|
|
223
220
|
if (this.closed) throw new Error('Raw socket is closed.');
|
|
224
221
|
}
|
|
225
|
-
|
|
226
|
-
protected receiveRecord(
|
|
227
|
-
dontWait: boolean
|
|
228
|
-
): ZLinkRawReceivedRecord | undefined {
|
|
229
|
-
const timeoutMs = dontWait ? 0 : -1;
|
|
230
|
-
try {
|
|
231
|
-
if (
|
|
232
|
-
this.readablePoller.wait(this.readableEvents, timeoutMs) <= 0
|
|
233
|
-
|| !this.readableEvents.hasEvent(0, PollEventFlag.PollIn)
|
|
234
|
-
) {
|
|
235
|
-
return undefined;
|
|
236
|
-
}
|
|
237
|
-
} catch (error) {
|
|
238
|
-
if (isPollerInterruptedError(error)) return undefined;
|
|
239
|
-
throw error;
|
|
240
|
-
}
|
|
241
|
-
return receiveRecord(this.socket as never, true);
|
|
242
|
-
}
|
|
243
222
|
}
|
|
244
223
|
|
|
245
224
|
class NodeRawRouterPort extends NodeRawSocketPort<RouterSocket> implements ZLinkRawRouterPort {
|
|
@@ -276,10 +255,13 @@ class NodeRawRouterPort extends NodeRawSocketPort<RouterSocket> implements ZLink
|
|
|
276
255
|
|
|
277
256
|
async send(targetRid: string, parts: readonly Uint8Array[]): Promise<void> {
|
|
278
257
|
this.requireOpen();
|
|
279
|
-
|
|
258
|
+
const submission = appendSendParts(
|
|
280
259
|
this.socket.send(bindingRoutingId(targetRid)),
|
|
281
260
|
parts
|
|
282
261
|
).submit();
|
|
262
|
+
if (submission.result === SubmitResult.Backpressured) {
|
|
263
|
+
await submission.admitted;
|
|
264
|
+
}
|
|
283
265
|
}
|
|
284
266
|
|
|
285
267
|
async request(
|
|
@@ -291,7 +273,7 @@ class NodeRawRouterPort extends NodeRawSocketPort<RouterSocket> implements ZLink
|
|
|
291
273
|
try {
|
|
292
274
|
const replies = await appendRequestParts(this.socket.request(bindingRoutingId(targetRid)), parts)
|
|
293
275
|
.timeout(timeoutMs)
|
|
294
|
-
.submit();
|
|
276
|
+
.submit().reply;
|
|
295
277
|
return copyAndClose(replies);
|
|
296
278
|
} catch (error) {
|
|
297
279
|
throw translateBindingResultError(error);
|
|
@@ -332,18 +314,21 @@ class NodeRawDealerPort extends NodeRawSocketPort<DealerSocket> implements ZLink
|
|
|
332
314
|
|
|
333
315
|
async send(parts: readonly Uint8Array[]): Promise<void> {
|
|
334
316
|
this.requireOpen();
|
|
335
|
-
|
|
317
|
+
const submission = appendSendParts(this.socket.send(), parts).submit();
|
|
318
|
+
if (submission.result === SubmitResult.Backpressured) {
|
|
319
|
+
await submission.admitted;
|
|
320
|
+
}
|
|
336
321
|
}
|
|
337
322
|
|
|
338
323
|
async request(parts: readonly Uint8Array[], timeoutMs: number): Promise<readonly Buffer[]> {
|
|
339
324
|
this.requireOpen();
|
|
340
|
-
const replies = await appendRequestParts(this.socket.request(), parts).timeout(timeoutMs).submit();
|
|
325
|
+
const replies = await appendRequestParts(this.socket.request(), parts).timeout(timeoutMs).submit().reply;
|
|
341
326
|
return copyAndClose(replies);
|
|
342
327
|
}
|
|
343
328
|
|
|
344
329
|
receive(dontWait = false): ZLinkRawReceivedRecord | undefined {
|
|
345
330
|
this.requireOpen();
|
|
346
|
-
return this.
|
|
331
|
+
return receiveRecord(this.socket, dontWait);
|
|
347
332
|
}
|
|
348
333
|
}
|
|
349
334
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { randomBytes } from 'node:crypto';
|
|
2
|
+
import { setImmediate as yieldToIO } from 'node:timers/promises';
|
|
2
3
|
import { disconnectStreamPeer } from './node-socket-backend-adapter';
|
|
3
4
|
import { ZLinkFrameworkException } from '../../../contracts';
|
|
4
5
|
import { internalFrameworkWireReply } from '../../framework-errors-internal';
|
|
@@ -17,6 +18,8 @@ import {
|
|
|
17
18
|
ZLinkBackendResultError,
|
|
18
19
|
type ZLinkBackendMessageLike as MessageLike
|
|
19
20
|
} from '../runtime-values';
|
|
21
|
+
import { ZLinkBufferMessage } from '../runtime-message';
|
|
22
|
+
import type { Message as FrameworkMessage } from '../../../contracts/Common/Message';
|
|
20
23
|
import type {
|
|
21
24
|
MeshOperationId,
|
|
22
25
|
MeshPeerEntry,
|
|
@@ -113,12 +116,9 @@ const MULTIPART_PACKET_NAME = SERVICE_FRAMEWORK_MULTIPART_PACKET_NAME;
|
|
|
113
116
|
const MULTIPART_CONTENT_TYPE = SERVICE_FRAMEWORK_MULTIPART_CONTENT_TYPE;
|
|
114
117
|
const FATAL_UTF8 = new TextDecoder('utf-8', { fatal: true });
|
|
115
118
|
const MAX_DRAIN_RECORDS = 64;
|
|
116
|
-
//
|
|
117
|
-
//
|
|
118
|
-
|
|
119
|
-
const MESH_BACKEND_IDLE_POLL_INTERVAL_MS = 1;
|
|
120
|
-
const MESH_RECEIVE_BATCH_BYTE_LIMIT = 4 * 1024 * 1024;
|
|
121
|
-
const MESH_RECEIVE_BATCH_TIME_LIMIT_MS = 2;
|
|
119
|
+
// Preserve the existing monitor/admission/liveness cadence. Only the binding
|
|
120
|
+
// readable handler admits receive work; this timer never probes the socket.
|
|
121
|
+
const MESH_BACKEND_MAINTENANCE_INTERVAL_MS = 1;
|
|
122
122
|
/**
|
|
123
123
|
* Conservative Actor Join admission cap for relocation state chunks (spec 15
|
|
124
124
|
* §4.2): a stable lower bound safe on any deployment, never lowered on
|
|
@@ -126,28 +126,6 @@ const MESH_RECEIVE_BATCH_TIME_LIMIT_MS = 2;
|
|
|
126
126
|
*/
|
|
127
127
|
const ACTOR_JOIN_ADVERTISED_RECEIVE_CHUNK_LIMIT_BYTES = 32 * 1024;
|
|
128
128
|
|
|
129
|
-
class ZLinkMeshReceiveBatchBudget {
|
|
130
|
-
private readonly peerMessages = new Map<string, number>();
|
|
131
|
-
private readonly peerBytes = new Map<string, number>();
|
|
132
|
-
private startedAtMs = 0;
|
|
133
|
-
|
|
134
|
-
reset(nowMs: number): void {
|
|
135
|
-
this.peerMessages.clear();
|
|
136
|
-
this.peerBytes.clear();
|
|
137
|
-
this.startedAtMs = nowMs;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
record(peer: string, byteCount: number, nowMs: number): boolean {
|
|
141
|
-
const messages = (this.peerMessages.get(peer) ?? 0) + 1;
|
|
142
|
-
const bytes = (this.peerBytes.get(peer) ?? 0) + byteCount;
|
|
143
|
-
this.peerMessages.set(peer, messages);
|
|
144
|
-
this.peerBytes.set(peer, bytes);
|
|
145
|
-
return messages >= MAX_DRAIN_RECORDS
|
|
146
|
-
|| bytes >= MESH_RECEIVE_BATCH_BYTE_LIMIT
|
|
147
|
-
|| nowMs - this.startedAtMs >= MESH_RECEIVE_BATCH_TIME_LIMIT_MS;
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
|
|
151
129
|
/**
|
|
152
130
|
* M6A MeshNode backend. Stateful Spot/Actor entry points stay explicit until
|
|
153
131
|
* their owning M6B runtime is connected; topology and node/channel dispatch do
|
|
@@ -172,7 +150,17 @@ export class ZLinkNodeRawMeshBackend implements ZLinkBackendMeshNode {
|
|
|
172
150
|
private runtime?: RawServiceMeshRuntime;
|
|
173
151
|
private stateful?: ServiceStatefulRuntime;
|
|
174
152
|
private readyHandler?: (domains: number) => number;
|
|
175
|
-
private
|
|
153
|
+
private maintenanceTimer?: NodeJS.Timeout;
|
|
154
|
+
private pumping = false;
|
|
155
|
+
private readable = false;
|
|
156
|
+
private readonly onReadable = (): void => {
|
|
157
|
+
this.readable = true;
|
|
158
|
+
if (!this.closed && !this.pumping) void this.pump();
|
|
159
|
+
};
|
|
160
|
+
private readonly onMaintenance = (): void => {
|
|
161
|
+
this.maintenanceTimer = undefined;
|
|
162
|
+
void this.pump();
|
|
163
|
+
};
|
|
176
164
|
private nextPeerIntent = 1n;
|
|
177
165
|
private closed = false;
|
|
178
166
|
private objectRole: ServiceNodeDescriptor['objectRole'] = 'none';
|
|
@@ -195,20 +183,6 @@ export class ZLinkNodeRawMeshBackend implements ZLinkBackendMeshNode {
|
|
|
195
183
|
record: import('../../foundation/service-stateful-wire-codec').ServiceMessageFollowRecord
|
|
196
184
|
) => void;
|
|
197
185
|
private readonly peerDisconnectedHandlers = new Set<(endpoint: string) => void>();
|
|
198
|
-
private readonly receiveBatchBudget = new ZLinkMeshReceiveBatchBudget();
|
|
199
|
-
private observedPumpSourceRoutingId?: string;
|
|
200
|
-
private observedPumpByteCount = 0;
|
|
201
|
-
private readonly observePump = (sourceRoutingId: string, byteCount: number): void => {
|
|
202
|
-
this.observedPumpSourceRoutingId = sourceRoutingId;
|
|
203
|
-
this.observedPumpByteCount = byteCount;
|
|
204
|
-
};
|
|
205
|
-
|
|
206
|
-
private takePumpObservation(): { readonly sourceRoutingId: string; readonly byteCount: number } | undefined {
|
|
207
|
-
const sourceRoutingId = this.observedPumpSourceRoutingId;
|
|
208
|
-
if (sourceRoutingId === undefined) return undefined;
|
|
209
|
-
return { sourceRoutingId, byteCount: this.observedPumpByteCount };
|
|
210
|
-
}
|
|
211
|
-
|
|
212
186
|
constructor(
|
|
213
187
|
private readonly meshName: string,
|
|
214
188
|
routingId: string | undefined,
|
|
@@ -414,7 +388,13 @@ export class ZLinkNodeRawMeshBackend implements ZLinkBackendMeshNode {
|
|
|
414
388
|
}
|
|
415
389
|
this.stateful.setMessageFollowHandler((record) => this.messageFollowHandler?.(record));
|
|
416
390
|
this.runtime = runtime;
|
|
417
|
-
|
|
391
|
+
try {
|
|
392
|
+
runtime.setReadableHandler(this.onReadable);
|
|
393
|
+
} catch (error) {
|
|
394
|
+
this.close();
|
|
395
|
+
throw error;
|
|
396
|
+
}
|
|
397
|
+
this.scheduleMaintenance();
|
|
418
398
|
}
|
|
419
399
|
|
|
420
400
|
setMessageFollowHandler(handler: (
|
|
@@ -446,8 +426,8 @@ export class ZLinkNodeRawMeshBackend implements ZLinkBackendMeshNode {
|
|
|
446
426
|
close(): void {
|
|
447
427
|
if (this.closed) return;
|
|
448
428
|
this.closed = true;
|
|
449
|
-
if (this.
|
|
450
|
-
this.
|
|
429
|
+
if (this.maintenanceTimer !== undefined) clearTimeout(this.maintenanceTimer);
|
|
430
|
+
this.maintenanceTimer = undefined;
|
|
451
431
|
this.stateful?.close();
|
|
452
432
|
this.stateful = undefined;
|
|
453
433
|
this.runtime?.close();
|
|
@@ -614,7 +594,7 @@ export class ZLinkNodeRawMeshBackend implements ZLinkBackendMeshNode {
|
|
|
614
594
|
): Promise<SubmitResultValue> {
|
|
615
595
|
return await this.requireRuntime().sendToNode(
|
|
616
596
|
String(targetRid),
|
|
617
|
-
|
|
597
|
+
encodeMultipartApplicationFrame(parts)
|
|
618
598
|
) ? SubmitResult.Ok : SubmitResult.NotConnected;
|
|
619
599
|
}
|
|
620
600
|
|
|
@@ -657,7 +637,7 @@ export class ZLinkNodeRawMeshBackend implements ZLinkBackendMeshNode {
|
|
|
657
637
|
): MeshOperationId {
|
|
658
638
|
const pending = this.requireRuntime().requestToNode(
|
|
659
639
|
String(targetRid),
|
|
660
|
-
|
|
640
|
+
encodeMultipartApplicationFrame(parts),
|
|
661
641
|
options?.timeoutMs ?? 30_000
|
|
662
642
|
);
|
|
663
643
|
return this.observeCompletion(pending.id, OperationKind.NodeRequest, pending.promise);
|
|
@@ -669,7 +649,7 @@ export class ZLinkNodeRawMeshBackend implements ZLinkBackendMeshNode {
|
|
|
669
649
|
): Promise<SubmitResultValue> {
|
|
670
650
|
return await this.requireRuntime().sendToChannel(
|
|
671
651
|
channelName,
|
|
672
|
-
|
|
652
|
+
encodeMultipartApplicationFrame(parts)
|
|
673
653
|
) ? SubmitResult.Ok : SubmitResult.NotConnected;
|
|
674
654
|
}
|
|
675
655
|
|
|
@@ -680,7 +660,7 @@ export class ZLinkNodeRawMeshBackend implements ZLinkBackendMeshNode {
|
|
|
680
660
|
): MeshOperationId {
|
|
681
661
|
const pending = this.requireRuntime().requestToChannel(
|
|
682
662
|
channelName,
|
|
683
|
-
|
|
663
|
+
encodeMultipartApplicationFrame(parts),
|
|
684
664
|
options?.timeoutMs ?? 30_000
|
|
685
665
|
);
|
|
686
666
|
if (pending === undefined) {
|
|
@@ -1501,49 +1481,39 @@ export class ZLinkNodeRawMeshBackend implements ZLinkBackendMeshNode {
|
|
|
1501
1481
|
return result;
|
|
1502
1482
|
}
|
|
1503
1483
|
|
|
1504
|
-
private
|
|
1505
|
-
if (this.closed || this.
|
|
1506
|
-
this.
|
|
1507
|
-
this.
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
// A zero-delay timer yields to I/O and other ready work first without
|
|
1511
|
-
// inserting the idle polling interval between readable batches.
|
|
1512
|
-
delayMs = received ? 0 : MESH_BACKEND_IDLE_POLL_INTERVAL_MS;
|
|
1513
|
-
} finally {
|
|
1514
|
-
if (!this.closed) this.schedulePoll(delayMs);
|
|
1515
|
-
}
|
|
1516
|
-
}, delayMs);
|
|
1484
|
+
private scheduleMaintenance(): void {
|
|
1485
|
+
if (this.closed || this.maintenanceTimer !== undefined) return;
|
|
1486
|
+
this.maintenanceTimer = setTimeout(
|
|
1487
|
+
this.onMaintenance,
|
|
1488
|
+
MESH_BACKEND_MAINTENANCE_INTERVAL_MS
|
|
1489
|
+
);
|
|
1517
1490
|
}
|
|
1518
1491
|
|
|
1519
|
-
private async
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
performance.now()
|
|
1540
|
-
)
|
|
1541
|
-
) break;
|
|
1492
|
+
private async pump(): Promise<void> {
|
|
1493
|
+
if (this.isClosed() || this.pumping) return;
|
|
1494
|
+
this.pumping = true;
|
|
1495
|
+
if (this.maintenanceTimer !== undefined) clearTimeout(this.maintenanceTimer);
|
|
1496
|
+
this.maintenanceTimer = undefined;
|
|
1497
|
+
try {
|
|
1498
|
+
do {
|
|
1499
|
+
const receiveReady = this.readable;
|
|
1500
|
+
this.readable = false;
|
|
1501
|
+
const more = await this.requireRuntime().pumpBatch(receiveReady);
|
|
1502
|
+
if (more) {
|
|
1503
|
+
// Yield only after actual progress, retaining readiness across the
|
|
1504
|
+
// existing batch limits until receive reports no data.
|
|
1505
|
+
this.readable = true;
|
|
1506
|
+
await yieldToIO();
|
|
1507
|
+
}
|
|
1508
|
+
} while (!this.closed && this.readable);
|
|
1509
|
+
} finally {
|
|
1510
|
+
this.pumping = false;
|
|
1511
|
+
this.scheduleMaintenance();
|
|
1542
1512
|
}
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
return
|
|
1513
|
+
}
|
|
1514
|
+
|
|
1515
|
+
private isClosed(): boolean {
|
|
1516
|
+
return this.closed;
|
|
1547
1517
|
}
|
|
1548
1518
|
|
|
1549
1519
|
private notifyReady(): void {
|
|
@@ -1654,16 +1624,7 @@ export class ZLinkNodeRawMeshBackend implements ZLinkBackendMeshNode {
|
|
|
1654
1624
|
const owner = readyOwner(claim.owner, this.routingId, this.stateful, domain);
|
|
1655
1625
|
batch.push(
|
|
1656
1626
|
owner,
|
|
1657
|
-
new MailboxClaim(runtime, claim
|
|
1658
|
-
// A receive batch may intentionally consume fewer records than the
|
|
1659
|
-
// mailbox claim. Releasing the claim re-queues the remainder, so
|
|
1660
|
-
// expose that newly ready work to the dispatch pump.
|
|
1661
|
-
if (runtime.mailbox.pendingMessages(domain) > 0) {
|
|
1662
|
-
this.readyHandler?.(
|
|
1663
|
-
domain === 'infrastructure' ? ReadyDomain.Infrastructure : ReadyDomain.Application
|
|
1664
|
-
);
|
|
1665
|
-
}
|
|
1666
|
-
})
|
|
1627
|
+
new MailboxClaim(runtime, claim)
|
|
1667
1628
|
);
|
|
1668
1629
|
}
|
|
1669
1630
|
}
|
|
@@ -2013,7 +1974,7 @@ class RawStreamSessionService implements StreamSessionService {
|
|
|
2013
1974
|
submit = submit.message(parts[index]!);
|
|
2014
1975
|
}
|
|
2015
1976
|
try {
|
|
2016
|
-
await submit.submit();
|
|
1977
|
+
await submit.submit().admitted;
|
|
2017
1978
|
return true;
|
|
2018
1979
|
} catch (error) {
|
|
2019
1980
|
if (!(error instanceof SubmitError)) throw error;
|
|
@@ -2087,16 +2048,28 @@ class RawReadyBatch implements ReadyBatch {
|
|
|
2087
2048
|
}
|
|
2088
2049
|
|
|
2089
2050
|
class RawReceiveBatch implements ReceiveBatch {
|
|
2051
|
+
private receiveCapacity: number;
|
|
2052
|
+
|
|
2090
2053
|
constructor(
|
|
2091
|
-
readonly
|
|
2054
|
+
private readonly capacity: number,
|
|
2092
2055
|
readonly partCapacity: number
|
|
2093
2056
|
) {
|
|
2094
|
-
if ([
|
|
2057
|
+
if ([capacity, partCapacity].some(value => !Number.isInteger(value) || value < 1)) {
|
|
2095
2058
|
throw new RangeError('Receive batch capacities must be positive.');
|
|
2096
2059
|
}
|
|
2060
|
+
this.receiveCapacity = capacity;
|
|
2097
2061
|
}
|
|
2098
2062
|
|
|
2099
|
-
|
|
2063
|
+
get messageCapacity(): number {
|
|
2064
|
+
return this.receiveCapacity;
|
|
2065
|
+
}
|
|
2066
|
+
|
|
2067
|
+
reset(messageCapacity = this.capacity): void {
|
|
2068
|
+
if (!Number.isInteger(messageCapacity) || messageCapacity < 1 || messageCapacity > this.capacity) {
|
|
2069
|
+
throw new RangeError('Receive limit must be within the batch capacity.');
|
|
2070
|
+
}
|
|
2071
|
+
this.receiveCapacity = messageCapacity;
|
|
2072
|
+
}
|
|
2100
2073
|
close(): void {}
|
|
2101
2074
|
}
|
|
2102
2075
|
|
|
@@ -2107,8 +2080,7 @@ class MailboxClaim implements RawClaim {
|
|
|
2107
2080
|
|
|
2108
2081
|
constructor(
|
|
2109
2082
|
private readonly runtime: RawServiceMeshRuntime,
|
|
2110
|
-
private readonly claim: ServiceMailboxClaim
|
|
2111
|
-
private readonly onRelease?: () => void
|
|
2083
|
+
private readonly claim: ServiceMailboxClaim
|
|
2112
2084
|
) {}
|
|
2113
2085
|
|
|
2114
2086
|
recvBatch(batch: ReceiveBatch) {
|
|
@@ -2130,6 +2102,12 @@ class MailboxClaim implements RawClaim {
|
|
|
2130
2102
|
}
|
|
2131
2103
|
this.runtime.mailbox.releaseClaimedPayload(record);
|
|
2132
2104
|
this.remaining = this.claim.records.slice(index + 1);
|
|
2105
|
+
// Nothing was transferred to the receive caller when decoding throws.
|
|
2106
|
+
// Earlier records therefore still belong to this failed batch.
|
|
2107
|
+
for (const decoded of records) {
|
|
2108
|
+
for (const part of decoded.parts) part.close();
|
|
2109
|
+
decoded.releaseRetainedIngress?.();
|
|
2110
|
+
}
|
|
2133
2111
|
throw error;
|
|
2134
2112
|
}
|
|
2135
2113
|
const nextParts = decoded.parts.length;
|
|
@@ -2152,7 +2130,6 @@ class MailboxClaim implements RawClaim {
|
|
|
2152
2130
|
if (this.released) return;
|
|
2153
2131
|
this.released = true;
|
|
2154
2132
|
this.runtime.mailbox.release(this.claim, this.remaining);
|
|
2155
|
-
this.onRelease?.();
|
|
2156
2133
|
}
|
|
2157
2134
|
}
|
|
2158
2135
|
|
|
@@ -2297,7 +2274,7 @@ function decodeMultipartRecord(
|
|
|
2297
2274
|
parts: decodeMultipart(application.payload),
|
|
2298
2275
|
reply(parts) {
|
|
2299
2276
|
if (record.correlation === undefined) return SubmitResult.InvalidState;
|
|
2300
|
-
runtime.reply(record,
|
|
2277
|
+
runtime.reply(record, encodeMultipartApplicationFrame(parts));
|
|
2301
2278
|
return SubmitResult.Ok;
|
|
2302
2279
|
},
|
|
2303
2280
|
replyActorJoin: () => SubmitResult.NotSupported
|
|
@@ -2585,9 +2562,19 @@ function decodeApplicationEnvelope(frame: Uint8Array) {
|
|
|
2585
2562
|
return { packetName, contentType, payload: bytes.subarray(offset) };
|
|
2586
2563
|
}
|
|
2587
2564
|
|
|
2588
|
-
function decodeMultipart(payload: Uint8Array):
|
|
2565
|
+
function decodeMultipart(payload: Uint8Array): FrameworkMessage[] {
|
|
2589
2566
|
const buffers = decodeMultipartBuffers(payload);
|
|
2590
|
-
|
|
2567
|
+
const parts = new Array<FrameworkMessage>(buffers.length);
|
|
2568
|
+
let materialized = 0;
|
|
2569
|
+
try {
|
|
2570
|
+
for (; materialized < buffers.length; materialized += 1) {
|
|
2571
|
+
parts[materialized] = ZLinkBufferMessage.fromOwned(buffers[materialized]!);
|
|
2572
|
+
}
|
|
2573
|
+
return parts;
|
|
2574
|
+
} catch (error) {
|
|
2575
|
+
for (let index = 0; index < materialized; index += 1) parts[index]!.close();
|
|
2576
|
+
throw error;
|
|
2577
|
+
}
|
|
2591
2578
|
}
|
|
2592
2579
|
|
|
2593
2580
|
function decodeMultipartBuffers(payload: Uint8Array): Buffer[] {
|