@peerbit/shared-log 16.0.21 → 16.0.22
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/src/index.d.ts +2 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +209 -18
- package/dist/src/index.js.map +1 -1
- package/dist/src/replication-info-v2-receive.d.ts +11 -0
- package/dist/src/replication-info-v2-receive.d.ts.map +1 -1
- package/dist/src/replication-info-v2-receive.js +24 -5
- package/dist/src/replication-info-v2-receive.js.map +1 -1
- package/dist/src/replication-info-v2-send.d.ts +15 -0
- package/dist/src/replication-info-v2-send.d.ts.map +1 -1
- package/dist/src/replication-info-v2-send.js +89 -30
- package/dist/src/replication-info-v2-send.js.map +1 -1
- package/package.json +11 -11
- package/src/index.ts +305 -17
- package/src/replication-info-v2-receive.ts +32 -5
- package/src/replication-info-v2-send.ts +150 -38
package/src/index.ts
CHANGED
|
@@ -719,6 +719,14 @@ type PersistedDeliveryPlanningRecord<T, R extends "u32" | "u64"> = Readonly<{
|
|
|
719
719
|
createFullPlanningSource?: () => Entry<T>;
|
|
720
720
|
}>;
|
|
721
721
|
|
|
722
|
+
type PersistedAppendBackfillSource<T, R extends "u32" | "u64"> = {
|
|
723
|
+
entry: Entry<T>;
|
|
724
|
+
coordinates: NumberFromType<R>[];
|
|
725
|
+
assignmentExtraLeaders: LeaderMap;
|
|
726
|
+
deliveryExtraTargets: Set<string>;
|
|
727
|
+
extrasOwnershipRevision?: number;
|
|
728
|
+
};
|
|
729
|
+
|
|
722
730
|
type NativeBackboneSimpleDocumentProjectionPlan = {
|
|
723
731
|
documentVariantType?: "u8" | "string";
|
|
724
732
|
documentVariantValue?: string;
|
|
@@ -5475,6 +5483,10 @@ export class SharedLog<
|
|
|
5475
5483
|
): { capabilitySession: bigint; peerSession: PeerSession } | undefined {
|
|
5476
5484
|
const capabilitySession = this._peerSyncCapabilitySessions.get(peerHash);
|
|
5477
5485
|
const peerSession = this._peerSessions.current(peerHash);
|
|
5486
|
+
const receiveEpoch = this._peerSessions.receiveEpoch(peerHash);
|
|
5487
|
+
const requiredCapabilities =
|
|
5488
|
+
SYNC_CAPABILITY_PERSISTED_ENTRY_RECEIPTS |
|
|
5489
|
+
SYNC_CAPABILITY_REPLICATION_INFO_V2_CONFIRM;
|
|
5478
5490
|
if (
|
|
5479
5491
|
capabilitySession == null ||
|
|
5480
5492
|
!peerSession ||
|
|
@@ -5482,8 +5494,14 @@ export class SharedLog<
|
|
|
5482
5494
|
!this._peerSessions.isCurrent(peerHash, peerSession) ||
|
|
5483
5495
|
!this._peerSyncCapabilityTimestamps.has(peerHash) ||
|
|
5484
5496
|
((this._peerSyncCapabilities.get(peerHash) ?? 0) &
|
|
5485
|
-
|
|
5486
|
-
|
|
5497
|
+
requiredCapabilities) !==
|
|
5498
|
+
requiredCapabilities ||
|
|
5499
|
+
!this._v2Receive.isCurrentActive({
|
|
5500
|
+
peerHash,
|
|
5501
|
+
peerSession,
|
|
5502
|
+
receiveEpoch,
|
|
5503
|
+
senderTransportSession: capabilitySession,
|
|
5504
|
+
})
|
|
5487
5505
|
) {
|
|
5488
5506
|
return undefined;
|
|
5489
5507
|
}
|
|
@@ -5754,6 +5772,10 @@ export class SharedLog<
|
|
|
5754
5772
|
ownershipLifecycleController = this.captureReplicationOwnershipLifecycle(),
|
|
5755
5773
|
persistedDeadline?: PersistedDeliveryDeadline,
|
|
5756
5774
|
transferOnFirstRound = false,
|
|
5775
|
+
onFreshLeaderPlan?: (
|
|
5776
|
+
leadersByEntry: readonly LeaderMap[],
|
|
5777
|
+
ownershipRevision: number,
|
|
5778
|
+
) => void,
|
|
5757
5779
|
): Promise<void> {
|
|
5758
5780
|
const minAcks = Math.floor(delivery.minAcks!);
|
|
5759
5781
|
const records = new Map(
|
|
@@ -5844,6 +5866,8 @@ export class SharedLog<
|
|
|
5844
5866
|
ownershipLifecycleController,
|
|
5845
5867
|
);
|
|
5846
5868
|
if (!isRoundOwnershipCurrent()) continue;
|
|
5869
|
+
onFreshLeaderPlan?.(leadersByEntry, ownershipRevision);
|
|
5870
|
+
if (!isRoundOwnershipCurrent()) continue;
|
|
5847
5871
|
const selfHash = this.node.identity.publicKey.hashcode();
|
|
5848
5872
|
if (needsInitialLeaderCheck) {
|
|
5849
5873
|
needsInitialLeaderCheck = false;
|
|
@@ -5946,6 +5970,32 @@ export class SharedLog<
|
|
|
5946
5970
|
current.peerSession === captured.peerSession
|
|
5947
5971
|
);
|
|
5948
5972
|
};
|
|
5973
|
+
try {
|
|
5974
|
+
await this._v2Send.confirmLatestForPeer(
|
|
5975
|
+
{
|
|
5976
|
+
peerHash: peer,
|
|
5977
|
+
peerSession: captured.peerSession,
|
|
5978
|
+
receiverTransportSession: captured.capabilitySession,
|
|
5979
|
+
},
|
|
5980
|
+
{
|
|
5981
|
+
timeout: getAttemptTimeout(),
|
|
5982
|
+
signal: roundSignal,
|
|
5983
|
+
},
|
|
5984
|
+
);
|
|
5985
|
+
} catch {
|
|
5986
|
+
// The exact receiver generation did not prove that it applied
|
|
5987
|
+
// our latest role state. Replan instead of transferring to a
|
|
5988
|
+
// peer that can still make a stale admission decision.
|
|
5989
|
+
if (transferAllOnRound && isPeerRoundCurrent()) {
|
|
5990
|
+
const state = ensureRepairState();
|
|
5991
|
+
for (const hash of hashes) state.hashes.add(hash);
|
|
5992
|
+
}
|
|
5993
|
+
return;
|
|
5994
|
+
}
|
|
5995
|
+
if (!isPeerRoundCurrent()) {
|
|
5996
|
+
purgePeerDeliveryState(peer);
|
|
5997
|
+
return;
|
|
5998
|
+
}
|
|
5949
5999
|
const repairs = repairsByPeer.get(peer)?.hashes;
|
|
5950
6000
|
const transferHashes = transferAllOnRound
|
|
5951
6001
|
? hashes
|
|
@@ -6122,6 +6172,9 @@ export class SharedLog<
|
|
|
6122
6172
|
replicas,
|
|
6123
6173
|
ownershipLifecycleController,
|
|
6124
6174
|
);
|
|
6175
|
+
if (!isRoundOwnershipCurrent()) return false;
|
|
6176
|
+
onFreshLeaderPlan?.(validatedLeaders, ownershipRevision);
|
|
6177
|
+
if (!isRoundOwnershipCurrent()) return false;
|
|
6125
6178
|
for (let index = 0; index < entryArray.length; index++) {
|
|
6126
6179
|
if (signal.aborted || !isRoundOwnershipCurrent()) {
|
|
6127
6180
|
if (signal.aborted) {
|
|
@@ -6205,6 +6258,75 @@ export class SharedLog<
|
|
|
6205
6258
|
}
|
|
6206
6259
|
}
|
|
6207
6260
|
|
|
6261
|
+
private async collectDeferredAppendBackfillExtras(
|
|
6262
|
+
entry: Entry<T>,
|
|
6263
|
+
replicas: number,
|
|
6264
|
+
baseLeaders: LeaderMap,
|
|
6265
|
+
nativeDeliveryPlan: AppendDeliveryPlan | undefined,
|
|
6266
|
+
ownershipLifecycleController: AbortController,
|
|
6267
|
+
): Promise<
|
|
6268
|
+
Omit<PersistedAppendBackfillSource<T, R>, "entry" | "coordinates">
|
|
6269
|
+
> {
|
|
6270
|
+
const ownershipRevision =
|
|
6271
|
+
this._instanceLifecycle?._receiveOwnershipRevision ?? 0;
|
|
6272
|
+
const ownershipWasStable =
|
|
6273
|
+
this.isReceiveOwnershipSnapshotStable(ownershipRevision);
|
|
6274
|
+
const assignmentExtraLeaders: LeaderMap = new Map();
|
|
6275
|
+
const deliveryExtraTargets = new Set<string>();
|
|
6276
|
+
if (nativeDeliveryPlan) {
|
|
6277
|
+
for (const peer of nativeDeliveryPlan.repairTargets) {
|
|
6278
|
+
// The sampled entry leaders are replaced by settlement's fresh plan.
|
|
6279
|
+
// Retain only native repair additions that were outside that base map.
|
|
6280
|
+
if (!baseLeaders.has(peer)) deliveryExtraTargets.add(peer);
|
|
6281
|
+
}
|
|
6282
|
+
} else {
|
|
6283
|
+
const selfHash = this.node.identity.publicKey.hashcode();
|
|
6284
|
+
const fullReplicaDeliveryCandidates =
|
|
6285
|
+
await this.getNativeFullReplicaDeliveryCandidates(replicas, selfHash);
|
|
6286
|
+
this.throwIfReplicationOwnershipLifecycleInactive(
|
|
6287
|
+
ownershipLifecycleController,
|
|
6288
|
+
);
|
|
6289
|
+
if (replicas >= Math.max(1, fullReplicaDeliveryCandidates.size)) {
|
|
6290
|
+
for (const peer of fullReplicaDeliveryCandidates) {
|
|
6291
|
+
if (!baseLeaders.has(peer)) {
|
|
6292
|
+
assignmentExtraLeaders.set(peer, { intersecting: true });
|
|
6293
|
+
}
|
|
6294
|
+
}
|
|
6295
|
+
}
|
|
6296
|
+
}
|
|
6297
|
+
|
|
6298
|
+
const referenceLeaders: LeaderMap = new Map();
|
|
6299
|
+
for await (const message of createExchangeHeadsMessages(this.log, [
|
|
6300
|
+
entry,
|
|
6301
|
+
])) {
|
|
6302
|
+
this.throwIfReplicationOwnershipLifecycleInactive(
|
|
6303
|
+
ownershipLifecycleController,
|
|
6304
|
+
);
|
|
6305
|
+
await this._mergeLeadersFromGidReferences(
|
|
6306
|
+
message,
|
|
6307
|
+
replicas,
|
|
6308
|
+
referenceLeaders,
|
|
6309
|
+
ownershipLifecycleController,
|
|
6310
|
+
{ freshLeaderPlan: true },
|
|
6311
|
+
);
|
|
6312
|
+
}
|
|
6313
|
+
this.throwIfReplicationOwnershipLifecycleInactive(
|
|
6314
|
+
ownershipLifecycleController,
|
|
6315
|
+
);
|
|
6316
|
+
for (const peer of referenceLeaders.keys()) {
|
|
6317
|
+
deliveryExtraTargets.add(peer);
|
|
6318
|
+
}
|
|
6319
|
+
return {
|
|
6320
|
+
assignmentExtraLeaders,
|
|
6321
|
+
deliveryExtraTargets,
|
|
6322
|
+
extrasOwnershipRevision:
|
|
6323
|
+
ownershipWasStable &&
|
|
6324
|
+
this.isReceiveOwnershipSnapshotStable(ownershipRevision)
|
|
6325
|
+
? ownershipRevision
|
|
6326
|
+
: undefined,
|
|
6327
|
+
};
|
|
6328
|
+
}
|
|
6329
|
+
|
|
6208
6330
|
private async _appendDeliverToReplicators(
|
|
6209
6331
|
entry: Entry<T>,
|
|
6210
6332
|
coordinates: NumberFromType<R>[],
|
|
@@ -6299,7 +6421,11 @@ export class SharedLog<
|
|
|
6299
6421
|
if (!delivery) {
|
|
6300
6422
|
for (const peer of nativeDeliveryPlan.repairTargets) {
|
|
6301
6423
|
throwIfInactive();
|
|
6302
|
-
this.queueAppendBackfill(
|
|
6424
|
+
this.queueAppendBackfill(
|
|
6425
|
+
peer,
|
|
6426
|
+
entryReplicatedForRepair,
|
|
6427
|
+
ownershipLifecycleController,
|
|
6428
|
+
);
|
|
6303
6429
|
}
|
|
6304
6430
|
if (nativeDeliveryPlan.defaultSendSilent) {
|
|
6305
6431
|
const rawTargets = this.canUseLiveRawGossip(
|
|
@@ -6372,7 +6498,11 @@ export class SharedLog<
|
|
|
6372
6498
|
}
|
|
6373
6499
|
for (const peer of nativeDeliveryPlan.repairTargets) {
|
|
6374
6500
|
throwIfInactive();
|
|
6375
|
-
this.queueAppendBackfill(
|
|
6501
|
+
this.queueAppendBackfill(
|
|
6502
|
+
peer,
|
|
6503
|
+
entryReplicatedForRepair,
|
|
6504
|
+
ownershipLifecycleController,
|
|
6505
|
+
);
|
|
6376
6506
|
}
|
|
6377
6507
|
continue;
|
|
6378
6508
|
}
|
|
@@ -6427,7 +6557,11 @@ export class SharedLog<
|
|
|
6427
6557
|
// delivery acks, we still need a targeted backfill source of truth for the
|
|
6428
6558
|
// authoritative recipients or one entry can get stuck at 2/3 replicas
|
|
6429
6559
|
// forever. Best-effort fallback subscribers are not repair-worthy.
|
|
6430
|
-
this.queueAppendBackfill(
|
|
6560
|
+
this.queueAppendBackfill(
|
|
6561
|
+
peer,
|
|
6562
|
+
entryReplicatedForRepair,
|
|
6563
|
+
ownershipLifecycleController,
|
|
6564
|
+
);
|
|
6431
6565
|
}
|
|
6432
6566
|
if (isLeader) {
|
|
6433
6567
|
const rawTargets = this.canUseLiveRawGossip(set, selfHash);
|
|
@@ -6536,7 +6670,11 @@ export class SharedLog<
|
|
|
6536
6670
|
// Direct append delivery is intentionally optimistic. Queue one delayed,
|
|
6537
6671
|
// batched maybe-sync pass for the intended recipients so stable 3-peer
|
|
6538
6672
|
// append workloads do not depend on perfect first-try delivery ordering.
|
|
6539
|
-
this.queueAppendBackfill(
|
|
6673
|
+
this.queueAppendBackfill(
|
|
6674
|
+
peer,
|
|
6675
|
+
entryReplicatedForRepair,
|
|
6676
|
+
ownershipLifecycleController,
|
|
6677
|
+
);
|
|
6540
6678
|
}
|
|
6541
6679
|
}
|
|
6542
6680
|
|
|
@@ -6551,6 +6689,7 @@ export class SharedLog<
|
|
|
6551
6689
|
minReplicasValue: number,
|
|
6552
6690
|
leaders: LeaderMap,
|
|
6553
6691
|
ownershipLifecycleController = this.captureReplicationOwnershipLifecycle(),
|
|
6692
|
+
options?: { freshLeaderPlan?: boolean },
|
|
6554
6693
|
) {
|
|
6555
6694
|
const throwIfInactive = () =>
|
|
6556
6695
|
this.throwIfReplicationOwnershipLifecycleInactive(
|
|
@@ -6576,13 +6715,13 @@ export class SharedLog<
|
|
|
6576
6715
|
found = await this.findLeadersFromEntry(
|
|
6577
6716
|
gidEntry,
|
|
6578
6717
|
minReplicasValue,
|
|
6579
|
-
undefined,
|
|
6718
|
+
options?.freshLeaderPlan ? { freshLeaderPlan: true } : undefined,
|
|
6580
6719
|
ownershipLifecycleController,
|
|
6581
6720
|
);
|
|
6582
6721
|
} else {
|
|
6583
6722
|
found = await this._findLeaders(
|
|
6584
6723
|
coordinates,
|
|
6585
|
-
undefined,
|
|
6724
|
+
options?.freshLeaderPlan ? { freshLeaderPlan: true } : undefined,
|
|
6586
6725
|
ownershipLifecycleController,
|
|
6587
6726
|
);
|
|
6588
6727
|
}
|
|
@@ -6891,8 +7030,13 @@ export class SharedLog<
|
|
|
6891
7030
|
private canCacheLeaderSelectionContext(options?: {
|
|
6892
7031
|
roleAge?: number;
|
|
6893
7032
|
candidates?: Iterable<string>;
|
|
7033
|
+
freshLeaderPlan?: boolean;
|
|
6894
7034
|
}) {
|
|
6895
|
-
return
|
|
7035
|
+
return (
|
|
7036
|
+
options?.roleAge == null &&
|
|
7037
|
+
options?.candidates == null &&
|
|
7038
|
+
options?.freshLeaderPlan !== true
|
|
7039
|
+
);
|
|
6896
7040
|
}
|
|
6897
7041
|
|
|
6898
7042
|
private cloneLeaderSelectionContext(
|
|
@@ -6910,6 +7054,7 @@ export class SharedLog<
|
|
|
6910
7054
|
private getCachedLeaderSelectionContext(options?: {
|
|
6911
7055
|
roleAge?: number;
|
|
6912
7056
|
candidates?: Iterable<string>;
|
|
7057
|
+
freshLeaderPlan?: boolean;
|
|
6913
7058
|
}): LeaderSelectionContext | undefined {
|
|
6914
7059
|
if (!this.canCacheLeaderSelectionContext(options)) {
|
|
6915
7060
|
return;
|
|
@@ -6926,6 +7071,7 @@ export class SharedLog<
|
|
|
6926
7071
|
| {
|
|
6927
7072
|
roleAge?: number;
|
|
6928
7073
|
candidates?: Iterable<string>;
|
|
7074
|
+
freshLeaderPlan?: boolean;
|
|
6929
7075
|
}
|
|
6930
7076
|
| undefined,
|
|
6931
7077
|
context: LeaderSelectionContext,
|
|
@@ -10191,10 +10337,7 @@ export class SharedLog<
|
|
|
10191
10337
|
});
|
|
10192
10338
|
}
|
|
10193
10339
|
|
|
10194
|
-
private flushAppendBackfill(
|
|
10195
|
-
repairLifecycleController: AbortController = this._instanceLifecycle
|
|
10196
|
-
?.ownershipLifecycleController as AbortController,
|
|
10197
|
-
) {
|
|
10340
|
+
private flushAppendBackfill(repairLifecycleController: AbortController) {
|
|
10198
10341
|
if (
|
|
10199
10342
|
!this.isRepairLifecycleActive(repairLifecycleController) ||
|
|
10200
10343
|
this._appendBackfillPendingByTarget.size === 0
|
|
@@ -10215,9 +10358,11 @@ export class SharedLog<
|
|
|
10215
10358
|
}
|
|
10216
10359
|
}
|
|
10217
10360
|
|
|
10218
|
-
private queueAppendBackfill(
|
|
10219
|
-
|
|
10220
|
-
|
|
10361
|
+
private queueAppendBackfill(
|
|
10362
|
+
target: string,
|
|
10363
|
+
entry: EntryReplicated<R>,
|
|
10364
|
+
repairLifecycleController: AbortController,
|
|
10365
|
+
) {
|
|
10221
10366
|
if (!this.isRepairLifecycleActive(repairLifecycleController)) {
|
|
10222
10367
|
return;
|
|
10223
10368
|
}
|
|
@@ -10249,6 +10394,62 @@ export class SharedLog<
|
|
|
10249
10394
|
this._appendBackfillTimer = timer;
|
|
10250
10395
|
}
|
|
10251
10396
|
|
|
10397
|
+
private queuePersistedAppendBackfill(
|
|
10398
|
+
source: PersistedAppendBackfillSource<T, R>,
|
|
10399
|
+
leaders: LeaderMap,
|
|
10400
|
+
replicas: number,
|
|
10401
|
+
repairLifecycleController: AbortController,
|
|
10402
|
+
ownershipRevision: number,
|
|
10403
|
+
): void {
|
|
10404
|
+
try {
|
|
10405
|
+
if (
|
|
10406
|
+
!this.isRepairLifecycleActive(repairLifecycleController) ||
|
|
10407
|
+
!this.isReceiveOwnershipSnapshotStable(ownershipRevision)
|
|
10408
|
+
) {
|
|
10409
|
+
return;
|
|
10410
|
+
}
|
|
10411
|
+
const assignmentLeaders = new Map(leaders);
|
|
10412
|
+
const deliveryTargets = new Set(leaders.keys());
|
|
10413
|
+
if (source.extrasOwnershipRevision === ownershipRevision) {
|
|
10414
|
+
for (const [peer, sample] of source.assignmentExtraLeaders) {
|
|
10415
|
+
assignmentLeaders.set(peer, sample);
|
|
10416
|
+
}
|
|
10417
|
+
for (const peer of source.deliveryExtraTargets) {
|
|
10418
|
+
deliveryTargets.add(peer);
|
|
10419
|
+
}
|
|
10420
|
+
}
|
|
10421
|
+
const repairEntry = this.createEntryReplicatedForRepair({
|
|
10422
|
+
entry: source.entry,
|
|
10423
|
+
coordinates: source.coordinates,
|
|
10424
|
+
leaders: assignmentLeaders,
|
|
10425
|
+
replicas,
|
|
10426
|
+
});
|
|
10427
|
+
const selfHash = this.node.identity.publicKey.hashcode();
|
|
10428
|
+
for (const peer of assignmentLeaders.keys()) {
|
|
10429
|
+
deliveryTargets.add(peer);
|
|
10430
|
+
}
|
|
10431
|
+
for (const peer of deliveryTargets) {
|
|
10432
|
+
if (peer === selfHash) continue;
|
|
10433
|
+
if (!this.isReceiveOwnershipSnapshotStable(ownershipRevision)) return;
|
|
10434
|
+
try {
|
|
10435
|
+
this.queueAppendBackfill(
|
|
10436
|
+
peer,
|
|
10437
|
+
repairEntry,
|
|
10438
|
+
repairLifecycleController,
|
|
10439
|
+
);
|
|
10440
|
+
} catch (error) {
|
|
10441
|
+
if (this.isRepairLifecycleActive(repairLifecycleController)) {
|
|
10442
|
+
logger.error(error);
|
|
10443
|
+
}
|
|
10444
|
+
}
|
|
10445
|
+
}
|
|
10446
|
+
} catch (error) {
|
|
10447
|
+
if (this.isRepairLifecycleActive(repairLifecycleController)) {
|
|
10448
|
+
logger.error(error);
|
|
10449
|
+
}
|
|
10450
|
+
}
|
|
10451
|
+
}
|
|
10452
|
+
|
|
10252
10453
|
private dispatchMaybeMissingEntries(
|
|
10253
10454
|
target: string,
|
|
10254
10455
|
entries: ReadonlyMap<string, RepairDispatchEntry<R>>,
|
|
@@ -11894,6 +12095,12 @@ export class SharedLog<
|
|
|
11894
12095
|
let persistedPlanningRecord:
|
|
11895
12096
|
| PersistedDeliveryPlanningRecord<T, R>
|
|
11896
12097
|
| undefined;
|
|
12098
|
+
let persistedBackfillSource:
|
|
12099
|
+
| PersistedAppendBackfillSource<T, R>
|
|
12100
|
+
| undefined;
|
|
12101
|
+
let persistedBackfillLeaders: LeaderMap | undefined;
|
|
12102
|
+
let persistedBackfillOwnershipRevision: number | undefined;
|
|
12103
|
+
let localAppendProcessed = false;
|
|
11897
12104
|
if (persistedDelivery) {
|
|
11898
12105
|
(appendOptions as TrustedLogAppendOptions<T>).__peerbitOnLocalCommit = (
|
|
11899
12106
|
hashes,
|
|
@@ -11947,8 +12154,17 @@ export class SharedLog<
|
|
|
11947
12154
|
await this.processLocalAppend(processingEntry, result.removed, options, {
|
|
11948
12155
|
minReplicasValue,
|
|
11949
12156
|
appendFacts: persistedAppendCommit,
|
|
12157
|
+
// Persisted settlement must confirm each exact receiver generation before
|
|
12158
|
+
// using its transfer as receipt evidence. Keep the optimistic append path
|
|
12159
|
+
// out of that ordering decision.
|
|
12160
|
+
captureDeferredBackfillSource: persistedDelivery
|
|
12161
|
+
? (source) => {
|
|
12162
|
+
persistedBackfillSource = source;
|
|
12163
|
+
}
|
|
12164
|
+
: undefined,
|
|
11950
12165
|
ownershipLifecycleController,
|
|
11951
12166
|
});
|
|
12167
|
+
localAppendProcessed = true;
|
|
11952
12168
|
throwIfDeliveryAborted();
|
|
11953
12169
|
if (persistedDelivery && persistedDeadline) {
|
|
11954
12170
|
await this.settlePersistedDelivery(
|
|
@@ -11957,6 +12173,12 @@ export class SharedLog<
|
|
|
11957
12173
|
persistedDelivery,
|
|
11958
12174
|
ownershipLifecycleController,
|
|
11959
12175
|
persistedDeadline,
|
|
12176
|
+
true,
|
|
12177
|
+
(leadersByEntry, ownershipRevision) => {
|
|
12178
|
+
const leaders = leadersByEntry[0];
|
|
12179
|
+
persistedBackfillLeaders = leaders ? new Map(leaders) : undefined;
|
|
12180
|
+
persistedBackfillOwnershipRevision = ownershipRevision;
|
|
12181
|
+
},
|
|
11960
12182
|
);
|
|
11961
12183
|
}
|
|
11962
12184
|
this.throwIfReplicationOwnershipLifecycleInactive(
|
|
@@ -11969,6 +12191,27 @@ export class SharedLog<
|
|
|
11969
12191
|
}
|
|
11970
12192
|
throw error;
|
|
11971
12193
|
} finally {
|
|
12194
|
+
if (
|
|
12195
|
+
persistedBackfillSource &&
|
|
12196
|
+
persistedBackfillLeaders &&
|
|
12197
|
+
persistedBackfillOwnershipRevision !== undefined &&
|
|
12198
|
+
localAppendProcessed
|
|
12199
|
+
) {
|
|
12200
|
+
// A persisted quorum changes the return condition, not the configured
|
|
12201
|
+
// replication degree. Reuse settlement's latest fresh leader plan rather
|
|
12202
|
+
// than carrying an optimistic target or extending the receipt deadline
|
|
12203
|
+
// with another plan. Fence best-effort repair to this append's ownership
|
|
12204
|
+
// generation, and never let it mask the primary result.
|
|
12205
|
+
try {
|
|
12206
|
+
this.queuePersistedAppendBackfill(
|
|
12207
|
+
persistedBackfillSource,
|
|
12208
|
+
persistedBackfillLeaders,
|
|
12209
|
+
minReplicasValue,
|
|
12210
|
+
ownershipLifecycleController,
|
|
12211
|
+
persistedBackfillOwnershipRevision,
|
|
12212
|
+
);
|
|
12213
|
+
} catch {}
|
|
12214
|
+
}
|
|
11972
12215
|
persistedDeadline?.dispose();
|
|
11973
12216
|
}
|
|
11974
12217
|
}
|
|
@@ -16080,6 +16323,9 @@ export class SharedLog<
|
|
|
16080
16323
|
minReplicasValue: number;
|
|
16081
16324
|
appendFacts?: PreparedAppendFacts;
|
|
16082
16325
|
deferHeadCoordinatePersistence?: boolean;
|
|
16326
|
+
captureDeferredBackfillSource?: (
|
|
16327
|
+
source: PersistedAppendBackfillSource<T, R>,
|
|
16328
|
+
) => void;
|
|
16083
16329
|
nativeAppendPlan?: NativeAppendEntryPlan<R>;
|
|
16084
16330
|
extraCoordinateDeleteHashes?: string[];
|
|
16085
16331
|
ownershipLifecycleController?: AbortController;
|
|
@@ -16223,7 +16469,41 @@ export class SharedLog<
|
|
|
16223
16469
|
ownershipLifecycleController,
|
|
16224
16470
|
);
|
|
16225
16471
|
|
|
16226
|
-
if (
|
|
16472
|
+
if (properties.captureDeferredBackfillSource) {
|
|
16473
|
+
let extras: Omit<
|
|
16474
|
+
PersistedAppendBackfillSource<T, R>,
|
|
16475
|
+
"entry" | "coordinates"
|
|
16476
|
+
> = {
|
|
16477
|
+
assignmentExtraLeaders: new Map(),
|
|
16478
|
+
deliveryExtraTargets: new Set(),
|
|
16479
|
+
};
|
|
16480
|
+
try {
|
|
16481
|
+
extras = await this.collectDeferredAppendBackfillExtras(
|
|
16482
|
+
entry,
|
|
16483
|
+
properties.minReplicasValue,
|
|
16484
|
+
leaders!,
|
|
16485
|
+
nativeDeliveryPlan,
|
|
16486
|
+
ownershipLifecycleController,
|
|
16487
|
+
);
|
|
16488
|
+
} catch (error) {
|
|
16489
|
+
if (this.isRepairLifecycleActive(ownershipLifecycleController)) {
|
|
16490
|
+
logger.error(error);
|
|
16491
|
+
}
|
|
16492
|
+
}
|
|
16493
|
+
this.throwIfReplicationOwnershipLifecycleInactive(
|
|
16494
|
+
ownershipLifecycleController,
|
|
16495
|
+
);
|
|
16496
|
+
properties.captureDeferredBackfillSource({
|
|
16497
|
+
entry,
|
|
16498
|
+
coordinates: [...coordinates],
|
|
16499
|
+
...extras,
|
|
16500
|
+
});
|
|
16501
|
+
}
|
|
16502
|
+
|
|
16503
|
+
if (
|
|
16504
|
+
options?.target !== "none" &&
|
|
16505
|
+
!properties.captureDeferredBackfillSource
|
|
16506
|
+
) {
|
|
16227
16507
|
const hasDelivery = !(deliveryArg === undefined || deliveryArg === false);
|
|
16228
16508
|
|
|
16229
16509
|
if (target === "all" && hasDelivery) {
|
|
@@ -22498,6 +22778,12 @@ export class SharedLog<
|
|
|
22498
22778
|
this._peerSyncCapabilitySessions.get(lane.fromHash) ===
|
|
22499
22779
|
context.message.header.session &&
|
|
22500
22780
|
this._peerSyncCapabilityTimestamps.has(lane.fromHash) &&
|
|
22781
|
+
this._v2Receive.isCurrentActive({
|
|
22782
|
+
peerHash: lane.fromHash,
|
|
22783
|
+
peerSession: session,
|
|
22784
|
+
receiveEpoch: lane.receiveEpoch,
|
|
22785
|
+
senderTransportSession: context.message.header.session,
|
|
22786
|
+
}) &&
|
|
22501
22787
|
this.isRepairLifecycleActive(lane.ownershipLifecycleController)
|
|
22502
22788
|
);
|
|
22503
22789
|
}
|
|
@@ -24866,6 +25152,7 @@ export class SharedLog<
|
|
|
24866
25152
|
options?: {
|
|
24867
25153
|
roleAge?: number;
|
|
24868
25154
|
candidates?: Iterable<string>;
|
|
25155
|
+
freshLeaderPlan?: boolean;
|
|
24869
25156
|
},
|
|
24870
25157
|
ownershipLifecycleController = this.captureReplicationOwnershipLifecycle(),
|
|
24871
25158
|
): Promise<LeaderSelectionContext> {
|
|
@@ -25027,6 +25314,7 @@ export class SharedLog<
|
|
|
25027
25314
|
options?: {
|
|
25028
25315
|
roleAge?: number;
|
|
25029
25316
|
candidates?: Iterable<string>;
|
|
25317
|
+
freshLeaderPlan?: boolean;
|
|
25030
25318
|
},
|
|
25031
25319
|
ownershipLifecycleController = this.captureReplicationOwnershipLifecycle(),
|
|
25032
25320
|
): Promise<Map<string, { intersecting: boolean }>> {
|
|
@@ -1235,6 +1235,31 @@ export class ReplicationInfoV2ReceiveCoordinator {
|
|
|
1235
1235
|
);
|
|
1236
1236
|
}
|
|
1237
1237
|
|
|
1238
|
+
/**
|
|
1239
|
+
* Exact active receive generation used by durability-sensitive callers.
|
|
1240
|
+
* An open PeerSession alone is not evidence that its authoritative Full has
|
|
1241
|
+
* crossed the host apply lane, especially during a same-key reconnect.
|
|
1242
|
+
*/
|
|
1243
|
+
isCurrentActive(properties: {
|
|
1244
|
+
peerHash: string;
|
|
1245
|
+
peerSession: object;
|
|
1246
|
+
receiveEpoch: object | null;
|
|
1247
|
+
senderTransportSession: bigint;
|
|
1248
|
+
}): boolean {
|
|
1249
|
+
const state = this._receiveStates.get(properties.peerHash);
|
|
1250
|
+
return (
|
|
1251
|
+
state !== undefined &&
|
|
1252
|
+
state.peerHash === properties.peerHash &&
|
|
1253
|
+
state.peerSession === properties.peerSession &&
|
|
1254
|
+
state.receiveEpoch === properties.receiveEpoch &&
|
|
1255
|
+
state.senderTransportSession === properties.senderTransportSession &&
|
|
1256
|
+
state.phase === "active" &&
|
|
1257
|
+
state.reservedAdmission === undefined &&
|
|
1258
|
+
!this._reservedAdmissionsByPeer.has(properties.peerHash) &&
|
|
1259
|
+
this.isStateCurrent(state)
|
|
1260
|
+
);
|
|
1261
|
+
}
|
|
1262
|
+
|
|
1238
1263
|
commit(admission: ReplicationInfoV2ReceiveAdmission): boolean {
|
|
1239
1264
|
if (admission.committed) {
|
|
1240
1265
|
return this.isAdmissionCurrent(admission);
|
|
@@ -1294,9 +1319,12 @@ export class ReplicationInfoV2ReceiveCoordinator {
|
|
|
1294
1319
|
const state = this._receiveStates.get(properties.from.hashcode());
|
|
1295
1320
|
if (
|
|
1296
1321
|
!state ||
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1322
|
+
!this.isCurrentActive({
|
|
1323
|
+
peerHash: properties.from.hashcode(),
|
|
1324
|
+
peerSession: properties.peerSession,
|
|
1325
|
+
receiveEpoch: properties.receiveEpoch,
|
|
1326
|
+
senderTransportSession: properties.senderTransportSession,
|
|
1327
|
+
}) ||
|
|
1300
1328
|
!state.target.equals(properties.from) ||
|
|
1301
1329
|
!state.receiverBinding ||
|
|
1302
1330
|
!bytesEqual(request.receiverChallenge, state.receiverBinding) ||
|
|
@@ -1304,8 +1332,7 @@ export class ReplicationInfoV2ReceiveCoordinator {
|
|
|
1304
1332
|
!bytesEqual(request.senderEpoch, state.senderEpoch) ||
|
|
1305
1333
|
request.sequence <= 0n ||
|
|
1306
1334
|
state.lastSequence === undefined ||
|
|
1307
|
-
state.lastSequence < request.sequence
|
|
1308
|
-
!this.isStateCurrent(state)
|
|
1335
|
+
state.lastSequence < request.sequence
|
|
1309
1336
|
) {
|
|
1310
1337
|
return undefined;
|
|
1311
1338
|
}
|