@zlink-systems/framework 0.29.0 → 0.30.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 (168) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/contracts/Configuration/Builders.d.ts +1 -2
  3. package/dist/contracts/Configuration/Configs.d.ts +0 -5
  4. package/dist/contracts/Configuration/RegistrationBuilders.js +6 -4
  5. package/dist/contracts/Configuration/RegistrationTypes.d.ts +1 -6
  6. package/dist/contracts/Configuration/RegistrationValidators.js +4 -4
  7. package/dist/contracts/Configuration/index.d.ts +1 -1
  8. package/dist/internal.d.ts +1 -0
  9. package/dist/internal.js +3 -1
  10. package/dist/runtime/actors/actor-handoff.d.ts +3 -2
  11. package/dist/runtime/actors/actor-handoff.js +9 -4
  12. package/dist/runtime/actors/actor-runtime-contracts.d.ts +1 -0
  13. package/dist/runtime/actors/index.d.ts +2 -0
  14. package/dist/runtime/actors/index.js +21 -6
  15. package/dist/runtime/application-jobs/application-ingress-record-owner.d.ts +3 -1
  16. package/dist/runtime/application-jobs/application-ingress-record-owner.js +6 -1
  17. package/dist/runtime/application-jobs/application-job-queue-scope.d.ts +3 -1
  18. package/dist/runtime/application-jobs/application-job-queue-scope.js +5 -0
  19. package/dist/runtime/application-jobs/contracts.d.ts +5 -1
  20. package/dist/runtime/backend/contracts/index.d.ts +10 -19
  21. package/dist/runtime/backend/mesh-completion-table.d.ts +6 -4
  22. package/dist/runtime/backend/mesh-completion-table.js +28 -18
  23. package/dist/runtime/backend/mesh-dispatch-pump.js +21 -25
  24. package/dist/runtime/backend/node/node-backend-adapter-factory.js +6 -1
  25. package/dist/runtime/backend/node/node-backend-adapter-support.d.ts +3 -3
  26. package/dist/runtime/backend/node/node-backend-adapter-support.js +24 -29
  27. package/dist/runtime/backend/node/node-mesh-backend-adapter.d.ts +1 -9
  28. package/dist/runtime/backend/node/node-mesh-backend-adapter.js +1 -1
  29. package/dist/runtime/backend/node/node-raw-binding-port.js +11 -6
  30. package/dist/runtime/backend/node/node-raw-mesh-backend.d.ts +10 -12
  31. package/dist/runtime/backend/node/node-raw-mesh-backend.js +64 -98
  32. package/dist/runtime/backend/node/node-socket-backend-adapter.js +10 -5
  33. package/dist/runtime/backend/raw-binding-port.d.ts +9 -2
  34. package/dist/runtime/channels/channel-receive-loops.js +3 -3
  35. package/dist/runtime/channels/channel-runtime-lifecycle.js +2 -1
  36. package/dist/runtime/channels/channel-socket-registry.d.ts +10 -3
  37. package/dist/runtime/channels/channel-socket-registry.js +59 -82
  38. package/dist/runtime/diagnostics/message-flow.d.ts +1 -0
  39. package/dist/runtime/diagnostics/message-flow.js +3 -3
  40. package/dist/runtime/foundation/raw-service-mesh-runtime.d.ts +15 -1
  41. package/dist/runtime/foundation/raw-service-mesh-runtime.js +193 -64
  42. package/dist/runtime/foundation/service-liveness-registry.d.ts +20 -8
  43. package/dist/runtime/foundation/service-liveness-registry.js +57 -44
  44. package/dist/runtime/foundation/service-mailbox.d.ts +3 -0
  45. package/dist/runtime/foundation/service-mailbox.js +2 -1
  46. package/dist/runtime/foundation/service-stateful-runtime.d.ts +7 -6
  47. package/dist/runtime/foundation/service-stateful-runtime.js +60 -41
  48. package/dist/runtime/foundation/service-stateful-wire-codec.d.ts +5 -2
  49. package/dist/runtime/foundation/service-stateful-wire-codec.js +43 -61
  50. package/dist/runtime/foundation/service-topology-registry.d.ts +5 -3
  51. package/dist/runtime/foundation/service-topology-registry.js +9 -4
  52. package/dist/runtime/handlers/handler-instance-scope.js +1 -1
  53. package/dist/runtime/host/actor-packet-relay.js +7 -7
  54. package/dist/runtime/host/actor-placement-coordinator.d.ts +2 -1
  55. package/dist/runtime/host/actor-placement-coordinator.js +2 -1
  56. package/dist/runtime/host/actor-runtime-options-factory.d.ts +2 -1
  57. package/dist/runtime/host/actor-runtime-options-factory.js +2 -1
  58. package/dist/runtime/host/application-job-queue.d.ts +11 -4
  59. package/dist/runtime/host/application-job-queue.js +66 -10
  60. package/dist/runtime/host/entry-actor-runtime.js +1 -1
  61. package/dist/runtime/host/index.d.ts +1 -1
  62. package/dist/runtime/host/index.js +18 -13
  63. package/dist/runtime/host/instance-activation-authority.d.ts +2 -1
  64. package/dist/runtime/host/instance-activation-authority.js +89 -25
  65. package/dist/runtime/host/service-relocation-host-runtime.js +2 -2
  66. package/dist/runtime/host/spot-address-transport.d.ts +1 -0
  67. package/dist/runtime/host/spot-address-transport.js +25 -12
  68. package/dist/runtime/host/spot-runtime-options-factory.js +2 -2
  69. package/dist/runtime/host/user-spot-creation-coordinator.d.ts +1 -0
  70. package/dist/runtime/host/user-spot-creation-coordinator.js +1 -0
  71. package/dist/runtime/locations/domain-store-contract.d.ts +3 -2
  72. package/dist/runtime/locations/in-memory-authority-store.d.ts +5 -2
  73. package/dist/runtime/locations/in-memory-authority-store.js +107 -14
  74. package/dist/runtime/locations/in-memory-location-store.d.ts +3 -3
  75. package/dist/runtime/locations/in-memory-location-store.js +31 -13
  76. package/dist/runtime/locations/internal-location-contracts.d.ts +8 -0
  77. package/dist/runtime/locations/location-store-repository.d.ts +4 -2
  78. package/dist/runtime/locations/location-store-repository.js +209 -33
  79. package/dist/runtime/locations/resolvers.d.ts +2 -1
  80. package/dist/runtime/locations/resolvers.js +10 -1
  81. package/dist/runtime/messaging/submission-result.d.ts +1 -1
  82. package/dist/runtime/messaging/submission-result.js +2 -2
  83. package/dist/runtime/spots/formal-remote-actor-admission-registry.d.ts +2 -0
  84. package/dist/runtime/spots/index.d.ts +4 -3
  85. package/dist/runtime/spots/index.js +14 -14
  86. package/dist/runtime/spots/spot-activation.d.ts +2 -1
  87. package/dist/runtime/spots/spot-activation.js +4 -4
  88. package/dist/runtime/spots/spot-actor-admission-coordinator.d.ts +2 -1
  89. package/dist/runtime/spots/spot-actor-admission-coordinator.js +11 -10
  90. package/dist/runtime/spots/spot-actor-packet-dispatch.d.ts +4 -1
  91. package/dist/runtime/spots/spot-actor-packet-dispatch.js +7 -5
  92. package/dist/runtime/spots/spot-actor-packet-drain.js +2 -0
  93. package/dist/runtime/spots/spot-actor-packet-relay-dispatch.js +1 -0
  94. package/dist/runtime/spots/spot-entry-activation.d.ts +1 -0
  95. package/dist/runtime/spots/spot-entry-activation.js +6 -1
  96. package/dist/runtime/spots/spot-node-runtime-manager.d.ts +3 -1
  97. package/dist/runtime/spots/spot-node-runtime-manager.js +10 -11
  98. package/dist/runtime/spots/spot-routing-internal.d.ts +6 -0
  99. package/dist/runtime/spots/spot-runtime-ports.d.ts +2 -1
  100. package/dist/runtime/spots/spot-serial-executor.d.ts +1 -0
  101. package/dist/runtime/spots/spot-serial-executor.js +10 -1
  102. package/dist/runtime/streams/stream-session-runtime.js +1 -1
  103. package/package.json +3 -3
  104. package/src/contracts/Configuration/Builders.ts +1 -2
  105. package/src/contracts/Configuration/Configs.ts +0 -6
  106. package/src/contracts/Configuration/RegistrationBuilders.ts +8 -12
  107. package/src/contracts/Configuration/RegistrationTypes.ts +1 -6
  108. package/src/contracts/Configuration/RegistrationValidators.ts +9 -10
  109. package/src/contracts/Configuration/index.ts +1 -6
  110. package/src/internal.ts +1 -0
  111. package/src/runtime/actors/actor-handoff.ts +13 -4
  112. package/src/runtime/actors/actor-runtime-contracts.ts +4 -0
  113. package/src/runtime/actors/index.ts +77 -42
  114. package/src/runtime/application-jobs/application-ingress-record-owner.ts +12 -2
  115. package/src/runtime/application-jobs/application-job-queue-scope.ts +6 -1
  116. package/src/runtime/application-jobs/contracts.ts +14 -1
  117. package/src/runtime/backend/contracts/index.ts +10 -18
  118. package/src/runtime/backend/mesh-completion-table.ts +36 -19
  119. package/src/runtime/backend/mesh-dispatch-pump.ts +19 -22
  120. package/src/runtime/backend/node/node-backend-adapter-factory.ts +12 -5
  121. package/src/runtime/backend/node/node-backend-adapter-support.ts +31 -29
  122. package/src/runtime/backend/node/node-mesh-backend-adapter.ts +3 -10
  123. package/src/runtime/backend/node/node-raw-binding-port.ts +19 -10
  124. package/src/runtime/backend/node/node-raw-mesh-backend.ts +81 -111
  125. package/src/runtime/backend/node/node-socket-backend-adapter.ts +12 -8
  126. package/src/runtime/backend/raw-binding-port.ts +12 -2
  127. package/src/runtime/channels/channel-receive-loops.ts +3 -3
  128. package/src/runtime/channels/channel-runtime-lifecycle.ts +7 -1
  129. package/src/runtime/channels/channel-socket-registry.ts +84 -130
  130. package/src/runtime/diagnostics/message-flow.ts +1 -1
  131. package/src/runtime/foundation/raw-service-mesh-runtime.ts +268 -70
  132. package/src/runtime/foundation/service-liveness-registry.ts +68 -59
  133. package/src/runtime/foundation/service-mailbox.ts +7 -1
  134. package/src/runtime/foundation/service-stateful-runtime.ts +109 -77
  135. package/src/runtime/foundation/service-stateful-wire-codec.ts +55 -79
  136. package/src/runtime/foundation/service-topology-registry.ts +13 -7
  137. package/src/runtime/handlers/handler-instance-scope.ts +2 -1
  138. package/src/runtime/host/actor-packet-relay.ts +7 -0
  139. package/src/runtime/host/actor-placement-coordinator.ts +8 -2
  140. package/src/runtime/host/actor-runtime-options-factory.ts +7 -0
  141. package/src/runtime/host/application-job-queue.ts +77 -10
  142. package/src/runtime/host/entry-actor-runtime.ts +1 -0
  143. package/src/runtime/host/index.ts +30 -7
  144. package/src/runtime/host/instance-activation-authority.ts +132 -27
  145. package/src/runtime/host/service-relocation-host-runtime.ts +2 -0
  146. package/src/runtime/host/spot-address-transport.ts +45 -22
  147. package/src/runtime/host/spot-runtime-options-factory.ts +2 -0
  148. package/src/runtime/host/user-spot-creation-coordinator.ts +8 -0
  149. package/src/runtime/locations/domain-store-contract.ts +6 -2
  150. package/src/runtime/locations/in-memory-authority-store.ts +153 -12
  151. package/src/runtime/locations/in-memory-location-store.ts +39 -13
  152. package/src/runtime/locations/internal-location-contracts.ts +10 -0
  153. package/src/runtime/locations/location-store-repository.ts +295 -36
  154. package/src/runtime/locations/resolvers.ts +25 -2
  155. package/src/runtime/messaging/submission-result.ts +2 -2
  156. package/src/runtime/spots/formal-remote-actor-admission-registry.ts +2 -0
  157. package/src/runtime/spots/index.ts +16 -0
  158. package/src/runtime/spots/spot-activation.ts +5 -0
  159. package/src/runtime/spots/spot-actor-admission-coordinator.ts +13 -0
  160. package/src/runtime/spots/spot-actor-packet-dispatch.ts +10 -0
  161. package/src/runtime/spots/spot-actor-packet-drain.ts +2 -0
  162. package/src/runtime/spots/spot-actor-packet-relay-dispatch.ts +1 -0
  163. package/src/runtime/spots/spot-entry-activation.ts +7 -0
  164. package/src/runtime/spots/spot-node-runtime-manager.ts +14 -12
  165. package/src/runtime/spots/spot-routing-internal.ts +14 -0
  166. package/src/runtime/spots/spot-runtime-ports.ts +2 -0
  167. package/src/runtime/spots/spot-serial-executor.ts +10 -4
  168. package/src/runtime/streams/stream-session-runtime.ts +1 -1
