@peerbit/shared-log 16.0.28 → 16.0.30
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/README.md +70 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +257 -42
- package/dist/src/index.js.map +1 -1
- package/dist/src/replication-info-v2-receive.d.ts.map +1 -1
- package/dist/src/replication-info-v2-receive.js +9 -2
- package/dist/src/replication-info-v2-receive.js.map +1 -1
- package/dist/src/replication-info-v2-send.d.ts +9 -2
- package/dist/src/replication-info-v2-send.d.ts.map +1 -1
- package/dist/src/replication-info-v2-send.js +67 -32
- package/dist/src/replication-info-v2-send.js.map +1 -1
- package/package.json +19 -19
- package/src/index.ts +267 -56
- package/src/replication-info-v2-receive.ts +13 -2
- package/src/replication-info-v2-send.ts +95 -42
package/src/index.ts
CHANGED
|
@@ -564,7 +564,7 @@ const emitAdvisorySyncProfileDuration = (
|
|
|
564
564
|
try {
|
|
565
565
|
emitSyncProfileDuration(profile, startedAt, event);
|
|
566
566
|
} catch {
|
|
567
|
-
//
|
|
567
|
+
// Advisory diagnostics must not change replication or lifecycle behavior.
|
|
568
568
|
}
|
|
569
569
|
};
|
|
570
570
|
|
|
@@ -4033,7 +4033,13 @@ export class SharedLog<
|
|
|
4033
4033
|
this._repairMetrics["join-warmup"].simpleFallbackPasses += 1;
|
|
4034
4034
|
},
|
|
4035
4035
|
sendEntriesSimple: (target, entries, options) =>
|
|
4036
|
-
this.sendRepairEntriesWithTransport(
|
|
4036
|
+
this.sendRepairEntriesWithTransport(
|
|
4037
|
+
target,
|
|
4038
|
+
entries,
|
|
4039
|
+
"simple",
|
|
4040
|
+
options,
|
|
4041
|
+
"join-warmup",
|
|
4042
|
+
),
|
|
4037
4043
|
logError: (error) => logger.error(error),
|
|
4038
4044
|
});
|
|
4039
4045
|
}
|
|
@@ -6524,6 +6530,24 @@ export class SharedLog<
|
|
|
6524
6530
|
);
|
|
6525
6531
|
};
|
|
6526
6532
|
try {
|
|
6533
|
+
if (!isPeerRoundCurrent()) return;
|
|
6534
|
+
if (
|
|
6535
|
+
!this._v2Send.hasCurrentStateForPeer({
|
|
6536
|
+
peerHash: peer,
|
|
6537
|
+
peerSession: captured.peerSession,
|
|
6538
|
+
receiverTransportSession: captured.capabilitySession,
|
|
6539
|
+
})
|
|
6540
|
+
) {
|
|
6541
|
+
// Preflight can precede replacement or loss of the sender
|
|
6542
|
+
// stream. Delivery must recover its own exact current
|
|
6543
|
+
// binding before waiting for application confirmation.
|
|
6544
|
+
this._v2Receive.reAdvertiseLocalCapabilityForRemoteFull({
|
|
6545
|
+
peerHash: peer,
|
|
6546
|
+
peerSession: captured.peerSession,
|
|
6547
|
+
receiveEpoch: captured.receiveEpoch,
|
|
6548
|
+
signal: roundSignal,
|
|
6549
|
+
});
|
|
6550
|
+
}
|
|
6527
6551
|
await this._v2Send.confirmLatestForPeer(
|
|
6528
6552
|
{
|
|
6529
6553
|
peerHash: peer,
|
|
@@ -10621,6 +10645,7 @@ export class SharedLog<
|
|
|
10621
10645
|
isStillCurrent?: () => boolean;
|
|
10622
10646
|
signal?: AbortSignal;
|
|
10623
10647
|
},
|
|
10648
|
+
mode?: RepairDispatchMode,
|
|
10624
10649
|
) {
|
|
10625
10650
|
const isStillCurrent = options?.isStillCurrent ?? (() => true);
|
|
10626
10651
|
if (!isStillCurrent()) {
|
|
@@ -10628,53 +10653,87 @@ export class SharedLog<
|
|
|
10628
10653
|
}
|
|
10629
10654
|
const unknownEntries = new Map<string, RepairDispatchEntry<R>>();
|
|
10630
10655
|
const knownHashes: string[] = [];
|
|
10631
|
-
|
|
10632
|
-
|
|
10633
|
-
|
|
10634
|
-
|
|
10635
|
-
|
|
10636
|
-
|
|
10637
|
-
|
|
10638
|
-
|
|
10639
|
-
|
|
10640
|
-
|
|
10641
|
-
|
|
10642
|
-
|
|
10643
|
-
|
|
10656
|
+
const profile = this._logProperties?.sync?.profile;
|
|
10657
|
+
const startedAt = syncProfileStart(profile);
|
|
10658
|
+
const inputEntries = profile ? entries.size : 0;
|
|
10659
|
+
let selectedEntries = 0;
|
|
10660
|
+
let lastObservedCurrent = true;
|
|
10661
|
+
let outcome = "stale";
|
|
10662
|
+
try {
|
|
10663
|
+
for (const [hash, entry] of entries) {
|
|
10664
|
+
if (
|
|
10665
|
+
(options?.bypassRecentKnownPeers ||
|
|
10666
|
+
!this.isEntryRecentlyKnownByPeer(
|
|
10667
|
+
hash,
|
|
10668
|
+
target,
|
|
10669
|
+
RECENT_KNOWN_REPAIR_SUPPRESSION_MS,
|
|
10670
|
+
)) &&
|
|
10671
|
+
(options?.bypassKnownPeers || !this.isEntryKnownByPeer(hash, target))
|
|
10672
|
+
) {
|
|
10673
|
+
unknownEntries.set(hash, entry);
|
|
10674
|
+
} else {
|
|
10675
|
+
knownHashes.push(hash);
|
|
10676
|
+
}
|
|
10644
10677
|
}
|
|
10645
|
-
|
|
10646
|
-
|
|
10647
|
-
return;
|
|
10648
|
-
|
|
10649
|
-
|
|
10650
|
-
|
|
10651
|
-
|
|
10652
|
-
|
|
10653
|
-
|
|
10654
|
-
|
|
10655
|
-
|
|
10656
|
-
|
|
10657
|
-
|
|
10658
|
-
|
|
10659
|
-
|
|
10660
|
-
|
|
10661
|
-
|
|
10662
|
-
return;
|
|
10663
|
-
}
|
|
10664
|
-
|
|
10665
|
-
const syncEntries = this._logProperties?.sync?.priority
|
|
10666
|
-
? (this._coordinates.materializeRepairDispatchEntries(
|
|
10678
|
+
// A custom synchronizer may mutate the Map once it receives it.
|
|
10679
|
+
if (profile) selectedEntries = unknownEntries.size;
|
|
10680
|
+
if (!isStillCurrent()) return;
|
|
10681
|
+
this.clearRepairFrontierHashes(target, knownHashes);
|
|
10682
|
+
if (unknownEntries.size === 0) {
|
|
10683
|
+
outcome = "known-suppressed";
|
|
10684
|
+
return;
|
|
10685
|
+
}
|
|
10686
|
+
if (transport === "simple") {
|
|
10687
|
+
// Observe only checks the lower path already makes, without adding
|
|
10688
|
+
// lifecycle decisions or wrapping the disabled-profiling path.
|
|
10689
|
+
const dispatchIsStillCurrent = profile
|
|
10690
|
+
? () => (lastObservedCurrent = isStillCurrent())
|
|
10691
|
+
: isStillCurrent;
|
|
10692
|
+
// Fallback repair does not wait for the maybe-sync round trip.
|
|
10693
|
+
await this.pushRepairEntries(
|
|
10694
|
+
target,
|
|
10667
10695
|
unknownEntries,
|
|
10668
|
-
|
|
10669
|
-
|
|
10670
|
-
|
|
10671
|
-
|
|
10696
|
+
dispatchIsStillCurrent,
|
|
10697
|
+
options?.signal,
|
|
10698
|
+
);
|
|
10699
|
+
} else {
|
|
10700
|
+
const syncEntries = this._logProperties?.sync?.priority
|
|
10701
|
+
? (this._coordinates.materializeRepairDispatchEntries(
|
|
10702
|
+
unknownEntries,
|
|
10703
|
+
) as unknown as Map<string, SyncEntryCoordinates<R>>)
|
|
10704
|
+
: (unknownEntries as Map<string, SyncEntryCoordinates<R>>);
|
|
10705
|
+
if (!isStillCurrent()) return;
|
|
10706
|
+
await this.syncronizer.onMaybeMissingEntries({
|
|
10707
|
+
entries: syncEntries,
|
|
10708
|
+
targets: [target],
|
|
10709
|
+
signal: options?.signal,
|
|
10710
|
+
});
|
|
10711
|
+
}
|
|
10712
|
+
outcome = !lastObservedCurrent
|
|
10713
|
+
? "stale"
|
|
10714
|
+
: options?.signal?.aborted
|
|
10715
|
+
? "cancelled"
|
|
10716
|
+
: "dispatched";
|
|
10717
|
+
} catch (error) {
|
|
10718
|
+
outcome = "error";
|
|
10719
|
+
throw error;
|
|
10720
|
+
} finally {
|
|
10721
|
+
if (profile) {
|
|
10722
|
+
emitAdvisorySyncProfileDuration(profile, startedAt, {
|
|
10723
|
+
name: "sharedLog.repair.dispatch",
|
|
10724
|
+
component: "shared-log",
|
|
10725
|
+
entries: inputEntries,
|
|
10726
|
+
count: selectedEntries,
|
|
10727
|
+
targets: 1,
|
|
10728
|
+
details: {
|
|
10729
|
+
mode,
|
|
10730
|
+
transport,
|
|
10731
|
+
outcome,
|
|
10732
|
+
knownSuppressedEntries: knownHashes.length,
|
|
10733
|
+
},
|
|
10734
|
+
});
|
|
10735
|
+
}
|
|
10672
10736
|
}
|
|
10673
|
-
await this.syncronizer.onMaybeMissingEntries({
|
|
10674
|
-
entries: syncEntries,
|
|
10675
|
-
targets: [target],
|
|
10676
|
-
signal: options?.signal,
|
|
10677
|
-
});
|
|
10678
10737
|
}
|
|
10679
10738
|
|
|
10680
10739
|
private async sendMaybeMissingEntriesNow(
|
|
@@ -10758,6 +10817,7 @@ export class SharedLog<
|
|
|
10758
10817
|
this.isRepairLifecycleActive(repairLifecycleController),
|
|
10759
10818
|
signal: repairLifecycleController.signal,
|
|
10760
10819
|
},
|
|
10820
|
+
options.mode,
|
|
10761
10821
|
),
|
|
10762
10822
|
).catch((error: any) => logger.error(error));
|
|
10763
10823
|
}
|
|
@@ -11148,6 +11208,7 @@ export class SharedLog<
|
|
|
11148
11208
|
this.isRepairLifecycleActive(repairLifecycleController),
|
|
11149
11209
|
signal: repairLifecycleController.signal,
|
|
11150
11210
|
},
|
|
11211
|
+
options.mode,
|
|
11151
11212
|
),
|
|
11152
11213
|
).catch((error: any) => logger.error(error));
|
|
11153
11214
|
};
|
|
@@ -11291,6 +11352,18 @@ export class SharedLog<
|
|
|
11291
11352
|
repairLifecycleController: AbortController = this._instanceLifecycle
|
|
11292
11353
|
?.ownershipLifecycleController as AbortController,
|
|
11293
11354
|
) {
|
|
11355
|
+
const profile = this._logProperties?.sync?.profile;
|
|
11356
|
+
const startedAt = syncProfileStart(profile);
|
|
11357
|
+
const profileCounts = profile
|
|
11358
|
+
? {
|
|
11359
|
+
passes: 0,
|
|
11360
|
+
inputEntries: 0,
|
|
11361
|
+
nativePasses: 0,
|
|
11362
|
+
repairCandidates: 0,
|
|
11363
|
+
repairBatches: 0,
|
|
11364
|
+
outcome: "stale",
|
|
11365
|
+
}
|
|
11366
|
+
: undefined;
|
|
11294
11367
|
try {
|
|
11295
11368
|
while (this.isRepairLifecycleActive(repairLifecycleController)) {
|
|
11296
11369
|
if (!this.isRepairLifecycleActive(repairLifecycleController)) {
|
|
@@ -11329,8 +11402,10 @@ export class SharedLog<
|
|
|
11329
11402
|
pruneStaleJoinWarmupPeers();
|
|
11330
11403
|
|
|
11331
11404
|
if (pendingModes.size === 0) {
|
|
11405
|
+
if (profileCounts) profileCounts.outcome = "completed";
|
|
11332
11406
|
return;
|
|
11333
11407
|
}
|
|
11408
|
+
if (profileCounts) profileCounts.passes += 1;
|
|
11334
11409
|
|
|
11335
11410
|
const optimisticGidPeersByMode = new Map<
|
|
11336
11411
|
RepairDispatchMode,
|
|
@@ -11429,6 +11504,10 @@ export class SharedLog<
|
|
|
11429
11504
|
}
|
|
11430
11505
|
return;
|
|
11431
11506
|
}
|
|
11507
|
+
if (profileCounts) {
|
|
11508
|
+
profileCounts.repairCandidates += entries.size;
|
|
11509
|
+
profileCounts.repairBatches += 1;
|
|
11510
|
+
}
|
|
11432
11511
|
this.dispatchMaybeMissingEntries(
|
|
11433
11512
|
target,
|
|
11434
11513
|
entries,
|
|
@@ -11495,6 +11574,10 @@ export class SharedLog<
|
|
|
11495
11574
|
residentEntriesByHash &&
|
|
11496
11575
|
!this.hasCustomFindLeaders()
|
|
11497
11576
|
) {
|
|
11577
|
+
if (profileCounts) {
|
|
11578
|
+
profileCounts.nativePasses += 1;
|
|
11579
|
+
profileCounts.inputEntries += residentEntriesByHash.size;
|
|
11580
|
+
}
|
|
11498
11581
|
const repairDispatchPlan = pruneStaleJoinWarmupPeers()
|
|
11499
11582
|
? await this.planResidentRepairDispatchBatch(
|
|
11500
11583
|
{
|
|
@@ -11533,6 +11616,7 @@ export class SharedLog<
|
|
|
11533
11616
|
const entries = await iterator.next(
|
|
11534
11617
|
REPAIR_SWEEP_ENTRY_BATCH_SIZE,
|
|
11535
11618
|
);
|
|
11619
|
+
if (profileCounts) profileCounts.inputEntries += entries.length;
|
|
11536
11620
|
if (!this.isRepairLifecycleActive(repairLifecycleController)) {
|
|
11537
11621
|
return;
|
|
11538
11622
|
}
|
|
@@ -11657,6 +11741,7 @@ export class SharedLog<
|
|
|
11657
11741
|
}
|
|
11658
11742
|
}
|
|
11659
11743
|
} catch (error: any) {
|
|
11744
|
+
if (profileCounts) profileCounts.outcome = "error";
|
|
11660
11745
|
if (
|
|
11661
11746
|
this.isRepairLifecycleActive(repairLifecycleController) &&
|
|
11662
11747
|
!isNotStartedError(error)
|
|
@@ -11678,6 +11763,21 @@ export class SharedLog<
|
|
|
11678
11763
|
void this.runRepairSweep(repairLifecycleController);
|
|
11679
11764
|
}
|
|
11680
11765
|
}
|
|
11766
|
+
if (profileCounts) {
|
|
11767
|
+
emitAdvisorySyncProfileDuration(profile, startedAt, {
|
|
11768
|
+
name: "sharedLog.placement.pass",
|
|
11769
|
+
component: "shared-log",
|
|
11770
|
+
entries: profileCounts.inputEntries,
|
|
11771
|
+
count: profileCounts.repairCandidates,
|
|
11772
|
+
details: {
|
|
11773
|
+
phase: "repair-sweep",
|
|
11774
|
+
outcome: profileCounts.outcome,
|
|
11775
|
+
passes: profileCounts.passes,
|
|
11776
|
+
nativePasses: profileCounts.nativePasses,
|
|
11777
|
+
repairBatches: profileCounts.repairBatches,
|
|
11778
|
+
},
|
|
11779
|
+
});
|
|
11780
|
+
}
|
|
11681
11781
|
}
|
|
11682
11782
|
}
|
|
11683
11783
|
|
|
@@ -21256,10 +21356,11 @@ export class SharedLog<
|
|
|
21256
21356
|
msg.heads.map((head) => head.hash),
|
|
21257
21357
|
);
|
|
21258
21358
|
if (syncProfile) {
|
|
21259
|
-
|
|
21359
|
+
emitAdvisorySyncProfileDuration(syncProfile, rawExistingStartedAt, {
|
|
21260
21360
|
name: "sharedLog.rawReceive.existingHeads",
|
|
21261
21361
|
component: "shared-log",
|
|
21262
21362
|
entries: msg.heads.length,
|
|
21363
|
+
count: rawExistingHashes.size,
|
|
21263
21364
|
messages: 1,
|
|
21264
21365
|
});
|
|
21265
21366
|
}
|
|
@@ -21693,10 +21794,11 @@ export class SharedLog<
|
|
|
21693
21794
|
? undefined
|
|
21694
21795
|
: await this.log.hasMany(headHashes);
|
|
21695
21796
|
if (syncProfile) {
|
|
21696
|
-
|
|
21797
|
+
emitAdvisorySyncProfileDuration(syncProfile, existingStartedAt, {
|
|
21697
21798
|
name: "sharedLog.receive.existingHeads",
|
|
21698
21799
|
component: "shared-log",
|
|
21699
21800
|
entries: heads.length,
|
|
21801
|
+
count: existingHashes?.size,
|
|
21700
21802
|
messages: 1,
|
|
21701
21803
|
details: { rawMaterializedKnownMissing },
|
|
21702
21804
|
});
|
|
@@ -24943,6 +25045,27 @@ export class SharedLog<
|
|
|
24943
25045
|
recoveryTimer = undefined;
|
|
24944
25046
|
if (!continueWait()) return;
|
|
24945
25047
|
const recoveryTarget = confirmationRecoveryTarget;
|
|
25048
|
+
if (confirmationController && recoveryTarget) {
|
|
25049
|
+
if (
|
|
25050
|
+
this._peerSessions.current(peerHash) !==
|
|
25051
|
+
recoveryTarget.peerSession ||
|
|
25052
|
+
this._peerSessions.receiveEpoch(peerHash) !==
|
|
25053
|
+
recoveryTarget.receiveEpoch ||
|
|
25054
|
+
this._peerSyncCapabilitySessions.get(peerHash) !==
|
|
25055
|
+
recoveryTarget.capabilitySession ||
|
|
25056
|
+
!this.uniqueReplicators.has(peerHash)
|
|
25057
|
+
) {
|
|
25058
|
+
// Events can be coalesced while application confirmation waits.
|
|
25059
|
+
// Release the obsolete target on the recovery tick as well,
|
|
25060
|
+
// including loss of replicator eligibility within one session.
|
|
25061
|
+
// A temporary receive reservation keeps this same target alive;
|
|
25062
|
+
// the final readiness inspection still checks every admission gate.
|
|
25063
|
+
confirmationRecoveryTarget = undefined;
|
|
25064
|
+
confirmationController.abort(
|
|
25065
|
+
new AbortError("Persisted-receipt readiness target changed"),
|
|
25066
|
+
);
|
|
25067
|
+
}
|
|
25068
|
+
}
|
|
24946
25069
|
this.nudgePersistedReceiptPeerReadiness(
|
|
24947
25070
|
key,
|
|
24948
25071
|
operationSignal,
|
|
@@ -29439,6 +29562,17 @@ export class SharedLog<
|
|
|
29439
29562
|
isOwnershipLifecycleCurrent() &&
|
|
29440
29563
|
[...warmupPeers].every(isCurrentJoinWarmupTarget);
|
|
29441
29564
|
|
|
29565
|
+
const profile = this._logProperties?.sync?.profile;
|
|
29566
|
+
const profileStartedAt = syncProfileStart(profile);
|
|
29567
|
+
const profileCounts = profile
|
|
29568
|
+
? {
|
|
29569
|
+
examinedEntries: 0,
|
|
29570
|
+
repairCandidates: 0,
|
|
29571
|
+
repairBatches: 0,
|
|
29572
|
+
pruneScan: false,
|
|
29573
|
+
outcome: "stale",
|
|
29574
|
+
}
|
|
29575
|
+
: undefined;
|
|
29442
29576
|
try {
|
|
29443
29577
|
const uncheckedDeliver: Map<
|
|
29444
29578
|
string,
|
|
@@ -29462,6 +29596,10 @@ export class SharedLog<
|
|
|
29462
29596
|
: isWarmupTarget
|
|
29463
29597
|
? "join-warmup"
|
|
29464
29598
|
: "join-authoritative";
|
|
29599
|
+
if (profileCounts) {
|
|
29600
|
+
profileCounts.repairCandidates += entries.size;
|
|
29601
|
+
profileCounts.repairBatches += 1;
|
|
29602
|
+
}
|
|
29465
29603
|
this.dispatchMaybeMissingEntries(
|
|
29466
29604
|
target,
|
|
29467
29605
|
entries,
|
|
@@ -29516,6 +29654,7 @@ export class SharedLog<
|
|
|
29516
29654
|
forceFresh: forceFreshDelivery || useJoinWarmupFastPath,
|
|
29517
29655
|
},
|
|
29518
29656
|
)) {
|
|
29657
|
+
if (profileCounts) profileCounts.examinedEntries += 1;
|
|
29519
29658
|
if (
|
|
29520
29659
|
!isOwnershipLifecycleCurrent() ||
|
|
29521
29660
|
(useJoinWarmupFastPath && !areJoinWarmupGenerationsCurrent())
|
|
@@ -29799,6 +29938,7 @@ export class SharedLog<
|
|
|
29799
29938
|
));
|
|
29800
29939
|
|
|
29801
29940
|
if (shouldRunLocalPruneScan) {
|
|
29941
|
+
if (profileCounts) profileCounts.pruneScan = true;
|
|
29802
29942
|
throwIfOwnershipLifecycleInactive();
|
|
29803
29943
|
// Adaptive range changes and fixed zero-width updates can make already-indexed
|
|
29804
29944
|
// local heads prunable even when the incremental rebalance scan misses them
|
|
@@ -29818,6 +29958,7 @@ export class SharedLog<
|
|
|
29818
29958
|
}
|
|
29819
29959
|
}
|
|
29820
29960
|
|
|
29961
|
+
if (profileCounts) profileCounts.outcome = "completed";
|
|
29821
29962
|
return changed;
|
|
29822
29963
|
} catch (error: any) {
|
|
29823
29964
|
if (!isOwnershipLifecycleCurrent()) {
|
|
@@ -29827,8 +29968,27 @@ export class SharedLog<
|
|
|
29827
29968
|
return false; // we are not started yet, so no changes
|
|
29828
29969
|
}
|
|
29829
29970
|
|
|
29971
|
+
if (profileCounts) profileCounts.outcome = "error";
|
|
29830
29972
|
logger.error(error.toString());
|
|
29831
29973
|
throw error;
|
|
29974
|
+
} finally {
|
|
29975
|
+
if (profileCounts) {
|
|
29976
|
+
emitAdvisorySyncProfileDuration(profile, profileStartedAt, {
|
|
29977
|
+
name: "sharedLog.placement.pass",
|
|
29978
|
+
component: "shared-log",
|
|
29979
|
+
entries: profileCounts.examinedEntries,
|
|
29980
|
+
count: profileCounts.repairCandidates,
|
|
29981
|
+
details: {
|
|
29982
|
+
phase: "range-change",
|
|
29983
|
+
outcome: profileCounts.outcome,
|
|
29984
|
+
changes: changes.length,
|
|
29985
|
+
repairBatches: profileCounts.repairBatches,
|
|
29986
|
+
pruneScan: profileCounts.pruneScan,
|
|
29987
|
+
forceFreshDelivery,
|
|
29988
|
+
joinWarmupFastPath: useJoinWarmupFastPath,
|
|
29989
|
+
},
|
|
29990
|
+
});
|
|
29991
|
+
}
|
|
29832
29992
|
}
|
|
29833
29993
|
}
|
|
29834
29994
|
|
|
@@ -29887,6 +30047,15 @@ export class SharedLog<
|
|
|
29887
30047
|
ownershipLifecycleController = this.captureReplicationOwnershipLifecycle(),
|
|
29888
30048
|
rebalanceParticipationDebounced = this.rebalanceParticipationDebounced,
|
|
29889
30049
|
) {
|
|
30050
|
+
const profile = this._isAdaptiveReplicating
|
|
30051
|
+
? this._logProperties?.sync?.profile
|
|
30052
|
+
: undefined;
|
|
30053
|
+
const profileStartedAt = syncProfileStart(profile);
|
|
30054
|
+
const profileDetails:
|
|
30055
|
+
| Record<string, string | number | boolean | undefined>
|
|
30056
|
+
| undefined = profile
|
|
30057
|
+
? { outcome: "stale", idleRemainingMs: 0 }
|
|
30058
|
+
: undefined;
|
|
29890
30059
|
// Stage 3: the lifecycle owns all three identity terms. `lifecycle` may
|
|
29891
30060
|
// go stale later; its deps late-bind to the host, so the debouncer term
|
|
29892
30061
|
// still reads the current host field, and the role term can disagree
|
|
@@ -29926,6 +30095,14 @@ export class SharedLog<
|
|
|
29926
30095
|
|
|
29927
30096
|
if (this._isAdaptiveReplicating) {
|
|
29928
30097
|
if (this.shouldDelayAdaptiveRebalance()) {
|
|
30098
|
+
if (profileDetails) {
|
|
30099
|
+
profileDetails.outcome = "idle-deferred";
|
|
30100
|
+
profileDetails.idleRemainingMs = Math.max(
|
|
30101
|
+
0,
|
|
30102
|
+
this.adaptiveRebalanceIdleMs -
|
|
30103
|
+
(Date.now() - this._lastLocalAppendAt),
|
|
30104
|
+
);
|
|
30105
|
+
}
|
|
29929
30106
|
if (isCurrent()) {
|
|
29930
30107
|
void rebalanceParticipationDebounced?.call();
|
|
29931
30108
|
}
|
|
@@ -29935,11 +30112,17 @@ export class SharedLog<
|
|
|
29935
30112
|
const peers = this.replicationIndex;
|
|
29936
30113
|
const usedMemory = await this.getMemoryUsage();
|
|
29937
30114
|
if (!isCurrent()) return false;
|
|
30115
|
+
if (profileDetails) {
|
|
30116
|
+
profileDetails.storageUsedBytes = usedMemory;
|
|
30117
|
+
profileDetails.storageObjectiveBytes =
|
|
30118
|
+
this.replicationController.maxMemoryLimit;
|
|
30119
|
+
}
|
|
29938
30120
|
this.scheduleReplicationStatusRefreshForStorage(usedMemory);
|
|
29939
30121
|
let dynamicRange = await this.getDynamicRange();
|
|
29940
30122
|
if (!isCurrent()) return false;
|
|
29941
30123
|
|
|
29942
30124
|
if (!dynamicRange) {
|
|
30125
|
+
if (profileDetails) profileDetails.outcome = "not-permitted";
|
|
29943
30126
|
return; // not allowed to replicate
|
|
29944
30127
|
}
|
|
29945
30128
|
|
|
@@ -29966,13 +30149,24 @@ export class SharedLog<
|
|
|
29966
30149
|
const totalParticipation = await this.calculateTotalParticipation();
|
|
29967
30150
|
if (!isCurrent()) return false;
|
|
29968
30151
|
|
|
30152
|
+
const cpuUsage = this.cpuUsage?.value();
|
|
30153
|
+
const stepStartedAt = syncProfileStart(profile);
|
|
29969
30154
|
const newFactor = this.replicationController.step({
|
|
29970
30155
|
memoryUsage: usedMemory,
|
|
29971
30156
|
currentFactor: dynamicRange.widthNormalized,
|
|
29972
30157
|
totalFactor: totalParticipation, // TODO use this._totalParticipation when flakiness is fixed
|
|
29973
30158
|
peerCount: peersSize,
|
|
29974
|
-
cpuUsage
|
|
30159
|
+
cpuUsage,
|
|
29975
30160
|
});
|
|
30161
|
+
if (profileDetails) {
|
|
30162
|
+
profileDetails.preStepMs = stepStartedAt - profileStartedAt;
|
|
30163
|
+
profileDetails.stepMs = syncProfileStart(profile) - stepStartedAt;
|
|
30164
|
+
profileDetails.currentFactor = dynamicRange.widthNormalized;
|
|
30165
|
+
profileDetails.proposedFactor = newFactor;
|
|
30166
|
+
profileDetails.totalFactor = totalParticipation;
|
|
30167
|
+
profileDetails.controllerPeerCount = peersSize;
|
|
30168
|
+
profileDetails.cpuUsage = cpuUsage;
|
|
30169
|
+
}
|
|
29976
30170
|
|
|
29977
30171
|
const absoluteDifference = Math.abs(
|
|
29978
30172
|
dynamicRange.widthNormalized - newFactor,
|
|
@@ -30009,9 +30203,11 @@ export class SharedLog<
|
|
|
30009
30203
|
(await this._isTrustedReplicator(this.node.identity.publicKey));
|
|
30010
30204
|
if (!isCurrent()) return false;
|
|
30011
30205
|
if (!canReplicate) {
|
|
30206
|
+
if (profileDetails) profileDetails.outcome = "not-permitted";
|
|
30012
30207
|
return false;
|
|
30013
30208
|
}
|
|
30014
30209
|
|
|
30210
|
+
const applyStartedAt = syncProfileStart(profile);
|
|
30015
30211
|
await this.startAnnounceReplicating(
|
|
30016
30212
|
[dynamicRange],
|
|
30017
30213
|
{
|
|
@@ -30022,6 +30218,10 @@ export class SharedLog<
|
|
|
30022
30218
|
ownershipLifecycleController,
|
|
30023
30219
|
);
|
|
30024
30220
|
if (!isCurrent()) return false;
|
|
30221
|
+
if (profileDetails) {
|
|
30222
|
+
profileDetails.outcome = "apply-settled";
|
|
30223
|
+
profileDetails.applyMs = syncProfileStart(profile) - applyStartedAt;
|
|
30224
|
+
}
|
|
30025
30225
|
|
|
30026
30226
|
/* await this._updateRole(newRole, onRoleChange); */
|
|
30027
30227
|
if (isCurrent()) {
|
|
@@ -30030,6 +30230,7 @@ export class SharedLog<
|
|
|
30030
30230
|
|
|
30031
30231
|
return true;
|
|
30032
30232
|
} else {
|
|
30233
|
+
if (profileDetails) profileDetails.outcome = "unchanged";
|
|
30033
30234
|
if (isCurrent()) {
|
|
30034
30235
|
void rebalanceParticipationDebounced?.call();
|
|
30035
30236
|
}
|
|
@@ -30039,14 +30240,24 @@ export class SharedLog<
|
|
|
30039
30240
|
return false;
|
|
30040
30241
|
};
|
|
30041
30242
|
|
|
30042
|
-
|
|
30043
|
-
|
|
30044
|
-
|
|
30243
|
+
try {
|
|
30244
|
+
return await fn().catch((error: any) => {
|
|
30245
|
+
if (isNotStartedError(error) || isClosedStoreRace(error)) {
|
|
30246
|
+
if (profileDetails) profileDetails.outcome = "stale";
|
|
30247
|
+
return false;
|
|
30248
|
+
}
|
|
30249
|
+
if (profileDetails) profileDetails.outcome = "error";
|
|
30250
|
+
throw error;
|
|
30251
|
+
});
|
|
30252
|
+
} finally {
|
|
30253
|
+
if (profileDetails) {
|
|
30254
|
+
emitAdvisorySyncProfileDuration(profile, profileStartedAt, {
|
|
30255
|
+
name: "sharedLog.adaptive.rebalance",
|
|
30256
|
+
component: "shared-log",
|
|
30257
|
+
details: profileDetails,
|
|
30258
|
+
});
|
|
30045
30259
|
}
|
|
30046
|
-
|
|
30047
|
-
});
|
|
30048
|
-
|
|
30049
|
-
return resp;
|
|
30260
|
+
}
|
|
30050
30261
|
}
|
|
30051
30262
|
|
|
30052
30263
|
private getDynamicRangeOffset(): NumberFromType<R> {
|
|
@@ -27,6 +27,7 @@ const DEFAULT_MAX_REQUEST_RETRY_MS = 30_000;
|
|
|
27
27
|
const DEFAULT_REQUEST_MAX_ATTEMPTS = 7;
|
|
28
28
|
const DEFAULT_REMOTE_FULL_REARM_ATTEMPT_TIMEOUT_MS = 2_000;
|
|
29
29
|
const DEFAULT_REMOTE_FULL_REARM_COOLDOWN_MS = 5_000;
|
|
30
|
+
const MAX_REMOTE_FULL_REARM_COOLDOWN_MS = 30_000;
|
|
30
31
|
const MAX_REMOTE_FULL_REARM_OUTSTANDING_PER_SESSION = 2;
|
|
31
32
|
const MAX_REMOTE_FULL_REARM_OUTSTANDING_GLOBAL = 64;
|
|
32
33
|
const MAX_TIMER_MS = 2_147_483_647;
|
|
@@ -825,18 +826,28 @@ export class ReplicationInfoV2ReceiveCoordinator {
|
|
|
825
826
|
) {
|
|
826
827
|
return true;
|
|
827
828
|
}
|
|
829
|
+
// Repeated hints must leave time for a slow Full and its Applied response
|
|
830
|
+
// to settle. Reuse this exact generation's bounded attempt counter so a
|
|
831
|
+
// watchdog cannot continually replace the binding it is trying to confirm.
|
|
832
|
+
const nextAttemptAt =
|
|
833
|
+
now +
|
|
834
|
+
Math.min(
|
|
835
|
+
MAX_REMOTE_FULL_REARM_COOLDOWN_MS,
|
|
836
|
+
this.remoteFullRearmCooldownMs *
|
|
837
|
+
2 ** Math.min(rearm?.attemptsStarted ?? 0, MAX_BACKOFF_EXPONENT),
|
|
838
|
+
);
|
|
828
839
|
if (!rearm) {
|
|
829
840
|
rearm = {
|
|
830
841
|
peerHash: properties.peerHash,
|
|
831
842
|
receiveEpoch: properties.receiveEpoch,
|
|
832
843
|
receiverTransportSession,
|
|
833
|
-
nextAttemptAt
|
|
844
|
+
nextAttemptAt,
|
|
834
845
|
attemptsStarted: 0,
|
|
835
846
|
outstandingAttempts: 0,
|
|
836
847
|
};
|
|
837
848
|
this._remoteFullRearmBySession.set(properties.peerSession, rearm);
|
|
838
849
|
} else {
|
|
839
|
-
rearm.nextAttemptAt =
|
|
850
|
+
rearm.nextAttemptAt = nextAttemptAt;
|
|
840
851
|
}
|
|
841
852
|
const attemptState = rearm;
|
|
842
853
|
const outstandingReservation = {};
|