@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.
- package/dist/.tsbuildinfo +1 -1
- package/dist/contracts/Configuration/EndpointNotation.d.ts +5 -4
- package/dist/contracts/Configuration/EndpointNotation.js +5 -4
- package/dist/contracts/Configuration/InternalDefaults.d.ts +0 -1
- package/dist/contracts/Configuration/InternalDefaults.js +1 -2
- package/dist/contracts/Configuration/RegistrationNormalizers.js +1 -2
- package/dist/contracts/Configuration/RegistrationTypes.d.ts +2 -5
- package/dist/contracts/Configuration/RegistrationValidators.js +0 -1
- package/dist/contracts/Errors/ZLinkFrameworkException.d.ts +6 -7
- package/dist/contracts/Errors/ZLinkFrameworkException.js +6 -7
- package/dist/runtime/actors/actor-client.js +1 -1
- package/dist/runtime/actors/actor-mailbox.js +1 -5
- package/dist/runtime/backend/contracts/index.d.ts +2 -0
- package/dist/runtime/backend/mesh-completion-table.d.ts +1 -3
- package/dist/runtime/backend/mesh-completion-table.js +11 -15
- package/dist/runtime/backend/mesh-dispatch-pump.d.ts +11 -4
- package/dist/runtime/backend/mesh-dispatch-pump.js +201 -134
- package/dist/runtime/backend/node/node-backend-adapter-factory.d.ts +3 -4
- package/dist/runtime/backend/node/node-backend-adapter-factory.js +51 -37
- package/dist/runtime/backend/node/node-backend-adapter-support.d.ts +2 -2
- package/dist/runtime/backend/node/node-backend-adapter-support.js +2 -2
- package/dist/runtime/backend/node/node-dealer-channel-client-transport.js +2 -2
- package/dist/runtime/backend/node/node-event-loop-poller.d.ts +16 -0
- package/dist/runtime/backend/node/node-event-loop-poller.js +49 -0
- package/dist/runtime/backend/node/node-raw-binding-port.js +24 -41
- package/dist/runtime/backend/node/node-raw-mesh-backend.d.ts +8 -8
- package/dist/runtime/backend/node/node-raw-mesh-backend.js +100 -107
- package/dist/runtime/backend/node/node-socket-backend-adapter.d.ts +3 -2
- package/dist/runtime/backend/node/node-socket-backend-adapter.js +18 -67
- package/dist/runtime/backend/raw-binding-port.d.ts +1 -0
- package/dist/runtime/backend/runtime-values.d.ts +6 -1
- package/dist/runtime/backend/runtime-values.js +6 -0
- package/dist/runtime/channels/channel-dispatchers.js +2 -2
- package/dist/runtime/channels/channel-envelope.js +324 -121
- package/dist/runtime/channels/channel-multipart.d.ts +8 -1
- package/dist/runtime/channels/channel-outbound-operations.js +0 -2
- package/dist/runtime/channels/channel-receive-loops.d.ts +1 -3
- package/dist/runtime/channels/channel-receive-loops.js +25 -65
- package/dist/runtime/channels/channel-socket-registry.js +14 -1
- package/dist/runtime/channels/channel-transports.d.ts +2 -1
- package/dist/runtime/channels/channel-transports.js +12 -10
- package/dist/runtime/channels/client-server-location-runtime.js +15 -3
- package/dist/runtime/execution/serial-execution-queue.d.ts +5 -18
- package/dist/runtime/execution/serial-execution-queue.js +16 -80
- package/dist/runtime/foundation/event-loop-resources.d.ts +0 -3
- package/dist/runtime/foundation/event-loop-resources.js +2 -11
- package/dist/runtime/foundation/operation-registry.d.ts +2 -8
- package/dist/runtime/foundation/operation-registry.js +3 -21
- package/dist/runtime/foundation/raw-service-mesh-runtime.d.ts +19 -7
- package/dist/runtime/foundation/raw-service-mesh-runtime.js +56 -10
- package/dist/runtime/foundation/service-mailbox.d.ts +1 -7
- package/dist/runtime/foundation/service-mailbox.js +3 -2
- package/dist/runtime/foundation/service-runtime-contracts.d.ts +2 -1
- package/dist/runtime/foundation/service-stateful-registry.d.ts +1 -1
- package/dist/runtime/foundation/service-stateful-registry.js +2 -2
- package/dist/runtime/foundation/service-stateful-runtime.js +14 -14
- package/dist/runtime/framework-errors-internal.d.ts +4 -4
- package/dist/runtime/framework-errors-internal.js +8 -10
- package/dist/runtime/host/actor-placement-coordinator.js +1 -1
- package/dist/runtime/host/index.js +1 -1
- package/dist/runtime/host/service-relocation-host-runtime.js +0 -4
- package/dist/runtime/host/spot-address-transport.js +1 -1
- package/dist/runtime/host/user-spot-creation-coordinator.js +1 -1
- package/dist/runtime/locations/location-store-repository.d.ts +1 -0
- package/dist/runtime/locations/location-store-repository.js +64 -75
- package/dist/runtime/spots/spot-node-runtime-manager.d.ts +0 -12
- package/dist/runtime/spots/spot-node-runtime-manager.js +6 -166
- package/dist/runtime/spots/spot-serial-turn-executor.js +1 -4
- package/dist/runtime/spots/spot-timer.d.ts +3 -0
- package/dist/runtime/spots/spot-timer.js +87 -18
- package/dist/runtime/streams/session-serial-executor.js +1 -3
- package/dist/runtime/streams/stream-session-runtime.d.ts +2 -2
- package/dist/runtime/streams/stream-session-runtime.js +13 -19
- package/dist/runtime/workers/index.d.ts +0 -1
- package/dist/runtime/workers/index.js +1 -9
- package/package.json +9 -3
- package/src/contracts/Configuration/EndpointNotation.ts +5 -4
- package/src/contracts/Configuration/InternalDefaults.ts +0 -1
- package/src/contracts/Configuration/RegistrationNormalizers.ts +2 -4
- package/src/contracts/Configuration/RegistrationTypes.ts +2 -5
- package/src/contracts/Configuration/RegistrationValidators.ts +0 -1
- package/src/contracts/Errors/ZLinkFrameworkException.ts +6 -7
- package/src/runtime/actors/actor-client.ts +1 -1
- package/src/runtime/actors/actor-mailbox.ts +1 -12
- package/src/runtime/backend/contracts/index.ts +2 -0
- package/src/runtime/backend/mesh-completion-table.ts +12 -23
- package/src/runtime/backend/mesh-dispatch-pump.ts +206 -138
- package/src/runtime/backend/node/node-backend-adapter-factory.ts +66 -35
- package/src/runtime/backend/node/node-backend-adapter-support.ts +4 -4
- package/src/runtime/backend/node/node-dealer-channel-client-transport.ts +2 -2
- package/src/runtime/backend/node/node-event-loop-poller.ts +55 -0
- package/src/runtime/backend/node/node-raw-binding-port.ts +30 -45
- package/src/runtime/backend/node/node-raw-mesh-backend.ts +97 -110
- package/src/runtime/backend/node/node-socket-backend-adapter.ts +23 -69
- package/src/runtime/backend/raw-binding-port.ts +1 -0
- package/src/runtime/backend/runtime-values.ts +13 -1
- package/src/runtime/channels/channel-dispatchers.ts +2 -2
- package/src/runtime/channels/channel-envelope.ts +379 -109
- package/src/runtime/channels/channel-multipart.ts +9 -1
- package/src/runtime/channels/channel-outbound-operations.ts +0 -1
- package/src/runtime/channels/channel-receive-loops.ts +24 -75
- package/src/runtime/channels/channel-socket-registry.ts +17 -1
- package/src/runtime/channels/channel-transports.ts +20 -10
- package/src/runtime/channels/client-server-location-runtime.ts +16 -2
- package/src/runtime/execution/serial-execution-queue.ts +16 -108
- package/src/runtime/foundation/event-loop-resources.ts +2 -11
- package/src/runtime/foundation/operation-registry.ts +2 -22
- package/src/runtime/foundation/raw-service-mesh-runtime.ts +74 -19
- package/src/runtime/foundation/service-mailbox.ts +3 -13
- package/src/runtime/foundation/service-runtime-contracts.ts +2 -1
- package/src/runtime/foundation/service-stateful-registry.ts +2 -2
- package/src/runtime/foundation/service-stateful-runtime.ts +14 -14
- package/src/runtime/framework-errors-internal.ts +8 -10
- package/src/runtime/host/actor-placement-coordinator.ts +1 -1
- package/src/runtime/host/index.ts +2 -1
- package/src/runtime/host/service-relocation-host-runtime.ts +0 -4
- package/src/runtime/host/spot-address-transport.ts +1 -1
- package/src/runtime/host/user-spot-creation-coordinator.ts +1 -1
- package/src/runtime/locations/location-store-repository.ts +118 -80
- package/src/runtime/spots/spot-node-runtime-manager.ts +13 -233
- package/src/runtime/spots/spot-serial-turn-executor.ts +1 -8
- package/src/runtime/spots/spot-timer.ts +109 -18
- package/src/runtime/streams/session-serial-executor.ts +1 -7
- package/src/runtime/streams/stream-session-runtime.ts +13 -17
- 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
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
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 (
|
|
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
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
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 (
|
|
3892
|
-
|
|
3893
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
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
|
-
|
|
1120
|
+
packetName,
|
|
1121
|
+
event,
|
|
1185
1122
|
undefined,
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
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
|
|