@zlink-systems/framework 0.10.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 (125) 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 +6 -1
  32. package/dist/runtime/backend/runtime-values.js +6 -0
  33. package/dist/runtime/channels/channel-dispatchers.js +2 -2
  34. package/dist/runtime/channels/channel-envelope.js +324 -121
  35. package/dist/runtime/channels/channel-multipart.d.ts +8 -1
  36. package/dist/runtime/channels/channel-outbound-operations.js +0 -2
  37. package/dist/runtime/channels/channel-receive-loops.d.ts +1 -3
  38. package/dist/runtime/channels/channel-receive-loops.js +25 -65
  39. package/dist/runtime/channels/channel-socket-registry.js +14 -1
  40. package/dist/runtime/channels/channel-transports.d.ts +2 -1
  41. package/dist/runtime/channels/channel-transports.js +12 -10
  42. package/dist/runtime/channels/client-server-location-runtime.js +15 -3
  43. package/dist/runtime/execution/serial-execution-queue.d.ts +5 -18
  44. package/dist/runtime/execution/serial-execution-queue.js +16 -80
  45. package/dist/runtime/foundation/event-loop-resources.d.ts +0 -3
  46. package/dist/runtime/foundation/event-loop-resources.js +2 -11
  47. package/dist/runtime/foundation/operation-registry.d.ts +2 -8
  48. package/dist/runtime/foundation/operation-registry.js +3 -21
  49. package/dist/runtime/foundation/raw-service-mesh-runtime.d.ts +19 -7
  50. package/dist/runtime/foundation/raw-service-mesh-runtime.js +56 -10
  51. package/dist/runtime/foundation/service-mailbox.d.ts +1 -7
  52. package/dist/runtime/foundation/service-mailbox.js +3 -2
  53. package/dist/runtime/foundation/service-runtime-contracts.d.ts +2 -1
  54. package/dist/runtime/foundation/service-stateful-registry.d.ts +1 -1
  55. package/dist/runtime/foundation/service-stateful-registry.js +2 -2
  56. package/dist/runtime/foundation/service-stateful-runtime.js +14 -14
  57. package/dist/runtime/framework-errors-internal.d.ts +4 -4
  58. package/dist/runtime/framework-errors-internal.js +8 -10
  59. package/dist/runtime/host/actor-placement-coordinator.js +1 -1
  60. package/dist/runtime/host/index.js +1 -1
  61. package/dist/runtime/host/service-relocation-host-runtime.js +0 -4
  62. package/dist/runtime/host/spot-address-transport.js +1 -1
  63. package/dist/runtime/host/user-spot-creation-coordinator.js +1 -1
  64. package/dist/runtime/locations/location-store-repository.d.ts +1 -0
  65. package/dist/runtime/locations/location-store-repository.js +64 -75
  66. package/dist/runtime/spots/spot-node-runtime-manager.d.ts +0 -12
  67. package/dist/runtime/spots/spot-node-runtime-manager.js +6 -166
  68. package/dist/runtime/spots/spot-serial-turn-executor.js +1 -4
  69. package/dist/runtime/spots/spot-timer.d.ts +3 -0
  70. package/dist/runtime/spots/spot-timer.js +87 -18
  71. package/dist/runtime/streams/session-serial-executor.js +1 -3
  72. package/dist/runtime/streams/stream-session-runtime.d.ts +2 -2
  73. package/dist/runtime/streams/stream-session-runtime.js +13 -19
  74. package/dist/runtime/workers/index.d.ts +0 -1
  75. package/dist/runtime/workers/index.js +1 -9
  76. package/package.json +9 -3
  77. package/src/contracts/Configuration/EndpointNotation.ts +5 -4
  78. package/src/contracts/Configuration/InternalDefaults.ts +0 -1
  79. package/src/contracts/Configuration/RegistrationNormalizers.ts +2 -4
  80. package/src/contracts/Configuration/RegistrationTypes.ts +2 -5
  81. package/src/contracts/Configuration/RegistrationValidators.ts +0 -1
  82. package/src/contracts/Errors/ZLinkFrameworkException.ts +6 -7
  83. package/src/runtime/actors/actor-client.ts +1 -1
  84. package/src/runtime/actors/actor-mailbox.ts +1 -12
  85. package/src/runtime/backend/contracts/index.ts +2 -0
  86. package/src/runtime/backend/mesh-completion-table.ts +12 -23
  87. package/src/runtime/backend/mesh-dispatch-pump.ts +206 -138
  88. package/src/runtime/backend/node/node-backend-adapter-factory.ts +66 -35
  89. package/src/runtime/backend/node/node-backend-adapter-support.ts +4 -4
  90. package/src/runtime/backend/node/node-dealer-channel-client-transport.ts +2 -2
  91. package/src/runtime/backend/node/node-event-loop-poller.ts +55 -0
  92. package/src/runtime/backend/node/node-raw-binding-port.ts +30 -45
  93. package/src/runtime/backend/node/node-raw-mesh-backend.ts +97 -110
  94. package/src/runtime/backend/node/node-socket-backend-adapter.ts +23 -69
  95. package/src/runtime/backend/raw-binding-port.ts +1 -0
  96. package/src/runtime/backend/runtime-values.ts +13 -1
  97. package/src/runtime/channels/channel-dispatchers.ts +2 -2
  98. package/src/runtime/channels/channel-envelope.ts +379 -109
  99. package/src/runtime/channels/channel-multipart.ts +9 -1
  100. package/src/runtime/channels/channel-outbound-operations.ts +0 -1
  101. package/src/runtime/channels/channel-receive-loops.ts +24 -75
  102. package/src/runtime/channels/channel-socket-registry.ts +17 -1
  103. package/src/runtime/channels/channel-transports.ts +20 -10
  104. package/src/runtime/channels/client-server-location-runtime.ts +16 -2
  105. package/src/runtime/execution/serial-execution-queue.ts +16 -108
  106. package/src/runtime/foundation/event-loop-resources.ts +2 -11
  107. package/src/runtime/foundation/operation-registry.ts +2 -22
  108. package/src/runtime/foundation/raw-service-mesh-runtime.ts +74 -19
  109. package/src/runtime/foundation/service-mailbox.ts +3 -13
  110. package/src/runtime/foundation/service-runtime-contracts.ts +2 -1
  111. package/src/runtime/foundation/service-stateful-registry.ts +2 -2
  112. package/src/runtime/foundation/service-stateful-runtime.ts +14 -14
  113. package/src/runtime/framework-errors-internal.ts +8 -10
  114. package/src/runtime/host/actor-placement-coordinator.ts +1 -1
  115. package/src/runtime/host/index.ts +2 -1
  116. package/src/runtime/host/service-relocation-host-runtime.ts +0 -4
  117. package/src/runtime/host/spot-address-transport.ts +1 -1
  118. package/src/runtime/host/user-spot-creation-coordinator.ts +1 -1
  119. package/src/runtime/locations/location-store-repository.ts +118 -80
  120. package/src/runtime/spots/spot-node-runtime-manager.ts +13 -233
  121. package/src/runtime/spots/spot-serial-turn-executor.ts +1 -8
  122. package/src/runtime/spots/spot-timer.ts +109 -18
  123. package/src/runtime/streams/session-serial-executor.ts +1 -7
  124. package/src/runtime/streams/stream-session-runtime.ts +13 -17
  125. package/src/runtime/workers/index.ts +2 -17