@@ -46,6 +46,12 @@ class RawServiceMeshRuntime {
46
46
  applicationJobQueue;
47
47
  peerAdmissionSealed;
48
48
  applicationJobStop = new AbortController();
49
+ receiveAcquisition;
50
+ receivePermit;
51
+ controlSends = new Map();
52
+ infrastructureFailure;
53
+ onReceiveReady;
54
+ infrastructureTaskRunner;
49
55
  onPeerNotRequired;
50
56
  onPeerDisconnected;
51
57
  onProtocolError;
@@ -58,6 +64,8 @@ class RawServiceMeshRuntime {
58
64
  serviceIngress;
59
65
  closed = false;
60
66
  constructor(options) {
67
+ this.onReceiveReady = options.onReceiveReady;
68
+ this.infrastructureTaskRunner = options.infrastructureTaskRunner;
61
69
  this.operations = new operation_registry_1.OperationRegistry(undefined, options.onPendingOperationsChanged);
62
70
  this.descriptor = options.descriptor;
63
71
  this.topology = new service_topology_registry_1.ServiceTopologyRegistry(options.descriptor);
@@ -222,22 +230,22 @@ class RawServiceMeshRuntime {
222
230
  for (const listener of this.peerConnectionIntentRemoved)
223
231
  listener(nodeRoutingId);
224
232
  }
225
- async announcePeer(nodeRoutingId) {
233
+ announcePeer(nodeRoutingId) {
226
234
  const expected = this.expectedPeers.get(nodeRoutingId);
227
235
  const generation = this.selectedRoutes.get(nodeRoutingId);
228
236
  if (expected === undefined ||
229
237
  generation === undefined ||
230
238
  expected.helloSubmittedGeneration === generation ||
231
239
  this.peerAdmissionSealed?.() === true)
232
- return false;
233
- const accepted = await this.send(nodeRoutingId, [
240
+ return Promise.resolve(false);
241
+ const current = () => this.expectedPeers.get(nodeRoutingId) === expected &&
242
+ this.selectedRoutes.get(nodeRoutingId) === generation;
243
+ return Promise.resolve(this.startControlSend(nodeRoutingId, service_wire_m6a_codec_1.M6aServiceWireCommand.hello, () => this.submitControl(nodeRoutingId, [
234
244
  (0, service_wire_m6a_codec_1.encodeRouteMeshAdmission)(service_wire_m6a_codec_1.M6aServiceWireCommand.hello, this.topology.localDescriptor())
235
- ]);
236
- if (accepted &&
237
- this.expectedPeers.get(nodeRoutingId) === expected &&
238
- this.selectedRoutes.get(nodeRoutingId) === generation)
239
- expected.helloSubmittedGeneration = generation;
240
- return accepted;
245
+ ], () => {
246
+ if (current())
247
+ expected.helloSubmittedGeneration = generation;
248
+ }), current));
241
249
  }
242
250
  isPeerRouteReady(nodeRoutingId, lifecycleGeneration) {
243
251
  // Admission is bound to the observed selected route: the route observer
@@ -245,8 +253,7 @@ class RawServiceMeshRuntime {
245
253
  const peer = this.topology.peer(nodeRoutingId);
246
254
  return (peer !== undefined &&
247
255
  (lifecycleGeneration === undefined ||
248
- peer.descriptor.lifecycleGeneration === lifecycleGeneration) &&
249
- this.liveness.isReady(nodeRoutingId, peer.connectionId));
256
+ peer.descriptor.lifecycleGeneration === lifecycleGeneration));
250
257
  }
251
258
  async announceExpectedPeers() {
252
259
  let accepted = 0;
@@ -351,14 +358,15 @@ class RawServiceMeshRuntime {
351
358
  const waitSignal = signal === undefined
352
359
  ? this.applicationJobStop.signal
353
360
  : AbortSignal.any([this.applicationJobStop.signal, signal]);
354
- const permit = await this.applicationJobQueue.acquire(waitSignal);
361
+ const permit = await this.applicationJobQueue.acquire(waitSignal, 'local');
355
362
  return application_ingress_record_owner_1.ApplicationIngressRecordOwner.create(this.applicationJobQueue, permit, { close() { } });
356
363
  }
357
364
  async sendService(targetNodeRoutingId, parts) {
358
365
  return this.send(targetNodeRoutingId, parts);
359
366
  }
360
367
  requestService(targetNodeRoutingId, parts, timeoutMs) {
361
- return this.requireStarted().request(targetNodeRoutingId, parts, timeoutMs);
368
+ const admission = this.topology.peer(targetNodeRoutingId)?.liveness;
369
+ return this.requireStarted().request(targetNodeRoutingId, parts, timeoutMs, admission?.recordReceived);
362
370
  }
363
371
  replyService(record, parts) {
364
372
  if (record.requestSequence === undefined) {
@@ -383,6 +391,22 @@ class RawServiceMeshRuntime {
383
391
  ]);
384
392
  }
385
393
  async pumpOne(nowMs = performance.now(), observe) {
394
+ const result = await this.receiveOne(nowMs, observe);
395
+ const acquisition = this.receiveAcquisition;
396
+ if (acquisition === undefined)
397
+ return result;
398
+ // A single receive operation may await its FIFO grant. Infrastructure
399
+ // rounds use receiveOne directly and end while that grant is pending.
400
+ try {
401
+ await acquisition;
402
+ }
403
+ catch (error) {
404
+ if (this.closed)
405
+ return 'noData';
406
+ throw error;
407
+ }
408
+ if (this.closed)
409
+ return 'noData';
386
410
  return this.receiveOne(nowMs, observe);
387
411
  }
388
412
  setReadableHandler(handler) {
@@ -390,6 +414,7 @@ class RawServiceMeshRuntime {
390
414
  }
391
415
  /** Returns whether a receive budget ended before no-data; idle ticks only maintain peers. */
392
416
  async pumpBatch(receiveReady = true, routeReady = false) {
417
+ this.observeInfrastructureFailure();
393
418
  if (routeReady)
394
419
  await this.observeSelectedRoutes();
395
420
  const startedAtMs = performance.now();
@@ -412,22 +437,49 @@ class RawServiceMeshRuntime {
412
437
  break;
413
438
  }
414
439
  if (!this.closed) {
415
- await this.announceExpectedPeers();
416
- await this.tickLiveness();
440
+ for (const nodeRoutingId of this.expectedPeers.keys()) {
441
+ if (this.topology.peer(nodeRoutingId) === undefined)
442
+ await this.announcePeer(nodeRoutingId);
443
+ }
444
+ this.tickLiveness();
417
445
  }
418
446
  return receiveReady && messages > 0;
419
447
  }
420
448
  async receiveOne(nowMs, observe) {
421
449
  const router = this.requireStarted();
422
- let permit;
423
- try {
424
- permit = await this.applicationJobQueue.acquire(this.applicationJobStop.signal);
425
- }
426
- catch (error) {
427
- if (this.closed || this.applicationJobStop.signal.aborted)
428
- return 'noData';
429
- throw error;
450
+ this.observeInfrastructureFailure();
451
+ if (this.receivePermit === undefined && this.receiveAcquisition === undefined) {
452
+ let resolveAcquisition;
453
+ let rejectAcquisition;
454
+ const acquisition = new Promise((resolve, reject) => {
455
+ resolveAcquisition = resolve;
456
+ rejectAcquisition = reject;
457
+ });
458
+ const permit = this.applicationJobQueue.acquireOrResume((permit) => {
459
+ this.receiveAcquisition = undefined;
460
+ if (this.closed)
461
+ permit.releaseAfterInternalProcessing();
462
+ else {
463
+ this.receivePermit = permit;
464
+ this.onReceiveReady?.();
465
+ }
466
+ resolveAcquisition(permit);
467
+ }, rejectAcquisition, this.applicationJobStop.signal, 'remote');
468
+ if (permit !== undefined)
469
+ this.receivePermit = permit;
470
+ else {
471
+ this.receiveAcquisition = acquisition;
472
+ acquisition.catch((error) => {
473
+ this.receiveAcquisition = undefined;
474
+ if (!this.applicationJobStop.signal.aborted)
475
+ this.reportInfrastructureFailure(error);
476
+ });
477
+ }
430
478
  }
479
+ const permit = this.receivePermit;
480
+ this.receivePermit = undefined;
481
+ if (permit === undefined)
482
+ return 'noData';
431
483
  if (this.closed) {
432
484
  permit.releaseAfterInternalProcessing();
433
485
  return 'noData';
@@ -498,7 +550,7 @@ class RawServiceMeshRuntime {
498
550
  (received.parts.length === 1 && received.parts[0].byteLength === 0)) {
499
551
  if (this.peerAdmissionSealed?.() === true)
500
552
  return 'dropped';
501
- return (await this.send(received.sourceRid, [
553
+ return (await this.sendControl(received.sourceRid, [
502
554
  (0, service_wire_m6a_codec_1.encodeRouteMeshAdmission)(service_wire_m6a_codec_1.M6aServiceWireCommand.hello, this.topology.localDescriptor())
503
555
  ]))
504
556
  ? 'infrastructure'
@@ -519,19 +571,19 @@ class RawServiceMeshRuntime {
519
571
  if (expected !== undefined &&
520
572
  (expected.meshName !== descriptor.meshName ||
521
573
  expected.nodeRoutingId !== descriptor.nodeRoutingId)) {
522
- await this.send(received.sourceRid, [
574
+ await this.sendControl(received.sourceRid, [
523
575
  (0, service_wire_m6a_codec_1.encodeReject)((0, service_wire_codec_generated_1.enumWireRejectReason)('identityMismatch'))
524
576
  ]);
525
577
  return 'infrastructure';
526
578
  }
527
- const result = this.admitPeer(descriptor, routeConnectionId(received.routeGeneration), nowMs, expected);
579
+ const result = this.admitPeer(descriptor, received.routeGeneration, nowMs, expected);
528
580
  if (result !== 'admitted' && result !== 'notRequired') {
529
581
  const detail = result === 'invalidDescriptor'
530
582
  ? describeInvalidAdmissionDescriptor(descriptor, expected, this.topology.peer(descriptor.nodeRoutingId)?.descriptor)
531
583
  : undefined;
532
584
  console.warn(`RouteMesh admission rejected peer '${received.sourceRid}' as ${result}` +
533
585
  (detail === undefined ? '.' : ` (${detail}).`));
534
- await this.send(received.sourceRid, [(0, service_wire_m6a_codec_1.encodeReject)(admissionReason(result))]);
586
+ await this.sendControl(received.sourceRid, [(0, service_wire_m6a_codec_1.encodeReject)(admissionReason(result))]);
535
587
  return 'infrastructure';
536
588
  }
537
589
  if (result === 'admitted')
@@ -540,7 +592,7 @@ class RawServiceMeshRuntime {
540
592
  // Hello supplies only the sender's descriptor. Even NotRequired
541
593
  // peers must receive ours before the intent owner closes transport;
542
594
  // submitting a response does not guarantee delivery before close.
543
- await this.send(received.sourceRid, [
595
+ await this.sendControl(received.sourceRid, [
544
596
  (0, service_wire_m6a_codec_1.encodeRouteMeshAdmission)(service_wire_m6a_codec_1.M6aServiceWireCommand.admit, this.topology.localDescriptor())
545
597
  ]);
546
598
  }
@@ -558,16 +610,18 @@ class RawServiceMeshRuntime {
558
610
  const peer = this.topology.peer(received.sourceRid);
559
611
  if (peer === undefined)
560
612
  return 'protocolError';
613
+ if (peer.liveness !== undefined && peer.liveness.connectionId === received.routeGeneration)
614
+ this.liveness.recordReceived(peer.liveness, nowMs);
561
615
  if (header.command === service_wire_m6a_codec_1.M6aServiceWireCommand.livenessProbe ||
562
616
  header.command === service_wire_m6a_codec_1.M6aServiceWireCommand.livenessAck) {
563
617
  if (received.parts.length !== 1)
564
618
  return 'protocolError';
565
619
  const record = livenessCodec.decodeLivenessRecord(received.parts[0]);
566
620
  if (record.command === service_wire_m6a_codec_1.M6aServiceWireCommand.livenessProbe) {
567
- const ack = this.liveness.acknowledgeProbe(received.sourceRid, peer.connectionId, record.probeId);
621
+ const ack = this.liveness.acknowledgeProbe(received.sourceRid, received.routeGeneration, record.probeId);
568
622
  if (ack === undefined)
569
623
  return 'protocolError';
570
- const sent = await this.send(received.sourceRid, [
624
+ const sent = await this.sendControl(received.sourceRid, [
571
625
  livenessCodec.encodeLivenessRecord({
572
626
  command: service_wire_m6a_codec_1.M6aServiceWireCommand.livenessAck,
573
627
  probeId: record.probeId
@@ -577,7 +631,7 @@ class RawServiceMeshRuntime {
577
631
  return 'dropped';
578
632
  }
579
633
  else {
580
- this.liveness.acknowledge(received.sourceRid, peer.connectionId, record.probeId, nowMs);
634
+ this.liveness.acknowledge(received.sourceRid, received.routeGeneration, record.probeId, nowMs);
581
635
  }
582
636
  return 'infrastructure';
583
637
  }
@@ -684,22 +738,23 @@ class RawServiceMeshRuntime {
684
738
  get pendingOperationCount() {
685
739
  return this.operations.size;
686
740
  }
687
- async tickLiveness(nowMs = performance.now()) {
741
+ tickLiveness(nowMs = performance.now()) {
688
742
  const result = this.liveness.tick(nowMs);
689
743
  this.requireStarted();
690
744
  for (const probe of result.probes) {
691
- await this.send(probe.nodeRoutingId, [
745
+ const parts = [
692
746
  livenessCodec.encodeLivenessRecord({
693
747
  command: service_wire_m6a_codec_1.M6aServiceWireCommand.livenessProbe,
694
748
  probeId: probe.probeId
695
749
  })
696
- ]);
750
+ ];
751
+ this.startControlSend(probe.nodeRoutingId, service_wire_m6a_codec_1.M6aServiceWireCommand.livenessProbe, () => this.submitControl(probe.nodeRoutingId, parts));
697
752
  }
698
753
  for (const nodeRoutingId of result.timedOutNodes) {
699
754
  const peer = this.topology.peer(nodeRoutingId);
700
755
  if (peer === undefined)
701
756
  continue;
702
- // Liveness timeout is a semantic peer removal of the admitted route.
757
+ this.requireStarted().disconnectRid(nodeRoutingId);
703
758
  this.removePeer(peer);
704
759
  }
705
760
  return result;
@@ -727,7 +782,7 @@ class RawServiceMeshRuntime {
727
782
  if (expected !== undefined)
728
783
  delete expected.helloSubmittedGeneration;
729
784
  const peer = this.topology.peer(nodeRoutingId);
730
- if (peer !== undefined && peer.connectionId === routeConnectionId(generation)) {
785
+ if (peer?.liveness?.connectionId === generation) {
731
786
  this.removePeer(peer);
732
787
  }
733
788
  }
@@ -748,7 +803,12 @@ class RawServiceMeshRuntime {
748
803
  close() {
749
804
  if (this.closed)
750
805
  return;
806
+ this.closed = true;
751
807
  this.applicationJobStop.abort(new Error('Raw service runtime application job admission stopped.'));
808
+ this.receivePermit?.releaseAfterInternalProcessing();
809
+ this.receivePermit = undefined;
810
+ for (const send of this.controlSends.values())
811
+ delete send.next;
752
812
  this.mailbox.close();
753
813
  this.operations.close('Raw service runtime closed.');
754
814
  this.serviceIngress = undefined;
@@ -761,7 +821,6 @@ class RawServiceMeshRuntime {
761
821
  host.close();
762
822
  this.router = undefined;
763
823
  this.host = undefined;
764
- this.closed = true;
765
824
  }
766
825
  requestToTarget(targetNodeRoutingId, payload, timeoutMs, channelName) {
767
826
  const correlation = this.nextCorrelation++;
@@ -836,7 +895,8 @@ class RawServiceMeshRuntime {
836
895
  })().catch((error) => this.operations.fail(pending.id, error));
837
896
  return pending;
838
897
  }
839
- if (this.topology.peer(selectedTargetNodeRoutingId) === undefined) {
898
+ const selectedPeer = this.topology.peer(selectedTargetNodeRoutingId);
899
+ if (selectedPeer === undefined) {
840
900
  // Logical target selection is the requester's decision: an RID that is
841
901
  // not an admitted peer is not a target. Whether an admitted target's
842
902
  // route can carry the request is Core's REQUEST result.
@@ -847,11 +907,9 @@ class RawServiceMeshRuntime {
847
907
  return pending;
848
908
  }
849
909
  const parts = [header, encodedPayload];
850
- let router;
851
910
  let request;
852
911
  try {
853
- router = this.requireStarted();
854
- request = router.request(selectedTargetNodeRoutingId, parts, timeoutMs);
912
+ request = this.requireStarted().request(selectedTargetNodeRoutingId, parts, timeoutMs, selectedPeer.liveness?.recordReceived);
855
913
  }
856
914
  catch (error) {
857
915
  this.operations.fail(pending.id, error);
@@ -909,13 +967,14 @@ class RawServiceMeshRuntime {
909
967
  !(0, service_topology_registry_1.sameServiceNodeDescriptor)(previous.descriptor, descriptor)))) {
910
968
  throw new service_wire_m6a_codec_1.ServiceWireProtocolError(`RouteMesh peer '${descriptor.nodeRoutingId}' descriptor revision is stale or conflicting.`);
911
969
  }
912
- const result = this.topology.admit(descriptor, connectionId, expected);
913
- if (result === 'admitted') {
914
- this.liveness.admit(descriptor.nodeRoutingId, connectionId, nowMs);
915
- this.liveness.requestProbe(descriptor.nodeRoutingId, connectionId, nowMs);
916
- }
917
- else if (result === 'notRequired' && previous !== undefined) {
918
- this.liveness.disconnect(descriptor.nodeRoutingId, previous.connectionId);
970
+ const topologyConnectionId = typeof connectionId === 'bigint' ? `route:${connectionId}` : connectionId;
971
+ const result = this.topology.admit(descriptor, topologyConnectionId, expected, () => {
972
+ const admission = this.liveness.admit(descriptor.nodeRoutingId, connectionId, nowMs);
973
+ this.liveness.recordReceived(admission, nowMs);
974
+ return admission;
975
+ });
976
+ if (result === 'notRequired' && previous !== undefined) {
977
+ this.liveness.disconnect(descriptor.nodeRoutingId, previous.liveness?.connectionId ?? previous.connectionId);
919
978
  }
920
979
  return result;
921
980
  }
@@ -950,7 +1009,7 @@ class RawServiceMeshRuntime {
950
1009
  }
951
1010
  removePeer(peer) {
952
1011
  this.topology.disconnect(peer.descriptor.nodeRoutingId, peer.connectionId);
953
- this.liveness.disconnect(peer.descriptor.nodeRoutingId, peer.connectionId);
1012
+ this.liveness.disconnect(peer.descriptor.nodeRoutingId, peer.liveness?.connectionId ?? peer.connectionId);
954
1013
  this.onPeerDisconnected?.(peer.descriptor.nodeRoutingId, peer.descriptor.advertisedEndpoint, peer.descriptor.lifecycleGeneration);
955
1014
  }
956
1015
  requireStarted() {
@@ -958,19 +1017,96 @@ class RawServiceMeshRuntime {
958
1017
  throw new Error('Raw service runtime is not started.');
959
1018
  return this.router;
960
1019
  }
961
- async send(targetNodeRoutingId, parts) {
1020
+ async sendControl(targetNodeRoutingId, parts) {
1021
+ return this.startControlSend(targetNodeRoutingId, (0, service_wire_m6a_codec_1.decodeHeader)(parts[0]).command, () => this.submitControl(targetNodeRoutingId, parts));
1022
+ }
1023
+ submitControl(target, parts, accepted) {
1024
+ let submission;
962
1025
  try {
963
- await this.requireStarted().send(targetNodeRoutingId, parts);
964
- return true;
1026
+ submission = this.requireStarted().submitSend(target, parts);
965
1027
  }
966
1028
  catch (error) {
967
- if ((0, runtime_values_1.isZLinkBackendResultError)(error) && error.operation === 'submit') {
968
- const terminal = (0, submission_result_1.submitToRequestResult)(error.result, error.phase);
969
- if (terminal === runtime_values_1.RequestResult.NotConnected || terminal === runtime_values_1.RequestResult.NotFound)
970
- return false;
1029
+ return this.sendFailure(error);
1030
+ }
1031
+ if (submission.result === runtime_values_1.SubmitResult.Ok) {
1032
+ accepted?.();
1033
+ return true;
1034
+ }
1035
+ return submission.admitted.then(() => {
1036
+ accepted?.();
1037
+ return true;
1038
+ }, (error) => this.sendFailure(error));
1039
+ }
1040
+ startControlSend(target, command, attempt, current) {
1041
+ const key = JSON.stringify([target, command]);
1042
+ const existing = this.controlSends.get(key);
1043
+ if (existing !== undefined &&
1044
+ (command !== service_wire_m6a_codec_1.M6aServiceWireCommand.hello || existing.current?.() === true)) {
1045
+ if (command !== service_wire_m6a_codec_1.M6aServiceWireCommand.hello) {
1046
+ existing.next = attempt;
1047
+ existing.current = current;
971
1048
  }
972
- throw error;
1049
+ return true;
1050
+ }
1051
+ const first = attempt();
1052
+ if (typeof first === 'boolean')
1053
+ return first;
1054
+ const owner = this;
1055
+ const state = {
1056
+ current,
1057
+ task: first.then(function drain(accepted) {
1058
+ if (!owner.closed && state.next !== undefined) {
1059
+ const next = state.next;
1060
+ delete state.next;
1061
+ return Promise.resolve(next()).then(drain);
1062
+ }
1063
+ if (owner.controlSends.get(key) === state)
1064
+ owner.controlSends.delete(key);
1065
+ return accepted;
1066
+ })
1067
+ };
1068
+ this.controlSends.set(key, state);
1069
+ state.task.then(() => { }, (error) => {
1070
+ if (this.controlSends.get(key) === state)
1071
+ this.controlSends.delete(key);
1072
+ if (this.infrastructureTaskRunner === undefined)
1073
+ this.reportInfrastructureFailure(error);
1074
+ });
1075
+ this.infrastructureTaskRunner?.runDetached('RouteMesh control SEND', async () => {
1076
+ await state.task;
1077
+ });
1078
+ return true;
1079
+ }
1080
+ reportInfrastructureFailure(error) {
1081
+ this.infrastructureFailure = {
1082
+ error: this.infrastructureFailure === undefined
1083
+ ? error
1084
+ : new AggregateError([this.infrastructureFailure.error, error], 'RouteMesh infrastructure failed.')
1085
+ };
1086
+ this.onReceiveReady?.();
1087
+ }
1088
+ observeInfrastructureFailure() {
1089
+ const failure = this.infrastructureFailure;
1090
+ this.infrastructureFailure = undefined;
1091
+ if (failure !== undefined)
1092
+ throw failure.error;
1093
+ }
1094
+ send(targetNodeRoutingId, parts) {
1095
+ return this.requireStarted()
1096
+ .send(targetNodeRoutingId, parts)
1097
+ .then(() => {
1098
+ return true;
1099
+ }, (error) => {
1100
+ return this.sendFailure(error);
1101
+ });
1102
+ }
1103
+ sendFailure(error) {
1104
+ if ((0, runtime_values_1.isZLinkBackendResultError)(error) && error.operation === 'submit') {
1105
+ const terminal = (0, submission_result_1.submitToRequestResult)(error.result, error.phase);
1106
+ if (terminal === runtime_values_1.RequestResult.NotConnected || terminal === runtime_values_1.RequestResult.NotFound)
1107
+ return false;
973
1108
  }
1109
+ throw error;
974
1110
  }
975
1111
  }
976
1112
  exports.RawServiceMeshRuntime = RawServiceMeshRuntime;
@@ -1010,13 +1146,6 @@ function isAlreadyDisconnectedError(error) {
1010
1146
  }
1011
1147
  return error.nativeErrno === nativeErrnoValues.ENOENT;
1012
1148
  }
1013
- /**
1014
- * An admission belongs to one Core selected route. Its connection identity is
1015
- * that route's generation, an opaque token compared only for equality.
1016
- */
1017
- function routeConnectionId(routeGeneration) {
1018
- return `route:${routeGeneration.toString()}`;
1019
- }
1020
1149
  function describeInvalidAdmissionDescriptor(descriptor, expected, current) {
1021
1150
  try {
1022
1151
  (0, service_topology_registry_1.validateDescriptor)(descriptor);
@@ -1,6 +1,6 @@
1
1
  export interface ServiceProbe {
2
2
  readonly nodeRoutingId: string;
3
- readonly connectionId: string;
3
+ readonly connectionId: string | bigint;
4
4
  readonly probeId: bigint;
5
5
  }
6
6
  export interface ServiceLivenessTick {
@@ -9,6 +9,19 @@ export interface ServiceLivenessTick {
9
9
  }
10
10
  export declare const DEFAULT_SERVICE_PROBE_INTERVAL_MS = 5000;
11
11
  export declare const DEFAULT_SERVICE_PEER_TIMEOUT_MS = 15000;
12
+ export declare class ServiceLivenessConnection {
13
+ readonly connectionId: string | bigint;
14
+ private readonly probeIntervalMs;
15
+ private readonly peerTimeoutMs;
16
+ deadlineMs: number;
17
+ nextProbeMs: number;
18
+ outstandingProbe?: bigint;
19
+ constructor(connectionId: string | bigint, nowMs: number, probeIntervalMs?: number, peerTimeoutMs?: number);
20
+ readonly recordReceived: (nowMs?: number) => void;
21
+ isExpired(nowMs: number): boolean;
22
+ tryGetProbe(nowMs: number, allocateProbeId: () => bigint): bigint | undefined;
23
+ acknowledge(probeId: bigint, nowMs: number): boolean;
24
+ }
12
25
  /** Uses monotonic milliseconds and never treats application traffic as a probe ACK. */
13
26
  export declare class ServiceLivenessRegistry {
14
27
  private readonly probeIntervalMs;
@@ -16,13 +29,12 @@ export declare class ServiceLivenessRegistry {
16
29
  private readonly peers;
17
30
  private nextProbeId;
18
31
  constructor(probeIntervalMs?: number, peerTimeoutMs?: number);
19
- admit(nodeRoutingId: string, connectionId: string, nowMs: number): void;
20
- requestProbe(nodeRoutingId: string, connectionId: string, nowMs: number): boolean;
21
- disconnect(nodeRoutingId: string, connectionId: string): boolean;
22
- acknowledge(nodeRoutingId: string, connectionId: string, probeId: bigint, nowMs: number): boolean;
23
- acknowledgeProbe(nodeRoutingId: string, connectionId: string, probeId: bigint): ServiceProbe | undefined;
32
+ admit(nodeRoutingId: string, connectionId: string | bigint, nowMs: number): ServiceLivenessConnection;
33
+ recordReceived(connection: ServiceLivenessConnection, nowMs: number): void;
34
+ disconnect(nodeRoutingId: string, connectionId: string | bigint): boolean;
35
+ acknowledge(nodeRoutingId: string, connectionId: string | bigint, probeId: bigint, nowMs: number): boolean;
36
+ acknowledgeProbe(nodeRoutingId: string, connectionId: string | bigint, probeId: bigint): ServiceProbe | undefined;
24
37
  tick(nowMs: number): ServiceLivenessTick;
25
38
  get size(): number;
26
- isReady(nodeRoutingId: string, connectionId: string): boolean;
27
- private allocateProbeId;
39
+ private readonly allocateProbeId;
28
40
  }
@@ -1,9 +1,47 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.ServiceLivenessRegistry = exports.DEFAULT_SERVICE_PEER_TIMEOUT_MS = exports.DEFAULT_SERVICE_PROBE_INTERVAL_MS = void 0;
3
+ exports.ServiceLivenessRegistry = exports.ServiceLivenessConnection = exports.DEFAULT_SERVICE_PEER_TIMEOUT_MS = exports.DEFAULT_SERVICE_PROBE_INTERVAL_MS = void 0;
4
4
  const stream_wire_1 = require("@zlink-systems/stream-wire");
5
5
  exports.DEFAULT_SERVICE_PROBE_INTERVAL_MS = 5_000;
6
6
  exports.DEFAULT_SERVICE_PEER_TIMEOUT_MS = 15_000;
7
+ class ServiceLivenessConnection {
8
+ connectionId;
9
+ probeIntervalMs;
10
+ peerTimeoutMs;
11
+ deadlineMs;
12
+ nextProbeMs;
13
+ outstandingProbe;
14
+ constructor(connectionId, nowMs, probeIntervalMs = exports.DEFAULT_SERVICE_PROBE_INTERVAL_MS, peerTimeoutMs = exports.DEFAULT_SERVICE_PEER_TIMEOUT_MS) {
15
+ this.connectionId = connectionId;
16
+ this.probeIntervalMs = probeIntervalMs;
17
+ this.peerTimeoutMs = peerTimeoutMs;
18
+ this.deadlineMs = nowMs + peerTimeoutMs;
19
+ this.nextProbeMs = nowMs;
20
+ }
21
+ recordReceived = (nowMs = performance.now()) => {
22
+ this.deadlineMs = Math.max(this.deadlineMs, nowMs + this.peerTimeoutMs);
23
+ };
24
+ isExpired(nowMs) {
25
+ return nowMs >= this.deadlineMs;
26
+ }
27
+ tryGetProbe(nowMs, allocateProbeId) {
28
+ if (nowMs < this.nextProbeMs)
29
+ return undefined;
30
+ this.outstandingProbe ??= allocateProbeId();
31
+ do {
32
+ this.nextProbeMs += this.probeIntervalMs;
33
+ } while (this.nextProbeMs <= nowMs);
34
+ return this.outstandingProbe;
35
+ }
36
+ acknowledge(probeId, nowMs) {
37
+ this.recordReceived(nowMs);
38
+ if (this.outstandingProbe !== probeId)
39
+ return false;
40
+ this.outstandingProbe = undefined;
41
+ return true;
42
+ }
43
+ }
44
+ exports.ServiceLivenessConnection = ServiceLivenessConnection;
7
45
  /** Uses monotonic milliseconds and never treats application traffic as a probe ACK. */
8
46
  class ServiceLivenessRegistry {
9
47
  probeIntervalMs;
@@ -22,26 +60,19 @@ class ServiceLivenessRegistry {
22
60
  }
23
61
  admit(nodeRoutingId, connectionId, nowMs) {
24
62
  requireIdentity(nodeRoutingId, 'nodeRoutingId');
25
- requireIdentity(connectionId, 'connectionId');
63
+ if (typeof connectionId === 'string')
64
+ requireIdentity(connectionId, 'connectionId');
65
+ else if (connectionId === 0n)
66
+ throw new TypeError('connectionId must be nonzero.');
26
67
  const current = this.peers.get(nodeRoutingId);
27
68
  if (current?.connectionId === connectionId)
28
- return;
29
- this.peers.set(nodeRoutingId, {
30
- connectionId,
31
- deadlineMs: nowMs + this.peerTimeoutMs,
32
- nextProbeMs: nowMs + this.probeIntervalMs,
33
- ready: false
34
- });
69
+ return current;
70
+ const admitted = new ServiceLivenessConnection(connectionId, nowMs, this.probeIntervalMs, this.peerTimeoutMs);
71
+ this.peers.set(nodeRoutingId, admitted);
72
+ return admitted;
35
73
  }
36
- requestProbe(nodeRoutingId, connectionId, nowMs) {
37
- const current = this.peers.get(nodeRoutingId);
38
- if (current === undefined ||
39
- current.connectionId !== connectionId ||
40
- current.ready ||
41
- current.outstandingProbe !== undefined)
42
- return false;
43
- current.nextProbeMs = Math.min(current.nextProbeMs, nowMs);
44
- return true;
74
+ recordReceived(connection, nowMs) {
75
+ connection.recordReceived(nowMs);
45
76
  }
46
77
  disconnect(nodeRoutingId, connectionId) {
47
78
  const current = this.peers.get(nodeRoutingId);
@@ -52,15 +83,9 @@ class ServiceLivenessRegistry {
52
83
  }
53
84
  acknowledge(nodeRoutingId, connectionId, probeId, nowMs) {
54
85
  const current = this.peers.get(nodeRoutingId);
55
- if (current === undefined ||
56
- current.connectionId !== connectionId ||
57
- current.outstandingProbe !== probeId) {
86
+ if (current === undefined || current.connectionId !== connectionId)
58
87
  return false;
59
- }
60
- current.outstandingProbe = undefined;
61
- current.deadlineMs = nowMs + this.peerTimeoutMs;
62
- current.ready = true;
63
- return true;
88
+ return current.acknowledge(probeId, nowMs);
64
89
  }
65
90
  acknowledgeProbe(nodeRoutingId, connectionId, probeId) {
66
91
  const current = this.peers.get(nodeRoutingId);
@@ -73,22 +98,14 @@ class ServiceLivenessRegistry {
73
98
  const probes = [];
74
99
  const timedOutNodes = [];
75
100
  for (const [nodeRoutingId, peer] of this.peers) {
76
- if (peer.deadlineMs <= nowMs) {
101
+ if (peer.isExpired(nowMs)) {
77
102
  timedOutNodes.push(nodeRoutingId);
78
103
  this.peers.delete(nodeRoutingId);
79
104
  continue;
80
105
  }
81
- if (peer.nextProbeMs > nowMs)
82
- continue;
83
- peer.outstandingProbe ??= this.allocateProbeId();
84
- probes.push({
85
- nodeRoutingId,
86
- connectionId: peer.connectionId,
87
- probeId: peer.outstandingProbe
88
- });
89
- do {
90
- peer.nextProbeMs += this.probeIntervalMs;
91
- } while (peer.nextProbeMs <= nowMs);
106
+ const probeId = peer.tryGetProbe(nowMs, this.allocateProbeId);
107
+ if (probeId !== undefined)
108
+ probes.push({ nodeRoutingId, connectionId: peer.connectionId, probeId });
92
109
  }
93
110
  timedOutNodes.sort();
94
111
  return { probes, timedOutNodes };
@@ -96,16 +113,12 @@ class ServiceLivenessRegistry {
96
113
  get size() {
97
114
  return this.peers.size;
98
115
  }
99
- isReady(nodeRoutingId, connectionId) {
100
- const current = this.peers.get(nodeRoutingId);
101
- return current?.connectionId === connectionId && current.ready;
102
- }
103
- allocateProbeId() {
116
+ allocateProbeId = () => {
104
117
  const result = this.nextProbeId++;
105
118
  if (this.nextProbeId > stream_wire_1.UINT64_MAX)
106
119
  this.nextProbeId = 1n;
107
120
  return result;
108
- }
121
+ };
109
122
  }
110
123
  exports.ServiceLivenessRegistry = ServiceLivenessRegistry;
111
124
  function requireIdentity(value, field) {