@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.
Files changed (119) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/contracts/Configuration/EndpointNotation.d.ts +5 -4
  3. package/dist/contracts/Configuration/EndpointNotation.js +5 -4
  4. package/dist/contracts/Configuration/InternalDefaults.d.ts +0 -1
  5. package/dist/contracts/Configuration/InternalDefaults.js +1 -2
  6. package/dist/contracts/Configuration/RegistrationNormalizers.js +1 -2
  7. package/dist/contracts/Configuration/RegistrationTypes.d.ts +2 -5
  8. package/dist/contracts/Configuration/RegistrationValidators.js +0 -1
  9. package/dist/contracts/Errors/ZLinkFrameworkException.d.ts +6 -7
  10. package/dist/contracts/Errors/ZLinkFrameworkException.js +6 -7
  11. package/dist/runtime/actors/actor-client.js +1 -1
  12. package/dist/runtime/actors/actor-mailbox.js +1 -5
  13. package/dist/runtime/backend/contracts/index.d.ts +2 -0
  14. package/dist/runtime/backend/mesh-completion-table.d.ts +1 -3
  15. package/dist/runtime/backend/mesh-completion-table.js +11 -15
  16. package/dist/runtime/backend/mesh-dispatch-pump.d.ts +11 -4
  17. package/dist/runtime/backend/mesh-dispatch-pump.js +201 -134
  18. package/dist/runtime/backend/node/node-backend-adapter-factory.d.ts +3 -4
  19. package/dist/runtime/backend/node/node-backend-adapter-factory.js +51 -37
  20. package/dist/runtime/backend/node/node-backend-adapter-support.d.ts +2 -2
  21. package/dist/runtime/backend/node/node-backend-adapter-support.js +2 -2
  22. package/dist/runtime/backend/node/node-dealer-channel-client-transport.js +2 -2
  23. package/dist/runtime/backend/node/node-event-loop-poller.d.ts +16 -0
  24. package/dist/runtime/backend/node/node-event-loop-poller.js +49 -0
  25. package/dist/runtime/backend/node/node-raw-binding-port.js +24 -41
  26. package/dist/runtime/backend/node/node-raw-mesh-backend.d.ts +8 -8
  27. package/dist/runtime/backend/node/node-raw-mesh-backend.js +100 -107
  28. package/dist/runtime/backend/node/node-socket-backend-adapter.d.ts +3 -2
  29. package/dist/runtime/backend/node/node-socket-backend-adapter.js +18 -67
  30. package/dist/runtime/backend/raw-binding-port.d.ts +1 -0
  31. package/dist/runtime/backend/runtime-values.d.ts +5 -1
  32. package/dist/runtime/channels/channel-dispatchers.js +2 -2
  33. package/dist/runtime/channels/channel-envelope.js +324 -121
  34. package/dist/runtime/channels/channel-multipart.d.ts +8 -1
  35. package/dist/runtime/channels/channel-outbound-operations.js +0 -2
  36. package/dist/runtime/channels/channel-receive-loops.d.ts +1 -3
  37. package/dist/runtime/channels/channel-receive-loops.js +25 -65
  38. package/dist/runtime/channels/channel-socket-registry.js +3 -1
  39. package/dist/runtime/channels/channel-transports.d.ts +2 -1
  40. package/dist/runtime/channels/channel-transports.js +12 -10
  41. package/dist/runtime/execution/serial-execution-queue.d.ts +5 -18
  42. package/dist/runtime/execution/serial-execution-queue.js +16 -80
  43. package/dist/runtime/foundation/event-loop-resources.d.ts +0 -3
  44. package/dist/runtime/foundation/event-loop-resources.js +2 -11
  45. package/dist/runtime/foundation/operation-registry.d.ts +2 -8
  46. package/dist/runtime/foundation/operation-registry.js +3 -21
  47. package/dist/runtime/foundation/raw-service-mesh-runtime.d.ts +19 -7
  48. package/dist/runtime/foundation/raw-service-mesh-runtime.js +56 -10
  49. package/dist/runtime/foundation/service-mailbox.d.ts +1 -7
  50. package/dist/runtime/foundation/service-mailbox.js +3 -2
  51. package/dist/runtime/foundation/service-runtime-contracts.d.ts +2 -1
  52. package/dist/runtime/foundation/service-stateful-registry.d.ts +1 -1
  53. package/dist/runtime/foundation/service-stateful-registry.js +2 -2
  54. package/dist/runtime/foundation/service-stateful-runtime.js +14 -14
  55. package/dist/runtime/framework-errors-internal.d.ts +4 -4
  56. package/dist/runtime/framework-errors-internal.js +8 -10
  57. package/dist/runtime/host/actor-placement-coordinator.js +1 -1
  58. package/dist/runtime/host/index.js +1 -1
  59. package/dist/runtime/host/service-relocation-host-runtime.js +0 -4
  60. package/dist/runtime/host/spot-address-transport.js +1 -1
  61. package/dist/runtime/host/user-spot-creation-coordinator.js +1 -1
  62. package/dist/runtime/spots/spot-node-runtime-manager.d.ts +0 -12
  63. package/dist/runtime/spots/spot-node-runtime-manager.js +6 -166
  64. package/dist/runtime/spots/spot-serial-turn-executor.js +1 -4
  65. package/dist/runtime/spots/spot-timer.d.ts +3 -0
  66. package/dist/runtime/spots/spot-timer.js +87 -18
  67. package/dist/runtime/streams/session-serial-executor.js +1 -3
  68. package/dist/runtime/streams/stream-session-runtime.d.ts +2 -2
  69. package/dist/runtime/streams/stream-session-runtime.js +13 -19
  70. package/dist/runtime/workers/index.d.ts +0 -1
  71. package/dist/runtime/workers/index.js +1 -9
  72. package/package.json +3 -3
  73. package/src/contracts/Configuration/EndpointNotation.ts +5 -4
  74. package/src/contracts/Configuration/InternalDefaults.ts +0 -1
  75. package/src/contracts/Configuration/RegistrationNormalizers.ts +2 -4
  76. package/src/contracts/Configuration/RegistrationTypes.ts +2 -5
  77. package/src/contracts/Configuration/RegistrationValidators.ts +0 -1
  78. package/src/contracts/Errors/ZLinkFrameworkException.ts +6 -7
  79. package/src/runtime/actors/actor-client.ts +1 -1
  80. package/src/runtime/actors/actor-mailbox.ts +1 -12
  81. package/src/runtime/backend/contracts/index.ts +2 -0
  82. package/src/runtime/backend/mesh-completion-table.ts +12 -23
  83. package/src/runtime/backend/mesh-dispatch-pump.ts +206 -138
  84. package/src/runtime/backend/node/node-backend-adapter-factory.ts +66 -35
  85. package/src/runtime/backend/node/node-backend-adapter-support.ts +4 -4
  86. package/src/runtime/backend/node/node-dealer-channel-client-transport.ts +2 -2
  87. package/src/runtime/backend/node/node-event-loop-poller.ts +55 -0
  88. package/src/runtime/backend/node/node-raw-binding-port.ts +30 -45
  89. package/src/runtime/backend/node/node-raw-mesh-backend.ts +97 -110
  90. package/src/runtime/backend/node/node-socket-backend-adapter.ts +23 -69
  91. package/src/runtime/backend/raw-binding-port.ts +1 -0
  92. package/src/runtime/backend/runtime-values.ts +7 -1
  93. package/src/runtime/channels/channel-dispatchers.ts +2 -2
  94. package/src/runtime/channels/channel-envelope.ts +379 -109
  95. package/src/runtime/channels/channel-multipart.ts +9 -1
  96. package/src/runtime/channels/channel-outbound-operations.ts +0 -1
  97. package/src/runtime/channels/channel-receive-loops.ts +24 -75
  98. package/src/runtime/channels/channel-socket-registry.ts +4 -1
  99. package/src/runtime/channels/channel-transports.ts +20 -10
  100. package/src/runtime/execution/serial-execution-queue.ts +16 -108
  101. package/src/runtime/foundation/event-loop-resources.ts +2 -11
  102. package/src/runtime/foundation/operation-registry.ts +2 -22
  103. package/src/runtime/foundation/raw-service-mesh-runtime.ts +74 -19
  104. package/src/runtime/foundation/service-mailbox.ts +3 -13
  105. package/src/runtime/foundation/service-runtime-contracts.ts +2 -1
  106. package/src/runtime/foundation/service-stateful-registry.ts +2 -2
  107. package/src/runtime/foundation/service-stateful-runtime.ts +14 -14
  108. package/src/runtime/framework-errors-internal.ts +8 -10
  109. package/src/runtime/host/actor-placement-coordinator.ts +1 -1
  110. package/src/runtime/host/index.ts +2 -1
  111. package/src/runtime/host/service-relocation-host-runtime.ts +0 -4
  112. package/src/runtime/host/spot-address-transport.ts +1 -1
  113. package/src/runtime/host/user-spot-creation-coordinator.ts +1 -1
  114. package/src/runtime/spots/spot-node-runtime-manager.ts +13 -233
  115. package/src/runtime/spots/spot-serial-turn-executor.ts +1 -8
  116. package/src/runtime/spots/spot-timer.ts +109 -18
  117. package/src/runtime/streams/session-serial-executor.ts +1 -7
  118. package/src/runtime/streams/stream-session-runtime.ts +13 -17
  119. 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 waitReceiveLoopIdle();
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
- if (!this.poller.wait(0)) {
263
- applicationJobPermit.releaseAfterInternalProcessing();
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 waitReceiveLoopIdle();
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 waitReceiveLoopIdle();
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 waitReceiveLoopIdle();
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 waitReceiveLoopIdle();
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 waitReceiveLoopIdle();
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 = 4n * 1024n * 1024n;
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 = 0n;
809
+ private bytes = 0;
830
810
  private startedAt = performance.now();
831
811
 
832
- record(bytes: bigint): boolean {
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 = 0n;
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[]): bigint {
889
- return parts.reduce((sum, part) => sum + messagePartBytes(part), 0n);
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): bigint {
841
+ function messagePartBytes(part: Message | undefined): number {
893
842
  if (part === undefined) {
894
- return 0n;
843
+ return 0;
895
844
  }
896
845
  const size = (part as Message & { size?: () => number }).size;
897
846
  if (typeof size === 'function') {
898
- return BigInt(size.call(part));
847
+ return size.call(part);
899
848
  }
900
- return BigInt(part.data().byteLength);
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) return;
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(true, () => this.submitNode(
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(true, () => this.submitNode(
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(true, () => this.requestScoped<TReply>(
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(true, () => this.submitToChannelScoped(
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(true, () => this.requestToChannelScoped<TReply>(
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(true, () => this.sendToSpotScoped(spotRouteTarget, message, options));
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(true, () => this.requestToSpotScoped<TReply>(spotRouteTarget, request, options));
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(true, () => this.requestRawToSpotScoped(spotRouteTarget, request, options));
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
- true,
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 reservation limits, never a shared ingress budget. */
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
- * Its reservations are owner-local and span queued plus running work until the
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
- validatePositive(configured.fixedWorkByteCost, 'fixedWorkByteCost');
105
- this.application = createLane(
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-reserved owner turn. It preserves
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, 'none');
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. Admission failures remain synchronous so the durable owner
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
- * Its owner-local reservation transfers from the mailbox without a second
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, 'transferred');
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.messageCount,
200
- applicationBytes: this.application.byteCount,
201
- lifecycleMessages: this.lifecycle.messageCount,
202
- lifecycleBytes: this.lifecycle.byteCount
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(messageCapacity: number, byteCapacity: number): ZLinkSerialAdmissionLane {
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 || this.infrastructureCount >= this.infrastructureLimit) return false;
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 || this.applicationCount >= this.applicationLimit) return false;
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
- reserve(timeoutMs: number, timeoutOwner: 'registry' | 'sender' = 'registry'): PendingOperation<T> {
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'];