@@ -92,6 +92,7 @@ const MAX_CREATION_TERMINAL_BYTES = 1024 * 1024;
92
92
  const CREATION_TERMINAL_RETENTION_MS = 5 * 60 * 1000;
93
93
  const AGGREGATE_COMMIT_RETRY_WINDOW_MS = 5_000;
94
94
  const MAX_AGGREGATE_COMMIT_CONFLICT_RETRIES = 64;
95
+ const MAX_DESCRIPTOR_WRITE_RETRIES = 3;
95
96
 
96
97
  type StoredAuthoritySnapshot = Omit<
97
98
  ZLinkAuthoritySnapshot,
@@ -1603,67 +1604,15 @@ export class ZLinkLocationStoreRepository extends ZLinkInMemoryLocationStore {
1603
1604
  intent: ZLinkLocationWriteIntent,
1604
1605
  signal?: AbortSignal
1605
1606
  ): Promise<ZLinkLocationWriteResult> {
1606
- const leaseKey = ownerKey(descriptor.ownerId);
1607
- const rowKey = meshKey(descriptor.meshName, descriptor.rid);
1608
- const [lease, current] = await Promise.all([
1609
- this.provider.read(leaseKey, signal),
1610
- this.provider.read(rowKey, signal)
1611
- ]);
1612
- if (lease.kind === 'missing') return rejected(lease.storeNow);
1613
- if (liveOwnerLeaseGeneration(lease) !== descriptor.leaseGeneration) {
1614
- return rejected(lease.value.storeNow);
1615
- }
1616
-
1617
- let generation = 1n;
1618
- let rowCondition: ZLinkStoreCondition = { kind: 'missing', key: rowKey };
1619
- if (current.kind === 'found') {
1620
- const record = decodeCanonicalDescriptorRecord<ZLinkMeshNodeDescriptor>(current.value.bytes);
1621
- const stored = reviveMeshDescriptor(record.descriptor);
1622
- generation = descriptorStoreGeneration(record, current.value.version.value);
1623
- if (sameMeshDescriptor(stored, descriptor)) {
1624
- return { status: WriteStatus.Stored, generation, updatedAt: current.value.storeNow };
1625
- }
1626
- const currentLease = await this.provider.read(ownerKey(stored.ownerId), signal);
1627
- const currentLeaseGeneration = liveOwnerLeaseGeneration(currentLease);
1628
- const takeover = canTakeOverStoredLocation(
1629
- intent,
1630
- stored.ownerId,
1631
- stored.leaseGeneration,
1632
- descriptor.ownerId,
1633
- descriptor.leaseGeneration,
1634
- currentLeaseGeneration
1635
- );
1636
- const renew = intent === 2
1637
- && stored.ownerId === descriptor.ownerId
1638
- && stored.leaseGeneration === descriptor.leaseGeneration
1639
- && stored.lifecycleGeneration === descriptor.lifecycleGeneration
1640
- && descriptor.descriptorRevision > stored.descriptorRevision;
1641
- if (!takeover && !renew) {
1642
- return {
1643
- status: WriteStatus.IgnoredStale,
1644
- generation,
1645
- updatedAt: current.value.storeNow
1646
- };
1647
- }
1648
- if (takeover) generation += 1n;
1649
- rowCondition = { kind: 'version', key: rowKey, expected: current.value.version };
1650
- } else if (intent === 2) {
1651
- return rejected(lease.value.storeNow);
1652
- }
1653
-
1654
- const result = await this.provider.write({
1655
- conditions: [
1656
- { kind: 'version', key: leaseKey, expected: lease.value.version },
1657
- rowCondition
1658
- ],
1659
- mutations: [{
1660
- kind: 'put',
1661
- key: rowKey,
1662
- bytes: encodeCanonicalDescriptorRecord(generation, persistMeshDescriptor(descriptor))
1663
- }]
1664
- }, signal);
1665
- if (result.kind === 'conflict') return rejected(result.storeNow);
1666
- return { status: WriteStatus.Stored, generation, updatedAt: result.storeNow };
1607
+ return this.updateDescriptor(
1608
+ meshKey(descriptor.meshName, descriptor.rid),
1609
+ descriptor,
1610
+ intent,
1611
+ reviveMeshDescriptor,
1612
+ sameMeshDescriptor,
1613
+ canRenewMesh,
1614
+ signal
1615
+ );
1667
1616
  }
