@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
@@ -2,13 +2,16 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.ZLinkMeshDispatchPump = void 0;
4
4
  const node_async_hooks_1 = require("node:async_hooks");
5
+ const node_os_1 = require("node:os");
5
6
  const runtime_values_1 = require("./runtime-values");
6
7
  const service_runtime_contracts_1 = require("../foundation/service-runtime-contracts");
7
8
  const execution_1 = require("../execution");
8
9
  const application_job_queue_scope_1 = require("../application-jobs/application-job-queue-scope");
9
- const MESH_DISPATCH_TIMER_YIELD_BATCHES = 16;
10
- const MESH_DISPATCH_TIMER_YIELD_INTERVAL_MS = 2;
10
+ const MESH_DISPATCH_YIELD_RECORDS = 16;
11
+ const MESH_DISPATCH_YIELD_INTERVAL_MS = 2;
11
12
  const MESH_DISPATCH_LIFECYCLE_CLAIM_BUDGET = 4;
13
+ const MESH_DISPATCH_RECEIVE_CAPACITY = 64;
14
+ const MESH_DISPATCH_MAX_APPLICATION_WORKERS = Math.max(2, (0, node_os_1.availableParallelism)());
12
15
  // Captured while the runtime module is loaded, before any application owner
13
16
  // exists. A ready callback may run inside a Spot turn; the shared Mesh pump
14
17
  // must enter like an independent receive-loop task, without inheriting that
