@zlink-systems/framework 0.11.0 → 0.13.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 +5 -1
- 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 +3 -1
- package/dist/runtime/channels/channel-transports.d.ts +2 -1
- package/dist/runtime/channels/channel-transports.js +12 -10
- 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/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 +3 -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 +7 -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 +4 -1
- package/src/runtime/channels/channel-transports.ts +20 -10
- 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/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
|
@@ -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[] {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { Socket } from '@zlink-systems/zlink';
|
|
1
2
|
import type { ZLinkBackendObject } from '../contracts';
|
|
2
3
|
import {
|
|
3
4
|
closeWithBusyRetry,
|
|
@@ -17,14 +18,14 @@ import {
|
|
|
17
18
|
type ZLinkBindingReplyOperation,
|
|
18
19
|
type ZLinkBindingAsyncSendOperation
|
|
19
20
|
} from './node-backend-adapter-support';
|
|
21
|
+
import { ZLinkNodeEventLoopPoller } from './node-event-loop-poller';
|
|
20
22
|
|
|
23
|
+
const eventLoopPollers = new WeakMap<object, ZLinkNodeEventLoopPoller>();
|
|
21
24
|
|
|
22
25
|
export function wrapSocket<T extends { close(): void }>(
|
|
23
|
-
nativeInstance: T
|
|
26
|
+
nativeInstance: T,
|
|
27
|
+
pollCompletion?: boolean
|
|
24
28
|
): T & ZLinkBackendObject {
|
|
25
|
-
const boundEndpoints = new Set<string>();
|
|
26
|
-
const connectedEndpoints = new Set<string>();
|
|
27
|
-
const peerRoutingIds = new Set<unknown>();
|
|
28
29
|
const socket = nativeInstance as T & {
|
|
29
30
|
options?: {
|
|
30
31
|
probe?: boolean;
|
|
@@ -40,32 +41,35 @@ export function wrapSocket<T extends { close(): void }>(
|
|
|
40
41
|
const hasRoutedPeer = hasRequest
|
|
41
42
|
&& typeof (nativeInstance as { reply?: unknown }).reply === 'function';
|
|
42
43
|
const hasStream = typeof (nativeInstance as { recvPacket?: unknown }).recvPacket === 'function';
|
|
44
|
+
const eventLoopPoller = pollCompletion === undefined
|
|
45
|
+
? undefined
|
|
46
|
+
: new ZLinkNodeEventLoopPoller(
|
|
47
|
+
nativeInstance as unknown as Socket,
|
|
48
|
+
pollCompletion,
|
|
49
|
+
() => {}
|
|
50
|
+
);
|
|
43
51
|
const adapter = {
|
|
44
52
|
nativeInstance,
|
|
45
53
|
async dispose(): Promise<void> {
|
|
54
|
+
eventLoopPoller?.dispose();
|
|
46
55
|
disableSocketLinger(nativeInstance);
|
|
47
|
-
closeSocketRoutes(nativeInstance, peerRoutingIds);
|
|
48
|
-
closeSocketEndpoints(nativeInstance, boundEndpoints, connectedEndpoints);
|
|
49
56
|
await closeWithBusyRetry(nativeInstance);
|
|
50
57
|
},
|
|
51
58
|
close(): void {
|
|
59
|
+
eventLoopPoller?.dispose();
|
|
52
60
|
nativeInstance.close();
|
|
53
61
|
},
|
|
54
62
|
bind(endpoint: string): void {
|
|
55
63
|
(nativeInstance as T & { bind(endpoint: string): void }).bind(endpoint);
|
|
56
|
-
boundEndpoints.add(endpoint);
|
|
57
64
|
},
|
|
58
65
|
unbind(endpoint: string): void {
|
|
59
66
|
(nativeInstance as T & { unbind(endpoint: string): void }).unbind(endpoint);
|
|
60
|
-
boundEndpoints.delete(endpoint);
|
|
61
67
|
},
|
|
62
68
|
connect(endpoint: string): void {
|
|
63
69
|
(nativeInstance as T & { connect(endpoint: string): void }).connect(endpoint);
|
|
64
|
-
connectedEndpoints.add(endpoint);
|
|
65
70
|
},
|
|
66
71
|
disconnect(endpoint: string): void {
|
|
67
72
|
(nativeInstance as T & { disconnect(endpoint: string): void }).disconnect(endpoint);
|
|
68
|
-
connectedEndpoints.delete(endpoint);
|
|
69
73
|
},
|
|
70
74
|
setChannelName(channelName: string): void {
|
|
71
75
|
const setChannelName = (nativeInstance as T & { setChannelName?: (value: string) => void }).setChannelName;
|
|
@@ -133,7 +137,6 @@ export function wrapSocket<T extends { close(): void }>(
|
|
|
133
137
|
send(...args: unknown[]): unknown {
|
|
134
138
|
if (hasStream) {
|
|
135
139
|
const [routingId, payload] = args as [unknown, unknown];
|
|
136
|
-
peerRoutingIds.add(routingId);
|
|
137
140
|
const operation = (nativeInstance as T & {
|
|
138
141
|
send(routingId: unknown): ZLinkBindingAsyncSendOperation;
|
|
139
142
|
}).send(toNativeRoutingId(routingId));
|
|
@@ -142,7 +145,6 @@ export function wrapSocket<T extends { close(): void }>(
|
|
|
142
145
|
}
|
|
143
146
|
if (hasRoutedPeer) {
|
|
144
147
|
const [routingId, payload] = args as [unknown, unknown];
|
|
145
|
-
peerRoutingIds.add(routingId);
|
|
146
148
|
return submitBindingAsyncSend(
|
|
147
149
|
(nativeInstance as T & { send(routingId: unknown): ZLinkBindingAsyncSendOperation })
|
|
148
150
|
.send(toNativeRoutingId(routingId)),
|
|
@@ -167,7 +169,6 @@ export function wrapSocket<T extends { close(): void }>(
|
|
|
167
169
|
},
|
|
168
170
|
submit(routingId: unknown, payload: unknown, _timeoutMs?: number): Promise<void> {
|
|
169
171
|
if (!hasStream) throw new TypeError('Async stream send requires a STREAM socket.');
|
|
170
|
-
peerRoutingIds.add(routingId);
|
|
171
172
|
return submitBindingAsyncSend(
|
|
172
173
|
(nativeInstance as T & { send(routingId: unknown): ZLinkBindingAsyncSendOperation })
|
|
173
174
|
.send(toNativeRoutingId(routingId)),
|
|
@@ -177,7 +178,6 @@ export function wrapSocket<T extends { close(): void }>(
|
|
|
177
178
|
request(...args: unknown[]): unknown {
|
|
178
179
|
if (hasRoutedPeer) {
|
|
179
180
|
const [routingId, payload, timeoutMs] = args as [unknown, unknown, number | undefined];
|
|
180
|
-
peerRoutingIds.add(routingId);
|
|
181
181
|
return submitBindingRequest(
|
|
182
182
|
(nativeInstance as T & { request(routingId: unknown): ZLinkBindingRequestOperation })
|
|
183
183
|
.request(toNativeRoutingId(routingId)),
|
|
@@ -197,7 +197,6 @@ export function wrapSocket<T extends { close(): void }>(
|
|
|
197
197
|
},
|
|
198
198
|
reply(...args: unknown[]): unknown {
|
|
199
199
|
const [routingId, replyToken, payload] = args as [unknown, unknown, unknown];
|
|
200
|
-
peerRoutingIds.add(routingId);
|
|
201
200
|
const operation = (nativeInstance as T & {
|
|
202
201
|
reply(routingId: unknown, replyToken: unknown): ZLinkBindingReplyOperation;
|
|
203
202
|
}).reply(toNativeRoutingId(routingId), replyToken);
|
|
@@ -287,9 +286,18 @@ export function wrapSocket<T extends { close(): void }>(
|
|
|
287
286
|
return false;
|
|
288
287
|
}
|
|
289
288
|
};
|
|
289
|
+
if (eventLoopPoller !== undefined) eventLoopPollers.set(adapter, eventLoopPoller);
|
|
290
290
|
return adapter as unknown as T & ZLinkBackendObject;
|
|
291
291
|
}
|
|
292
292
|
|
|
293
|
+
export function nodeEventLoopPollerOf(socket: object): ZLinkNodeEventLoopPoller {
|
|
294
|
+
const poller = eventLoopPollers.get(socket);
|
|
295
|
+
if (poller === undefined) {
|
|
296
|
+
throw new TypeError('Node backend socket does not own an event-loop poller.');
|
|
297
|
+
}
|
|
298
|
+
return poller;
|
|
299
|
+
}
|
|
300
|
+
|
|
293
301
|
export function disconnectStreamPeer(
|
|
294
302
|
socket: { disconnectRid(routingId: unknown): void },
|
|
295
303
|
routingId: unknown
|
|
@@ -310,60 +318,6 @@ function requireSocketOptions<TOptions>(socket: { readonly options?: TOptions })
|
|
|
310
318
|
return socket.options;
|
|
311
319
|
}
|
|
312
320
|
|
|
313
|
-
function closeSocketRoutes(target: unknown, peerRoutingIds: Set<unknown>): void {
|
|
314
|
-
if (peerRoutingIds.size === 0 || !hasDisconnectRid(target)) {
|
|
315
|
-
return;
|
|
316
|
-
}
|
|
317
|
-
for (const routingId of peerRoutingIds) {
|
|
318
|
-
try {
|
|
319
|
-
target.disconnectRid(toNativeRoutingId(routingId));
|
|
320
|
-
} catch (error) {
|
|
321
|
-
if (!isDisconnectRouteNotFoundError(error)) {
|
|
322
|
-
throw error;
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
peerRoutingIds.delete(routingId);
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
export function isDisconnectRouteNotFoundError(error: unknown): boolean {
|
|
330
|
-
return isBindingNotFound(error) || isContextTerminatedError(error) || (
|
|
331
|
-
error instanceof Error && 'code' in error &&
|
|
332
|
-
((error as { code: unknown }).code === zlink.ConnectResult.NotFound ||
|
|
333
|
-
(error as { code: unknown }).code === zlink.ConnectResult.Busy ||
|
|
334
|
-
((error as { code: unknown }).code === zlink.ConnectResult.InternalError &&
|
|
335
|
-
/current state/i.test(error.message)))
|
|
336
|
-
);
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
function hasDisconnectRid(target: unknown): target is { disconnectRid(routingId: unknown): void } {
|
|
340
|
-
return target !== null && typeof target === 'object' && 'disconnectRid' in target &&
|
|
341
|
-
typeof (target as { disconnectRid: unknown }).disconnectRid === 'function';
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
function closeSocketEndpoints(target: unknown, boundEndpoints: Set<string>, connectedEndpoints: Set<string>): void {
|
|
345
|
-
for (const endpoint of connectedEndpoints) {
|
|
346
|
-
try {
|
|
347
|
-
(target as { disconnect(endpoint: string): void }).disconnect(endpoint);
|
|
348
|
-
} catch (error) {
|
|
349
|
-
if (!isEndpointCloseIgnorableError(error)) {
|
|
350
|
-
throw error;
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
connectedEndpoints.delete(endpoint);
|
|
354
|
-
}
|
|
355
|
-
for (const endpoint of boundEndpoints) {
|
|
356
|
-
try {
|
|
357
|
-
(target as { unbind(endpoint: string): void }).unbind(endpoint);
|
|
358
|
-
} catch (error) {
|
|
359
|
-
if (!isEndpointCloseIgnorableError(error)) {
|
|
360
|
-
throw error;
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
boundEndpoints.delete(endpoint);
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
|
|
367
321
|
export function isEndpointCloseIgnorableError(error: unknown): boolean {
|
|
368
322
|
return isContextTerminatedError(error) || (
|
|
369
323
|
error instanceof Error && 'code' in error &&
|
|
@@ -75,9 +75,15 @@ export function isBackendRequestTimeoutError(error: unknown): boolean {
|
|
|
75
75
|
&& error.result === RequestResult.TimedOut;
|
|
76
76
|
}
|
|
77
77
|
export type ZLinkBackendMessageLike = Message | Buffer | Uint8Array | string;
|
|
78
|
+
/** Binding의 SendSubmission과 구조적으로 호환된다. binding 타입을 import 하지 않는다. */
|
|
79
|
+
export interface ZLinkBackendSendSubmission {
|
|
80
|
+
readonly admitted: Promise<void>;
|
|
81
|
+
}
|
|
82
|
+
|
|
78
83
|
export interface ZLinkBackendSendSubmitBuilder {
|
|
79
84
|
message(message: ZLinkBackendMessageLike): ZLinkBackendSendSubmitBuilder;
|
|
80
|
-
|
|
85
|
+
// 제출 스냅샷을 그대로 돌려준다. admission을 기다리려면 .admitted를 await 한다.
|
|
86
|
+
submit(): ZLinkBackendSendSubmission;
|
|
81
87
|
}
|
|
82
88
|
|
|
83
89
|
export interface ZLinkBackendSendBuilder {
|
|
@@ -115,7 +115,7 @@ export class ZLinkChannelRequestDispatcher {
|
|
|
115
115
|
if (received.send === undefined) {
|
|
116
116
|
throw new ZLinkConfigurationException('Routed SPOT packet is missing a local SPOT delivery context.');
|
|
117
117
|
}
|
|
118
|
-
await appendParts(received.send(), received.parts).submit();
|
|
118
|
+
await appendParts(received.send(), received.parts).submit().admitted;
|
|
119
119
|
return true;
|
|
120
120
|
}
|
|
121
121
|
if (received.parts.length === 0 || received.parts[0].data().length === 0) {
|
|
@@ -523,7 +523,7 @@ export class ZLinkRoutePacketDispatcher {
|
|
|
523
523
|
if (received.send === undefined) {
|
|
524
524
|
throw new ZLinkConfigurationException('Routed SPOT packet is missing a local SPOT delivery context.');
|
|
525
525
|
}
|
|
526
|
-
return appendParts(received.send(), received.parts).submit();
|
|
526
|
+
return appendParts(received.send(), received.parts).submit().admitted;
|
|
527
527
|
}
|
|
528
528
|
return false;
|
|
529
529
|
}
|