@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
|
@@ -12,6 +12,8 @@ import {
|
|
|
12
12
|
closeMessages
|
|
13
13
|
} from './channel-envelope';
|
|
14
14
|
import { tryDecodeChannelHeader } from './channel-envelope-inspection';
|
|
15
|
+
// 같은 계약을 두 곳에 두지 않는다 — channel-multipart가 소유한다.
|
|
16
|
+
import type { ZLinkMultipartAsyncSubmitOperation } from './channel-multipart';
|
|
15
17
|
import type { ZLinkChannelEnvelopeHeader } from './channel-envelope';
|
|
16
18
|
import {
|
|
17
19
|
ZLinkReceiveTaskTracker,
|
|
@@ -39,10 +41,6 @@ interface ZLinkMultipartSubmitOperation extends ZLinkMultipartOperation<ZLinkMul
|
|
|
39
41
|
submit(): unknown;
|
|
40
42
|
}
|
|
41
43
|
|
|
42
|
-
interface ZLinkMultipartAsyncSubmitOperation extends ZLinkMultipartOperation<ZLinkMultipartAsyncSubmitOperation> {
|
|
43
|
-
submit(): Promise<void>;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
44
|
type ZLinkMultipartReplyOperation = ZLinkMultipartSubmitOperation;
|
|
47
45
|
|
|
48
46
|
interface ZLinkChannelRequestDispatchLoop {
|
|
@@ -254,19 +252,13 @@ export class ZLinkChannelReceiveLoop {
|
|
|
254
252
|
this.roundRobin?.setReady(this.receiveOwner, false);
|
|
255
253
|
this.roundRobin?.release(this.receiveOwner);
|
|
256
254
|
batch.reset();
|
|
257
|
-
await
|
|
255
|
+
if (!await this.poller.waitForReadable(capacitySignal)) break;
|
|
258
256
|
continue;
|
|
259
257
|
}
|
|
260
258
|
const applicationJobPermit = await this.acquirePermit(capacitySignal);
|
|
261
259
|
if (applicationJobPermit === undefined) break;
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
this.roundRobin?.setReady(this.receiveOwner, false);
|
|
265
|
-
this.roundRobin?.release(this.receiveOwner);
|
|
266
|
-
batch.reset();
|
|
267
|
-
await waitReceiveLoopIdle();
|
|
268
|
-
continue;
|
|
269
|
-
}
|
|
260
|
+
// Readiness can become stale while admission waits. The nonblocking
|
|
261
|
+
// recv below owns the empty-result check and returns the permit then.
|
|
270
262
|
this.roundRobin?.setReady(this.receiveOwner, true);
|
|
271
263
|
if (this.roundRobin?.tryAcquire(this.receiveOwner) === false) {
|
|
272
264
|
applicationJobPermit.releaseAfterInternalProcessing();
|
|
@@ -284,12 +276,13 @@ export class ZLinkChannelReceiveLoop {
|
|
|
284
276
|
}
|
|
285
277
|
const received = this.router.recv(1);
|
|
286
278
|
if (received == null) {
|
|
279
|
+
this.poller.markDrained();
|
|
287
280
|
applicationJobPermit.releaseAfterInternalProcessing();
|
|
288
281
|
releaseRawReceive();
|
|
289
282
|
this.roundRobin?.setReady(this.receiveOwner, false);
|
|
290
283
|
this.roundRobin?.release(this.receiveOwner);
|
|
291
284
|
batch.reset();
|
|
292
|
-
await
|
|
285
|
+
if (!await this.poller.waitForReadable(capacitySignal)) break;
|
|
293
286
|
continue;
|
|
294
287
|
}
|
|
295
288
|
const receivedBytes = messageBytes(received.parts);
|
|
@@ -479,19 +472,11 @@ export class ZLinkSubscriberReceiveLoop {
|
|
|
479
472
|
this.roundRobin?.setReady(this.receiveOwner, false);
|
|
480
473
|
this.roundRobin?.release(this.receiveOwner);
|
|
481
474
|
batch.reset();
|
|
482
|
-
await
|
|
475
|
+
if (!await this.poller.waitForReadable(capacitySignal)) break;
|
|
483
476
|
continue;
|
|
484
477
|
}
|
|
485
478
|
const applicationJobPermit = await this.acquirePermit(capacitySignal);
|
|
486
479
|
if (applicationJobPermit === undefined) break;
|
|
487
|
-
if (!this.poller.wait(0)) {
|
|
488
|
-
applicationJobPermit.releaseAfterInternalProcessing();
|
|
489
|
-
this.roundRobin?.setReady(this.receiveOwner, false);
|
|
490
|
-
this.roundRobin?.release(this.receiveOwner);
|
|
491
|
-
batch.reset();
|
|
492
|
-
await waitReceiveLoopIdle();
|
|
493
|
-
continue;
|
|
494
|
-
}
|
|
495
480
|
this.roundRobin?.setReady(this.receiveOwner, true);
|
|
496
481
|
if (this.roundRobin?.tryAcquire(this.receiveOwner) === false) {
|
|
497
482
|
applicationJobPermit.releaseAfterInternalProcessing();
|
|
@@ -508,14 +493,15 @@ export class ZLinkSubscriberReceiveLoop {
|
|
|
508
493
|
continue;
|
|
509
494
|
}
|
|
510
495
|
const topicMessage = this.adapter.createTopicMessage();
|
|
511
|
-
if (!this.subscriber.subscribe(topicMessage)) {
|
|
496
|
+
if (!this.subscriber.subscribe(topicMessage, 1)) {
|
|
497
|
+
this.poller.markDrained();
|
|
512
498
|
applicationJobPermit.releaseAfterInternalProcessing();
|
|
513
499
|
releaseRawReceive();
|
|
514
500
|
topicMessage.close();
|
|
515
501
|
this.roundRobin?.setReady(this.receiveOwner, false);
|
|
516
502
|
this.roundRobin?.release(this.receiveOwner);
|
|
517
503
|
batch.reset();
|
|
518
|
-
await
|
|
504
|
+
if (!await this.poller.waitForReadable(capacitySignal)) break;
|
|
519
505
|
continue;
|
|
520
506
|
}
|
|
521
507
|
const receivedBytes = messageBytes(topicMessage.parts as readonly Message[]);
|
|
@@ -679,14 +665,7 @@ export class ZLinkRouteReceiveLoop {
|
|
|
679
665
|
this.roundRobin?.setReady(this.receiveOwner, false);
|
|
680
666
|
this.roundRobin?.release(this.receiveOwner);
|
|
681
667
|
batch.reset();
|
|
682
|
-
await
|
|
683
|
-
continue;
|
|
684
|
-
}
|
|
685
|
-
if (!this.poller.wait(0)) {
|
|
686
|
-
this.roundRobin?.setReady(this.receiveOwner, false);
|
|
687
|
-
this.roundRobin?.release(this.receiveOwner);
|
|
688
|
-
batch.reset();
|
|
689
|
-
await waitReceiveLoopIdle();
|
|
668
|
+
if (!await this.poller.waitForReadable(capacitySignal)) break;
|
|
690
669
|
continue;
|
|
691
670
|
}
|
|
692
671
|
this.roundRobin?.setReady(this.receiveOwner, true);
|
|
@@ -704,11 +683,12 @@ export class ZLinkRouteReceiveLoop {
|
|
|
704
683
|
}
|
|
705
684
|
const received = this.router.recv(1);
|
|
706
685
|
if (received == null) {
|
|
686
|
+
this.poller.markDrained();
|
|
707
687
|
releaseRawReceive();
|
|
708
688
|
this.roundRobin?.setReady(this.receiveOwner, false);
|
|
709
689
|
this.roundRobin?.release(this.receiveOwner);
|
|
710
690
|
batch.reset();
|
|
711
|
-
await
|
|
691
|
+
if (!await this.poller.waitForReadable(capacitySignal)) break;
|
|
712
692
|
continue;
|
|
713
693
|
}
|
|
714
694
|
const receivedBytes = messageBytes(received.parts);
|
|
@@ -821,15 +801,15 @@ export class ZLinkRouteReceiveLoop {
|
|
|
821
801
|
}
|
|
822
802
|
|
|
823
803
|
const RECEIVE_BATCH_MESSAGE_LIMIT = 64;
|
|
824
|
-
const RECEIVE_BATCH_BYTE_LIMIT =
|
|
804
|
+
const RECEIVE_BATCH_BYTE_LIMIT = 4 * 1024 * 1024;
|
|
825
805
|
const RECEIVE_BATCH_TIME_LIMIT_MS = 2;
|
|
826
806
|
|
|
827
807
|
class ZLinkReceiveBatchBudget {
|
|
828
808
|
private messages = 0;
|
|
829
|
-
private bytes =
|
|
809
|
+
private bytes = 0;
|
|
830
810
|
private startedAt = performance.now();
|
|
831
811
|
|
|
832
|
-
record(bytes:
|
|
812
|
+
record(bytes: number): boolean {
|
|
833
813
|
this.messages += 1;
|
|
834
814
|
this.bytes += bytes;
|
|
835
815
|
return this.messages >= RECEIVE_BATCH_MESSAGE_LIMIT
|
|
@@ -844,58 +824,27 @@ class ZLinkReceiveBatchBudget {
|
|
|
844
824
|
|
|
845
825
|
reset(): void {
|
|
846
826
|
this.messages = 0;
|
|
847
|
-
this.bytes =
|
|
827
|
+
this.bytes = 0;
|
|
848
828
|
this.startedAt = performance.now();
|
|
849
829
|
}
|
|
850
830
|
}
|
|
851
831
|
|
|
852
|
-
class ZLinkSharedIdleWaiter {
|
|
853
|
-
private pending?: Promise<void>;
|
|
854
|
-
private resolvePending?: () => void;
|
|
855
|
-
private readonly wake = (): void => {
|
|
856
|
-
const resolve = this.resolvePending;
|
|
857
|
-
this.pending = undefined;
|
|
858
|
-
this.resolvePending = undefined;
|
|
859
|
-
resolve?.();
|
|
860
|
-
};
|
|
861
|
-
|
|
862
|
-
constructor(private readonly delayMs: number) {}
|
|
863
|
-
|
|
864
|
-
wait(): Promise<void> {
|
|
865
|
-
if (this.pending !== undefined) return this.pending;
|
|
866
|
-
this.pending = new Promise<void>((resolve) => {
|
|
867
|
-
this.resolvePending = resolve;
|
|
868
|
-
});
|
|
869
|
-
setTimeout(this.wake, this.delayMs);
|
|
870
|
-
return this.pending;
|
|
871
|
-
}
|
|
872
|
-
}
|
|
873
|
-
|
|
874
|
-
const CHANNEL_RECEIVE_IDLE_POLL_INTERVAL_MS = 5;
|
|
875
|
-
const receiveLoopIdleWaiter = new ZLinkSharedIdleWaiter(
|
|
876
|
-
CHANNEL_RECEIVE_IDLE_POLL_INTERVAL_MS
|
|
877
|
-
);
|
|
878
|
-
|
|
879
|
-
function waitReceiveLoopIdle(): Promise<void> {
|
|
880
|
-
return receiveLoopIdleWaiter.wait();
|
|
881
|
-
}
|
|
882
|
-
|
|
883
832
|
function waitReceiveLoopTurn(): Promise<void> {
|
|
884
833
|
return new Promise<void>(resolve => setImmediate(resolve));
|
|
885
834
|
}
|
|
886
835
|
|
|
887
836
|
|
|
888
|
-
function messageBytes(parts: readonly Message[]):
|
|
889
|
-
return parts.reduce((sum, part) => sum + messagePartBytes(part),
|
|
837
|
+
function messageBytes(parts: readonly Message[]): number {
|
|
838
|
+
return parts.reduce((sum, part) => sum + messagePartBytes(part), 0);
|
|
890
839
|
}
|
|
891
840
|
|
|
892
|
-
function messagePartBytes(part: Message | undefined):
|
|
841
|
+
function messagePartBytes(part: Message | undefined): number {
|
|
893
842
|
if (part === undefined) {
|
|
894
|
-
return
|
|
843
|
+
return 0;
|
|
895
844
|
}
|
|
896
845
|
const size = (part as Message & { size?: () => number }).size;
|
|
897
846
|
if (typeof size === 'function') {
|
|
898
|
-
return
|
|
847
|
+
return size.call(part);
|
|
899
848
|
}
|
|
900
|
-
return
|
|
849
|
+
return part.data().byteLength;
|
|
901
850
|
}
|
|
@@ -1272,7 +1272,10 @@ export class ZLinkChannelSocketRegistry {
|
|
|
1272
1272
|
for (;;) {
|
|
1273
1273
|
if (!connection.readablePoller.wait(0)) return;
|
|
1274
1274
|
const received = connection.dealer.recv(1);
|
|
1275
|
-
if (received === undefined)
|
|
1275
|
+
if (received === undefined) {
|
|
1276
|
+
connection.readablePoller.markDrained();
|
|
1277
|
+
return;
|
|
1278
|
+
}
|
|
1276
1279
|
try {
|
|
1277
1280
|
if (received.parts.length !== 1
|
|
1278
1281
|
|| !isClientServerControlFrame(received.parts[0]!.data())) {
|
|
@@ -281,7 +281,8 @@ export class ZLinkRuntimeRouteTransport implements ZLinkRouteClientTransport {
|
|
|
281
281
|
sourceNodeRid: string,
|
|
282
282
|
parts: readonly MessageLike[]
|
|
283
283
|
) => ZLinkSubmitResult,
|
|
284
|
-
private readonly metrics?: ZLinkRuntimeMetrics
|
|
284
|
+
private readonly metrics?: ZLinkRuntimeMetrics,
|
|
285
|
+
private readonly flowCreationEnabled: () => boolean = () => true
|
|
285
286
|
) {}
|
|
286
287
|
|
|
287
288
|
canRouteChannel(routerChannelId: string): boolean {
|
|
@@ -308,7 +309,7 @@ export class ZLinkRuntimeRouteTransport implements ZLinkRouteClientTransport {
|
|
|
308
309
|
): Promise<ZLinkSubmitResult> {
|
|
309
310
|
// Call-scoped flow (spec 27 §4): the envelope flow pair lives only for
|
|
310
311
|
// the duration of this outbound call, never in the caller's context.
|
|
311
|
-
return runWithOutboundFlow(
|
|
312
|
+
return runWithOutboundFlow(this.flowCreationEnabled(), () => this.submitNode(
|
|
312
313
|
meshName,
|
|
313
314
|
targetNodeRid,
|
|
314
315
|
packetName,
|
|
@@ -327,7 +328,7 @@ export class ZLinkRuntimeRouteTransport implements ZLinkRouteClientTransport {
|
|
|
327
328
|
signal?: AbortSignal,
|
|
328
329
|
metadata?: ReadonlyMap<string, string>
|
|
329
330
|
): Promise<ZLinkSubmitResult> {
|
|
330
|
-
return runWithOutboundFlow(
|
|
331
|
+
return runWithOutboundFlow(this.flowCreationEnabled(), () => this.submitNode(
|
|
331
332
|
meshName,
|
|
332
333
|
targetNodeRid,
|
|
333
334
|
packetName,
|
|
@@ -399,7 +400,7 @@ export class ZLinkRuntimeRouteTransport implements ZLinkRouteClientTransport {
|
|
|
399
400
|
signal?: AbortSignal,
|
|
400
401
|
metadata?: ReadonlyMap<string, string>
|
|
401
402
|
): Promise<TReply> {
|
|
402
|
-
return runWithOutboundFlow(
|
|
403
|
+
return runWithOutboundFlow(this.flowCreationEnabled(), () => this.requestScoped<TReply>(
|
|
403
404
|
meshName,
|
|
404
405
|
targetNodeRid,
|
|
405
406
|
packetName,
|
|
@@ -468,7 +469,7 @@ export class ZLinkRuntimeRouteTransport implements ZLinkRouteClientTransport {
|
|
|
468
469
|
signal?: AbortSignal,
|
|
469
470
|
metadata?: ReadonlyMap<string, string>
|
|
470
471
|
): Promise<ZLinkSubmitResult> {
|
|
471
|
-
return runWithOutboundFlow(
|
|
472
|
+
return runWithOutboundFlow(this.flowCreationEnabled(), () => this.submitToChannelScoped(
|
|
472
473
|
meshName,
|
|
473
474
|
channelName,
|
|
474
475
|
packetName,
|
|
@@ -521,7 +522,7 @@ export class ZLinkRuntimeRouteTransport implements ZLinkRouteClientTransport {
|
|
|
521
522
|
signal?: AbortSignal,
|
|
522
523
|
metadata?: ReadonlyMap<string, string>
|
|
523
524
|
): Promise<TReply> {
|
|
524
|
-
return runWithOutboundFlow(
|
|
525
|
+
return runWithOutboundFlow(this.flowCreationEnabled(), () => this.requestToChannelScoped<TReply>(
|
|
525
526
|
meshName,
|
|
526
527
|
channelName,
|
|
527
528
|
packetName,
|
|
@@ -614,7 +615,10 @@ export class ZLinkRuntimeRouteTransport implements ZLinkRouteClientTransport {
|
|
|
614
615
|
readonly metadata?: ReadonlyMap<string, string>;
|
|
615
616
|
}
|
|
616
617
|
): Promise<ZLinkSubmitResult> {
|
|
617
|
-
return runWithOutboundFlow(
|
|
618
|
+
return runWithOutboundFlow(
|
|
619
|
+
this.flowCreationEnabled(),
|
|
620
|
+
() => this.sendToSpotScoped(spotRouteTarget, message, options)
|
|
621
|
+
);
|
|
618
622
|
}
|
|
619
623
|
|
|
620
624
|
private async sendToSpotScoped(
|
|
@@ -712,7 +716,10 @@ export class ZLinkRuntimeRouteTransport implements ZLinkRouteClientTransport {
|
|
|
712
716
|
readonly metadata?: ReadonlyMap<string, string>;
|
|
713
717
|
}
|
|
714
718
|
): Promise<TReply> {
|
|
715
|
-
return runWithOutboundFlow(
|
|
719
|
+
return runWithOutboundFlow(
|
|
720
|
+
this.flowCreationEnabled(),
|
|
721
|
+
() => this.requestToSpotScoped<TReply>(spotRouteTarget, request, options)
|
|
722
|
+
);
|
|
716
723
|
}
|
|
717
724
|
|
|
718
725
|
private async requestToSpotScoped<TReply = unknown>(
|
|
@@ -819,7 +826,10 @@ export class ZLinkRuntimeRouteTransport implements ZLinkRouteClientTransport {
|
|
|
819
826
|
request: Message,
|
|
820
827
|
options: { readonly timeoutMs?: number; readonly signal?: AbortSignal }
|
|
821
828
|
): Promise<readonly Message[]> {
|
|
822
|
-
return runWithOutboundFlow(
|
|
829
|
+
return runWithOutboundFlow(
|
|
830
|
+
this.flowCreationEnabled(),
|
|
831
|
+
() => this.requestRawToSpotScoped(spotRouteTarget, request, options)
|
|
832
|
+
);
|
|
823
833
|
}
|
|
824
834
|
|
|
825
835
|
private async requestRawToSpotScoped(
|
|
@@ -963,7 +973,7 @@ export class ZLinkRuntimeRouteTransport implements ZLinkRouteClientTransport {
|
|
|
963
973
|
undefined,
|
|
964
974
|
this.codecs,
|
|
965
975
|
undefined,
|
|
966
|
-
|
|
976
|
+
this.flowCreationEnabled(),
|
|
967
977
|
metadata
|
|
968
978
|
);
|
|
969
979
|
}
|
|
@@ -6,36 +6,20 @@ export interface ZLinkSerialWorkOptions {
|
|
|
6
6
|
readonly metadataBytes?: number;
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
-
/** Internal per-owner
|
|
9
|
+
/** Internal per-owner fairness settings; ingress capacity belongs to the host queue. */
|
|
10
10
|
export interface ZLinkSerialSchedulerOptions {
|
|
11
|
-
readonly applicationMessageCapacity?: number;
|
|
12
|
-
readonly applicationByteCapacity?: number;
|
|
13
|
-
readonly lifecycleMessageCapacity?: number;
|
|
14
|
-
readonly lifecycleByteCapacity?: number;
|
|
15
11
|
readonly ownerTimeBudget?: number;
|
|
16
12
|
readonly lifecycleBurstLimit?: number;
|
|
17
|
-
readonly fixedWorkByteCost?: number;
|
|
18
|
-
readonly capacityError?: (lane: ZLinkSerialWorkLane) => unknown;
|
|
19
13
|
}
|
|
20
14
|
|
|
21
|
-
export const ZLINK_DEFAULT_SERIAL_SCHEDULER_OPTIONS: Required<
|
|
22
|
-
Omit<ZLinkSerialSchedulerOptions, 'capacityError'>
|
|
23
|
-
> = Object.freeze({
|
|
24
|
-
applicationMessageCapacity: 1_024,
|
|
25
|
-
applicationByteCapacity: 64 * 1024 * 1024,
|
|
26
|
-
lifecycleMessageCapacity: 128,
|
|
27
|
-
lifecycleByteCapacity: 4 * 1024 * 1024,
|
|
15
|
+
export const ZLINK_DEFAULT_SERIAL_SCHEDULER_OPTIONS: Required<ZLinkSerialSchedulerOptions> = Object.freeze({
|
|
28
16
|
ownerTimeBudget: 10,
|
|
29
17
|
lifecycleBurstLimit: 8,
|
|
30
|
-
fixedWorkByteCost: 256
|
|
31
18
|
});
|
|
32
19
|
|
|
33
|
-
const MAX_SAFE_INTEGER = Number.MAX_SAFE_INTEGER;
|
|
34
|
-
|
|
35
20
|
export interface ZLinkSerialWorkRecord<T> {
|
|
36
21
|
readonly acceptedSequence: bigint;
|
|
37
22
|
readonly lane: ZLinkSerialWorkLane;
|
|
38
|
-
readonly byteCost: number;
|
|
39
23
|
readonly operation: () => Promise<T> | T;
|
|
40
24
|
readonly context?: unknown;
|
|
41
25
|
resolve(value: T): void;
|
|
@@ -61,26 +45,17 @@ type SerialWorkRecord<T> = Omit<ZLinkSerialWorkRecord<T>, 'acceptedSequence'> &
|
|
|
61
45
|
|
|
62
46
|
interface ZLinkSerialAdmissionLane {
|
|
63
47
|
readonly records: Array<SerialWorkRecord<unknown>>;
|
|
64
|
-
readonly messageCapacity: number;
|
|
65
|
-
readonly byteCapacity: number;
|
|
66
|
-
messageCount: number;
|
|
67
|
-
byteCount: number;
|
|
68
48
|
}
|
|
69
49
|
|
|
70
|
-
type ZLinkSerialReservationMode = 'enforce-capacity' | 'transferred' | 'none';
|
|
71
|
-
|
|
72
50
|
/**
|
|
73
51
|
* One event-loop serial execution queue per Spot, Actor mailbox, or Stream session.
|
|
74
|
-
*
|
|
75
|
-
* execution owner reaches its terminal transition.
|
|
52
|
+
* Application work carries the host permit through the queue until the handler starts.
|
|
76
53
|
*/
|
|
77
54
|
export class ZLinkSerialExecutionQueue {
|
|
78
55
|
private readonly application: ZLinkSerialAdmissionLane;
|
|
79
56
|
private readonly lifecycle: ZLinkSerialAdmissionLane;
|
|
80
57
|
private readonly ownerTimeBudget: number;
|
|
81
58
|
private readonly lifecycleBurstLimit: number;
|
|
82
|
-
private readonly fixedWorkByteCost: number;
|
|
83
|
-
private readonly capacityError: (lane: ZLinkSerialWorkLane) => unknown;
|
|
84
59
|
private nextAcceptedSequence = 1n;
|
|
85
60
|
private draining = false;
|
|
86
61
|
private drainScheduled = false;
|
|
@@ -95,26 +70,12 @@ export class ZLinkSerialExecutionQueue {
|
|
|
95
70
|
options: ZLinkSerialSchedulerOptions = {}
|
|
96
71
|
) {
|
|
97
72
|
const configured = { ...ZLINK_DEFAULT_SERIAL_SCHEDULER_OPTIONS, ...options };
|
|
98
|
-
validatePositive(configured.applicationMessageCapacity, 'applicationMessageCapacity');
|
|
99
|
-
validatePositive(configured.applicationByteCapacity, 'applicationByteCapacity');
|
|
100
|
-
validatePositive(configured.lifecycleMessageCapacity, 'lifecycleMessageCapacity');
|
|
101
|
-
validatePositive(configured.lifecycleByteCapacity, 'lifecycleByteCapacity');
|
|
102
73
|
validateNonNegative(configured.ownerTimeBudget, 'ownerTimeBudget');
|
|
103
74
|
validatePositive(configured.lifecycleBurstLimit, 'lifecycleBurstLimit');
|
|
104
|
-
|
|
105
|
-
this.
|
|
106
|
-
configured.applicationMessageCapacity,
|
|
107
|
-
configured.applicationByteCapacity
|
|
108
|
-
);
|
|
109
|
-
this.lifecycle = createLane(
|
|
110
|
-
configured.lifecycleMessageCapacity,
|
|
111
|
-
configured.lifecycleByteCapacity
|
|
112
|
-
);
|
|
75
|
+
this.application = createLane();
|
|
76
|
+
this.lifecycle = createLane();
|
|
113
77
|
this.ownerTimeBudget = configured.ownerTimeBudget;
|
|
114
78
|
this.lifecycleBurstLimit = configured.lifecycleBurstLimit;
|
|
115
|
-
this.fixedWorkByteCost = configured.fixedWorkByteCost;
|
|
116
|
-
this.capacityError = options.capacityError
|
|
117
|
-
?? (lane => new Error(`The ${lane} execution queue is full.`));
|
|
118
79
|
}
|
|
119
80
|
|
|
120
81
|
submit<T>(
|
|
@@ -139,15 +100,14 @@ export class ZLinkSerialExecutionQueue {
|
|
|
139
100
|
}
|
|
140
101
|
|
|
141
102
|
/**
|
|
142
|
-
* Enqueues a continuation for an already-
|
|
143
|
-
* application FIFO order without taking a second owner-local reservation.
|
|
103
|
+
* Enqueues a continuation for an already-admitted owner turn.
|
|
144
104
|
*/
|
|
145
105
|
submitContinuation<T>(
|
|
146
106
|
operation: () => Promise<T> | T,
|
|
147
107
|
context?: unknown
|
|
148
108
|
): Promise<T> {
|
|
149
109
|
try {
|
|
150
|
-
return this.admit(operation, { lane: 'application' }, context
|
|
110
|
+
return this.admit(operation, { lane: 'application' }, context);
|
|
151
111
|
} catch (error) {
|
|
152
112
|
return Promise.reject(error);
|
|
153
113
|
}
|
|
@@ -155,9 +115,7 @@ export class ZLinkSerialExecutionQueue {
|
|
|
155
115
|
|
|
156
116
|
/**
|
|
157
117
|
* Atomically transfers an already-accepted durable FIFO prefix into this
|
|
158
|
-
* owner queue.
|
|
159
|
-
* cannot publish a restore linearization point for a prefix that was never
|
|
160
|
-
* queued.
|
|
118
|
+
* owner queue.
|
|
161
119
|
*
|
|
162
120
|
* @internal
|
|
163
121
|
*/
|
|
@@ -171,22 +129,20 @@ export class ZLinkSerialExecutionQueue {
|
|
|
171
129
|
operation,
|
|
172
130
|
{ ...options, lane: 'application' },
|
|
173
131
|
context,
|
|
174
|
-
'transferred',
|
|
175
132
|
preparation
|
|
176
133
|
);
|
|
177
134
|
}
|
|
178
135
|
|
|
179
136
|
/**
|
|
180
137
|
* Enqueues work that already owns the host-wide application job permit.
|
|
181
|
-
*
|
|
182
|
-
* capacity decision and remains held through handler terminal completion.
|
|
138
|
+
* It remains held through handler terminal completion.
|
|
183
139
|
*/
|
|
184
140
|
submitPreAdmitted<T>(
|
|
185
141
|
operation: () => Promise<T> | T,
|
|
186
142
|
options: ZLinkSerialWorkOptions = {},
|
|
187
143
|
context?: unknown
|
|
188
144
|
): Promise<T> {
|
|
189
|
-
return this.admit(operation, options, context
|
|
145
|
+
return this.admit(operation, options, context);
|
|
190
146
|
}
|
|
191
147
|
|
|
192
148
|
snapshot(): {
|
|
@@ -196,10 +152,10 @@ export class ZLinkSerialExecutionQueue {
|
|
|
196
152
|
readonly lifecycleBytes: number;
|
|
197
153
|
} {
|
|
198
154
|
return {
|
|
199
|
-
applicationMessages: this.application.
|
|
200
|
-
applicationBytes:
|
|
201
|
-
lifecycleMessages: this.lifecycle.
|
|
202
|
-
lifecycleBytes:
|
|
155
|
+
applicationMessages: this.application.records.length,
|
|
156
|
+
applicationBytes: 0,
|
|
157
|
+
lifecycleMessages: this.lifecycle.records.length,
|
|
158
|
+
lifecycleBytes: 0
|
|
203
159
|
};
|
|
204
160
|
}
|
|
205
161
|
|
|
@@ -224,19 +180,11 @@ export class ZLinkSerialExecutionQueue {
|
|
|
224
180
|
operation: () => Promise<T> | T,
|
|
225
181
|
options: ZLinkSerialWorkOptions,
|
|
226
182
|
context?: unknown,
|
|
227
|
-
reservationMode: ZLinkSerialReservationMode = 'enforce-capacity',
|
|
228
183
|
preparation?: ZLinkSerialWorkPreparation
|
|
229
184
|
): Promise<T> {
|
|
230
185
|
if (this.closed) throw new Error('The serial execution queue is closed.');
|
|
231
186
|
const lane = options.lane ?? 'application';
|
|
232
|
-
const reservationHeld = reservationMode !== 'none';
|
|
233
|
-
const byteCost = reservationHeld ? this.byteCost(options) : 0;
|
|
234
187
|
const target = lane === 'application' ? this.application : this.lifecycle;
|
|
235
|
-
if (reservationMode === 'enforce-capacity' && !reserve(target, byteCost)) {
|
|
236
|
-
throw this.capacityError(lane);
|
|
237
|
-
}
|
|
238
|
-
if (reservationMode === 'transferred') reserveTransferred(target, byteCost);
|
|
239
|
-
|
|
240
188
|
let settled = false;
|
|
241
189
|
let released = false;
|
|
242
190
|
let resolveResult!: (value: T | PromiseLike<T>) => void;
|
|
@@ -248,7 +196,6 @@ export class ZLinkSerialExecutionQueue {
|
|
|
248
196
|
const release = (): void => {
|
|
249
197
|
if (released) return;
|
|
250
198
|
released = true;
|
|
251
|
-
if (reservationHeld) releaseReservation(target, byteCost);
|
|
252
199
|
};
|
|
253
200
|
const resolve = (value: T): void => {
|
|
254
201
|
if (settled) return;
|
|
@@ -263,7 +210,6 @@ export class ZLinkSerialExecutionQueue {
|
|
|
263
210
|
const record: SerialWorkRecord<T> = {
|
|
264
211
|
acceptedSequence: 0n,
|
|
265
212
|
lane,
|
|
266
|
-
byteCost,
|
|
267
213
|
operation,
|
|
268
214
|
context,
|
|
269
215
|
preparation,
|
|
@@ -291,18 +237,6 @@ export class ZLinkSerialExecutionQueue {
|
|
|
291
237
|
return result;
|
|
292
238
|
}
|
|
293
239
|
|
|
294
|
-
private byteCost(options: ZLinkSerialWorkOptions): number {
|
|
295
|
-
const payloadBytes = options.payloadBytes ?? 0;
|
|
296
|
-
const metadataBytes = options.metadataBytes ?? 0;
|
|
297
|
-
validateNonNegative(payloadBytes, 'payloadBytes');
|
|
298
|
-
validateNonNegative(metadataBytes, 'metadataBytes');
|
|
299
|
-
if (payloadBytes > MAX_SAFE_INTEGER - metadataBytes) return MAX_SAFE_INTEGER;
|
|
300
|
-
const payloadAndMetadata = payloadBytes + metadataBytes;
|
|
301
|
-
return payloadAndMetadata > MAX_SAFE_INTEGER - this.fixedWorkByteCost
|
|
302
|
-
? MAX_SAFE_INTEGER
|
|
303
|
-
: payloadAndMetadata + this.fixedWorkByteCost;
|
|
304
|
-
}
|
|
305
|
-
|
|
306
240
|
private scheduleDrain(): void {
|
|
307
241
|
if (this.drainScheduled || this.draining) return;
|
|
308
242
|
this.drainScheduled = true;
|
|
@@ -485,38 +419,12 @@ export class ZLinkSerialExecutionQueue {
|
|
|
485
419
|
}
|
|
486
420
|
}
|
|
487
421
|
|
|
488
|
-
function createLane(
|
|
422
|
+
function createLane(): ZLinkSerialAdmissionLane {
|
|
489
423
|
return {
|
|
490
|
-
records: []
|
|
491
|
-
messageCapacity,
|
|
492
|
-
byteCapacity,
|
|
493
|
-
messageCount: 0,
|
|
494
|
-
byteCount: 0
|
|
424
|
+
records: []
|
|
495
425
|
};
|
|
496
426
|
}
|
|
497
427
|
|
|
498
|
-
function reserve(lane: ZLinkSerialAdmissionLane, byteCost: number): boolean {
|
|
499
|
-
if (
|
|
500
|
-
lane.messageCount >= lane.messageCapacity
|
|
501
|
-
|| byteCost > lane.byteCapacity - lane.byteCount
|
|
502
|
-
) {
|
|
503
|
-
return false;
|
|
504
|
-
}
|
|
505
|
-
lane.messageCount += 1;
|
|
506
|
-
lane.byteCount += byteCost;
|
|
507
|
-
return true;
|
|
508
|
-
}
|
|
509
|
-
|
|
510
|
-
function reserveTransferred(lane: ZLinkSerialAdmissionLane, byteCost: number): void {
|
|
511
|
-
lane.messageCount += 1;
|
|
512
|
-
lane.byteCount += byteCost;
|
|
513
|
-
}
|
|
514
|
-
|
|
515
|
-
function releaseReservation(lane: ZLinkSerialAdmissionLane, byteCost: number): void {
|
|
516
|
-
lane.messageCount -= 1;
|
|
517
|
-
lane.byteCount -= byteCost;
|
|
518
|
-
}
|
|
519
|
-
|
|
520
428
|
function validatePositive(value: number, field: string): void {
|
|
521
429
|
if (!Number.isSafeInteger(value) || value < 1) {
|
|
522
430
|
throw new RangeError(`${field} must be a positive safe integer.`);
|
|
@@ -51,17 +51,8 @@ export class EventLoopWorkQueues {
|
|
|
51
51
|
private applicationScheduled = false;
|
|
52
52
|
private accepting = true;
|
|
53
53
|
|
|
54
|
-
constructor(
|
|
55
|
-
private readonly infrastructureLimit: number,
|
|
56
|
-
private readonly applicationLimit: number
|
|
57
|
-
) {
|
|
58
|
-
if (infrastructureLimit < 1 || applicationLimit < 1) {
|
|
59
|
-
throw new RangeError('Queue limits must be positive.');
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
54
|
submitInfrastructure(task: EventLoopTask): boolean {
|
|
64
|
-
if (!this.accepting
|
|
55
|
+
if (!this.accepting) return false;
|
|
65
56
|
this.infrastructure.push(task);
|
|
66
57
|
this.infrastructureCount += 1;
|
|
67
58
|
this.scheduleInfrastructure();
|
|
@@ -69,7 +60,7 @@ export class EventLoopWorkQueues {
|
|
|
69
60
|
}
|
|
70
61
|
|
|
71
62
|
submitApplication(task: EventLoopTask): boolean {
|
|
72
|
-
if (!this.accepting
|
|
63
|
+
if (!this.accepting) return false;
|
|
73
64
|
this.application.push(task);
|
|
74
65
|
this.applicationCount += 1;
|
|
75
66
|
this.scheduleApplication();
|
|
@@ -17,13 +17,6 @@ export class OperationCancelledError extends Error {
|
|
|
17
17
|
}
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
export class OperationCapacityExceededError extends Error {
|
|
21
|
-
constructor(readonly maxPendingOperations: number) {
|
|
22
|
-
super(`Operation capacity ${maxPendingOperations} is exhausted.`);
|
|
23
|
-
this.name = 'OperationCapacityExceededError';
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
|
|
27
20
|
export interface PendingOperation<T> {
|
|
28
21
|
readonly id: bigint;
|
|
29
22
|
readonly promise: Promise<T>;
|
|
@@ -50,31 +43,18 @@ const systemClock: OperationClock = {
|
|
|
50
43
|
|
|
51
44
|
/** Owns request completion so reply, timeout, cancellation, and shutdown race safely. */
|
|
52
45
|
export class OperationRegistry<T> {
|
|
53
|
-
static readonly DEFAULT_MAX_PENDING_OPERATIONS = 4_096;
|
|
54
|
-
|
|
55
46
|
private readonly entries = new Map<bigint, Entry<T>>();
|
|
56
47
|
private nextId = 1n;
|
|
57
48
|
private generation = 1n;
|
|
58
49
|
private closed = false;
|
|
59
50
|
|
|
60
|
-
constructor(
|
|
61
|
-
private readonly clock: OperationClock = systemClock,
|
|
62
|
-
private readonly maxPendingOperations = OperationRegistry.DEFAULT_MAX_PENDING_OPERATIONS
|
|
63
|
-
) {
|
|
64
|
-
if (!Number.isSafeInteger(maxPendingOperations) || maxPendingOperations <= 0) {
|
|
65
|
-
throw new RangeError('maxPendingOperations must be a positive safe integer.');
|
|
66
|
-
}
|
|
67
|
-
}
|
|
51
|
+
constructor(private readonly clock: OperationClock = systemClock) {}
|
|
68
52
|
|
|
69
|
-
|
|
53
|
+
register(timeoutMs: number, timeoutOwner: 'registry' | 'sender' = 'registry'): PendingOperation<T> {
|
|
70
54
|
if (this.closed) throw new Error('Operation registry is closed.');
|
|
71
55
|
if (!Number.isFinite(timeoutMs) || timeoutMs < 0) {
|
|
72
56
|
throw new RangeError('timeoutMs must be a non-negative finite number.');
|
|
73
57
|
}
|
|
74
|
-
if (this.entries.size >= this.maxPendingOperations) {
|
|
75
|
-
throw new OperationCapacityExceededError(this.maxPendingOperations);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
58
|
const id = this.nextId++;
|
|
79
59
|
const generation = this.generation;
|
|
80
60
|
let resolve!: Entry<T>['resolve'];
|