@@ -28,24 +31,27 @@ class ZLinkMeshDispatchPump {
28
31
  node;
29
32
  options;
30
33
  pendingDomains = service_runtime_contracts_1.ReadyDomain.None;
31
- scheduled = false;
32
34
  infrastructureScheduled = false;
33
35
  disposed = false;
34
- drainPromise;
35
36
  infrastructureDrainPromise;
36
37
  activeDrains = new Set();
38
+ idleApplicationWorkers = new Set();
39
+ applicationWorkerCount = 0;
37
40
  capacityStop = new AbortController();
41
+ recordsSinceYield = 0;
42
+ yieldStartedAtMs = 0;
38
43
  constructor(node, options) {
39
44
  this.node = node;
40
45
  this.options = options;
41
46
  }
42
47
  start() {
48
+ if (this.applicationWorkerCount === 0)
49
+ this.startApplicationWorker();
43
50
  this.node.setReadyHandler((domains) => {
44
51
  if (this.disposed) {
45
52
  return service_runtime_contracts_1.ReadyDomain.None;
46
53
  }
47
- this.pendingDomains |= domains;
48
- this.schedule();
54
+ this.markReady(domains);
49
55
  return domains;
50
56
  });
51
57
  }
@@ -56,42 +62,100 @@ class ZLinkMeshDispatchPump {
56
62
  this.disposed = true;
57
63
  this.capacityStop.abort();
58
64
  this.pendingDomains = service_runtime_contracts_1.ReadyDomain.None;
65
+ for (const wake of this.idleApplicationWorkers)
66
+ wake();
67
+ this.idleApplicationWorkers.clear();
59
68
  while (this.activeDrains.size > 0) {
60
69
  await Promise.all([...this.activeDrains]);
61
70
  }
62
71
  }
63
- schedule() {
64
- if ((this.pendingDomains & service_runtime_contracts_1.ReadyDomain.Infrastructure) !== 0) {
72
+ markReady(domains) {
73
+ if (domains === service_runtime_contracts_1.ReadyDomain.None || this.disposed)
74
+ return;
75
+ const previous = this.pendingDomains;
76
+ this.pendingDomains |= domains;
77
+ const startInfrastructure = (domains & service_runtime_contracts_1.ReadyDomain.Infrastructure) !== 0
78
+ && (previous & service_runtime_contracts_1.ReadyDomain.Infrastructure) === 0;
79
+ const startApplication = (domains & service_runtime_contracts_1.ReadyDomain.Application) !== 0
80
+ && (previous & service_runtime_contracts_1.ReadyDomain.Application) === 0;
81
+ if (startInfrastructure && startApplication) {
82
+ const startTurn = this.yieldIfNeeded() ?? Promise.resolve();
83
+ this.scheduleInfrastructure(startTurn);
84
+ return;
85
+ }
86
+ if (startInfrastructure)
65
87
  this.scheduleInfrastructure();
88
+ if (startApplication) {
89
+ if ((domains & service_runtime_contracts_1.ReadyDomain.Infrastructure) !== 0) {
90
+ setImmediate(() => this.wakeApplicationWorker());
91
+ }
92
+ else {
93
+ this.wakeApplicationWorker();
94
+ }
66
95
  }
67
- if ((this.pendingDomains & service_runtime_contracts_1.ReadyDomain.Application) === 0) {
96
+ }
97
+ wakeApplicationWorker() {
98
+ if (this.disposed || (this.pendingDomains & service_runtime_contracts_1.ReadyDomain.Application) === 0)
68
99
  return;
69
- }
70
- if (this.scheduled) {
100
+ const idle = this.idleApplicationWorkers.values().next().value;
101
+ if (idle !== undefined) {
102
+ this.idleApplicationWorkers.delete(idle);
103
+ this.pendingDomains &= ~service_runtime_contracts_1.ReadyDomain.Application;
104
+ idle();
71
105
  return;
72
106
  }
73
- this.scheduled = true;
74
- const drain = yieldToEventLoop()
75
- .then(() => detachedMeshDispatchScope(() => this.drain()))
76
- .catch((error) => {
77
- if (error instanceof ZLinkMeshDispatchFailure) {
78
- this.options.reportError?.(error.dispatchCause, error.context);
79
- return;
80
- }
81
- this.options.reportError?.(error);
107
+ if (this.applicationWorkerCount < MESH_DISPATCH_MAX_APPLICATION_WORKERS) {
108
+ this.startApplicationWorker();
109
+ }
110
+ }
111
+ startApplicationWorker() {
112
+ this.applicationWorkerCount += 1;
113
+ let worker;
114
+ worker = detachedMeshDispatchScope(() => (0, execution_1.runZLinkExecutionArea)('application', () => this.runApplicationWorker()))
115
+ .finally(() => {
116
+ this.applicationWorkerCount -= 1;
117
+ this.activeDrains.delete(worker);
82
118
  });
83
- this.activeDrains.add(drain);
84
- this.drainPromise = drain;
85
- void drain.finally(() => {
86
- this.activeDrains.delete(drain);
87
- if (this.drainPromise === drain) {
88
- this.drainPromise = undefined;
89
- this.scheduled = false;
90
- if (!this.disposed && this.pendingDomains !== service_runtime_contracts_1.ReadyDomain.None) {
91
- this.schedule();
119
+ this.activeDrains.add(worker);
120
+ }
121
+ async runApplicationWorker() {
122
+ // A worker claims one owner at a time so a suspended handler cannot hold
123
+ // unrelated owners. Each owner may supply up to 64 pre-admitted records.
124
+ const readyBatch = this.node.createReadyBatch(1);
125
+ const receiveBatch = this.node.createReceiveBatch(MESH_DISPATCH_RECEIVE_CAPACITY, this.options.partCapacity ?? 256);
126
+ try {
127
+ for (;;) {
128
+ await this.waitForApplicationReady();
129
+ if (this.disposed)
130
+ return;
131
+ try {
132
+ await this.drainDomain(service_runtime_contracts_1.ReadyDomain.Application, readyBatch, receiveBatch);
133
+ }
134
+ catch (error) {
135
+ this.reportDispatchError(error);
92
136
  }
93
137
  }
94
- });
138
+ }
139
+ finally {
140
+ receiveBatch.close();
141
+ readyBatch.close();
142
+ }
143
+ }
144
+ waitForApplicationReady() {
145
+ if (this.disposed)
146
+ return Promise.resolve();
147
+ if ((this.pendingDomains & service_runtime_contracts_1.ReadyDomain.Application) !== 0) {
148
+ this.pendingDomains &= ~service_runtime_contracts_1.ReadyDomain.Application;
149
+ return Promise.resolve();
150
+ }
151
+ return new Promise(resolve => this.idleApplicationWorkers.add(resolve));
152
+ }
153
+ reportDispatchError(error) {
154
+ if (error instanceof ZLinkMeshDispatchFailure) {
155
+ this.options.reportError?.(error.dispatchCause, error.context);
156
+ return;
157
+ }
158
+ this.options.reportError?.(error);
95
159
  }
96
160
  /**
97
161
  * Infrastructure controls must not wait for an application permit. During
@@ -100,13 +164,13 @@ class ZLinkMeshDispatchPump {
100
164
  * the infrastructure mailbox and prevent the relocation coordinator from
101
165
  * completing the drain that releases the application work.
102
166
  */
103
- scheduleInfrastructure() {
167
+ scheduleInfrastructure(startTurn) {
104
168
  if (this.infrastructureScheduled) {
105
169
  return;
106
170
  }
107
171
  this.infrastructureScheduled = true;
108
- const drain = yieldToEventLoop()
109
- .then(() => detachedMeshDispatchScope(() => this.drainInfrastructure()))
172
+ const drain = (startTurn ?? this.yieldIfNeeded() ?? Promise.resolve())
173
+ .then(() => detachedMeshDispatchScope(() => (0, execution_1.runZLinkExecutionArea)('infrastructure', () => this.drainInfrastructure())))
110
174
  .catch((error) => {
111
175
  if (error instanceof ZLinkMeshDispatchFailure) {
112
176
  this.options.reportError?.(error.dispatchCause, error.context);
@@ -127,39 +191,31 @@ class ZLinkMeshDispatchPump {
127
191
  }
128
192
  });
129
193
  }
130
- async drain() {
131
- while (!this.disposed) {
132
- if ((this.pendingDomains & service_runtime_contracts_1.ReadyDomain.Application) === 0) {
133
- return;
134
- }
135
- this.pendingDomains &= ~service_runtime_contracts_1.ReadyDomain.Application;
136
- await this.drainDomain(service_runtime_contracts_1.ReadyDomain.Application);
137
- }
138
- }
139
194
  async drainInfrastructure() {
140
- while (!this.disposed) {
141
- if ((this.pendingDomains & service_runtime_contracts_1.ReadyDomain.Infrastructure) === 0) {
142
- return;
143
- }
144
- this.pendingDomains &= ~service_runtime_contracts_1.ReadyDomain.Infrastructure;
145
- const lifecycleBudgetExhausted = await this.drainDomain(service_runtime_contracts_1.ReadyDomain.Infrastructure, MESH_DISPATCH_LIFECYCLE_CLAIM_BUDGET);
146
- if (lifecycleBudgetExhausted) {
147
- this.pendingDomains |= service_runtime_contracts_1.ReadyDomain.Infrastructure;
195
+ const readyBatch = this.node.createReadyBatch(Math.min(this.options.readyCapacity ?? 32, MESH_DISPATCH_LIFECYCLE_CLAIM_BUDGET));
196
+ const receiveBatch = this.node.createReceiveBatch(MESH_DISPATCH_RECEIVE_CAPACITY, this.options.partCapacity ?? 256);
197
+ try {
198
+ while (!this.disposed) {
199
+ if ((this.pendingDomains & service_runtime_contracts_1.ReadyDomain.Infrastructure) === 0) {
200
+ return;
201
+ }
202
+ this.pendingDomains &= ~service_runtime_contracts_1.ReadyDomain.Infrastructure;
203
+ if ((this.pendingDomains & service_runtime_contracts_1.ReadyDomain.Application) !== 0) {
204
+ setImmediate(() => this.wakeApplicationWorker());
205
+ }
206
+ const lifecycleBudgetExhausted = await this.drainDomain(service_runtime_contracts_1.ReadyDomain.Infrastructure, readyBatch, receiveBatch, MESH_DISPATCH_LIFECYCLE_CLAIM_BUDGET);
207
+ if (lifecycleBudgetExhausted) {
208
+ this.pendingDomains |= service_runtime_contracts_1.ReadyDomain.Infrastructure;
209
+ await yieldToEventLoop();
210
+ }
148
211
  }
149
212
  }
213
+ finally {
214
+ receiveBatch.close();
215
+ readyBatch.close();
216
+ }
150
217
  }
151
- async drainDomain(domain, claimBudget) {
152
- const readyCapacity = domain === service_runtime_contracts_1.ReadyDomain.Application
153
- ? 1
154
- : claimBudget === undefined
155
- ? (this.options.readyCapacity ?? 32)
156
- : Math.min(this.options.readyCapacity ?? 32, claimBudget);
157
- const readyBatch = this.node.createReadyBatch(readyCapacity);
158
- // One record per claim receive keeps one ordinary-ingress permit paired
159
- // with exactly one dispatch turn. Terminal completion claims bypass it.
160
- const receiveBatch = this.node.createReceiveBatch(1, this.options.partCapacity ?? 256);
161
- let receiveBatchesSinceTimerYield = 0;
162
- let timerYieldStartedAtMs = this.nowMs();
218
+ async drainDomain(domain, readyBatch, receiveBatch, claimBudget) {
163
219
  let claimsDrained = 0;
164
220
  try {
165
221
  for (;;) {
@@ -174,113 +230,124 @@ class ZLinkMeshDispatchPump {
174
230
  return false;
175
231
  }
176
232
  if (domain === service_runtime_contracts_1.ReadyDomain.Application && drained.hasResidue) {
177
- this.pendingDomains |= service_runtime_contracts_1.ReadyDomain.Application;
233
+ this.markReady(service_runtime_contracts_1.ReadyDomain.Application);
178
234
  }
179
235
  for (let index = 0; index < drained.records.length; index += 1) {
180
236
  const claim = readyBatch.takeClaim(index);
181
237
  claimsDrained += 1;
238
+ const owner = drained.records[index];
239
+ // Raw ingress already attached one host permit to each record.
240
+ // A claim that reserves admission here may receive only that permit's record.
182
241
  try {
183
- receiveBatch.reset();
242
+ const capacity = owner.ordinaryIngressPreAdmitted === true
243
+ ? MESH_DISPATCH_RECEIVE_CAPACITY : 1;
184
244
  for (;;) {
185
- const owner = drained.records[index];
186
- const claimPermit = owner.terminalCompletion === true
245
+ let claimPermit = owner.terminalCompletion === true
187
246
  || owner.ordinaryIngressPreAdmitted === true
188
247
  || domain === service_runtime_contracts_1.ReadyDomain.Infrastructure
189
248
  ? undefined
190
249
  : await this.acquirePermit();
191
- if (claimPermit === undefined
192
- && this.disposed
193
- && owner.terminalCompletion !== true
194
- && owner.ordinaryIngressPreAdmitted !== true)
195
- return false;
196
- const received = claim.recvBatch(receiveBatch, runtime_values_1.ZLINK_BACKEND_RECV_DONT_WAIT);
197
- if (!received.ok) {
198
- claimPermit?.releaseAfterInternalProcessing();
199
- break;
200
- }
201
- if (received.records.length === 0) {
202
- claimPermit?.releaseAfterInternalProcessing();
203
- }
204
- for (const record of received.records) {
205
- const permit = record.applicationJobPermit ?? claimPermit;
206
- if (owner.ordinaryIngressPreAdmitted === true && permit === undefined) {
207
- record.releaseRetainedIngress?.();
208
- throw new Error('Pre-admitted raw ingress record lost its Application Job Queue permit.');
250
+ try {
251
+ if (this.capacityStop.signal.aborted)
252
+ return false;
253
+ receiveBatch.reset(capacity);
254
+ const received = claim.recvBatch(receiveBatch, runtime_values_1.ZLINK_BACKEND_RECV_DONT_WAIT);
255
+ if (!received.ok)
256
+ break;
257
+ // This owner keeps its claim while it drains. Other owners and
258
+ // infrastructure may progress if one of its handlers suspends.
259
+ if (domain === service_runtime_contracts_1.ReadyDomain.Infrastructure) {
260
+ this.infrastructureScheduled = false;
261
+ this.infrastructureDrainPromise = undefined;
262
+ if ((this.pendingDomains & service_runtime_contracts_1.ReadyDomain.Infrastructure) !== 0) {
263
+ this.scheduleInfrastructure();
264
+ }
209
265
  }
210
266
  try {
211
- // A handler may synchronously submit an operation whose
212
- // control/completion is owned by this same MeshNode (e.g. a
213
- // native Completion record fed back through this node's own
214
- // Infrastructure claim). The active lane's scheduling state
215
- // must be released here, or a nested re-entrant drain can
216
- // never start to deliver that completion, and the awaiting
217
- // handler deadlocks against its own claim (spec
218
- // 46-internal-dispatch-loop, receive/dispatch wake/ordering).
219
- this.scheduled = false;
220
- this.drainPromise = undefined;
221
- if (domain === service_runtime_contracts_1.ReadyDomain.Infrastructure) {
222
- this.infrastructureScheduled = false;
223
- this.infrastructureDrainPromise = undefined;
224
- }
225
- if (this.pendingDomains !== service_runtime_contracts_1.ReadyDomain.None) {
226
- this.schedule();
227
- }
228
- const dispatch = () => (0, execution_1.runZLinkExecutionArea)(domain === service_runtime_contracts_1.ReadyDomain.Infrastructure ? 'infrastructure' : 'application', async () => {
229
- try {
230
- await this.options.dispatch(drained.records[index], record);
267
+ for (const record of received.records) {
268
+ if (this.disposed)
269
+ break;
270
+ const permit = record.applicationJobPermit ?? claimPermit;
271
+ if (owner.ordinaryIngressPreAdmitted === true && permit === undefined) {
272
+ throw new Error('Pre-admitted raw ingress record lost its Application Job Queue permit.');
231
273
  }
232
- catch (error) {
233
- throw new ZLinkMeshDispatchFailure(meshDispatchFailureContext(record), error);
274
+ this.recordsSinceYield += 1;
275
+ const dispatch = async () => {
276
+ try {
277
+ await this.options.dispatch(drained.records[index], record);
278
+ }
279
+ catch (error) {
280
+ throw new ZLinkMeshDispatchFailure(meshDispatchFailureContext(record), error);
281
+ }
282
+ await record.onTerminalCompletion?.();
283
+ };
284
+ if (permit === undefined) {
285
+ await dispatch();
234
286
  }
235
- await record.onTerminalCompletion?.();
236
- });
237
- if (permit === undefined) {
238
- await dispatch();
239
- }
240
- else {
241
- if (domain === service_runtime_contracts_1.ReadyDomain.Application
242
- && record.applicationJobPermit === undefined)
243
- permit.markApplicationQueued();
244
- await (0, application_job_queue_scope_1.runWithApplicationJobPermit)(permit, dispatch);
287
+ else {
288
+ if (domain === service_runtime_contracts_1.ReadyDomain.Application
289
+ && record.applicationJobPermit === undefined)
290
+ permit.markApplicationQueued();
291
+ // The scope now owns this reservation, including failure
292
+ // and detached handler handoff. Only unused permits remain
293
+ // owned by the receive attempt's finally block.
294
+ if (permit === claimPermit)
295
+ claimPermit = undefined;
296
+ await (0, application_job_queue_scope_1.runWithApplicationJobPermit)(permit, dispatch);
297
+ }
298
+ const yieldTurn = this.yieldIfNeeded();
299
+ if (yieldTurn !== undefined)
300
+ await yieldTurn;
245
301
  }
246
302
  }
247
303
  finally {
248
- for (const part of record.parts) {
249
- part.close();
304
+ // Batch ownership includes records whose dispatch never began,
305
+ // including on failure or shutdown in the middle of the batch.
306
+ for (const record of received.records) {
307
+ for (const part of record.parts)
308
+ part.close();
309
+ record.releaseRetainedIngress?.();
250
310
  }
251
- record.releaseRetainedIngress?.();
252
311
  }
253
312
  }
254
- // A continuously readable owner must not keep the timers phase
255
- // from running, but a timer turn for every single record adds a
256
- // Promise, closure, and timer allocation to the hot path.
257
- receiveBatchesSinceTimerYield += 1;
258
- if (receiveBatchesSinceTimerYield >= MESH_DISPATCH_TIMER_YIELD_BATCHES
259
- || this.nowMs() - timerYieldStartedAtMs >= MESH_DISPATCH_TIMER_YIELD_INTERVAL_MS) {
260
- receiveBatchesSinceTimerYield = 0;
261
- await yieldToTimers();
262
- timerYieldStartedAtMs = this.nowMs();
313
+ finally {
314
+ claimPermit?.releaseAfterInternalProcessing();
263
315
  }
264
- receiveBatch.reset();
265
316
  }
266
317
  }
267
318
  finally {
268
319
  claim.release();
269
320
  }
270
321
  }
271
- await yieldToTimers();
272
- receiveBatchesSinceTimerYield = 0;
273
- timerYieldStartedAtMs = this.nowMs();
322
+ if (claimBudget !== undefined && claimsDrained >= claimBudget) {
323
+ this.wakeApplicationWorker();
324
+ return true;
325
+ }
326
+ // Empty ready claims also count as work, so they cannot monopolize
327
+ // the microtask queue without giving I/O and deadlines a turn.
328
+ this.recordsSinceYield += 1;
329
+ const yieldTurn = this.yieldIfNeeded();
330
+ if (yieldTurn !== undefined)
331
+ await yieldTurn;
274
332
  if (!drained.hasResidue) {
275
333
  return false;
276
334
  }
277
335
  }
278
336
  }
279
337
  finally {
280
- receiveBatch.close();
281
- readyBatch.close();
338
+ readyBatch.reset();
282
339
  }
283
340
  }
341
+ yieldIfNeeded() {
342
+ const nowMs = this.nowMs();
343
+ if (this.recordsSinceYield < MESH_DISPATCH_YIELD_RECORDS
344
+ && nowMs - this.yieldStartedAtMs < MESH_DISPATCH_YIELD_INTERVAL_MS)
345
+ return undefined;
346
+ this.recordsSinceYield = 0;
347
+ return yieldToTimers().then(() => {
348
+ this.yieldStartedAtMs = this.nowMs();
349
+ });
350
+ }
284
351
  nowMs() {
285
352
  return this.options.monotonicNowMs?.() ?? performance.now();
286
353
  }
@@ -1,9 +1,8 @@
1
- import type { Received, TopicMessage } from '@zlink-systems/zlink';
2
- import type { ZLinkBackendAdapterFactory, ZLinkChannelBackendAdapter, ZLinkMonitoringBackendAdapter, ZLinkStreamBackendAdapter } from '../contracts';
1
+ import type { TopicMessage } from '@zlink-systems/zlink';
2
+ import type { ZLinkBackendAdapterFactory, ZLinkBackendReceived, ZLinkChannelBackendAdapter, ZLinkMonitoringBackendAdapter, ZLinkStreamBackendAdapter } from '../contracts';
3
3
  import { ZLinkNodeMeshBackendAdapter } from './node-mesh-backend-adapter';
4
- export { isDisconnectRouteNotFoundError } from './node-socket-backend-adapter';
5
4
  export declare class ZLinkNodeBackendAdapterFactory implements ZLinkBackendAdapterFactory {
6
- createReceived(): Received;
5
+ createReceived(): ZLinkBackendReceived;
7
6
  createTopicMessage(): TopicMessage;
8
7
  createChannelAdapter(): ZLinkChannelBackendAdapter;
9
8
  createMeshAdapter(): ZLinkNodeMeshBackendAdapter;
@@ -1,12 +1,10 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.ZLinkNodeBackendAdapterFactory = exports.isDisconnectRouteNotFoundError = void 0;
3
+ exports.ZLinkNodeBackendAdapterFactory = void 0;
4
4
  const node_backend_adapter_support_1 = require("./node-backend-adapter-support");
5
5
  const node_monitor_backend_adapter_1 = require("./node-monitor-backend-adapter");
6
6
  const node_socket_backend_adapter_1 = require("./node-socket-backend-adapter");
7
7
  const node_mesh_backend_adapter_1 = require("./node-mesh-backend-adapter");
8
- var node_socket_backend_adapter_2 = require("./node-socket-backend-adapter");
9
- Object.defineProperty(exports, "isDisconnectRouteNotFoundError", { enumerable: true, get: function () { return node_socket_backend_adapter_2.isDisconnectRouteNotFoundError; } });
10
8
  class ZLinkNodeBackendAdapterFactory {
11
9
  createReceived() {
12
10
  return new node_backend_adapter_support_1.zlink.Received();
@@ -36,16 +34,16 @@ class ZLinkNodeChannelBackendAdapter {
36
34
  return new node_backend_adapter_support_1.zlink.TopicMessage();
37
35
  }
38
36
  createDealerSocket(context) {
39
- return (0, node_socket_backend_adapter_1.wrapSocket)(node_backend_adapter_support_1.zlink.createDealerSocket(asNodeContext(context)));
37
+ return (0, node_socket_backend_adapter_1.wrapSocket)(node_backend_adapter_support_1.zlink.createDealerSocket(asNodeContext(context)), true);
40
38
  }
41
39
  createRouterSocket(context) {
42
- return (0, node_socket_backend_adapter_1.wrapSocket)(node_backend_adapter_support_1.zlink.createRouterSocket(asNodeContext(context)));
40
+ return (0, node_socket_backend_adapter_1.wrapSocket)(node_backend_adapter_support_1.zlink.createRouterSocket(asNodeContext(context)), true);
43
41
  }
44
42
  createPublisherSocket(context) {
45
43
  return (0, node_socket_backend_adapter_1.wrapSocket)(node_backend_adapter_support_1.zlink.createPubSocket(asNodeContext(context)));
46
44
  }
47
45
  createSubscriberSocket(context) {
48
- return (0, node_socket_backend_adapter_1.wrapSocket)(node_backend_adapter_support_1.zlink.createSubSocket(asNodeContext(context)));
46
+ return (0, node_socket_backend_adapter_1.wrapSocket)(node_backend_adapter_support_1.zlink.createSubSocket(asNodeContext(context)), false);
49
47
  }
50
48
  createReadablePoller(socket) {
51
49
  return createNodeReadablePoller(socket);
@@ -55,7 +53,7 @@ class ZLinkNodeStreamBackendAdapter {
55
53
  createStreamSocket(context) {
56
54
  const socket = node_backend_adapter_support_1.zlink.createStreamSocket(asNodeContext(context));
57
55
  socket.options.recvMode = node_backend_adapter_support_1.zlink.StreamRecvMode.Packet;
58
- return (0, node_socket_backend_adapter_1.wrapSocket)(socket);
56
+ return (0, node_socket_backend_adapter_1.wrapSocket)(socket, true);
59
57
  }
60
58
  createStreamPacket() {
61
59
  return new node_backend_adapter_support_1.zlink.StreamPacket();
@@ -118,44 +116,60 @@ function asNodeContext(context) {
118
116
  return context.nativeInstance;
119
117
  }
120
118
  function createNodeReadablePoller(socket) {
121
- const poller = node_backend_adapter_support_1.zlink.createPoller();
122
- const events = node_backend_adapter_support_1.zlink.createPollEvents(1);
123
- try {
124
- poller.add(socket.nativeInstance, [node_backend_adapter_support_1.zlink.PollEventFlag.PollIn], 0);
125
- }
126
- catch (error) {
127
- events.close();
128
- poller.close();
129
- throw error;
130
- }
119
+ const eventLoopPoller = (0, node_socket_backend_adapter_1.nodeEventLoopPollerOf)(socket);
131
120
  let disposed = false;
121
+ let readable = false;
122
+ let pending;
123
+ const settlePending = (value) => {
124
+ const current = pending;
125
+ if (current === undefined)
126
+ return;
127
+ pending = undefined;
128
+ if (current.signal !== undefined && current.onAbort !== undefined) {
129
+ current.signal.removeEventListener('abort', current.onAbort);
130
+ }
131
+ current.resolve(value);
132
+ };
133
+ eventLoopPoller.setReadableHandler(() => {
134
+ if (disposed)
135
+ return;
136
+ readable = true;
137
+ settlePending(true);
138
+ });
132
139
  return {
133
- wait(timeoutMs) {
134
- try {
135
- return poller.wait(events, timeoutMs) > 0
136
- && events.hasEvent(0, node_backend_adapter_support_1.zlink.PollEventFlag.PollIn);
137
- }
138
- catch (error) {
139
- if ((0, node_backend_adapter_support_1.isPollerInterruptedError)(error))
140
- return false;
141
- throw error;
140
+ wait(_timeoutMs) {
141
+ return !disposed && readable;
142
+ },
143
+ waitForReadable(signal) {
144
+ if (disposed || signal?.aborted === true)
145
+ return Promise.resolve(false);
146
+ if (readable)
147
+ return Promise.resolve(true);
148
+ if (pending !== undefined)
149
+ return pending.promise;
150
+ let resolvePending;
151
+ const promise = new Promise((resolve) => {
152
+ resolvePending = resolve;
153
+ });
154
+ const onAbort = signal === undefined
155
+ ? undefined
156
+ : () => settlePending(false);
157
+ pending = { promise, resolve: resolvePending, signal, onAbort };
158
+ if (signal !== undefined && onAbort !== undefined) {
159
+ signal.addEventListener('abort', onAbort, { once: true });
142
160
  }
161
+ return promise;
162
+ },
163
+ markDrained() {
164
+ readable = false;
143
165
  },
144
166
  dispose() {
145
167
  if (disposed)
146
168
  return;
147
169
  disposed = true;
148
- try {
149
- poller.remove(socket.nativeInstance);
150
- }
151
- finally {
152
- try {
153
- events.close();
154
- }
155
- finally {
156
- poller.close();
157
- }
158
- }
170
+ readable = false;
171
+ settlePending(false);
172
+ eventLoopPoller.setReadableHandler(() => { });
159
173
  }
160
174
  };
161
175
  }
@@ -22,7 +22,7 @@ interface ZLinkBindingReplySubmitOperation {
22
22
  }
23
23
  interface ZLinkBindingAsyncSendSubmitOperation {
24
24
  message(message: unknown): ZLinkBindingAsyncSendSubmitOperation;
25
- submit(): Promise<void>;
25
+ submit(): import('@zlink-systems/zlink').SendSubmission;
26
26
  submit_sync(): void;
27
27
  }
28
28
  export interface ZLinkBindingRequestOperation {
@@ -31,7 +31,7 @@ export interface ZLinkBindingRequestOperation {
31
31
  interface ZLinkBindingRequestSubmitOperation {
32
32
  message(message: unknown): ZLinkBindingRequestSubmitOperation;
33
33
  timeout(timeoutMs: number): ZLinkBindingRequestSubmitOperation;
34
- submit(): Promise<readonly unknown[]>;
34
+ submit(): import('@zlink-systems/zlink').RequestSubmission;
35
35
  }
36
36
  export declare function isBindingNotFound(error: unknown): boolean;
37
37
  export declare function isRouteRecvRetryable(error: unknown): boolean;
@@ -73,7 +73,7 @@ async function submitBindingAsyncSend(operation, payload) {
73
73
  current = current === undefined ? operation.message(nativePart) : current.message(nativePart);
74
74
  }
75
75
  current ??= operation.message(Buffer.alloc(0));
76
- await current.submit();
76
+ await current.submit().admitted;
77
77
  }
78
78
  catch (error) {
79
79
  throw translateBindingResultError(error);
@@ -110,7 +110,7 @@ async function submitBindingRequest(operation, payload, timeoutMs) {
110
110
  if (timeoutMs !== undefined) {
111
111
  current = current.timeout(timeoutMs);
112
112
  }
113
- return await current.submit();
113
+ return await current.submit().reply;
114
114
  }
115
115
  catch (error) {
116
116
  throw translateBindingResultError(error);