@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
@@ -20,7 +20,7 @@ class ZLinkDealerChannelClientTransport {
20
20
  (0, abort_1.throwIfAborted)(signal);
21
21
  // Call-scoped flow (spec 27 §4): the envelope flow pair does not outlive
22
22
  // this outbound call.
23
- await (0, flow_context_1.runWithOutboundFlow)(true, () => (0, channel_multipart_1.appendParts)(this.dealer.send(), (0, channel_envelope_1.encodeChannelEnvelopeParts)(3 /* ZLinkChannelMessageKind.Command */, channelName, packetName, message, undefined, undefined, undefined, undefined, true, metadata)).submit());
23
+ await (0, flow_context_1.runWithOutboundFlow)(true, () => (0, channel_multipart_1.appendParts)(this.dealer.send(), (0, channel_envelope_1.encodeChannelEnvelopeParts)(3 /* ZLinkChannelMessageKind.Command */, channelName, packetName, message, undefined, undefined, undefined, undefined, true, metadata)).submit().admitted);
24
24
  return { status: submission_result_1.ZLinkSubmitStatus.Submitted };
25
25
  }
26
26
  async request(channelName, packetName, request, timeoutMs, signal, metadata = EMPTY_METADATA) {
@@ -28,7 +28,7 @@ class ZLinkDealerChannelClientTransport {
28
28
  const operation = (0, flow_context_1.runWithOutboundFlow)(true, () => (0, channel_multipart_1.appendParts)(this.dealer.request(), (0, channel_envelope_1.encodeChannelEnvelopeParts)(1 /* ZLinkChannelMessageKind.Request */, channelName, packetName, request, timeoutMs, undefined, undefined, undefined, true, metadata)));
29
29
  if (timeoutMs !== undefined)
30
30
  operation.timeout(timeoutMs);
31
- const reply = await (0, channel_multipart_1.submitRequestOperation)(operation, 'channel request');
31
+ const reply = await (0, channel_multipart_1.submitRequestOperation)({ submit: () => operation.submit().reply }, 'channel request');
32
32
  try {
33
33
  return (0, channel_envelope_1.decodeChannelReply)(reply);
34
34
  }
@@ -0,0 +1,16 @@
1
+ import type { Socket } from '@zlink-systems/zlink';
2
+ /**
3
+ * Owns one binding public Poller and drives it from the socket's libuv
4
+ * readiness callback. PollCompletion is drained only by Poller.wait; the
5
+ * socket callback itself is only the event-loop wakeup.
6
+ */
7
+ export declare class ZLinkNodeEventLoopPoller {
8
+ private readonly poller;
9
+ private readonly events;
10
+ private readableHandler;
11
+ private disposed;
12
+ constructor(socket: Socket, pollCompletion: boolean, readableHandler: () => void);
13
+ setReadableHandler(handler: () => void): void;
14
+ dispose(): void;
15
+ private readonly onReady;
16
+ }
@@ -0,0 +1,49 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ZLinkNodeEventLoopPoller = void 0;
4
+ const node_backend_adapter_support_1 = require("./node-backend-adapter-support");
5
+ /**
6
+ * Owns one binding public Poller and drives it from the socket's libuv
7
+ * readiness callback. PollCompletion is drained only by Poller.wait; the
8
+ * socket callback itself is only the event-loop wakeup.
9
+ */
10
+ class ZLinkNodeEventLoopPoller {
11
+ poller = node_backend_adapter_support_1.zlink.createPoller();
12
+ events = node_backend_adapter_support_1.zlink.createPollEvents(1);
13
+ readableHandler;
14
+ disposed = false;
15
+ constructor(socket, pollCompletion, readableHandler) {
16
+ this.readableHandler = readableHandler;
17
+ try {
18
+ const pollEvents = [node_backend_adapter_support_1.zlink.PollEventFlag.PollIn];
19
+ if (pollCompletion)
20
+ pollEvents.push(node_backend_adapter_support_1.zlink.PollEventFlag.PollCompletion);
21
+ this.poller.add(socket, pollEvents, 0);
22
+ socket.setReadableHandler(this.onReady);
23
+ }
24
+ catch (error) {
25
+ this.events.close();
26
+ this.poller.close();
27
+ throw error;
28
+ }
29
+ }
30
+ setReadableHandler(handler) {
31
+ this.readableHandler = handler;
32
+ }
33
+ dispose() {
34
+ if (this.disposed)
35
+ return;
36
+ this.disposed = true;
37
+ this.events.close();
38
+ this.poller.close();
39
+ }
40
+ onReady = () => {
41
+ if (this.disposed)
42
+ return;
43
+ const ready = this.poller.wait(this.events, 0);
44
+ if (ready === 0 || this.events.hasEvent(0, node_backend_adapter_support_1.zlink.PollEventFlag.PollIn)) {
45
+ this.readableHandler();
46
+ }
47
+ };
48
+ }
49
+ exports.ZLinkNodeEventLoopPoller = ZLinkNodeEventLoopPoller;
@@ -4,6 +4,7 @@ exports.ZLinkNodeRawBindingPort = void 0;
4
4
  const zlink_1 = require("@zlink-systems/zlink");
5
5
  const node_backend_adapter_support_1 = require("./node-backend-adapter-support");
6
6
  const node_socket_backend_adapter_1 = require("./node-socket-backend-adapter");
7
+ const node_event_loop_poller_1 = require("./node-event-loop-poller");
7
8
  class ZLinkNodeRawBindingPort {
8
9
  sharedContext;
9
10
  constructor(sharedContext) {
@@ -78,12 +79,15 @@ class NodeRawSocketPort {
78
79
  socket;
79
80
  endpoints = new Set();
80
81
  monitors = new Set();
81
- readablePoller = (0, zlink_1.createPoller)();
82
- readableEvents = (0, zlink_1.createPollEvents)(1);
82
+ eventLoopPoller;
83
83
  closed = false;
84
84
  constructor(socket) {
85
85
  this.socket = socket;
86
- this.readablePoller.add(socket, [zlink_1.PollEventFlag.PollIn], 0);
86
+ this.eventLoopPoller = new node_event_loop_poller_1.ZLinkNodeEventLoopPoller(socket, true, () => { });
87
+ }
88
+ setReadableHandler(handler) {
89
+ this.requireOpen();
90
+ this.eventLoopPoller.setReadableHandler(handler);
87
91
  }
88
92
  bind(endpoint) {
89
93
  this.requireOpen();
@@ -120,6 +124,12 @@ class NodeRawSocketPort {
120
124
  if (this.closed)
121
125
  return;
122
126
  const failures = [];
127
+ try {
128
+ this.eventLoopPoller.dispose();
129
+ }
130
+ catch (error) {
131
+ failures.push(error);
132
+ }
123
133
  for (const monitor of [...this.monitors].reverse()) {
124
134
  try {
125
135
  monitor.close();
@@ -145,24 +155,6 @@ class NodeRawSocketPort {
145
155
  }
146
156
  }
147
157
  this.endpoints.clear();
148
- try {
149
- this.readablePoller.remove(this.socket);
150
- }
151
- catch (error) {
152
- failures.push(error);
153
- }
154
- try {
155
- this.readableEvents.close();
156
- }
157
- catch (error) {
158
- failures.push(error);
159
- }
160
- try {
161
- this.readablePoller.close();
162
- }
163
- catch (error) {
164
- failures.push(error);
165
- }
166
158
  try {
167
159
  this.socket.close();
168
160
  }
@@ -179,21 +171,6 @@ class NodeRawSocketPort {
179
171
  if (this.closed)
180
172
  throw new Error('Raw socket is closed.');
181
173
  }
182
- receiveRecord(dontWait) {
183
- const timeoutMs = dontWait ? 0 : -1;
184
- try {
185
- if (this.readablePoller.wait(this.readableEvents, timeoutMs) <= 0
186
- || !this.readableEvents.hasEvent(0, zlink_1.PollEventFlag.PollIn)) {
187
- return undefined;
188
- }
189
- }
190
- catch (error) {
191
- if ((0, node_backend_adapter_support_1.isPollerInterruptedError)(error))
192
- return undefined;
193
- throw error;
194
- }
195
- return receiveRecord(this.socket, true);
196
- }
197
174
  }
198
175
  class NodeRawRouterPort extends NodeRawSocketPort {
199
176
  nextIncomingRequestSequence = 1n;
@@ -222,14 +199,17 @@ class NodeRawRouterPort extends NodeRawSocketPort {
222
199
  }
223
200
  async send(targetRid, parts) {
224
201
  this.requireOpen();
225
- await appendSendParts(this.socket.send(bindingRoutingId(targetRid)), parts).submit();
202
+ const submission = appendSendParts(this.socket.send(bindingRoutingId(targetRid)), parts).submit();
203
+ if (submission.result === zlink_1.SubmitResult.Backpressured) {
204
+ await submission.admitted;
205
+ }
226
206
  }
227
207
  async request(targetRid, parts, timeoutMs) {
228
208
  this.requireOpen();
229
209
  try {
230
210
  const replies = await appendRequestParts(this.socket.request(bindingRoutingId(targetRid)), parts)
231
211
  .timeout(timeoutMs)
232
- .submit();
212
+ .submit().reply;
233
213
  return copyAndClose(replies);
234
214
  }
235
215
  catch (error) {
@@ -259,16 +239,19 @@ class NodeRawDealerPort extends NodeRawSocketPort {
259
239
  }
260
240
  async send(parts) {
261
241
  this.requireOpen();
262
- await appendSendParts(this.socket.send(), parts).submit();
242
+ const submission = appendSendParts(this.socket.send(), parts).submit();
243
+ if (submission.result === zlink_1.SubmitResult.Backpressured) {
244
+ await submission.admitted;
245
+ }
263
246
  }
264
247
  async request(parts, timeoutMs) {
265
248
  this.requireOpen();
266
- const replies = await appendRequestParts(this.socket.request(), parts).timeout(timeoutMs).submit();
249
+ const replies = await appendRequestParts(this.socket.request(), parts).timeout(timeoutMs).submit().reply;
267
250
  return copyAndClose(replies);
268
251
  }
269
252
  receive(dontWait = false) {
270
253
  this.requireOpen();
271
- return this.receiveRecord(dontWait);
254
+ return receiveRecord(this.socket, dontWait);
272
255
  }
273
256
  }
274
257
  class NodeRawMonitorPort {
@@ -36,7 +36,11 @@ export declare class ZLinkNodeRawMeshBackend implements ZLinkBackendMeshNode {
36
36
  private runtime?;
37
37
  private stateful?;
38
38
  private readyHandler?;
39
- private pollTimer?;
39
+ private maintenanceTimer?;
40
+ private pumping;
41
+ private readable;
42
+ private readonly onReadable;
43
+ private readonly onMaintenance;
40
44
  private nextPeerIntent;
41
45
  private closed;
42
46
  private objectRole;
@@ -49,11 +53,6 @@ export declare class ZLinkNodeRawMeshBackend implements ZLinkBackendMeshNode {
49
53
  private protocolError?;
50
54
  private messageFollowHandler?;
51
55
  private readonly peerDisconnectedHandlers;
52
- private readonly receiveBatchBudget;
53
- private observedPumpSourceRoutingId?;
54
- private observedPumpByteCount;
55
- private readonly observePump;
56
- private takePumpObservation;
57
56
  constructor(meshName: string, routingId: string | undefined, bindingPort: ZLinkRawBindingPort, applicationJobQueue?: ApplicationJobQueuePort | undefined, applicationJobReceiveFlowFailureSink?: ((error: unknown) => void) | undefined, peerAdmissionSealed?: (() => boolean) | undefined);
58
57
  configureObjectPlacement(options: {
59
58
  readonly role: ServiceNodeDescriptor['objectRole'];
@@ -239,8 +238,9 @@ export declare class ZLinkNodeRawMeshBackend implements ZLinkBackendMeshNode {
239
238
  clearSpotSubscriptions(state: ServiceSpotState): void;
240
239
  private createDescriptor;
241
240
  private resolveAdvertisedEndpoint;
242
- private schedulePoll;
243
- private poll;
241
+ private scheduleMaintenance;
242
+ private pump;
243
+ private isClosed;
244
244
  private notifyReady;
245
245
  private observeCompletion;
246
246
  private observeStateful;
@@ -3,11 +3,13 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.ZLinkNodeRawMeshBackend = void 0;
4
4
  exports.genericOperationFailure = genericOperationFailure;
5
5
  const node_crypto_1 = require("node:crypto");
6
+ const promises_1 = require("node:timers/promises");
6
7
  const node_socket_backend_adapter_1 = require("./node-socket-backend-adapter");
7
8
  const contracts_1 = require("../../../contracts");
8
9
  const framework_errors_internal_1 = require("../../framework-errors-internal");
9
10
  const zlink_1 = require("@zlink-systems/zlink");
10
11
  const runtime_values_1 = require("../runtime-values");
12
+ const runtime_message_1 = require("../runtime-message");
11
13
  const service_runtime_contracts_1 = require("../../foundation/service-runtime-contracts");
12
14
  const operation_registry_1 = require("../../foundation/operation-registry");
13
15
  const raw_service_mesh_runtime_1 = require("../../foundation/raw-service-mesh-runtime");
@@ -22,37 +24,15 @@ const MULTIPART_PACKET_NAME = service_wire_constants_generated_1.SERVICE_FRAMEWO
22
24
  const MULTIPART_CONTENT_TYPE = service_wire_constants_generated_1.SERVICE_FRAMEWORK_MULTIPART_CONTENT_TYPE;
23
25
  const FATAL_UTF8 = new TextDecoder('utf-8', { fatal: true });
24
26
  const MAX_DRAIN_RECORDS = 64;
25
- // A timer is only a no-data backoff. Once the binding has reported readable
26
- // work, continue from the next event-loop turn so a full socket does not wait
27
- // for an arbitrary timer cadence between batches.
28
- const MESH_BACKEND_IDLE_POLL_INTERVAL_MS = 1;
29
- const MESH_RECEIVE_BATCH_BYTE_LIMIT = 4 * 1024 * 1024;
30
- const MESH_RECEIVE_BATCH_TIME_LIMIT_MS = 2;
27
+ // Preserve the existing monitor/admission/liveness cadence. Only the binding
28
+ // readable handler admits receive work; this timer never probes the socket.
29
+ const MESH_BACKEND_MAINTENANCE_INTERVAL_MS = 1;
31
30
  /**
32
31
  * Conservative Actor Join admission cap for relocation state chunks (spec 15
33
32
  * §4.2): a stable lower bound safe on any deployment, never lowered on
34
33
  * recompute. Matches the source's own conservative chunk floor.
35
34
  */
36
35
  const ACTOR_JOIN_ADVERTISED_RECEIVE_CHUNK_LIMIT_BYTES = 32 * 1024;
37
- class ZLinkMeshReceiveBatchBudget {
38
- peerMessages = new Map();
39
- peerBytes = new Map();
40
- startedAtMs = 0;
41
- reset(nowMs) {
42
- this.peerMessages.clear();
43
- this.peerBytes.clear();
44
- this.startedAtMs = nowMs;
45
- }
46
- record(peer, byteCount, nowMs) {
47
- const messages = (this.peerMessages.get(peer) ?? 0) + 1;
48
- const bytes = (this.peerBytes.get(peer) ?? 0) + byteCount;
49
- this.peerMessages.set(peer, messages);
50
- this.peerBytes.set(peer, bytes);
51
- return messages >= MAX_DRAIN_RECORDS
52
- || bytes >= MESH_RECEIVE_BATCH_BYTE_LIMIT
53
- || nowMs - this.startedAtMs >= MESH_RECEIVE_BATCH_TIME_LIMIT_MS;
54
- }
55
- }
56
36
  /**
57
37
  * M6A MeshNode backend. Stateful Spot/Actor entry points stay explicit until
58
38
  * their owning M6B runtime is connected; topology and node/channel dispatch do
@@ -77,7 +57,18 @@ class ZLinkNodeRawMeshBackend {
77
57
  runtime;
78
58
  stateful;
79
59
  readyHandler;
80
- pollTimer;
60
+ maintenanceTimer;
61
+ pumping = false;
62
+ readable = false;
63
+ onReadable = () => {
64
+ this.readable = true;
65
+ if (!this.closed && !this.pumping)
66
+ void this.pump();
67
+ };
68
+ onMaintenance = () => {
69
+ this.maintenanceTimer = undefined;
70
+ void this.pump();
71
+ };
81
72
  nextPeerIntent = 1n;
82
73
  closed = false;
83
74
  objectRole = 'none';
@@ -90,19 +81,6 @@ class ZLinkNodeRawMeshBackend {
90
81
  protocolError;
91
82
  messageFollowHandler;
92
83
  peerDisconnectedHandlers = new Set();
93
- receiveBatchBudget = new ZLinkMeshReceiveBatchBudget();
94
- observedPumpSourceRoutingId;
95
- observedPumpByteCount = 0;
96
- observePump = (sourceRoutingId, byteCount) => {
97
- this.observedPumpSourceRoutingId = sourceRoutingId;
98
- this.observedPumpByteCount = byteCount;
99
- };
100
- takePumpObservation() {
101
- const sourceRoutingId = this.observedPumpSourceRoutingId;
102
- if (sourceRoutingId === undefined)
103
- return undefined;
104
- return { sourceRoutingId, byteCount: this.observedPumpByteCount };
105
- }
106
84
  constructor(meshName, routingId, bindingPort, applicationJobQueue, applicationJobReceiveFlowFailureSink, peerAdmissionSealed) {
107
85
  this.meshName = meshName;
108
86
  this.bindingPort = bindingPort;
@@ -232,7 +210,14 @@ class ZLinkNodeRawMeshBackend {
232
210
  }
233
211
  this.stateful.setMessageFollowHandler((record) => this.messageFollowHandler?.(record));
234
212
  this.runtime = runtime;
235
- this.schedulePoll();
213
+ try {
214
+ runtime.setReadableHandler(this.onReadable);
215
+ }
216
+ catch (error) {
217
+ this.close();
218
+ throw error;
219
+ }
220
+ this.scheduleMaintenance();
236
221
  }
237
222
  setMessageFollowHandler(handler) {
238
223
  this.messageFollowHandler = handler;
@@ -253,9 +238,9 @@ class ZLinkNodeRawMeshBackend {
253
238
  if (this.closed)
254
239
  return;
255
240
  this.closed = true;
256
- if (this.pollTimer !== undefined)
257
- clearTimeout(this.pollTimer);
258
- this.pollTimer = undefined;
241
+ if (this.maintenanceTimer !== undefined)
242
+ clearTimeout(this.maintenanceTimer);
243
+ this.maintenanceTimer = undefined;
259
244
  this.stateful?.close();
260
245
  this.stateful = undefined;
261
246
  this.runtime?.close();
@@ -372,7 +357,7 @@ class ZLinkNodeRawMeshBackend {
372
357
  return this.requireRuntime().isPeerRouteReady(String(targetRid), lifecycleGeneration);
373
358
  }
374
359
  async sendToNode(targetRid, parts) {
375
- return await this.requireRuntime().sendToNode(String(targetRid), encodeMultipart(parts)) ? zlink_1.SubmitResult.Ok : zlink_1.SubmitResult.NotConnected;
360
+ return await this.requireRuntime().sendToNode(String(targetRid), encodeMultipartApplicationFrame(parts)) ? zlink_1.SubmitResult.Ok : zlink_1.SubmitResult.NotConnected;
376
361
  }
377
362
  async sendInfrastructureControl(targetRid, record) {
378
363
  return await this.requireRuntime().sendService(String(targetRid), (Array.isArray(record) ? record : [record]).map(value => Buffer.from(value))) ? zlink_1.SubmitResult.Ok : zlink_1.SubmitResult.NotConnected;
@@ -384,14 +369,14 @@ class ZLinkNodeRawMeshBackend {
384
369
  return await this.requireRuntime().requestService(String(targetRid), frames.map(frame => Buffer.from(frame)), options?.timeoutMs ?? 30_000);
385
370
  }
386
371
  requestToNode(targetRid, parts, options) {
387
- const pending = this.requireRuntime().requestToNode(String(targetRid), encodeMultipart(parts), options?.timeoutMs ?? 30_000);
372
+ const pending = this.requireRuntime().requestToNode(String(targetRid), encodeMultipartApplicationFrame(parts), options?.timeoutMs ?? 30_000);
388
373
  return this.observeCompletion(pending.id, service_runtime_contracts_1.OperationKind.NodeRequest, pending.promise);
389
374
  }
390
375
  async sendToChannel(channelName, parts) {
391
- return await this.requireRuntime().sendToChannel(channelName, encodeMultipart(parts)) ? zlink_1.SubmitResult.Ok : zlink_1.SubmitResult.NotConnected;
376
+ return await this.requireRuntime().sendToChannel(channelName, encodeMultipartApplicationFrame(parts)) ? zlink_1.SubmitResult.Ok : zlink_1.SubmitResult.NotConnected;
392
377
  }
393
378
  requestToChannel(channelName, parts, options) {
394
- const pending = this.requireRuntime().requestToChannel(channelName, encodeMultipart(parts), options?.timeoutMs ?? 30_000);
379
+ const pending = this.requireRuntime().requestToChannel(channelName, encodeMultipartApplicationFrame(parts), options?.timeoutMs ?? 30_000);
395
380
  if (pending === undefined) {
396
381
  return this.enqueueImmediateFailure(service_runtime_contracts_1.OperationKind.ChannelRequest, zlink_1.RequestResult.NotFound);
397
382
  }
@@ -768,49 +753,38 @@ class ZLinkNodeRawMeshBackend {
768
753
  }
769
754
  return result;
770
755
  }
771
- schedulePoll(delayMs = MESH_BACKEND_IDLE_POLL_INTERVAL_MS) {
772
- if (this.closed || this.pollTimer !== undefined)
756
+ scheduleMaintenance() {
757
+ if (this.closed || this.maintenanceTimer !== undefined)
773
758
  return;
774
- this.pollTimer = setTimeout(async () => {
775
- this.pollTimer = undefined;
776
- try {
777
- const received = await this.poll();
778
- // A zero-delay timer yields to I/O and other ready work first without
779
- // inserting the idle polling interval between readable batches.
780
- delayMs = received ? 0 : MESH_BACKEND_IDLE_POLL_INTERVAL_MS;
781
- }
782
- finally {
783
- if (!this.closed)
784
- this.schedulePoll(delayMs);
785
- }
786
- }, delayMs);
759
+ this.maintenanceTimer = setTimeout(this.onMaintenance, MESH_BACKEND_MAINTENANCE_INTERVAL_MS);
787
760
  }
788
- async poll() {
789
- const runtime = this.runtime;
790
- if (runtime === undefined)
791
- return false;
792
- let received = false;
793
- await runtime.drainMonitorEvents();
794
- this.receiveBatchBudget.reset(performance.now());
795
- for (;;) {
796
- this.observedPumpSourceRoutingId = undefined;
797
- const result = await runtime.pumpOne(performance.now(), this.observePump);
798
- if (result === 'noData')
799
- break;
800
- received = true;
801
- if (result === 'application')
802
- this.readyHandler?.(service_runtime_contracts_1.ReadyDomain.Application);
803
- const observation = this.takePumpObservation();
804
- // Core ROUTER advances its fair-queue cursor after each complete
805
- // multipart message, so the next poll resumes from the following pipe.
806
- if (observation !== undefined
807
- && this.receiveBatchBudget.record(observation.sourceRoutingId, observation.byteCount, performance.now()))
808
- break;
761
+ async pump() {
762
+ if (this.isClosed() || this.pumping)
763
+ return;
764
+ this.pumping = true;
765
+ if (this.maintenanceTimer !== undefined)
766
+ clearTimeout(this.maintenanceTimer);
767
+ this.maintenanceTimer = undefined;
768
+ try {
769
+ do {
770
+ const receiveReady = this.readable;
771
+ this.readable = false;
772
+ const more = await this.requireRuntime().pumpBatch(receiveReady);
773
+ if (more) {
774
+ // Yield only after actual progress, retaining readiness across the
775
+ // existing batch limits until receive reports no data.
776
+ this.readable = true;
777
+ await (0, promises_1.setImmediate)();
778
+ }
779
+ } while (!this.closed && this.readable);
809
780
  }
810
- await runtime.announceExpectedPeers();
811
- await runtime.tickLiveness();
812
- this.notifyReady();
813
- return received;
781
+ finally {
782
+ this.pumping = false;
783
+ this.scheduleMaintenance();
784
+ }
785
+ }
786
+ isClosed() {
787
+ return this.closed;
814
788
  }
815
789
  notifyReady() {
816
790
  const runtime = this.runtime;
@@ -892,14 +866,7 @@ class ZLinkNodeRawMeshBackend {
892
866
  if (claim === undefined)
893
867
  break;
894
868
  const owner = readyOwner(claim.owner, this.routingId, this.stateful, domain);
895
- batch.push(owner, new MailboxClaim(runtime, claim, () => {
896
- // A receive batch may intentionally consume fewer records than the
897
- // mailbox claim. Releasing the claim re-queues the remainder, so
898
- // expose that newly ready work to the dispatch pump.
899
- if (runtime.mailbox.pendingMessages(domain) > 0) {
900
- this.readyHandler?.(domain === 'infrastructure' ? service_runtime_contracts_1.ReadyDomain.Infrastructure : service_runtime_contracts_1.ReadyDomain.Application);
901
- }
902
- }));
869
+ batch.push(owner, new MailboxClaim(runtime, claim));
903
870
  }
904
871
  }
905
872
  requireRuntime() {
@@ -1100,7 +1067,7 @@ class RawStreamSessionService {
1100
1067
  submit = submit.message(parts[index]);
1101
1068
  }
1102
1069
  try {
1103
- await submit.submit();
1070
+ await submit.submit().admitted;
1104
1071
  return true;
1105
1072
  }
1106
1073
  catch (error) {
@@ -1161,29 +1128,37 @@ class RawReadyBatch {
1161
1128
  }
1162
1129
  }
1163
1130
  class RawReceiveBatch {
1164
- messageCapacity;
1131
+ capacity;
1165
1132
  partCapacity;
1166
- constructor(messageCapacity, partCapacity) {
1167
- this.messageCapacity = messageCapacity;
1133
+ receiveCapacity;
1134
+ constructor(capacity, partCapacity) {
1135
+ this.capacity = capacity;
1168
1136
  this.partCapacity = partCapacity;
1169
- if ([messageCapacity, partCapacity].some(value => !Number.isInteger(value) || value < 1)) {
1137
+ if ([capacity, partCapacity].some(value => !Number.isInteger(value) || value < 1)) {
1170
1138
  throw new RangeError('Receive batch capacities must be positive.');
1171
1139
  }
1140
+ this.receiveCapacity = capacity;
1141
+ }
1142
+ get messageCapacity() {
1143
+ return this.receiveCapacity;
1144
+ }
1145
+ reset(messageCapacity = this.capacity) {
1146
+ if (!Number.isInteger(messageCapacity) || messageCapacity < 1 || messageCapacity > this.capacity) {
1147
+ throw new RangeError('Receive limit must be within the batch capacity.');
1148
+ }
1149
+ this.receiveCapacity = messageCapacity;
1172
1150
  }
1173
- reset() { }
1174
1151
  close() { }
1175
1152
  }
1176
1153
  class MailboxClaim {
1177
1154
  runtime;
1178
1155
  claim;
1179
- onRelease;
1180
1156
  consumed = false;
1181
1157
  released = false;
1182
1158
  remaining = [];
1183
- constructor(runtime, claim, onRelease) {
1159
+ constructor(runtime, claim) {
1184
1160
  this.runtime = runtime;
1185
1161
  this.claim = claim;
1186
- this.onRelease = onRelease;
1187
1162
  }
1188
1163
  recvBatch(batch) {
1189
1164
  if (this.consumed)
@@ -1206,6 +1181,13 @@ class MailboxClaim {
1206
1181
  }
1207
1182
  this.runtime.mailbox.releaseClaimedPayload(record);
1208
1183
  this.remaining = this.claim.records.slice(index + 1);
1184
+ // Nothing was transferred to the receive caller when decoding throws.
1185
+ // Earlier records therefore still belong to this failed batch.
1186
+ for (const decoded of records) {
1187
+ for (const part of decoded.parts)
1188
+ part.close();
1189
+ decoded.releaseRetainedIngress?.();
1190
+ }
1209
1191
  throw error;
1210
1192
  }
1211
1193
  const nextParts = decoded.parts.length;
@@ -1227,7 +1209,6 @@ class MailboxClaim {
1227
1209
  return;
1228
1210
  this.released = true;
1229
1211
  this.runtime.mailbox.release(this.claim, this.remaining);
1230
- this.onRelease?.();
1231
1212
  }
1232
1213
  }
1233
1214
  class CompletionClaim {
@@ -1371,7 +1352,7 @@ function decodeMultipartRecord(runtime, record) {
1371
1352
  reply(parts) {
1372
1353
  if (record.correlation === undefined)
1373
1354
  return zlink_1.SubmitResult.InvalidState;
1374
- runtime.reply(record, encodeMultipart(parts));
1355
+ runtime.reply(record, encodeMultipartApplicationFrame(parts));
1375
1356
  return zlink_1.SubmitResult.Ok;
1376
1357
  },
1377
1358
  replyActorJoin: () => zlink_1.SubmitResult.NotSupported
@@ -1630,7 +1611,19 @@ function decodeApplicationEnvelope(frame) {
1630
1611
  }
1631
1612
  function decodeMultipart(payload) {
1632
1613
  const buffers = decodeMultipartBuffers(payload);
1633
- return buffers.map(part => zlink_1.Message.from(part));
1614
+ const parts = new Array(buffers.length);
1615
+ let materialized = 0;
1616
+ try {
1617
+ for (; materialized < buffers.length; materialized += 1) {
1618
+ parts[materialized] = runtime_message_1.ZLinkBufferMessage.fromOwned(buffers[materialized]);
1619
+ }
1620
+ return parts;
1621
+ }
1622
+ catch (error) {
1623
+ for (let index = 0; index < materialized; index += 1)
1624
+ parts[index].close();
1625
+ throw error;
1626
+ }
1634
1627
  }
1635
1628
  function decodeMultipartBuffers(payload) {
1636
1629
  const bytes = Buffer.from(payload.buffer, payload.byteOffset, payload.byteLength);
@@ -1,9 +1,10 @@
1
1
  import type { ZLinkBackendObject } from '../contracts';
2
+ import { ZLinkNodeEventLoopPoller } from './node-event-loop-poller';
2
3
  export declare function wrapSocket<T extends {
3
4
  close(): void;
4
- }>(nativeInstance: T): T & ZLinkBackendObject;
5
+ }>(nativeInstance: T, pollCompletion?: boolean): T & ZLinkBackendObject;
6
+ export declare function nodeEventLoopPollerOf(socket: object): ZLinkNodeEventLoopPoller;
5
7
  export declare function disconnectStreamPeer(socket: {
6
8
  disconnectRid(routingId: unknown): void;
7
9
  }, routingId: unknown): void;
8
- export declare function isDisconnectRouteNotFoundError(error: unknown): boolean;
9
10
  export declare function isEndpointCloseIgnorableError(error: unknown): boolean;