1668
1617
 
1669
1618
  override async removeMeshNode(
@@ -2253,12 +2202,15 @@ export class ZLinkLocationStoreRepository extends ZLinkInMemoryLocationStore {
2253
2202
  this.provider.read(rowKey, signal)
2254
2203
  ]);
2255
2204
  if (lease.kind === 'missing') return rejected(lease.storeNow);
2256
- if (liveOwnerLeaseGeneration(lease) !== descriptor.leaseGeneration) {
2205
+ if (!matchesLiveOwnerLease(
2206
+ lease,
2207
+ descriptor.ownerId,
2208
+ descriptor.leaseGeneration
2209
+ )) {
2257
2210
  return rejected(lease.value.storeNow);
2258
2211
  }
2259
2212
 
2260
2213
  let generation = 1n;
2261
- let rowCondition: ZLinkStoreCondition = { kind: 'missing', key: rowKey };
2262
2214
  if (current.kind === 'found') {
2263
2215
  const record = decodeCanonicalDescriptorRecord<T>(current.value.bytes);
2264
2216
  const stored = revive(record.descriptor);
@@ -2285,24 +2237,67 @@ export class ZLinkLocationStoreRepository extends ZLinkInMemoryLocationStore {
2285
2237
  };
2286
2238
  }
2287
2239
  if (takeover) generation += 1n;
2288
- rowCondition = { kind: 'version', key: rowKey, expected: current.value.version };
2289
2240
  } else if (intent === 2) {
2290
2241
  return rejected(lease.value.storeNow);
2291
2242
  }
