@zlink-systems/framework 0.11.0 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
@@ -34,8 +34,6 @@ class ZLinkSpotNodeRuntimeManager {
34
34
  entryActivations = new Map();
35
35
  entryActivationStarts = new Map();
36
36
  publishers = new Map();
37
- activePublishes = new Set();
38
- publishSlotWaiters = new Map();
39
37
  disposed = false;
40
38
  autoConnectLoops = [];
41
39
  publishedMeshNodeDescriptors = new Map();
@@ -579,7 +577,6 @@ class ZLinkSpotNodeRuntimeManager {
579
577
  this.meshPumps.clear();
580
578
  const publishers = [...this.publishers.values()];
581
579
  this.publishers.clear();
582
- this.rejectPublishSlotWaiters(runtimeShutdownError());
583
580
  const errors = [];
584
581
  const settle = async (operations) => {
585
582
  const results = await Promise.allSettled(operations);
@@ -802,35 +799,13 @@ class ZLinkSpotNodeRuntimeManager {
802
799
  if (publisher === undefined) {
803
800
  throw new configuration_1.ZLinkConfigurationException(`RouteMesh '${meshName}' publisher is not started.`);
804
801
  }
805
- const sendTimeoutMs = this.options.registration.spotNodes
806
- .get(meshName)?.publisherConfig?.sendTimeoutMs ?? 1000;
807
- const slot = this.acquirePublishSlot(meshName, sendTimeoutMs, signal);
808
- if (slot === false) {
809
- // The bounded executor-admission tokens are exhausted. Do not retain the
810
- // payload, cancellation state, or a timer for this hard-overload call.
811
- return Promise.resolve(this.publishTimedOut());
812
- }
813
- if (slot === true) {
814
- return this.executePublish(publisher, meshName, channelName, topic, packetName, event, metadata);
815
- }
816
- return slot.then((acquired) => acquired
817
- ? this.executePublish(publisher, meshName, channelName, topic, packetName, event, metadata)
818
- : this.publishTimedOut());
802
+ return this.executePublish(publisher, channelName, topic, packetName, event, metadata);
819
803
  }
820
- async executePublish(publisher, meshName, channelName, topic, packetName, event, metadata) {
821
- // The slot is the source-local admission boundary. Once publishAsync takes
822
- // the owned frames, target processing cannot change the caller terminal.
823
- // Call-scoped flow (spec 27 §4): the fanout envelope flow pair does not
824
- // outlive this publish call.
825
- try {
826
- const parts = (0, flow_context_1.runWithOutboundFlow)(this.options.dispatchErrors?.flow.flowCreationEnabled() ?? true, () => (0, channel_envelope_1.encodeChannelPublishEnvelopeParts)(channelName, topic, packetName, event, undefined, this.options.dispatchErrors?.flow.flowCreationEnabled() ?? true, metadata));
827
- const processing = publisher.publishAsync(channelName, topic, parts, undefined, undefined);
828
- void Promise.resolve(processing).catch(() => undefined);
829
- return Promise.resolve({ status: submission_result_1.ZLinkSubmitStatus.Submitted });
830
- }
831
- finally {
832
- this.releasePublishSlot(meshName);
833
- }
804
+ async executePublish(publisher, channelName, topic, packetName, event, metadata) {
805
+ const parts = (0, flow_context_1.runWithOutboundFlow)(this.options.dispatchErrors?.flow.flowCreationEnabled() ?? true, () => (0, channel_envelope_1.encodeChannelPublishEnvelopeParts)(channelName, topic, packetName, event, undefined, this.options.dispatchErrors?.flow.flowCreationEnabled() ?? true, metadata));
806
+ const processing = publisher.publishAsync(channelName, topic, parts, undefined, undefined);
807
+ void Promise.resolve(processing).catch(() => undefined);
808
+ return Promise.resolve({ status: submission_result_1.ZLinkSubmitStatus.Submitted });
834
809
  }
835
810
  tryPublish(meshName, channelName, topic, packetName, event, metadata = EMPTY_SPOT_METADATA) {
836
811
  return this.publishWithFlags(meshName, channelName, topic, packetName, event, ZLINK_SEND_DONT_WAIT, metadata);
@@ -855,141 +830,6 @@ class ZLinkSpotNodeRuntimeManager {
855
830
  throw error;
856
831
  }
857
832
  }
858
- acquirePublishSlot(meshName, timeoutMs, signal) {
859
- if (!this.activePublishes.has(meshName)) {
860
- this.activePublishes.add(meshName);
861
- return true;
862
- }
863
- const waiterCapacity = Math.max(1, this.options.registration.spotNodes
864
- .get(meshName)?.publisherConfig?.sendHighWaterMark ?? 1);
865
- const queue = this.publishSlotWaiters.get(meshName) ?? {
866
- waiters: [],
867
- head: 0,
868
- count: 0
869
- };
870
- if (queue.count >= waiterCapacity) {
871
- // This call has no bounded executor-admission token. Fail without
872
- // retaining its payload in a pending work queue.
873
- return false;
874
- }
875
- return new Promise((resolve, reject) => {
876
- const waiter = {
877
- resolve,
878
- reject,
879
- signal,
880
- settled: false
881
- };
882
- if (timeoutMs !== -1) {
883
- waiter.deadlineMs = performance.now() + Math.max(0, timeoutMs);
884
- }
885
- queue.waiters.push(waiter);
886
- queue.count += 1;
887
- this.publishSlotWaiters.set(meshName, queue);
888
- if (timeoutMs !== -1) {
889
- waiter.timeout = setTimeout(() => this.settlePublishSlotWaiter(meshName, waiter, false), Math.max(0, timeoutMs));
890
- }
891
- if (signal !== undefined) {
892
- waiter.abortHandler = () => this.rejectPublishSlotWaiter(meshName, waiter, signal.reason ?? (0, abort_1.createAbortError)());
893
- signal.addEventListener('abort', waiter.abortHandler, { once: true });
894
- }
895
- });
896
- }
897
- publishTimedOut() {
898
- return { status: submission_result_1.ZLinkSubmitStatus.TimedOut };
899
- }
900
- releasePublishSlot(meshName) {
901
- const queue = this.publishSlotWaiters.get(meshName);
902
- while (queue !== undefined && queue.count > 0) {
903
- const waiter = this.takePublishSlotWaiter(queue);
904
- if (waiter === undefined)
905
- continue;
906
- if (waiter.settled)
907
- continue;
908
- if (waiter.deadlineMs !== undefined && performance.now() >= waiter.deadlineMs) {
909
- this.cleanupPublishSlotWaiter(waiter);
910
- waiter.settled = true;
911
- waiter.resolve(false);
912
- continue;
913
- }
914
- this.cleanupPublishSlotWaiter(waiter);
915
- waiter.settled = true;
916
- waiter.resolve(true);
917
- if (queue.count === 0)
918
- this.publishSlotWaiters.delete(meshName);
919
- return;
920
- }
921
- this.publishSlotWaiters.delete(meshName);
922
- this.activePublishes.delete(meshName);
923
- }
924
- settlePublishSlotWaiter(meshName, waiter, acquired) {
925
- if (waiter.settled)
926
- return;
927
- waiter.settled = true;
928
- this.removePublishSlotWaiter(meshName, waiter);
929
- this.cleanupPublishSlotWaiter(waiter);
930
- waiter.resolve(acquired);
931
- }
932
- rejectPublishSlotWaiter(meshName, waiter, error) {
933
- if (waiter.settled)
934
- return;
935
- waiter.settled = true;
936
- this.removePublishSlotWaiter(meshName, waiter);
937
- this.cleanupPublishSlotWaiter(waiter);
938
- waiter.reject(error);
939
- }
940
- removePublishSlotWaiter(meshName, waiter) {
941
- const queue = this.publishSlotWaiters.get(meshName);
942
- if (queue === undefined)
943
- return;
944
- const index = queue.waiters.indexOf(waiter, queue.head);
945
- if (index >= 0) {
946
- queue.waiters[index] = undefined;
947
- queue.count -= 1;
948
- this.compactPublishSlotWaiters(queue);
949
- }
950
- if (queue.count === 0)
951
- this.publishSlotWaiters.delete(meshName);
952
- }
953
- cleanupPublishSlotWaiter(waiter) {
954
- if (waiter.timeout !== undefined)
955
- clearTimeout(waiter.timeout);
956
- if (waiter.abortHandler !== undefined) {
957
- waiter.signal?.removeEventListener('abort', waiter.abortHandler);
958
- }
959
- }
960
- rejectPublishSlotWaiters(error) {
961
- for (const [meshName, queue] of this.publishSlotWaiters) {
962
- let waiter;
963
- while ((waiter = this.takePublishSlotWaiter(queue)) !== undefined) {
964
- this.rejectPublishSlotWaiter(meshName, waiter, error);
965
- }
966
- this.publishSlotWaiters.delete(meshName);
967
- }
968
- this.activePublishes.clear();
969
- }
970
- takePublishSlotWaiter(queue) {
971
- while (queue.head < queue.waiters.length && queue.waiters[queue.head] === undefined) {
972
- queue.head += 1;
973
- }
974
- const waiter = queue.waiters[queue.head];
975
- if (waiter === undefined)
976
- return undefined;
977
- queue.waiters[queue.head] = undefined;
978
- queue.head += 1;
979
- queue.count -= 1;
980
- this.compactPublishSlotWaiters(queue);
981
- return waiter;
982
- }
983
- compactPublishSlotWaiters(queue) {
984
- if (queue.count === 0) {
985
- queue.waiters.length = 0;
986
- queue.head = 0;
987
- }
988
- else if (queue.head >= 1024 && queue.head * 2 >= queue.waiters.length) {
989
- queue.waiters.splice(0, queue.head);
990
- queue.head = 0;
991
- }
992
- }
993
833
  }
994
834
  exports.ZLinkSpotNodeRuntimeManager = ZLinkSpotNodeRuntimeManager;
995
835
  function runtimeShutdownError() {
@@ -18,10 +18,7 @@ class ZLinkSpotSerialTurnExecutor {
18
18
  constructor(yieldAllowed = true, sourceSpotId, schedulerOptions) {
19
19
  this.yieldAllowed = yieldAllowed;
20
20
  this.sourceSpotId = sourceSpotId;
21
- this.scheduler = new serial_execution_queue_1.ZLinkSerialExecutionQueue((record) => this.runQueuedRecord(record), {
22
- ...schedulerOptions,
23
- capacityError: schedulerOptions?.capacityError ?? (() => (0, framework_errors_internal_1.createInternalFrameworkException)(framework_errors_internal_1.ZLinkFrameworkInternalErrorKind.WorkerQueueFull, 'Spot execution queue capacity was exceeded.'))
24
- });
21
+ this.scheduler = new serial_execution_queue_1.ZLinkSerialExecutionQueue((record) => this.runQueuedRecord(record), schedulerOptions);
25
22
  }
26
23
  get isExecuting() {
27
24
  return this.depth > 0;
@@ -74,6 +74,8 @@ export declare class ZLinkManagedTimer implements ZLinkTimer {
74
74
  private lastScheduledIndex;
75
75
  private timeout;
76
76
  private running;
77
+ private dispatchFailure;
78
+ private finalization;
77
79
  constructor(name: string, periodMs: number, options: Required<ZLinkTimerOptions>, onTick: (tick: ZLinkTimerTick) => Promise<void>, onFailure?: ZLinkTimerFailureReporter | undefined, shouldWaitForRunningOnCancel?: () => boolean, clock?: ZLinkTimerClock);
78
80
  get isDisposed(): boolean;
79
81
  cancel(_signal?: AbortSignal): Promise<void>;
@@ -83,6 +85,7 @@ export declare class ZLinkManagedTimer implements ZLinkTimer {
83
85
  private scheduleNext;
84
86
  private fire;
85
87
  private cancelCore;
88
+ private completeFinalization;
86
89
  private prepareCaptureCore;
87
90
  private prepareFireCore;
88
91
  private completeFireCore;
@@ -59,7 +59,9 @@ class ZLinkSpotTimerRegistry {
59
59
  : undefined;
60
60
  if (this.executionBarrier !== undefined)
61
61
  executionSerial?.setExecutionBarrier(this.executionBarrier);
62
+ let callbackStarted = false;
62
63
  const timer = startOutsideStateLane(() => new ZLinkManagedTimer(name, periodMs, normalizeTimerOptions(options), async (tick) => {
64
+ callbackStarted = false;
63
65
  const timerFlow = (0, flow_context_1.createInboundFlow)(undefined, 'Timer', this.flowCreationEnabled());
64
66
  const operation = () => {
65
67
  const current = this.timers.get(name);
@@ -68,6 +70,7 @@ class ZLinkSpotTimerRegistry {
68
70
  }
69
71
  if (!this.executionAllowed())
70
72
  return undefined;
73
+ callbackStarted = true;
71
74
  return (0, flow_context_1.runWithFlow)(timerFlow, () => handler.handle(spot, tick));
72
75
  };
73
76
  if (this.executeTimer !== undefined) {
@@ -76,9 +79,7 @@ class ZLinkSpotTimerRegistry {
76
79
  else {
77
80
  await executionSerial.execute(operation);
78
81
  }
79
- }, reportFailure, () => this.executeTimer === undefined
80
- ? !executionSerial.isExecuting
81
- : this.isTimerExecuting?.(name) !== true, this.clock));
82
+ }, reportFailure, () => callbackStarted, this.clock));
82
83
  const registered = await this.lane.run(() => this.completeAddCore(name, prepared.generation, handlerType.name, timer));
83
84
  if (!registered)
84
85
  await timer.cancel(signal);
@@ -86,13 +87,18 @@ class ZLinkSpotTimerRegistry {
86
87
  }
87
88
  async dispose() {
88
89
  const timers = await this.lane.run(() => {
89
- const active = [...this.timers.values()].map((entry) => entry.timer);
90
+ const active = [...this.timers.entries()].map(([name, entry]) => ({
91
+ name,
92
+ timer: entry.timer,
93
+ wait: this.executeTimer === undefined
94
+ ? true
95
+ : this.isTimerExecuting?.(name) !== true
96
+ }));
90
97
  this.timers.clear();
91
98
  return active;
92
99
  });
93
- for (const timer of timers) {
94
- await timer.dispose();
95
- }
100
+ const finalizations = timers.map(({ timer }) => timer.dispose());
101
+ await Promise.all(finalizations.filter((_, index) => timers[index].wait));
96
102
  }
97
103
  async captureRelocation() {
98
104
  const timers = await this.lane.run(() => [...this.timers.entries()].sort(([left], [right]) => left.localeCompare(right)));
@@ -155,6 +161,7 @@ class ZLinkRegisteredTimer {
155
161
  name;
156
162
  generation;
157
163
  timer;
164
+ finalization;
158
165
  constructor(registry, name, generation, timer) {
159
166
  this.registry = registry;
160
167
  this.name = name;
@@ -165,7 +172,16 @@ class ZLinkRegisteredTimer {
165
172
  return this.timer.isDisposed;
166
173
  }
167
174
  cancel(signal) {
168
- return this.registry.cancel(this.name, this.generation, this.timer, signal);
175
+ if (this.finalization !== undefined)
176
+ return this.finalization;
177
+ try {
178
+ (0, abort_1.throwIfAborted)(signal);
179
+ }
180
+ catch (failure) {
181
+ return Promise.reject(failure);
182
+ }
183
+ this.finalization = this.registry.cancel(this.name, this.generation, this.timer);
184
+ return this.finalization;
169
185
  }
170
186
  dispose() {
171
187
  return this.cancel();
@@ -188,6 +204,8 @@ class ZLinkManagedTimer {
188
204
  lastScheduledIndex = 0n;
189
205
  timeout;
190
206
  running = Promise.resolve();
207
+ dispatchFailure;
208
+ finalization;
191
209
  constructor(name, periodMs, options, onTick, onFailure, shouldWaitForRunningOnCancel = () => true, clock = systemTimerClock) {
192
210
  this.name = name;
193
211
  this.periodMs = periodMs;
@@ -203,9 +221,27 @@ class ZLinkManagedTimer {
203
221
  get isDisposed() {
204
222
  return this.disposed;
205
223
  }
206
- async cancel(_signal) {
207
- const prepared = await this.lane.run(() => this.cancelCore());
208
- await prepared.running;
224
+ cancel(_signal) {
225
+ if (this.finalization !== undefined)
226
+ return this.finalization.promise;
227
+ let resolve;
228
+ let reject;
229
+ const promise = new Promise((complete, fail) => {
230
+ resolve = complete;
231
+ reject = fail;
232
+ });
233
+ const finalization = { promise, resolve, reject };
234
+ this.finalization = finalization;
235
+ try {
236
+ const preparation = this.lane.run(() => this.cancelCore());
237
+ void preparation.then(start => {
238
+ void this.completeFinalization(start, finalization).then(undefined, reject);
239
+ }, reject);
240
+ }
241
+ catch (failure) {
242
+ reject(failure);
243
+ }
244
+ return promise;
209
245
  }
210
246
  dispose() {
211
247
  return this.cancel();
@@ -264,7 +300,10 @@ class ZLinkManagedTimer {
264
300
  : Math.max(0, Number(this.lastScheduledIndex + 1n) * this.periodMs - this.elapsedMs());
265
301
  this.timeout = this.clock.setTimeout(() => {
266
302
  this.timeout = undefined;
267
- this.running = startOutsideStateLane(() => this.fire()).catch(() => undefined);
303
+ const execution = startOutsideStateLane(() => this.fire());
304
+ this.running = execution.then(() => undefined, cause => this.lane.run(() => {
305
+ this.dispatchFailure ??= { cause };
306
+ }));
268
307
  }, delayMs);
269
308
  }
270
309
  async fire() {
@@ -284,14 +323,44 @@ class ZLinkManagedTimer {
284
323
  await this.lane.run(() => this.completeFireCore(prepared.scheduledIndex, shouldContinue));
285
324
  }
286
325
  cancelCore() {
287
- if (this.disposed)
288
- return { running: Promise.resolve() };
289
326
  this.disposed = true;
290
- if (this.timeout !== undefined) {
291
- this.clock.clearTimeout(this.timeout);
292
- this.timeout = undefined;
327
+ const timeout = this.timeout;
328
+ this.timeout = undefined;
329
+ return {
330
+ timeout,
331
+ running: this.shouldWaitForRunningOnCancel()
332
+ ? this.running
333
+ : Promise.resolve()
334
+ };
335
+ }
336
+ async completeFinalization(start, finalization) {
337
+ const failures = [];
338
+ if (start.timeout !== undefined) {
339
+ try {
340
+ this.clock.clearTimeout(start.timeout);
341
+ }
342
+ catch (failure) {
343
+ failures.push(failure);
344
+ }
345
+ }
346
+ try {
347
+ await start.running;
348
+ }
349
+ catch (failure) {
350
+ failures.push(failure);
351
+ }
352
+ const dispatchFailure = await this.lane.run(() => this.dispatchFailure);
353
+ if (dispatchFailure !== undefined)
354
+ failures.push(dispatchFailure.cause);
355
+ if (failures.length === 0) {
356
+ finalization.resolve();
357
+ }
358
+ else if (failures.length === 1) {
359
+ finalization.reject(failures[0]);
360
+ }
361
+ else {
362
+ finalization.reject(new AggregateError(failures, `Timer '${this.name}' cleanup failed.`));
293
363
  }
294
- return { running: this.shouldWaitForRunningOnCancel() ? this.running : Promise.resolve() };
295
364
  }
296
365
  prepareCaptureCore() {
297
366
  if (this.disposed)
@@ -5,9 +5,7 @@ const serial_execution_queue_1 = require("../execution/serial-execution-queue");
5
5
  const framework_errors_internal_1 = require("../framework-errors-internal");
6
6
  const application_job_queue_scope_1 = require("../application-jobs/application-job-queue-scope");
7
7
  class ZLinkSessionSerialExecutor {
8
- scheduler = new serial_execution_queue_1.ZLinkSerialExecutionQueue(async (record) => this.execute(record), {
9
- capacityError: (lane) => (0, framework_errors_internal_1.createInternalFrameworkException)(framework_errors_internal_1.ZLinkFrameworkInternalErrorKind.WorkerQueueFull, `Stream Session ${lane} execution capacity is full.`, true)
10
- });
8
+ scheduler = new serial_execution_queue_1.ZLinkSerialExecutionQueue(async (record) => this.execute(record), {});
11
9
  closed = false;
12
10
  executeApplication(work, options = {}, onRejected) {
13
11
  return this.submit(work, 'application', options, onRejected);
@@ -131,7 +131,7 @@ export declare class ZLinkStreamSessionNodeRuntime {
131
131
  private stopped;
132
132
  private readonly receiveAbortController;
133
133
  private receiveLoop;
134
- private readonly receiveIdleWaiter;
134
+ private readonly receiveWake;
135
135
  private receiveWorkSinceYield;
136
136
  constructor(options: ZLinkStreamSessionNodeRuntimeOptions & ZLinkStreamLivenessOptions);
137
137
  start(): void;
@@ -159,7 +159,7 @@ export declare class ZLinkStreamSessionNodeRuntime {
159
159
  private removePendingConnectionMetadata;
160
160
  private resolveMonitorSession;
161
161
  private enqueueEndpointlessDisconnect;
162
- private waitReceiveLoopIdle;
162
+ private waitReceiveLoopWake;
163
163
  private wakeReceiveLoop;
164
164
  private getOrCreateSession;
165
165
  }
@@ -30,31 +30,21 @@ const systemLivenessClock = {
30
30
  setTimer: (callback, delayMs) => setTimeout(callback, delayMs),
31
31
  clearTimer: (timer) => clearTimeout(timer)
32
32
  };
33
- class ZLinkStreamIdleWaiter {
34
- delayMs;
33
+ class ZLinkStreamReceiveWake {
35
34
  pending;
36
35
  resolvePending;
37
- timer;
38
36
  finish = () => {
39
- if (this.timer !== undefined) {
40
- clearTimeout(this.timer);
41
- this.timer = undefined;
42
- }
43
37
  const resolve = this.resolvePending;
44
38
  this.pending = undefined;
45
39
  this.resolvePending = undefined;
46
40
  resolve?.();
47
41
  };
48
- constructor(delayMs) {
49
- this.delayMs = delayMs;
50
- }
51
42
  wait() {
52
43
  if (this.pending !== undefined)
53
44
  return this.pending;
54
45
  this.pending = new Promise((resolve) => {
55
46
  this.resolvePending = resolve;
56
47
  });
57
- this.timer = setTimeout(this.finish, this.delayMs);
58
48
  return this.pending;
59
49
  }
60
50
  wake() {
@@ -664,7 +654,7 @@ class ZLinkStreamSessionNodeRuntime {
664
654
  stopped = false;
665
655
  receiveAbortController = new AbortController();
666
656
  receiveLoop;
667
- receiveIdleWaiter = new ZLinkStreamIdleWaiter(5);
657
+ receiveWake = new ZLinkStreamReceiveWake();
668
658
  receiveWorkSinceYield = 0;
669
659
  constructor(options) {
670
660
  this.options = options;
@@ -734,14 +724,15 @@ class ZLinkStreamSessionNodeRuntime {
734
724
  }
735
725
  if (!this.options.readablePoller.wait(0)) {
736
726
  permit.releaseAfterInternalProcessing();
737
- await this.waitReceiveLoopIdle();
727
+ await this.waitReceiveLoopWake(signal);
738
728
  continue;
739
729
  }
740
730
  packet = this.takePacket();
741
731
  if (!this.options.socket.recvPacket(packet, ZLINK_RECV_DONT_WAIT)) {
732
+ this.options.readablePoller.markDrained();
742
733
  permit.releaseAfterInternalProcessing();
743
734
  this.recyclePacket(packet);
744
- await this.waitReceiveLoopIdle();
735
+ await this.waitReceiveLoopWake(signal);
745
736
  continue;
746
737
  }
747
738
  packetTransferred = true;
@@ -757,11 +748,11 @@ class ZLinkStreamSessionNodeRuntime {
757
748
  if (!this.isReceiveStopped(signal) && !packetTransferred) {
758
749
  this.options.onError?.(error);
759
750
  this.receiveWorkSinceYield = 0;
760
- await this.waitReceiveLoopIdle();
751
+ await new Promise((resolve) => setImmediate(resolve));
761
752
  }
762
753
  else if (!this.isReceiveStopped(signal)) {
763
754
  this.receiveWorkSinceYield = 0;
764
- await this.waitReceiveLoopIdle();
755
+ await new Promise((resolve) => setImmediate(resolve));
765
756
  }
766
757
  continue;
767
758
  }
@@ -1091,11 +1082,14 @@ class ZLinkStreamSessionNodeRuntime {
1091
1082
  pending.session.enqueueDisconnected(pending.error);
1092
1083
  });
1093
1084
  }
1094
- waitReceiveLoopIdle() {
1095
- return this.receiveIdleWaiter.wait();
1085
+ async waitReceiveLoopWake(signal) {
1086
+ await Promise.race([
1087
+ this.options.readablePoller.waitForReadable(signal),
1088
+ this.receiveWake.wait()
1089
+ ]);
1096
1090
  }
1097
1091
  wakeReceiveLoop() {
1098
- this.receiveIdleWaiter.wake();
1092
+ this.receiveWake.wake();
1099
1093
  }
1100
1094
  getOrCreateSession(routingId) {
1101
1095
  const sessionId = (0, managed_stream_1.streamSessionIdFromRoutingId)(routingId);
@@ -6,7 +6,6 @@ export interface ZLinkWorkerRuntimeOptions {
6
6
  readonly minThreads: number;
7
7
  readonly maxThreads: number;
8
8
  readonly idleTimeoutMs: number;
9
- readonly maxQueueLength: number;
10
9
  }
11
10
  export declare function resolveWorkerRuntimeOptions(options?: ZLinkWorkerOptions): ZLinkWorkerRuntimeOptions;
12
11
  export declare class ZLinkWorkerRuntime {
@@ -13,13 +13,11 @@ function resolveWorkerRuntimeOptions(options) {
13
13
  const resolved = {
14
14
  minThreads: options?.minThreads ?? InternalDefaults_1.DEFAULT_WORKER_MIN_THREADS,
15
15
  maxThreads: options?.maxThreads ?? (0, InternalDefaults_1.defaultWorkerMaxThreads)(),
16
- idleTimeoutMs: options?.idleTimeoutMs ?? InternalDefaults_1.DEFAULT_WORKER_IDLE_TIMEOUT_MS,
17
- maxQueueLength: options?.maxQueueLength ?? InternalDefaults_1.DEFAULT_WORKER_QUEUE_LENGTH
16
+ idleTimeoutMs: options?.idleTimeoutMs ?? InternalDefaults_1.DEFAULT_WORKER_IDLE_TIMEOUT_MS
18
17
  };
19
18
  requireNonNegativeInteger('Worker minThreads', resolved.minThreads);
20
19
  requirePositiveInteger('Worker maxThreads', resolved.maxThreads);
21
20
  requireNonNegativeInteger('Worker idleTimeoutMs', resolved.idleTimeoutMs);
22
- requirePositiveInteger('Worker maxQueueLength', resolved.maxQueueLength);
23
21
  if (resolved.maxThreads < resolved.minThreads) {
24
22
  throw new configuration_1.ZLinkConfigurationException('Worker maxThreads must be greater than or equal to minThreads.');
25
23
  }
@@ -53,9 +51,6 @@ class ZLinkCpuWorkerPool {
53
51
  if (signal?.aborted === true) {
54
52
  return Promise.reject((0, abort_1.createAbortError)());
55
53
  }
56
- if (this.queueCount >= this.options.maxQueueLength) {
57
- return Promise.reject(workerQueueFull(this.options.maxQueueLength));
58
- }
59
54
  return new Promise((resolve, reject) => {
60
55
  const job = {
61
56
  source: work.toString(),
@@ -446,9 +441,6 @@ class ZLinkSerialDeliveredPromise {
446
441
  return this.then((value) => this.serial.execute(() => { onfinally?.(); return value; }), (reason) => this.serial.execute(() => { onfinally?.(); throw reason; }));
447
442
  }
448
443
  }
449
- function workerQueueFull(maxQueueLength) {
450
- return (0, framework_errors_internal_1.createInternalFrameworkException)(framework_errors_internal_1.ZLinkFrameworkInternalErrorKind.WorkerQueueFull, `CPU worker queue is full (maxQueueLength=${maxQueueLength}).`, true);
451
- }
452
444
  function workerTimedOut(timeoutMs) {
453
445
  return (0, framework_errors_internal_1.createInternalFrameworkException)(framework_errors_internal_1.ZLinkFrameworkInternalErrorKind.WorkerTimedOut, `Worker job timed out after ${timeoutMs}ms.`, true);
454
446
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zlink-systems/framework",
3
- "version": "0.11.0",
3
+ "version": "0.12.0",
4
4
  "license": "SEE LICENSE IN LICENSE",
5
5
  "repository": {
6
6
  "type": "git",
@@ -19,7 +19,7 @@
19
19
  "dependencies": {
20
20
  "@opentelemetry/api": "^1.9.1",
21
21
  "@opentelemetry/api-logs": "^0.221.0",
22
- "@zlink-systems/stream-wire": "0.11.0",
23
- "@zlink-systems/zlink": "0.17.6"
22
+ "@zlink-systems/stream-wire": "0.12.0",
23
+ "@zlink-systems/zlink": "1.1.0"
24
24
  }
25
25
  }
@@ -1,6 +1,7 @@
1
1
  /**
2
- * Deterministic endpoint-notation normalization (endpoint 표기 정책,
3
- * doc/plan/endpoint-notation-policy.ko.md).
2
+ * Deterministic endpoint-notation normalization (endpoint 표기 정책),
3
+ * applied identically across all four language bindings so endpoint
4
+ * strings compare equal.
4
5
  *
5
6
  * `new URL()` cannot represent this domain: it throws on bracketed IPv6
6
7
  * literals that carry a zone id (`tcp://[fe80::1%eth0]:80`, even with the
@@ -16,7 +17,7 @@
16
17
  * public connect/disconnect APIs). Everything downstream compares the
17
18
  * resulting strings with plain `===`.
18
19
  *
19
- * Rules (policy 2.2), applied only to authority-bearing schemes
20
+ * Rules, applied only to authority-bearing schemes
20
21
  * (`tcp`, `tls`, `ws`, `wss` -- schemes whose grammar after `scheme://` is
21
22
  * `host:port`):
22
23
  * - scheme: lowercased
@@ -39,7 +40,7 @@
39
40
  * `normalizeEndpoint(normalizeEndpoint(x)) === normalizeEndpoint(x)`.
40
41
  *
41
42
  * DNS names are never resolved. `localhost` and `127.0.0.1` remain distinct
42
- * endpoints (policy 2.1).
43
+ * endpoints.
43
44
  */
44
45
 
45
46
  const SCHEME_PATTERN = /^([A-Za-z][A-Za-z0-9+.-]*):\/\/(.*)$/s;
@@ -6,7 +6,6 @@ export const DEFAULT_STREAM_NODE_MAX_MESSAGE_SIZE = 64 * 1024;
6
6
  /** Defaults shared by registration normalization and the runtime worker pool. */
7
7
  export const DEFAULT_WORKER_MIN_THREADS = 0;
8
8
  export const DEFAULT_WORKER_IDLE_TIMEOUT_MS = 30_000;
9
- export const DEFAULT_WORKER_QUEUE_LENGTH = 1024;
10
9
 
11
10
  export function defaultWorkerMaxThreads(): number {
12
11
  return Math.max(2, os.availableParallelism());
@@ -25,8 +25,7 @@ import {
25
25
  defaultWorkerMaxThreads,
26
26
  DEFAULT_STREAM_NODE_MAX_MESSAGE_SIZE,
27
27
  DEFAULT_WORKER_IDLE_TIMEOUT_MS,
28
- DEFAULT_WORKER_MIN_THREADS,
29
- DEFAULT_WORKER_QUEUE_LENGTH
28
+ DEFAULT_WORKER_MIN_THREADS
30
29
  } from './InternalDefaults';
31
30
 
32
31
  export function normalizeWorkerOptions(
@@ -37,8 +36,7 @@ export function normalizeWorkerOptions(
37
36
  return {
38
37
  minThreads: partial.minThreads ?? DEFAULT_WORKER_MIN_THREADS,
39
38
  maxThreads: partial.maxThreads ?? defaultWorkerMaxThreads(),
40
- idleTimeoutMs: partial.idleTimeoutMs ?? DEFAULT_WORKER_IDLE_TIMEOUT_MS,
41
- maxQueueLength: partial.maxQueueLength ?? DEFAULT_WORKER_QUEUE_LENGTH
39
+ idleTimeoutMs: partial.idleTimeoutMs ?? DEFAULT_WORKER_IDLE_TIMEOUT_MS
42
40
  };
43
41
  }
44
42