@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
@@ -142,22 +142,6 @@ export interface ZLinkSpotNodeRuntimeManagerOptions {
142
142
  };
143
143
  }
144
144
 
145
- interface ZLinkPublishSlotWaiter {
146
- readonly resolve: (acquired: boolean) => void;
147
- readonly reject: (error: unknown) => void;
148
- readonly signal?: AbortSignal;
149
- abortHandler?: () => void;
150
- timeout?: ReturnType<typeof setTimeout>;
151
- deadlineMs?: number;
152
- settled: boolean;
153
- }
154
-
155
- interface ZLinkPublishSlotQueue {
156
- readonly waiters: Array<ZLinkPublishSlotWaiter | undefined>;
157
- head: number;
158
- count: number;
159
- }
160
-
161
145
  export class ZLinkSpotNodeRuntimeManager {
162
146
  private readonly meshNodes = new Map<string, ZLinkBackendMeshNode>();
163
147
  private readonly meshPumps = new Map<string, ZLinkMeshDispatchPump>();
@@ -168,8 +152,6 @@ export class ZLinkSpotNodeRuntimeManager {
168
152
  string,
169
153
  ReturnType<ZLinkBackendMeshNode['createPublisher']>
170
154
  >();
171
- private readonly activePublishes = new Set<string>();
172
- private readonly publishSlotWaiters = new Map<string, ZLinkPublishSlotQueue>();
173
155
  private disposed = false;
174
156
  private readonly autoConnectLoops: ZLinkAutoConnectLoop[] = [];
175
157
  private readonly publishedMeshNodeDescriptors =
@@ -829,7 +811,6 @@ export class ZLinkSpotNodeRuntimeManager {
829
811
  this.meshPumps.clear();
830
812
  const publishers = [...this.publishers.values()];
831
813
  this.publishers.clear();
832
- this.rejectPublishSlotWaiters(runtimeShutdownError());
833
814
  const errors: unknown[] = [];
834
815
  const settle = async (operations: readonly Promise<unknown>[]) => {
835
816
  const results = await Promise.allSettled(operations);
@@ -1120,76 +1101,32 @@ export class ZLinkSpotNodeRuntimeManager {
1120
1101
  if (publisher === undefined) {
1121
1102
  throw new ZLinkConfigurationException(`RouteMesh '${meshName}' publisher is not started.`);
1122
1103
  }
1123
- const sendTimeoutMs = this.options.registration.spotNodes
1124
- .get(meshName)?.publisherConfig?.sendTimeoutMs ?? 1000;
1125
- const slot = this.acquirePublishSlot(meshName, sendTimeoutMs, signal);
1126
- if (slot === false) {
1127
- // The bounded executor-admission tokens are exhausted. Do not retain the
1128
- // payload, cancellation state, or a timer for this hard-overload call.
1129
- return Promise.resolve(this.publishTimedOut());
1130
- }
1131
- if (slot === true) {
1132
- return this.executePublish(
1133
- publisher,
1134
- meshName,
1135
- channelName,
1136
- topic,
1137
- packetName,
1138
- event,
1139
- metadata
1140
- );
1141
- }
1142
- return slot.then((acquired) => acquired
1143
- ? this.executePublish(
1144
- publisher,
1145
- meshName,
1146
- channelName,
1147
- topic,
1148
- packetName,
1149
- event,
1150
- metadata
1151
- )
1152
- : this.publishTimedOut());
1104
+ return this.executePublish(publisher, channelName, topic, packetName, event, metadata);
1153
1105
  }
1154
1106
 
1155
1107
  private async executePublish(
1156
1108
  publisher: ReturnType<ZLinkBackendMeshNode['createPublisher']>,
1157
- meshName: string,
1158
1109
  channelName: string,
1159
1110
  topic: string,
1160
1111
  packetName: string | undefined,
1161
1112
  event: Message,
1162
1113
  metadata: ReadonlyMap<string, string>
1163
1114
  ): Promise<ZLinkSubmitResult> {
1164
- // The slot is the source-local admission boundary. Once publishAsync takes
1165
- // the owned frames, target processing cannot change the caller terminal.
1166
- // Call-scoped flow (spec 27 §4): the fanout envelope flow pair does not
1167
- // outlive this publish call.
1168
- try {
1169
- const parts = runWithOutboundFlow(
1170
- this.options.dispatchErrors?.flow.flowCreationEnabled() ?? true,
1171
- () => encodeChannelPublishEnvelopeParts(
1172
- channelName,
1173
- topic,
1174
- packetName,
1175
- event,
1176
- undefined,
1177
- this.options.dispatchErrors?.flow.flowCreationEnabled() ?? true,
1178
- metadata
1179
- )
1180
- );
1181
- const processing = publisher.publishAsync(
1115
+ const parts = runWithOutboundFlow(
1116
+ this.options.dispatchErrors?.flow.flowCreationEnabled() ?? true,
1117
+ () => encodeChannelPublishEnvelopeParts(
1182
1118
  channelName,
1183
1119
  topic,
1184
- parts,
1120
+ packetName,
1121
+ event,
1185
1122
  undefined,
1186
- undefined
1187
- );
1188
- void Promise.resolve(processing).catch(() => undefined);
1189
- return Promise.resolve({ status: ZLinkSubmitStatus.Submitted });
1190
- } finally {
1191
- this.releasePublishSlot(meshName);
1192
- }
1123
+ this.options.dispatchErrors?.flow.flowCreationEnabled() ?? true,
1124
+ metadata
1125
+ )
1126
+ );
1127
+ const processing = publisher.publishAsync(channelName, topic, parts, undefined, undefined);
1128
+ void Promise.resolve(processing).catch(() => undefined);
1129
+ return Promise.resolve({ status: ZLinkSubmitStatus.Submitted });
1193
1130
  }
1194
1131
 
1195
1132
  tryPublish(
@@ -1242,163 +1179,6 @@ export class ZLinkSpotNodeRuntimeManager {
1242
1179
  }
1243
1180
  }
1244
1181
 
1245
- private acquirePublishSlot(
1246
- meshName: string,
1247
- timeoutMs: number,
1248
- signal?: AbortSignal
1249
- ): boolean | Promise<boolean> {
1250
- if (!this.activePublishes.has(meshName)) {
1251
- this.activePublishes.add(meshName);
1252
- return true;
1253
- }
1254
- const waiterCapacity = Math.max(
1255
- 1,
1256
- this.options.registration.spotNodes
1257
- .get(meshName)?.publisherConfig?.sendHighWaterMark ?? 1
1258
- );
1259
- const queue = this.publishSlotWaiters.get(meshName) ?? {
1260
- waiters: [],
1261
- head: 0,
1262
- count: 0
1263
- };
1264
- if (queue.count >= waiterCapacity) {
1265
- // This call has no bounded executor-admission token. Fail without
1266
- // retaining its payload in a pending work queue.
1267
- return false;
1268
- }
1269
- return new Promise<boolean>((resolve, reject) => {
1270
- const waiter: ZLinkPublishSlotWaiter = {
1271
- resolve,
1272
- reject,
1273
- signal,
1274
- settled: false
1275
- };
1276
- if (timeoutMs !== -1) {
1277
- waiter.deadlineMs = performance.now() + Math.max(0, timeoutMs);
1278
- }
1279
- queue.waiters.push(waiter);
1280
- queue.count += 1;
1281
- this.publishSlotWaiters.set(meshName, queue);
1282
- if (timeoutMs !== -1) {
1283
- waiter.timeout = setTimeout(() => this.settlePublishSlotWaiter(
1284
- meshName,
1285
- waiter,
1286
- false
1287
- ), Math.max(0, timeoutMs));
1288
- }
1289
- if (signal !== undefined) {
1290
- waiter.abortHandler = () => this.rejectPublishSlotWaiter(
1291
- meshName,
1292
- waiter,
1293
- signal.reason ?? createAbortError()
1294
- );
1295
- signal.addEventListener('abort', waiter.abortHandler, { once: true });
1296
- }
1297
- });
1298
- }
1299
-
1300
- private publishTimedOut(): ZLinkSubmitResult {
1301
- return { status: ZLinkSubmitStatus.TimedOut };
1302
- }
1303
-
1304
- private releasePublishSlot(meshName: string): void {
1305
- const queue = this.publishSlotWaiters.get(meshName);
1306
- while (queue !== undefined && queue.count > 0) {
1307
- const waiter = this.takePublishSlotWaiter(queue);
1308
- if (waiter === undefined) continue;
1309
- if (waiter.settled) continue;
1310
- if (waiter.deadlineMs !== undefined && performance.now() >= waiter.deadlineMs) {
1311
- this.cleanupPublishSlotWaiter(waiter);
1312
- waiter.settled = true;
1313
- waiter.resolve(false);
1314
- continue;
1315
- }
1316
- this.cleanupPublishSlotWaiter(waiter);
1317
- waiter.settled = true;
1318
- waiter.resolve(true);
1319
- if (queue.count === 0) this.publishSlotWaiters.delete(meshName);
1320
- return;
1321
- }
1322
- this.publishSlotWaiters.delete(meshName);
1323
- this.activePublishes.delete(meshName);
1324
- }
1325
-
1326
- private settlePublishSlotWaiter(
1327
- meshName: string,
1328
- waiter: ZLinkPublishSlotWaiter,
1329
- acquired: boolean
1330
- ): void {
1331
- if (waiter.settled) return;
1332
- waiter.settled = true;
1333
- this.removePublishSlotWaiter(meshName, waiter);
1334
- this.cleanupPublishSlotWaiter(waiter);
1335
- waiter.resolve(acquired);
1336
- }
1337
-
1338
- private rejectPublishSlotWaiter(
1339
- meshName: string,
1340
- waiter: ZLinkPublishSlotWaiter,
1341
- error: unknown
1342
- ): void {
1343
- if (waiter.settled) return;
1344
- waiter.settled = true;
1345
- this.removePublishSlotWaiter(meshName, waiter);
1346
- this.cleanupPublishSlotWaiter(waiter);
1347
- waiter.reject(error);
1348
- }
1349
-
1350
- private removePublishSlotWaiter(meshName: string, waiter: ZLinkPublishSlotWaiter): void {
1351
- const queue = this.publishSlotWaiters.get(meshName);
1352
- if (queue === undefined) return;
1353
- const index = queue.waiters.indexOf(waiter, queue.head);
1354
- if (index >= 0) {
1355
- queue.waiters[index] = undefined;
1356
- queue.count -= 1;
1357
- this.compactPublishSlotWaiters(queue);
1358
- }
1359
- if (queue.count === 0) this.publishSlotWaiters.delete(meshName);
1360
- }
1361
-
1362
- private cleanupPublishSlotWaiter(waiter: ZLinkPublishSlotWaiter): void {
1363
- if (waiter.timeout !== undefined) clearTimeout(waiter.timeout);
1364
- if (waiter.abortHandler !== undefined) {
1365
- waiter.signal?.removeEventListener('abort', waiter.abortHandler);
1366
- }
1367
- }
1368
-
1369
- private rejectPublishSlotWaiters(error: unknown): void {
1370
- for (const [meshName, queue] of this.publishSlotWaiters) {
1371
- let waiter: ZLinkPublishSlotWaiter | undefined;
1372
- while ((waiter = this.takePublishSlotWaiter(queue)) !== undefined) {
1373
- this.rejectPublishSlotWaiter(meshName, waiter, error);
1374
- }
1375
- this.publishSlotWaiters.delete(meshName);
1376
- }
1377
- this.activePublishes.clear();
1378
- }
1379
-
1380
- private takePublishSlotWaiter(queue: ZLinkPublishSlotQueue): ZLinkPublishSlotWaiter | undefined {
1381
- while (queue.head < queue.waiters.length && queue.waiters[queue.head] === undefined) {
1382
- queue.head += 1;
1383
- }
1384
- const waiter = queue.waiters[queue.head];
1385
- if (waiter === undefined) return undefined;
1386
- queue.waiters[queue.head] = undefined;
1387
- queue.head += 1;
1388
- queue.count -= 1;
1389
- this.compactPublishSlotWaiters(queue);
1390
- return waiter;
1391
- }
1392
-
1393
- private compactPublishSlotWaiters(queue: ZLinkPublishSlotQueue): void {
1394
- if (queue.count === 0) {
1395
- queue.waiters.length = 0;
1396
- queue.head = 0;
1397
- } else if (queue.head >= 1024 && queue.head * 2 >= queue.waiters.length) {
1398
- queue.waiters.splice(0, queue.head);
1399
- queue.head = 0;
1400
- }
1401
- }
1402
1182
  }
1403
1183
 
1404
1184
  function runtimeShutdownError(): ZLinkFrameworkException {
@@ -38,14 +38,7 @@ export class ZLinkSpotSerialTurnExecutor {
38
38
  ) {
39
39
  this.scheduler = new ZLinkSerialExecutionQueue(
40
40
  (record) => this.runQueuedRecord(record),
41
- {
42
- ...schedulerOptions,
43
- capacityError: schedulerOptions?.capacityError ?? (() =>
44
- createInternalFrameworkException(
45
- ZLinkFrameworkInternalErrorKind.WorkerQueueFull,
46
- 'Spot execution queue capacity was exceeded.'
47
- ))
48
- }
41
+ schedulerOptions
49
42
  );
50
43
  }
51
44
 
@@ -41,6 +41,18 @@ const systemTimerClock: ZLinkTimerClock = {
41
41
  };
42
42
 
43
43
  type ZLinkTimerOwnerSpot = ZLinkSpot | ZLinkEntrySpot;
44
+
45
+ interface ZLinkTimerFinalization {
46
+ readonly promise: Promise<void>;
47
+ readonly resolve: () => void;
48
+ readonly reject: (cause: unknown) => void;
49
+ }
50
+
51
+ interface ZLinkTimerFinalizationStart {
52
+ readonly timeout: unknown;
53
+ readonly running: Promise<void>;
54
+ }
55
+
44
56
  type ZLinkTimerFailureReporter = (
45
57
  tick: ZLinkTimerTick,
46
58
  cause: unknown,
@@ -125,11 +137,13 @@ export class ZLinkSpotTimerRegistry {
125
137
  ? this.executionSerialForTimer?.(name, serial) ?? serial
126
138
  : undefined;
127
139
  if (this.executionBarrier !== undefined) executionSerial?.setExecutionBarrier(this.executionBarrier);
140
+ let callbackStarted = false;
128
141
  const timer = startOutsideStateLane(() => new ZLinkManagedTimer(
129
142
  name,
130
143
  periodMs,
131
144
  normalizeTimerOptions(options),
132
145
  async (tick) => {
146
+ callbackStarted = false;
133
147
  const timerFlow = createInboundFlow(undefined, 'Timer', this.flowCreationEnabled());
134
148
  const operation = () => {
135
149
  const current = this.timers.get(name);
@@ -137,6 +151,7 @@ export class ZLinkSpotTimerRegistry {
137
151
  return undefined;
138
152
  }
139
153
  if (!this.executionAllowed()) return undefined;
154
+ callbackStarted = true;
140
155
  return runWithFlow(timerFlow, () => handler.handle(spot, tick));
141
156
  };
142
157
  if (this.executeTimer !== undefined) {
@@ -146,9 +161,7 @@ export class ZLinkSpotTimerRegistry {
146
161
  }
147
162
  },
148
163
  reportFailure,
149
- () => this.executeTimer === undefined
150
- ? !executionSerial!.isExecuting
151
- : this.isTimerExecuting?.(name) !== true,
164
+ () => callbackStarted,
152
165
  this.clock
153
166
  ));
154
167
  const registered = await this.lane.run(() => this.completeAddCore(
@@ -163,13 +176,18 @@ export class ZLinkSpotTimerRegistry {
163
176
 
164
177
  async dispose(): Promise<void> {
165
178
  const timers = await this.lane.run(() => {
166
- const active = [...this.timers.values()].map((entry) => entry.timer);
179
+ const active = [...this.timers.entries()].map(([name, entry]) => ({
180
+ name,
181
+ timer: entry.timer,
182
+ wait: this.executeTimer === undefined
183
+ ? true
184
+ : this.isTimerExecuting?.(name) !== true
185
+ }));
167
186
  this.timers.clear();
168
187
  return active;
169
188
  });
170
- for (const timer of timers) {
171
- await timer.dispose();
172
- }
189
+ const finalizations = timers.map(({ timer }) => timer.dispose());
190
+ await Promise.all(finalizations.filter((_, index) => timers[index]!.wait));
173
191
  }
174
192
 
175
193
  async captureRelocation(): Promise<readonly ZLinkTimerRelocationState[]> {
@@ -248,6 +266,8 @@ export class ZLinkSpotTimerRegistry {
248
266
  }
249
267
 
250
268
  class ZLinkRegisteredTimer implements ZLinkTimer {
269
+ private finalization: Promise<void> | undefined;
270
+
251
271
  constructor(
252
272
  private readonly registry: ZLinkSpotTimerRegistry,
253
273
  private readonly name: string,
@@ -260,7 +280,18 @@ class ZLinkRegisteredTimer implements ZLinkTimer {
260
280
  }
261
281
 
262
282
  cancel(signal?: AbortSignal): Promise<void> {
263
- return this.registry.cancel(this.name, this.generation, this.timer, signal);
283
+ if (this.finalization !== undefined) return this.finalization;
284
+ try {
285
+ throwIfAborted(signal);
286
+ } catch (failure) {
287
+ return Promise.reject(failure);
288
+ }
289
+ this.finalization = this.registry.cancel(
290
+ this.name,
291
+ this.generation,
292
+ this.timer
293
+ );
294
+ return this.finalization;
264
295
  }
265
296
 
266
297
  dispose(): Promise<void> {
@@ -278,6 +309,8 @@ export class ZLinkManagedTimer implements ZLinkTimer {
278
309
  private lastScheduledIndex = 0n;
279
310
  private timeout: unknown;
280
311
  private running: Promise<void> = Promise.resolve();
312
+ private dispatchFailure: { readonly cause: unknown } | undefined;
313
+ private finalization: ZLinkTimerFinalization | undefined;
281
314
 
282
315
  constructor(
283
316
  private readonly name: string,
@@ -297,9 +330,30 @@ export class ZLinkManagedTimer implements ZLinkTimer {
297
330
  return this.disposed;
298
331
  }
299
332
 
300
- async cancel(_signal?: AbortSignal): Promise<void> {
301
- const prepared = await this.lane.run(() => this.cancelCore());
302
- await prepared.running;
333
+ cancel(_signal?: AbortSignal): Promise<void> {
334
+ if (this.finalization !== undefined) return this.finalization.promise;
335
+
336
+ let resolve!: () => void;
337
+ let reject!: (cause: unknown) => void;
338
+ const promise = new Promise<void>((complete, fail) => {
339
+ resolve = complete;
340
+ reject = fail;
341
+ });
342
+ const finalization = { promise, resolve, reject };
343
+ this.finalization = finalization;
344
+
345
+ try {
346
+ const preparation = this.lane.run(() => this.cancelCore());
347
+ void preparation.then(
348
+ start => {
349
+ void this.completeFinalization(start, finalization).then(undefined, reject);
350
+ },
351
+ reject
352
+ );
353
+ } catch (failure) {
354
+ reject(failure);
355
+ }
356
+ return promise;
303
357
  }
304
358
 
305
359
  dispose(): Promise<void> {
@@ -363,7 +417,13 @@ export class ZLinkManagedTimer implements ZLinkTimer {
363
417
  : Math.max(0, Number(this.lastScheduledIndex + 1n) * this.periodMs - this.elapsedMs());
364
418
  this.timeout = this.clock.setTimeout(() => {
365
419
  this.timeout = undefined;
366
- this.running = startOutsideStateLane(() => this.fire()).catch(() => undefined);
420
+ const execution = startOutsideStateLane(() => this.fire());
421
+ this.running = execution.then(
422
+ () => undefined,
423
+ cause => this.lane.run(() => {
424
+ this.dispatchFailure ??= { cause };
425
+ })
426
+ );
367
427
  }, delayMs);
368
428
  }
369
429
 
@@ -388,14 +448,45 @@ export class ZLinkManagedTimer implements ZLinkTimer {
388
448
  await this.lane.run(() => this.completeFireCore(prepared.scheduledIndex, shouldContinue));
389
449
  }
390
450
 
391
- private cancelCore(): { readonly running: Promise<void> } {
392
- if (this.disposed) return { running: Promise.resolve() };
451
+ private cancelCore(): ZLinkTimerFinalizationStart {
393
452
  this.disposed = true;
394
- if (this.timeout !== undefined) {
395
- this.clock.clearTimeout(this.timeout);
396
- this.timeout = undefined;
453
+ const timeout = this.timeout;
454
+ this.timeout = undefined;
455
+ return {
456
+ timeout,
457
+ running: this.shouldWaitForRunningOnCancel()
458
+ ? this.running
459
+ : Promise.resolve()
460
+ };
461
+ }
462
+
463
+ private async completeFinalization(
464
+ start: ZLinkTimerFinalizationStart,
465
+ finalization: ZLinkTimerFinalization
466
+ ): Promise<void> {
467
+ const failures: unknown[] = [];
468
+ if (start.timeout !== undefined) {
469
+ try {
470
+ this.clock.clearTimeout(start.timeout);
471
+ } catch (failure) {
472
+ failures.push(failure);
473
+ }
474
+ }
475
+ try {
476
+ await start.running;
477
+ } catch (failure) {
478
+ failures.push(failure);
479
+ }
480
+ const dispatchFailure = await this.lane.run(() => this.dispatchFailure);
481
+ if (dispatchFailure !== undefined) failures.push(dispatchFailure.cause);
482
+
483
+ if (failures.length === 0) {
484
+ finalization.resolve();
485
+ } else if (failures.length === 1) {
486
+ finalization.reject(failures[0]);
487
+ } else {
488
+ finalization.reject(new AggregateError(failures, `Timer '${this.name}' cleanup failed.`));
397
489
  }
398
- return { running: this.shouldWaitForRunningOnCancel() ? this.running : Promise.resolve() };
399
490
  }
400
491
 
401
492
  private prepareCaptureCore(): { readonly running: Promise<void> } {
@@ -16,13 +16,7 @@ import {
16
16
  export class ZLinkSessionSerialExecutor {
17
17
  private readonly scheduler = new ZLinkSerialExecutionQueue(
18
18
  async (record) => this.execute(record),
19
- {
20
- capacityError: (lane) => createInternalFrameworkException(
21
- ZLinkFrameworkInternalErrorKind.WorkerQueueFull,
22
- `Stream Session ${lane} execution capacity is full.`,
23
- true
24
- )
25
- }
19
+ {}
26
20
  );
27
21
  private closed = false;
28
22
 
@@ -92,29 +92,21 @@ const systemLivenessClock: ZLinkStreamLivenessClock = {
92
92
  clearTimer: (timer) => clearTimeout(timer as ReturnType<typeof setTimeout>)
93
93
  };
94
94
 
95
- class ZLinkStreamIdleWaiter {
95
+ class ZLinkStreamReceiveWake {
96
96
  private pending?: Promise<void>;
97
97
  private resolvePending?: () => void;
98
- private timer?: ReturnType<typeof setTimeout>;
99
98
  private readonly finish = (): void => {
100
- if (this.timer !== undefined) {
101
- clearTimeout(this.timer);
102
- this.timer = undefined;
103
- }
104
99
  const resolve = this.resolvePending;
105
100
  this.pending = undefined;
106
101
  this.resolvePending = undefined;
107
102
  resolve?.();
108
103
  };
109
104
 
110
- constructor(private readonly delayMs: number) {}
111
-
112
105
  wait(): Promise<void> {
113
106
  if (this.pending !== undefined) return this.pending;
114
107
  this.pending = new Promise<void>((resolve) => {
115
108
  this.resolvePending = resolve;
116
109
  });
117
- this.timer = setTimeout(this.finish, this.delayMs);
118
110
  return this.pending;
119
111
  }
120
112
 
@@ -892,7 +884,7 @@ export class ZLinkStreamSessionNodeRuntime {
892
884
  private stopped = false;
893
885
  private readonly receiveAbortController = new AbortController();
894
886
  private receiveLoop: Promise<void> | undefined;
895
- private readonly receiveIdleWaiter = new ZLinkStreamIdleWaiter(5);
887
+ private readonly receiveWake = new ZLinkStreamReceiveWake();
896
888
  private receiveWorkSinceYield = 0;
897
889
  constructor(
898
890
  private readonly options: ZLinkStreamSessionNodeRuntimeOptions & ZLinkStreamLivenessOptions
@@ -967,14 +959,15 @@ export class ZLinkStreamSessionNodeRuntime {
967
959
  }
968
960
  if (!this.options.readablePoller.wait(0)) {
969
961
  permit.releaseAfterInternalProcessing();
970
- await this.waitReceiveLoopIdle();
962
+ await this.waitReceiveLoopWake(signal);
971
963
  continue;
972
964
  }
973
965
  packet = this.takePacket();
974
966
  if (!this.options.socket.recvPacket(packet, ZLINK_RECV_DONT_WAIT)) {
967
+ this.options.readablePoller.markDrained();
975
968
  permit.releaseAfterInternalProcessing();
976
969
  this.recyclePacket(packet);
977
- await this.waitReceiveLoopIdle();
970
+ await this.waitReceiveLoopWake(signal);
978
971
  continue;
979
972
  }
980
973
  packetTransferred = true;
@@ -987,10 +980,10 @@ export class ZLinkStreamSessionNodeRuntime {
987
980
  if (!this.isReceiveStopped(signal) && !packetTransferred) {
988
981
  this.options.onError?.(error);
989
982
  this.receiveWorkSinceYield = 0;
990
- await this.waitReceiveLoopIdle();
983
+ await new Promise<void>((resolve) => setImmediate(resolve));
991
984
  } else if (!this.isReceiveStopped(signal)) {
992
985
  this.receiveWorkSinceYield = 0;
993
- await this.waitReceiveLoopIdle();
986
+ await new Promise<void>((resolve) => setImmediate(resolve));
994
987
  }
995
988
  continue;
996
989
  }
@@ -1350,12 +1343,15 @@ export class ZLinkStreamSessionNodeRuntime {
1350
1343
  });
1351
1344
  }
1352
1345
 
1353
- private waitReceiveLoopIdle(): Promise<void> {
1354
- return this.receiveIdleWaiter.wait();
1346
+ private async waitReceiveLoopWake(signal: AbortSignal): Promise<void> {
1347
+ await Promise.race([
1348
+ this.options.readablePoller.waitForReadable(signal),
1349
+ this.receiveWake.wait()
1350
+ ]);
1355
1351
  }
1356
1352
 
1357
1353
  private wakeReceiveLoop(): void {
1358
- this.receiveIdleWaiter.wake();
1354
+ this.receiveWake.wake();
1359
1355
  }
1360
1356
 
1361
1357
  private getOrCreateSession(routingId: unknown): ZLinkStreamSessionRuntime | undefined {
@@ -7,8 +7,7 @@ import { ZLinkConfigurationException } from '../configuration';
7
7
  import {
8
8
  defaultWorkerMaxThreads,
9
9
  DEFAULT_WORKER_IDLE_TIMEOUT_MS,
10
- DEFAULT_WORKER_MIN_THREADS,
11
- DEFAULT_WORKER_QUEUE_LENGTH
10
+ DEFAULT_WORKER_MIN_THREADS
12
11
  } from '../../contracts/Configuration/InternalDefaults';
13
12
  import { createAbortError } from '../abort';
14
13
  import {
@@ -24,20 +23,17 @@ export interface ZLinkWorkerRuntimeOptions {
24
23
  readonly minThreads: number;
25
24
  readonly maxThreads: number;
26
25
  readonly idleTimeoutMs: number;
27
- readonly maxQueueLength: number;
28
26
  }
29
27
 
30
28
  export function resolveWorkerRuntimeOptions(options?: ZLinkWorkerOptions): ZLinkWorkerRuntimeOptions {
31
29
  const resolved = {
32
30
  minThreads: options?.minThreads ?? DEFAULT_WORKER_MIN_THREADS,
33
31
  maxThreads: options?.maxThreads ?? defaultWorkerMaxThreads(),
34
- idleTimeoutMs: options?.idleTimeoutMs ?? DEFAULT_WORKER_IDLE_TIMEOUT_MS,
35
- maxQueueLength: options?.maxQueueLength ?? DEFAULT_WORKER_QUEUE_LENGTH
32
+ idleTimeoutMs: options?.idleTimeoutMs ?? DEFAULT_WORKER_IDLE_TIMEOUT_MS
36
33
  };
37
34
  requireNonNegativeInteger('Worker minThreads', resolved.minThreads);
38
35
  requirePositiveInteger('Worker maxThreads', resolved.maxThreads);
39
36
  requireNonNegativeInteger('Worker idleTimeoutMs', resolved.idleTimeoutMs);
40
- requirePositiveInteger('Worker maxQueueLength', resolved.maxQueueLength);
41
37
  if (resolved.maxThreads < resolved.minThreads) {
42
38
  throw new ZLinkConfigurationException('Worker maxThreads must be greater than or equal to minThreads.');
43
39
  }
@@ -98,9 +94,6 @@ class ZLinkCpuWorkerPool {
98
94
  if (signal?.aborted === true) {
99
95
  return Promise.reject(createAbortError());
100
96
  }
101
- if (this.queueCount >= this.options.maxQueueLength) {
102
- return Promise.reject(workerQueueFull(this.options.maxQueueLength));
103
- }
104
97
  return new Promise<T>((resolve, reject) => {
105
98
  const job = {
106
99
  source: work.toString(),
@@ -535,14 +528,6 @@ class ZLinkSerialDeliveredPromise<T> implements Promise<T> {
535
528
  }
536
529
  }
537
530
 
538
- function workerQueueFull(maxQueueLength: number): ZLinkFrameworkException {
539
- return createInternalFrameworkException(
540
- ZLinkFrameworkInternalErrorKind.WorkerQueueFull,
541
- `CPU worker queue is full (maxQueueLength=${maxQueueLength}).`,
542
- true
543
- );
544
- }
545
-
546
531
  function workerTimedOut(timeoutMs: number): ZLinkFrameworkException {
547
532
  return createInternalFrameworkException(
548
533
  ZLinkFrameworkInternalErrorKind.WorkerTimedOut,