2292
2243
 
2293
- const result = await this.provider.write({
2294
- conditions: [
2295
- { kind: 'version', key: leaseKey, expected: lease.value.version },
2296
- rowCondition
2297
- ],
2298
- mutations: [{
2299
- kind: 'put',
2300
- key: rowKey,
2301
- bytes: encodeCanonicalDescriptorRecord(generation, descriptor)
2302
- }]
2303
- }, signal);
2304
- if (result.kind === 'conflict') return rejected(result.storeNow);
2305
- return { status: WriteStatus.Stored, generation, updatedAt: result.storeNow };
2244
+ return this.writeDescriptorWithReconciliation(
2245
+ leaseKey,
2246
+ descriptor.ownerId,
2247
+ descriptor.leaseGeneration,
2248
+ lease,
2249
+ rowKey,
2250
+ current,
2251
+ encodeCanonicalDescriptorRecord(generation, descriptor),
2252
+ generation,
2253
+ signal
2254
+ );
2255
+ }
2256
+
2257
+ private async writeDescriptorWithReconciliation(
2258
+ leaseKey: ZLinkStoreKey,
2259
+ ownerId: string,
2260
+ leaseGeneration: bigint,
2261
+ liveLease: Extract<ZLinkStoreReadResult, { kind: 'found' }>,
2262
+ rowKey: ZLinkStoreKey,
2263
+ predecessor: ZLinkStoreReadResult,
2264
+ encodedDescriptor: Uint8Array,
2265
+ generation: bigint,
2266
+ signal?: AbortSignal
2267
+ ): Promise<ZLinkLocationWriteResult> {
2268
+ let currentLease = liveLease;
2269
+ let currentRow = predecessor;
2270
+
2271
+ for (let attempt = 0; attempt <= MAX_DESCRIPTOR_WRITE_RETRIES; attempt += 1) {
2272
+ const rowCondition: ZLinkStoreCondition = currentRow.kind === 'found'
2273
+ ? { kind: 'version', key: rowKey, expected: currentRow.value.version }
2274
+ : { kind: 'missing', key: rowKey };
2275
+ const result = await this.provider.write({
2276
+ conditions: [
2277
+ { kind: 'version', key: leaseKey, expected: currentLease.value.version },
2278
+ rowCondition
2279
+ ],
2280
+ mutations: [{ kind: 'put', key: rowKey, bytes: encodedDescriptor }]
2281
+ }, signal);
2282
+ if (result.kind === 'applied') {
2283
+ return { status: WriteStatus.Stored, generation, updatedAt: result.storeNow };
2284
+ }
2285
+ if (attempt === MAX_DESCRIPTOR_WRITE_RETRIES) return ignoredStale(result.storeNow);
2286
+
2287
+ const [refreshedLease, refreshedRow] = await Promise.all([
2288
+ this.provider.read(leaseKey, signal),
2289
+ this.provider.read(rowKey, signal)
2290
+ ]);
2291
+ if (refreshedLease.kind === 'missing'
2292
+ || !matchesLiveOwnerLease(refreshedLease, ownerId, leaseGeneration)
2293
+ || !sameDescriptorPredecessor(predecessor, refreshedRow)) {
2294
+ return ignoredStale(result.storeNow);
2295
+ }
2296
+
2297
+ currentLease = refreshedLease;
2298
+ currentRow = refreshedRow;
2299
+ }
2300
+ throw new Error('Descriptor write retry loop exhausted unexpectedly.');
2306
2301
  }
2307
2302
 
