@zlink-systems/framework 0.10.0 → 0.11.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/runtime/backend/runtime-values.d.ts +1 -0
- package/dist/runtime/backend/runtime-values.js +6 -0
- package/dist/runtime/channels/channel-socket-registry.js +11 -0
- package/dist/runtime/channels/client-server-location-runtime.js +15 -3
- package/dist/runtime/locations/location-store-repository.d.ts +1 -0
- package/dist/runtime/locations/location-store-repository.js +64 -75
- package/package.json +9 -3
- package/src/runtime/backend/runtime-values.ts +6 -0
- package/src/runtime/channels/channel-socket-registry.ts +13 -0
- package/src/runtime/channels/client-server-location-runtime.ts +16 -2
- package/src/runtime/locations/location-store-repository.ts +118 -80
|
@@ -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.');
|