2308
2303
  private async removeDescriptor<T extends OwnedDescriptor>(
@@ -3888,9 +3883,36 @@ function sameLiveOwner(
3888
3883
  lease: ZLinkStoreReadResult,
3889
3884
  snapshot: StoredAuthoritySnapshot
3890
3885
  ): boolean {
3891
- if (liveOwnerLeaseGeneration(lease) !== snapshot.ownerLeaseGeneration) return false;
3892
- const owner = decodeOwnerRecord((lease as Extract<ZLinkStoreReadResult, { kind: 'found' }>).value.bytes);
3893
- return owner.ownerId === snapshot.ownerId;
3886
+ if (lease.kind === 'missing') return false;
3887
+ return matchesLiveOwnerLease(
3888
+ lease,
3889
+ snapshot.ownerId,
3890
+ snapshot.ownerLeaseGeneration
3891
+ );
3892
+ }
3893
+
3894
+ function matchesLiveOwnerLease(
3895
+ lease: Extract<ZLinkStoreReadResult, { kind: 'found' }>,
3896
+ ownerId: string,
3897
+ leaseGeneration: bigint
3898
+ ): boolean {
3899
+ if (lease.value.expiresAt === undefined
3900
+ || lease.value.expiresAt.getTime() <= lease.value.storeNow.getTime()) {
3901
+ return false;
3902
+ }
3903
+ const owner = decodeOwnerRecord(lease.value.bytes);
3904
+ return owner.ownerId === ownerId
3905
+ && BigInt(owner.leaseGeneration) === leaseGeneration;
3906
+ }
3907
+
3908
+ function sameDescriptorPredecessor(
3909
+ expected: ZLinkStoreReadResult,
3910
+ current: ZLinkStoreReadResult
3911
+ ): boolean {
3912
+ if (expected.kind === 'missing' || current.kind === 'missing') {
3913
+ return expected.kind === current.kind;
3914
+ }
3915
+ return Buffer.from(expected.value.bytes).equals(Buffer.from(current.value.bytes));
3894
3916
  }
3895
3917
 
3896
3918
  function liveOwnerLeaseGeneration(
@@ -4113,6 +4135,14 @@ function sameFanoutDescriptor(
4113
4135
  return descriptorFingerprint(left, ['updatedAt']) === descriptorFingerprint(right, ['updatedAt']);
4114
4136
  }
4115
4137
 
4138
+ function canRenewMesh(
4139
+ current: ZLinkMeshNodeDescriptor,
4140
+ next: ZLinkMeshNodeDescriptor
4141
+ ): boolean {
4142
+ return sameOwnerGeneration(current, next)
4143
+ && next.descriptorRevision > current.descriptorRevision;
4144
+ }
4145
+
4116
4146
  function canRenewClientServer(
4117
4147
  current: ZLinkClientServerServerDescriptor,
4118
4148
  next: ZLinkClientServerServerDescriptor
@@ -4201,6 +4231,14 @@ function rejected(updatedAt: Date): ZLinkLocationWriteResult {
4201
4231
  };
4202
4232
  }
4203
4233
 
4234
+ function ignoredStale(updatedAt: Date): ZLinkLocationWriteResult {
4235
+ return {
4236
+ status: WriteStatus.IgnoredStale,
4237
+ generation: 0n,
4238
+ updatedAt
4239
+ };
4240
+ }
4241
+
4204
4242
  function requireOwnerInput(ownerId: string, leaseTtlMs: number): void {
4205
4243
  if (Buffer.byteLength(ownerId, 'utf8') < 1 || ownerId.includes('\0')) {
4206
4244
  throw new TypeError('Owner ID must be non-empty UTF-8 without NUL.');
@@ -142,22 +142,6 @@ export interface ZLinkSpotNodeRuntimeManagerOptions {
142
142
  };
143
143
  }
144
144
 
145
- interface ZLinkPublishSlotWaiter {
146
- readonly resolve: (acquired: boolean) => void;
147
- readonly reject: (error: unknown) => void;
148
- readonly signal?: AbortSignal;
149
- abortHandler?: () => void;
150
- timeout?: ReturnType<typeof setTimeout>;
151
- deadlineMs?: number;
152
- settled: boolean;
153
- }
154
-
155
- interface ZLinkPublishSlotQueue {
156
- readonly waiters: Array<ZLinkPublishSlotWaiter | undefined>;
157
- head: number;
158
- count: number;
159
- }
160
-
161
145
  export class ZLinkSpotNodeRuntimeManager {
162
146
  private readonly meshNodes = new Map<string, ZLinkBackendMeshNode>();
163
147
  private readonly meshPumps = new Map<string, ZLinkMeshDispatchPump>();
@@ -168,8 +152,6 @@ export class ZLinkSpotNodeRuntimeManager {
168
152
  string,
169
153
  ReturnType<ZLinkBackendMeshNode['createPublisher']>
170
154
  >();
171
- private readonly activePublishes = new Set<string>();
172
- private readonly publishSlotWaiters = new Map<string, ZLinkPublishSlotQueue>();
173
155
  private disposed = false;
174
156
  private readonly autoConnectLoops: ZLinkAutoConnectLoop[] = [];
175
157
  private readonly publishedMeshNodeDescriptors =
@@ -829,7 +811,6 @@ export class ZLinkSpotNodeRuntimeManager {
829
811
  this.meshPumps.clear();
830
812
  const publishers = [...this.publishers.values()];
831
813
  this.publishers.clear();
832
- this.rejectPublishSlotWaiters(runtimeShutdownError());
833
814
  const errors: unknown[] = [];
834
815
  const settle = async (operations: readonly Promise<unknown>[]) => {
835
816
  const results = await Promise.allSettled(operations);
@@ -1120,76 +1101,32 @@ export class ZLinkSpotNodeRuntimeManager {
1120
1101
  if (publisher === undefined) {
1121
1102
  throw new ZLinkConfigurationException(`RouteMesh '${meshName}' publisher is not started.`);
1122
1103
  }
1123
- const sendTimeoutMs = this.options.registration.spotNodes
1124
- .get(meshName)?.publisherConfig?.sendTimeoutMs ?? 1000;
1125
- const slot = this.acquirePublishSlot(meshName, sendTimeoutMs, signal);
1126
- if (slot === false) {
1127
- // The bounded executor-admission tokens are exhausted. Do not retain the
1128
- // payload, cancellation state, or a timer for this hard-overload call.
1129
- return Promise.resolve(this.publishTimedOut());
1130
- }
1131
- if (slot === true) {
1132
- return this.executePublish(
1133
- publisher,
1134
- meshName,
1135
- channelName,
1136
- topic,
1137
- packetName,
1138
- event,
1139
- metadata
1140
- );
1141
- }
1142
- return slot.then((acquired) => acquired
1143
- ? this.executePublish(
1144
- publisher,
1145
- meshName,
1146
- channelName,
1147
- topic,
1148
- packetName,
1149
- event,
1150
- metadata
1151
- )
1152
- : this.publishTimedOut());
1104
+ return this.executePublish(publisher, channelName, topic, packetName, event, metadata);
1153
1105
  }
1154
1106
 
1155
1107
  private async executePublish(
1156
1108
  publisher: ReturnType<ZLinkBackendMeshNode['createPublisher']>,
1157
- meshName: string,
1158
1109
  channelName: string,
1159
1110
  topic: string,
1160
1111
  packetName: string | undefined,
1161
1112
  event: Message,
1162
1113
  metadata: ReadonlyMap<string, string>
1163
1114
  ): Promise<ZLinkSubmitResult> {
1164
- // The slot is the source-local admission boundary. Once publishAsync takes
1165
- // the owned frames, target processing cannot change the caller terminal.
1166
- // Call-scoped flow (spec 27 §4): the fanout envelope flow pair does not
1167
- // outlive this publish call.
1168
- try {
1169
- const parts = runWithOutboundFlow(
1170
- this.options.dispatchErrors?.flow.flowCreationEnabled() ?? true,
1171
- () => encodeChannelPublishEnvelopeParts(
1172
- channelName,
1173
- topic,
1174
- packetName,
1175
- event,
1176
- undefined,
1177
- this.options.dispatchErrors?.flow.flowCreationEnabled() ?? true,
1178
- metadata
1179
- )
1180
- );
1181
- const processing = publisher.publishAsync(
1115
+ const parts = runWithOutboundFlow(
1116
+ this.options.dispatchErrors?.flow.flowCreationEnabled() ?? true,
1117
+ () => encodeChannelPublishEnvelopeParts(
1182
1118
  channelName,
1183
1119
  topic,
1184
- parts,
1120
+ packetName,
1121
+ event,
1185
1122
  undefined,
1186
- undefined
1187
- );
1188
- void Promise.resolve(processing).catch(() => undefined);
1189
- return Promise.resolve({ status: ZLinkSubmitStatus.Submitted });
1190
- } finally {
1191
- this.releasePublishSlot(meshName);
1192
- }
1123
+ this.options.dispatchErrors?.flow.flowCreationEnabled() ?? true,
1124
+ metadata
1125
+ )
1126
+ );
1127
+ const processing = publisher.publishAsync(channelName, topic, parts, undefined, undefined);
1128
+ void Promise.resolve(processing).catch(() => undefined);
1129
+ return Promise.resolve({ status: ZLinkSubmitStatus.Submitted });
1193
1130
  }
1194
1131
 
1195
1132
  tryPublish(
@@ -1242,163 +1179,6 @@ export class ZLinkSpotNodeRuntimeManager {
1242
1179
  }
1243
1180
  }
1244
1181
 
1245
- private acquirePublishSlot(
1246
- meshName: string,
1247
- timeoutMs: number,
1248
- signal?: AbortSignal
1249
- ): boolean | Promise<boolean> {
1250
- if (!this.activePublishes.has(meshName)) {
1251
- this.activePublishes.add(meshName);
1252
- return true;
1253
- }
1254
- const waiterCapacity = Math.max(
1255
- 1,
1256
- this.options.registration.spotNodes
1257
- .get(meshName)?.publisherConfig?.sendHighWaterMark ?? 1
1258
- );
1259
- const queue = this.publishSlotWaiters.get(meshName) ?? {
1260
- waiters: [],
1261
- head: 0,
1262
- count: 0
1263
- };
1264
- if (queue.count >= waiterCapacity) {
1265
- // This call has no bounded executor-admission token. Fail without
1266
- // retaining its payload in a pending work queue.
1267
- return false;
1268
- }
1269
- return new Promise<boolean>((resolve, reject) => {
1270
- const waiter: ZLinkPublishSlotWaiter = {
1271
- resolve,
1272
- reject,
1273
- signal,
1274
- settled: false
1275
- };
1276
- if (timeoutMs !== -1) {
1277
- waiter.deadlineMs = performance.now() + Math.max(0, timeoutMs);
1278
- }
1279
- queue.waiters.push(waiter);
1280
- queue.count += 1;
1281
- this.publishSlotWaiters.set(meshName, queue);
1282
- if (timeoutMs !== -1) {
1283
- waiter.timeout = setTimeout(() => this.settlePublishSlotWaiter(
1284
- meshName,
1285
- waiter,
1286
- false
1287
- ), Math.max(0, timeoutMs));
1288
- }
1289
- if (signal !== undefined) {
1290
- waiter.abortHandler = () => this.rejectPublishSlotWaiter(
1291
- meshName,
1292
- waiter,
1293
- signal.reason ?? createAbortError()
1294
- );
1295
- signal.addEventListener('abort', waiter.abortHandler, { once: true });
1296
- }
1297
- });
1298
- }
1299
-
1300
- private publishTimedOut(): ZLinkSubmitResult {
1301
- return { status: ZLinkSubmitStatus.TimedOut };
1302
- }
1303
-
1304
- private releasePublishSlot(meshName: string): void {
1305
- const queue = this.publishSlotWaiters.get(meshName);
1306
- while (queue !== undefined && queue.count > 0) {
1307
- const waiter = this.takePublishSlotWaiter(queue);
1308
- if (waiter === undefined) continue;
1309
- if (waiter.settled) continue;
1310
- if (waiter.deadlineMs !== undefined && performance.now() >= waiter.deadlineMs) {
1311
- this.cleanupPublishSlotWaiter(waiter);
1312
- waiter.settled = true;
1313
- waiter.resolve(false);
1314
- continue;
1315
- }
1316
- this.cleanupPublishSlotWaiter(waiter);
1317
- waiter.settled = true;
1318
- waiter.resolve(true);
1319
- if (queue.count === 0) this.publishSlotWaiters.delete(meshName);
1320
- return;
1321
- }
1322
- this.publishSlotWaiters.delete(meshName);
1323
- this.activePublishes.delete(meshName);
1324
- }
1325
-
1326
- private settlePublishSlotWaiter(
1327
- meshName: string,
1328
- waiter: ZLinkPublishSlotWaiter,
1329
- acquired: boolean
1330
- ): void {
1331
- if (waiter.settled) return;
1332
- waiter.settled = true;
1333
- this.removePublishSlotWaiter(meshName, waiter);
1334
- this.cleanupPublishSlotWaiter(waiter);
1335
- waiter.resolve(acquired);
1336
- }
1337
-
1338
- private rejectPublishSlotWaiter(
1339
- meshName: string,
1340
- waiter: ZLinkPublishSlotWaiter,
1341
- error: unknown
1342
- ): void {
1343
- if (waiter.settled) return;
1344
- waiter.settled = true;
1345
- this.removePublishSlotWaiter(meshName, waiter);
1346
- this.cleanupPublishSlotWaiter(waiter);
1347
- waiter.reject(error);
1348
- }
1349
-
1350
- private removePublishSlotWaiter(meshName: string, waiter: ZLinkPublishSlotWaiter): void {
1351
- const queue = this.publishSlotWaiters.get(meshName);
1352
- if (queue === undefined) return;
1353
- const index = queue.waiters.indexOf(waiter, queue.head);
1354
- if (index >= 0) {
1355
- queue.waiters[index] = undefined;
1356
- queue.count -= 1;
1357
- this.compactPublishSlotWaiters(queue);
1358
- }
1359
- if (queue.count === 0) this.publishSlotWaiters.delete(meshName);
1360
- }
1361
-
1362
- private cleanupPublishSlotWaiter(waiter: ZLinkPublishSlotWaiter): void {
1363
- if (waiter.timeout !== undefined) clearTimeout(waiter.timeout);
1364
- if (waiter.abortHandler !== undefined) {
1365
- waiter.signal?.removeEventListener('abort', waiter.abortHandler);
1366
- }
1367
- }
1368
-
1369
- private rejectPublishSlotWaiters(error: unknown): void {
1370
- for (const [meshName, queue] of this.publishSlotWaiters) {
1371
- let waiter: ZLinkPublishSlotWaiter | undefined;
1372
- while ((waiter = this.takePublishSlotWaiter(queue)) !== undefined) {
1373
- this.rejectPublishSlotWaiter(meshName, waiter, error);
1374
- }
1375
- this.publishSlotWaiters.delete(meshName);
1376
- }
1377
- this.activePublishes.clear();
1378
- }
1379
-
1380
- private takePublishSlotWaiter(queue: ZLinkPublishSlotQueue): ZLinkPublishSlotWaiter | undefined {
1381
- while (queue.head < queue.waiters.length && queue.waiters[queue.head] === undefined) {
1382
- queue.head += 1;
1383
- }
1384
- const waiter = queue.waiters[queue.head];
1385
- if (waiter === undefined) return undefined;
1386
- queue.waiters[queue.head] = undefined;
1387
- queue.head += 1;
1388
- queue.count -= 1;
1389
- this.compactPublishSlotWaiters(queue);
1390
- return waiter;
1391
- }
1392
-
1393
- private compactPublishSlotWaiters(queue: ZLinkPublishSlotQueue): void {
1394
- if (queue.count === 0) {
1395
- queue.waiters.length = 0;
1396
- queue.head = 0;
1397
- } else if (queue.head >= 1024 && queue.head * 2 >= queue.waiters.length) {
1398
- queue.waiters.splice(0, queue.head);
1399
- queue.head = 0;
1400
- }
1401
- }
1402
1182
  }
1403
1183
 
1404
1184
  function runtimeShutdownError(): ZLinkFrameworkException {
@@ -38,14 +38,7 @@ export class ZLinkSpotSerialTurnExecutor {
38
38
  ) {
39
39
  this.scheduler = new ZLinkSerialExecutionQueue(
40
40
  (record) => this.runQueuedRecord(record),
41
- {
42
- ...schedulerOptions,
43
- capacityError: schedulerOptions?.capacityError ?? (() =>
44
- createInternalFrameworkException(
45
- ZLinkFrameworkInternalErrorKind.WorkerQueueFull,
46
- 'Spot execution queue capacity was exceeded.'
47
- ))
48
- }
41
+ schedulerOptions
49
42
  );
50
43
  }
51
44