@peerbit/shared-log 13.1.16 → 13.1.17
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/benchmark/native-graph.d.ts +2 -0
- package/dist/benchmark/native-graph.d.ts.map +1 -0
- package/dist/benchmark/native-graph.js +249 -0
- package/dist/benchmark/native-graph.js.map +1 -0
- package/dist/benchmark/sync-batch-sweep.js +72 -24
- package/dist/benchmark/sync-batch-sweep.js.map +1 -1
- package/dist/benchmark/sync-phase-profile.d.ts +2 -0
- package/dist/benchmark/sync-phase-profile.d.ts.map +1 -0
- package/dist/benchmark/sync-phase-profile.js +303 -0
- package/dist/benchmark/sync-phase-profile.js.map +1 -0
- package/dist/src/checked-prune.d.ts +55 -0
- package/dist/src/checked-prune.d.ts.map +1 -0
- package/dist/src/checked-prune.js +244 -0
- package/dist/src/checked-prune.js.map +1 -0
- package/dist/src/index.d.ts +10 -9
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +265 -164
- package/dist/src/index.js.map +1 -1
- package/dist/src/sync/index.d.ts +9 -1
- package/dist/src/sync/index.d.ts.map +1 -1
- package/dist/src/sync/profile.d.ts +3 -0
- package/dist/src/sync/profile.d.ts.map +1 -0
- package/dist/src/sync/profile.js +2 -0
- package/dist/src/sync/profile.js.map +1 -0
- package/dist/src/sync/rateless-iblt.d.ts +25 -11
- package/dist/src/sync/rateless-iblt.d.ts.map +1 -1
- package/dist/src/sync/rateless-iblt.js +597 -106
- package/dist/src/sync/rateless-iblt.js.map +1 -1
- package/dist/src/sync/simple.d.ts +5 -0
- package/dist/src/sync/simple.d.ts.map +1 -1
- package/dist/src/sync/simple.js +224 -74
- package/dist/src/sync/simple.js.map +1 -1
- package/package.json +17 -13
- package/src/checked-prune.ts +331 -0
- package/src/index.ts +429 -282
- package/src/sync/index.ts +11 -1
- package/src/sync/profile.ts +9 -0
- package/src/sync/rateless-iblt.ts +768 -128
- package/src/sync/simple.ts +256 -94
package/dist/src/index.js
CHANGED
|
@@ -44,12 +44,13 @@ import { ClosedError, Program } from "@peerbit/program";
|
|
|
44
44
|
import { FanoutChannel, waitForSubscribers, } from "@peerbit/pubsub";
|
|
45
45
|
import { SubscriptionEvent, UnsubcriptionEvent, } from "@peerbit/pubsub-interface";
|
|
46
46
|
import { RPC } from "@peerbit/rpc";
|
|
47
|
-
import { ACK_CONTROL_PRIORITY, AcknowledgeDelivery, AnyWhere, createRequestTransportContext, DataMessage, MessageHeader, NotStartedError, SilentDelivery, } from "@peerbit/stream-interface";
|
|
47
|
+
import { ACK_CONTROL_PRIORITY, AcknowledgeDelivery, AnyWhere, BACKGROUND_MESSAGE_PRIORITY, CONVERGENCE_MESSAGE_PRIORITY, createRequestTransportContext, DataMessage, MessageHeader, NotStartedError, SilentDelivery, } from "@peerbit/stream-interface";
|
|
48
48
|
import { AbortError, TimeoutError, debounceAccumulator, debounceFixedInterval, waitFor, } from "@peerbit/time";
|
|
49
49
|
import pDefer, {} from "p-defer";
|
|
50
50
|
import PQueue from "p-queue";
|
|
51
51
|
import { concat, fromString } from "uint8arrays";
|
|
52
52
|
import { BlocksMessage } from "./blocks.js";
|
|
53
|
+
import { CheckedPruneCoordinator, } from "./checked-prune.js";
|
|
53
54
|
import { CPUUsageIntervalLag } from "./cpu.js";
|
|
54
55
|
import { debouncedAccumulatorMap, } from "./debounce.js";
|
|
55
56
|
import { NoPeersError } from "./errors.js";
|
|
@@ -66,7 +67,7 @@ import {} from "./replication-domain.js";
|
|
|
66
67
|
import { AbsoluteReplicas, AddedReplicationSegmentMessage, AllReplicatingSegmentsMessage, MinReplicas, ReplicationPingMessage, ReplicationError, RequestReplicationInfoMessage, ResponseRoleMessage, StoppedReplicating, decodeReplicas, encodeReplicas, maxReplicas, } from "./replication.js";
|
|
67
68
|
import { Observer, Replicator } from "./role.js";
|
|
68
69
|
import { RatelessIBLTSynchronizer } from "./sync/rateless-iblt.js";
|
|
69
|
-
import { ConfirmEntriesMessage, SimpleSyncronizer } from "./sync/simple.js";
|
|
70
|
+
import { ConfirmEntriesMessage, SYNC_MESSAGE_PRIORITY, SimpleSyncronizer, } from "./sync/simple.js";
|
|
70
71
|
import { groupByGid } from "./utils.js";
|
|
71
72
|
const toLocalPublicSignKey = (key) => {
|
|
72
73
|
if (typeof key === "string") {
|
|
@@ -238,6 +239,7 @@ export const WAIT_FOR_PRUNE_DELAY = 0;
|
|
|
238
239
|
const PRUNE_DEBOUNCE_INTERVAL = 500;
|
|
239
240
|
const CHECKED_PRUNE_RESEND_INTERVAL_MIN_MS = 250;
|
|
240
241
|
const CHECKED_PRUNE_RESEND_INTERVAL_MAX_MS = 5_000;
|
|
242
|
+
const CHECKED_PRUNE_BACKGROUND_TIMEOUT_MIN_MS = 120_000;
|
|
241
243
|
const CHECKED_PRUNE_RETRY_MAX_ATTEMPTS = 3;
|
|
242
244
|
const CHECKED_PRUNE_RETRY_MAX_DELAY_MS = 30_000;
|
|
243
245
|
// DONT SET THIS ANY LOWER, because it will make the pid controller unstable as the system responses are not fast enough to updates from the pid controller
|
|
@@ -426,7 +428,10 @@ let SharedLog = (() => {
|
|
|
426
428
|
_logProperties;
|
|
427
429
|
_closeController;
|
|
428
430
|
_respondToIHaveTimeout;
|
|
429
|
-
|
|
431
|
+
_checkedPrune;
|
|
432
|
+
get _pendingDeletes() {
|
|
433
|
+
return this._checkedPrune.pendingDeletes;
|
|
434
|
+
}
|
|
430
435
|
_pendingIHave;
|
|
431
436
|
// public key hash to range id to range
|
|
432
437
|
pendingMaturity; // map of peerId to timeout
|
|
@@ -453,9 +458,15 @@ let SharedLog = (() => {
|
|
|
453
458
|
// A fn for debouncing the calls for pruning
|
|
454
459
|
pruneDebouncedFn;
|
|
455
460
|
responseToPruneDebouncedFn;
|
|
456
|
-
_requestIPruneSent
|
|
457
|
-
|
|
458
|
-
|
|
461
|
+
get _requestIPruneSent() {
|
|
462
|
+
return this._checkedPrune.requestIPruneSent;
|
|
463
|
+
}
|
|
464
|
+
get _requestIPruneResponseReplicatorSet() {
|
|
465
|
+
return this._checkedPrune.responseReplicatorSet;
|
|
466
|
+
}
|
|
467
|
+
get _checkedPruneRetries() {
|
|
468
|
+
return this._checkedPrune.retries;
|
|
469
|
+
}
|
|
459
470
|
replicationChangeDebounceFn;
|
|
460
471
|
_repairRetryTimers;
|
|
461
472
|
_recentRepairDispatch;
|
|
@@ -606,7 +617,7 @@ let SharedLog = (() => {
|
|
|
606
617
|
header: new MessageHeader({
|
|
607
618
|
session: 0,
|
|
608
619
|
mode: new AnyWhere(),
|
|
609
|
-
priority:
|
|
620
|
+
priority: BACKGROUND_MESSAGE_PRIORITY,
|
|
610
621
|
}),
|
|
611
622
|
});
|
|
612
623
|
contextMessage.header.timestamp = envelope.timestamp;
|
|
@@ -634,7 +645,7 @@ let SharedLog = (() => {
|
|
|
634
645
|
header: new MessageHeader({
|
|
635
646
|
session: 0,
|
|
636
647
|
mode: new AnyWhere(),
|
|
637
|
-
priority:
|
|
648
|
+
priority: BACKGROUND_MESSAGE_PRIORITY,
|
|
638
649
|
}),
|
|
639
650
|
});
|
|
640
651
|
contextMessage.header.timestamp = detail.timestamp;
|
|
@@ -1347,7 +1358,7 @@ let SharedLog = (() => {
|
|
|
1347
1358
|
await this.rpc.send(new StoppedReplicating({
|
|
1348
1359
|
segmentIds: rangesToUnreplicate.map((x) => x.id),
|
|
1349
1360
|
}), {
|
|
1350
|
-
priority:
|
|
1361
|
+
priority: CONVERGENCE_MESSAGE_PRIORITY,
|
|
1351
1362
|
});
|
|
1352
1363
|
}
|
|
1353
1364
|
return rangesToReplicate;
|
|
@@ -1459,7 +1470,7 @@ let SharedLog = (() => {
|
|
|
1459
1470
|
const rangesToRemove = await this.resolveReplicationRangesFromIdsAndKey(segmentIds, this.node.identity.publicKey);
|
|
1460
1471
|
await this.removeReplicationRanges(rangesToRemove, this.node.identity.publicKey);
|
|
1461
1472
|
await this.rpc.send(new StoppedReplicating({ segmentIds }), {
|
|
1462
|
-
priority:
|
|
1473
|
+
priority: CONVERGENCE_MESSAGE_PRIORITY,
|
|
1463
1474
|
});
|
|
1464
1475
|
}
|
|
1465
1476
|
async removeReplicator(key, options) {
|
|
@@ -1477,7 +1488,7 @@ let SharedLog = (() => {
|
|
|
1477
1488
|
if (isMe) {
|
|
1478
1489
|
// announce that we are no longer replicating
|
|
1479
1490
|
await this.rpc.send(new AllReplicatingSegmentsMessage({ segments: [] }), {
|
|
1480
|
-
priority:
|
|
1491
|
+
priority: CONVERGENCE_MESSAGE_PRIORITY,
|
|
1481
1492
|
});
|
|
1482
1493
|
}
|
|
1483
1494
|
if (options?.noEvent !== true) {
|
|
@@ -1840,7 +1851,7 @@ let SharedLog = (() => {
|
|
|
1840
1851
|
}
|
|
1841
1852
|
else {
|
|
1842
1853
|
await this.rpc.send(message, {
|
|
1843
|
-
priority:
|
|
1854
|
+
priority: CONVERGENCE_MESSAGE_PRIORITY,
|
|
1844
1855
|
});
|
|
1845
1856
|
}
|
|
1846
1857
|
}
|
|
@@ -2027,7 +2038,7 @@ let SharedLog = (() => {
|
|
|
2027
2038
|
for (let i = 0; i < uniqueHashes.length; i += REPAIR_CONFIRMATION_HASH_BATCH_SIZE) {
|
|
2028
2039
|
const chunk = uniqueHashes.slice(i, i + REPAIR_CONFIRMATION_HASH_BATCH_SIZE);
|
|
2029
2040
|
await this.rpc.send(new ConfirmEntriesMessage({ hashes: chunk }), {
|
|
2030
|
-
priority:
|
|
2041
|
+
priority: CONVERGENCE_MESSAGE_PRIORITY,
|
|
2031
2042
|
mode: new SilentDelivery({ to: [target], redundancy: 1 }),
|
|
2032
2043
|
});
|
|
2033
2044
|
}
|
|
@@ -2036,7 +2047,7 @@ let SharedLog = (() => {
|
|
|
2036
2047
|
for await (const message of createExchangeHeadsMessages(this.log, [...entries.keys()])) {
|
|
2037
2048
|
message.reserved[0] |= EXCHANGE_HEADS_REPAIR_HINT;
|
|
2038
2049
|
await this.rpc.send(message, {
|
|
2039
|
-
priority:
|
|
2050
|
+
priority: SYNC_MESSAGE_PRIORITY,
|
|
2040
2051
|
mode: new SilentDelivery({ to: [target], redundancy: 1 }),
|
|
2041
2052
|
});
|
|
2042
2053
|
}
|
|
@@ -2551,25 +2562,22 @@ let SharedLog = (() => {
|
|
|
2551
2562
|
if (this.keep && (await this.keep(args.value.entry))) {
|
|
2552
2563
|
return false;
|
|
2553
2564
|
}
|
|
2565
|
+
this._checkedPrune.trackCandidate(args.key, args.value.entry, args.value.leaders);
|
|
2554
2566
|
void this.pruneDebouncedFn.add(args);
|
|
2555
2567
|
return true;
|
|
2556
2568
|
}
|
|
2557
|
-
async cancelCheckedPruneForLocalLeader(hash) {
|
|
2569
|
+
async cancelCheckedPruneForLocalLeader(hash, options) {
|
|
2558
2570
|
this.pruneDebouncedFn.delete(hash);
|
|
2559
|
-
this.
|
|
2560
|
-
this.
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
?.reject(new Error("Failed to delete, is leader again"));
|
|
2571
|
+
const pendingDelete = this._checkedPrune.getPendingDelete(hash);
|
|
2572
|
+
this._checkedPrune.markCancelled(hash, {
|
|
2573
|
+
preserveRetry: options?.preserveRetry,
|
|
2574
|
+
});
|
|
2575
|
+
await pendingDelete?.reject(new Error("Failed to delete, is leader again"));
|
|
2565
2576
|
}
|
|
2566
2577
|
hasActiveCheckedPruneWork(hash) {
|
|
2567
|
-
return
|
|
2568
|
-
this._requestIPruneSent.has(hash) ||
|
|
2569
|
-
this._requestIPruneResponseReplicatorSet.has(hash) ||
|
|
2570
|
-
this._checkedPruneRetries.has(hash));
|
|
2578
|
+
return this._checkedPrune.hasActiveWork(hash);
|
|
2571
2579
|
}
|
|
2572
|
-
async
|
|
2580
|
+
async revalidateCheckedPruneOwnership(args) {
|
|
2573
2581
|
const selfHash = this.node.identity.publicKey.hashcode();
|
|
2574
2582
|
if (args.leaders.has(selfHash)) {
|
|
2575
2583
|
if (args.selfReplicating === false) {
|
|
@@ -2615,7 +2623,7 @@ let SharedLog = (() => {
|
|
|
2615
2623
|
await this.cancelCheckedPruneForLocalLeader(entry.hash);
|
|
2616
2624
|
continue;
|
|
2617
2625
|
}
|
|
2618
|
-
if (this.
|
|
2626
|
+
if (this._checkedPrune.hasPendingDelete(entry.hash)) {
|
|
2619
2627
|
continue;
|
|
2620
2628
|
}
|
|
2621
2629
|
if (leaders.size === 0) {
|
|
@@ -2628,7 +2636,7 @@ let SharedLog = (() => {
|
|
|
2628
2636
|
this.responseToPruneDebouncedFn.delete(entry.hash);
|
|
2629
2637
|
}
|
|
2630
2638
|
}
|
|
2631
|
-
async pruneIndexedEntriesNoLongerLed() {
|
|
2639
|
+
async pruneIndexedEntriesNoLongerLed(options) {
|
|
2632
2640
|
const selfHash = this.node.identity.publicKey.hashcode();
|
|
2633
2641
|
const iterator = this.entryCoordinatesIndex.iterate({});
|
|
2634
2642
|
let enqueuedPrune = false;
|
|
@@ -2640,12 +2648,12 @@ let SharedLog = (() => {
|
|
|
2640
2648
|
if (this.closed) {
|
|
2641
2649
|
continue;
|
|
2642
2650
|
}
|
|
2643
|
-
const leaders = await this.findLeaders(entryReplicated.coordinates, entryReplicated, { roleAge: 0 });
|
|
2651
|
+
const leaders = await this.findLeaders(entryReplicated.coordinates, entryReplicated, options?.useDefaultRoleAge ? undefined : { roleAge: 0 });
|
|
2644
2652
|
if (leaders.has(selfHash)) {
|
|
2645
2653
|
await this.cancelCheckedPruneForLocalLeader(entryReplicated.hash);
|
|
2646
2654
|
continue;
|
|
2647
2655
|
}
|
|
2648
|
-
if (this.
|
|
2656
|
+
if (this._checkedPrune.hasPendingDelete(entryReplicated.hash)) {
|
|
2649
2657
|
continue;
|
|
2650
2658
|
}
|
|
2651
2659
|
if (leaders.size === 0) {
|
|
@@ -2667,20 +2675,63 @@ let SharedLog = (() => {
|
|
|
2667
2675
|
await this.pruneDebouncedFn.flush();
|
|
2668
2676
|
}
|
|
2669
2677
|
}
|
|
2670
|
-
|
|
2671
|
-
const
|
|
2672
|
-
|
|
2673
|
-
|
|
2678
|
+
async pruneCurrentHeadsNoLongerLed(options) {
|
|
2679
|
+
const selfHash = this.node.identity.publicKey.hashcode();
|
|
2680
|
+
const heads = await this.log.getHeads(true).all();
|
|
2681
|
+
let enqueuedPrune = false;
|
|
2682
|
+
for (const head of heads) {
|
|
2683
|
+
if (this.closed) {
|
|
2684
|
+
break;
|
|
2685
|
+
}
|
|
2686
|
+
const leaders = await this.findLeadersFromEntry(head, maxReplicas(this, [head]), options?.useDefaultRoleAge ? undefined : { roleAge: 0 });
|
|
2687
|
+
if (leaders.has(selfHash)) {
|
|
2688
|
+
await this.cancelCheckedPruneForLocalLeader(head.hash);
|
|
2689
|
+
continue;
|
|
2690
|
+
}
|
|
2691
|
+
if (this._checkedPrune.hasPendingDelete(head.hash)) {
|
|
2692
|
+
continue;
|
|
2693
|
+
}
|
|
2694
|
+
if (leaders.size === 0) {
|
|
2695
|
+
continue;
|
|
2696
|
+
}
|
|
2697
|
+
enqueuedPrune =
|
|
2698
|
+
(await this.pruneDebouncedFnAddIfNotKeeping({
|
|
2699
|
+
key: head.hash,
|
|
2700
|
+
value: { entry: head, leaders },
|
|
2701
|
+
})) || enqueuedPrune;
|
|
2702
|
+
this.responseToPruneDebouncedFn.delete(head.hash);
|
|
2703
|
+
}
|
|
2704
|
+
if (enqueuedPrune && !this.closed) {
|
|
2705
|
+
await this.pruneDebouncedFn.flush();
|
|
2706
|
+
}
|
|
2707
|
+
}
|
|
2708
|
+
checkedPruneLeadersToMap(leaders) {
|
|
2709
|
+
if (leaders instanceof Map) {
|
|
2710
|
+
return new Map(leaders);
|
|
2711
|
+
}
|
|
2712
|
+
const leadersMap = new Map();
|
|
2713
|
+
for (const leader of leaders) {
|
|
2714
|
+
leadersMap.set(leader, { intersecting: true });
|
|
2674
2715
|
}
|
|
2675
|
-
|
|
2716
|
+
return leadersMap;
|
|
2717
|
+
}
|
|
2718
|
+
clearCheckedPruneRetry(hash) {
|
|
2719
|
+
this._checkedPrune.clearRetry(hash);
|
|
2676
2720
|
}
|
|
2677
2721
|
scheduleCheckedPruneRetry(args) {
|
|
2678
2722
|
if (this.closed)
|
|
2679
2723
|
return;
|
|
2680
|
-
if (this.
|
|
2724
|
+
if (this._checkedPrune.hasPendingDelete(args.entry.hash))
|
|
2681
2725
|
return;
|
|
2682
2726
|
const hash = args.entry.hash;
|
|
2683
|
-
const state = this.
|
|
2727
|
+
const state = this._checkedPrune.getRetry(hash) ??
|
|
2728
|
+
{
|
|
2729
|
+
attempts: 0,
|
|
2730
|
+
entry: args.entry,
|
|
2731
|
+
leaders: args.leaders,
|
|
2732
|
+
};
|
|
2733
|
+
state.entry = args.entry;
|
|
2734
|
+
state.leaders = args.leaders;
|
|
2684
2735
|
if (state.timer)
|
|
2685
2736
|
return;
|
|
2686
2737
|
if (state.attempts >= CHECKED_PRUNE_RETRY_MAX_ATTEMPTS) {
|
|
@@ -2693,17 +2744,19 @@ let SharedLog = (() => {
|
|
|
2693
2744
|
const delayMs = Math.min(CHECKED_PRUNE_RETRY_MAX_DELAY_MS, 1_000 * 2 ** (attempt - 1) + jitterMs);
|
|
2694
2745
|
state.attempts = attempt;
|
|
2695
2746
|
state.timer = setTimeout(async () => {
|
|
2696
|
-
const st = this.
|
|
2747
|
+
const st = this._checkedPrune.getRetry(hash);
|
|
2697
2748
|
if (st)
|
|
2698
2749
|
st.timer = undefined;
|
|
2699
2750
|
if (this.closed)
|
|
2700
2751
|
return;
|
|
2701
|
-
if (this.
|
|
2752
|
+
if (this._checkedPrune.hasPendingDelete(hash))
|
|
2702
2753
|
return;
|
|
2754
|
+
const retryEntry = st?.entry ?? args.entry;
|
|
2755
|
+
const retryLeaders = st?.leaders ?? args.leaders;
|
|
2703
2756
|
let leadersMap;
|
|
2704
2757
|
try {
|
|
2705
|
-
const replicas = decodeReplicas(
|
|
2706
|
-
leadersMap = await this.findLeadersFromEntry(
|
|
2758
|
+
const replicas = decodeReplicas(retryEntry).getValue(this);
|
|
2759
|
+
leadersMap = await this.findLeadersFromEntry(retryEntry, replicas, {
|
|
2707
2760
|
roleAge: 0,
|
|
2708
2761
|
});
|
|
2709
2762
|
}
|
|
@@ -2711,21 +2764,13 @@ let SharedLog = (() => {
|
|
|
2711
2764
|
// Best-effort only.
|
|
2712
2765
|
}
|
|
2713
2766
|
if (!leadersMap || leadersMap.size === 0) {
|
|
2714
|
-
|
|
2715
|
-
leadersMap = args.leaders;
|
|
2716
|
-
}
|
|
2717
|
-
else {
|
|
2718
|
-
leadersMap = new Map();
|
|
2719
|
-
for (const k of args.leaders) {
|
|
2720
|
-
leadersMap.set(k, { intersecting: true });
|
|
2721
|
-
}
|
|
2722
|
-
}
|
|
2767
|
+
leadersMap = this.checkedPruneLeadersToMap(retryLeaders);
|
|
2723
2768
|
}
|
|
2724
2769
|
try {
|
|
2725
2770
|
const leadersForRetry = leadersMap ?? new Map();
|
|
2726
2771
|
await this.pruneDebouncedFnAddIfNotKeeping({
|
|
2727
2772
|
key: hash,
|
|
2728
|
-
value: { entry:
|
|
2773
|
+
value: { entry: retryEntry, leaders: leadersForRetry },
|
|
2729
2774
|
});
|
|
2730
2775
|
}
|
|
2731
2776
|
catch {
|
|
@@ -2733,7 +2778,56 @@ let SharedLog = (() => {
|
|
|
2733
2778
|
}
|
|
2734
2779
|
}, delayMs);
|
|
2735
2780
|
state.timer.unref?.();
|
|
2736
|
-
this.
|
|
2781
|
+
this._checkedPrune.setRetry(hash, state);
|
|
2782
|
+
}
|
|
2783
|
+
async recoverCheckedPruneFromLateResponses(hashes, publicKeyHash) {
|
|
2784
|
+
if (this.closed)
|
|
2785
|
+
return;
|
|
2786
|
+
const selfHash = this.node.identity.publicKey.hashcode();
|
|
2787
|
+
const toPrune = new Map();
|
|
2788
|
+
const responseStillApplies = [];
|
|
2789
|
+
for (const hash of hashes) {
|
|
2790
|
+
if (this.closed) {
|
|
2791
|
+
break;
|
|
2792
|
+
}
|
|
2793
|
+
if (this._checkedPrune.hasPendingDelete(hash)) {
|
|
2794
|
+
continue;
|
|
2795
|
+
}
|
|
2796
|
+
const retry = this._checkedPrune.clearRetryTimer(hash);
|
|
2797
|
+
if (!retry) {
|
|
2798
|
+
continue;
|
|
2799
|
+
}
|
|
2800
|
+
const entry = retry.entry;
|
|
2801
|
+
let leaders = this.checkedPruneLeadersToMap(retry.leaders);
|
|
2802
|
+
try {
|
|
2803
|
+
const currentLeaders = await this.findLeadersFromEntry(entry, decodeReplicas(entry).getValue(this), { roleAge: 0 });
|
|
2804
|
+
if (currentLeaders.size > 0) {
|
|
2805
|
+
leaders = currentLeaders;
|
|
2806
|
+
}
|
|
2807
|
+
}
|
|
2808
|
+
catch {
|
|
2809
|
+
// Best-effort only; the stored retry leaders came from a previous
|
|
2810
|
+
// checked-prune decision for this exact entry.
|
|
2811
|
+
}
|
|
2812
|
+
if (leaders.has(selfHash)) {
|
|
2813
|
+
await this.cancelCheckedPruneForLocalLeader(hash);
|
|
2814
|
+
continue;
|
|
2815
|
+
}
|
|
2816
|
+
if (leaders.size === 0) {
|
|
2817
|
+
continue;
|
|
2818
|
+
}
|
|
2819
|
+
toPrune.set(hash, { entry, leaders });
|
|
2820
|
+
if (leaders.has(publicKeyHash)) {
|
|
2821
|
+
responseStillApplies.push(hash);
|
|
2822
|
+
}
|
|
2823
|
+
}
|
|
2824
|
+
if (toPrune.size === 0) {
|
|
2825
|
+
return;
|
|
2826
|
+
}
|
|
2827
|
+
void Promise.allSettled(this.prune(toPrune));
|
|
2828
|
+
for (const hash of responseStillApplies) {
|
|
2829
|
+
void this._checkedPrune.getPendingDelete(hash)?.resolve(publicKeyHash);
|
|
2830
|
+
}
|
|
2737
2831
|
}
|
|
2738
2832
|
async append(data, options) {
|
|
2739
2833
|
if (this._isAdaptiveReplicating) {
|
|
@@ -2841,7 +2935,7 @@ let SharedLog = (() => {
|
|
|
2841
2935
|
: createReplicationDomainHash(options?.compatibility && options?.compatibility < 10 ? "u32" : "u64")(this);
|
|
2842
2936
|
this.indexableDomain = createIndexableDomainFromResolution(this.domain.resolution);
|
|
2843
2937
|
this._respondToIHaveTimeout = options?.respondToIHaveTimeout ?? 2e4;
|
|
2844
|
-
this.
|
|
2938
|
+
this._checkedPrune = new CheckedPruneCoordinator();
|
|
2845
2939
|
this._pendingIHave = new Map();
|
|
2846
2940
|
this.latestReplicationInfoMessage = new Map();
|
|
2847
2941
|
this._replicationInfoBlockedPeers = new Set();
|
|
@@ -2999,22 +3093,28 @@ let SharedLog = (() => {
|
|
|
2999
3093
|
],
|
|
3000
3094
|
})) > 0;
|
|
3001
3095
|
this._gidPeersHistory = new Map();
|
|
3002
|
-
this._requestIPruneSent = new Map();
|
|
3003
|
-
this._requestIPruneResponseReplicatorSet = new Map();
|
|
3004
|
-
this._checkedPruneRetries = new Map();
|
|
3005
3096
|
this.replicationChangeDebounceFn = debounceAggregationChanges((change) => this.onReplicationChange(change).then(() => this.rebalanceParticipationDebounced?.call()), this.distributionDebounceTime);
|
|
3006
3097
|
this.pruneDebouncedFn = debouncedAccumulatorMap(async (map) => {
|
|
3007
3098
|
const current = new Map();
|
|
3008
3099
|
const selfReplicating = await this.isReplicating();
|
|
3009
3100
|
for (const [hash, value] of map) {
|
|
3010
|
-
const checkedPruneLeaders = await this.
|
|
3101
|
+
const checkedPruneLeaders = await this.revalidateCheckedPruneOwnership({
|
|
3011
3102
|
hash,
|
|
3012
3103
|
entry: value.entry,
|
|
3013
3104
|
leaders: value.leaders,
|
|
3014
3105
|
selfReplicating,
|
|
3015
3106
|
});
|
|
3016
3107
|
if (checkedPruneLeaders.localLeader) {
|
|
3017
|
-
|
|
3108
|
+
const preserveRetry = this._checkedPrune.hasRetry(hash);
|
|
3109
|
+
await this.cancelCheckedPruneForLocalLeader(hash, {
|
|
3110
|
+
preserveRetry,
|
|
3111
|
+
});
|
|
3112
|
+
if (preserveRetry) {
|
|
3113
|
+
this.scheduleCheckedPruneRetry({
|
|
3114
|
+
entry: value.entry,
|
|
3115
|
+
leaders: checkedPruneLeaders.leaders,
|
|
3116
|
+
});
|
|
3117
|
+
}
|
|
3018
3118
|
continue;
|
|
3019
3119
|
}
|
|
3020
3120
|
current.set(hash, {
|
|
@@ -3048,7 +3148,7 @@ let SharedLog = (() => {
|
|
|
3048
3148
|
to: allRequestingPeers,
|
|
3049
3149
|
redundancy: 1,
|
|
3050
3150
|
}),
|
|
3051
|
-
priority:
|
|
3151
|
+
priority: CONVERGENCE_MESSAGE_PRIORITY,
|
|
3052
3152
|
});
|
|
3053
3153
|
}, () => {
|
|
3054
3154
|
let accumulator = new Map();
|
|
@@ -3337,18 +3437,7 @@ let SharedLog = (() => {
|
|
|
3337
3437
|
this.cancelReplicationInfoRequests(peerHash);
|
|
3338
3438
|
this._replicatorLivenessFailures.delete(peerHash);
|
|
3339
3439
|
this._replicatorLastActivityAt.delete(peerHash);
|
|
3340
|
-
|
|
3341
|
-
peers.delete(peerHash);
|
|
3342
|
-
if (peers.size === 0) {
|
|
3343
|
-
this._requestIPruneSent.delete(hash);
|
|
3344
|
-
}
|
|
3345
|
-
}
|
|
3346
|
-
for (const [hash, peers] of this._requestIPruneResponseReplicatorSet) {
|
|
3347
|
-
peers.delete(peerHash);
|
|
3348
|
-
if (peers.size === 0) {
|
|
3349
|
-
this._requestIPruneResponseReplicatorSet.delete(hash);
|
|
3350
|
-
}
|
|
3351
|
-
}
|
|
3440
|
+
this._checkedPrune.cleanupPeer(peerHash);
|
|
3352
3441
|
}
|
|
3353
3442
|
markReplicatorActivity(peerHash, now = Date.now()) {
|
|
3354
3443
|
this._replicatorLastActivityAt.set(peerHash, now);
|
|
@@ -3394,14 +3483,14 @@ let SharedLog = (() => {
|
|
|
3394
3483
|
const maxPeers = options?.maxPeers ?? 8;
|
|
3395
3484
|
const self = this.node.identity.publicKey.hashcode();
|
|
3396
3485
|
const seed = hashToSeed32(hash);
|
|
3397
|
-
const hinted = this.
|
|
3486
|
+
const hinted = this._checkedPrune.getConfirmedReplicators(hash);
|
|
3398
3487
|
if (hinted && hinted.size > 0) {
|
|
3399
3488
|
const peers = [...hinted].filter((p) => p !== self);
|
|
3400
3489
|
return peers.length > 0
|
|
3401
3490
|
? pickDeterministicSubset(peers, seed, maxPeers)
|
|
3402
3491
|
: undefined;
|
|
3403
3492
|
}
|
|
3404
|
-
const contacted = this.
|
|
3493
|
+
const contacted = this._checkedPrune.getContactedReplicators(hash);
|
|
3405
3494
|
if (contacted && contacted.size > 0) {
|
|
3406
3495
|
const peers = [...contacted].filter((p) => p !== self);
|
|
3407
3496
|
return peers.length > 0
|
|
@@ -3758,25 +3847,14 @@ let SharedLog = (() => {
|
|
|
3758
3847
|
this._appendBackfillTimer = undefined;
|
|
3759
3848
|
}
|
|
3760
3849
|
this._appendBackfillPendingByTarget.clear();
|
|
3761
|
-
for (const [_k, v] of this._pendingDeletes) {
|
|
3762
|
-
v.clear();
|
|
3763
|
-
v.promise.resolve(); // TODO or reject?
|
|
3764
|
-
}
|
|
3765
3850
|
for (const [_k, v] of this._pendingIHave) {
|
|
3766
3851
|
v.clear();
|
|
3767
3852
|
}
|
|
3768
|
-
|
|
3769
|
-
if (v.timer)
|
|
3770
|
-
clearTimeout(v.timer);
|
|
3771
|
-
}
|
|
3853
|
+
this._checkedPrune.close();
|
|
3772
3854
|
await this.remoteBlocks.stop();
|
|
3773
|
-
this._pendingDeletes.clear();
|
|
3774
3855
|
this._pendingIHave.clear();
|
|
3775
|
-
this._checkedPruneRetries.clear();
|
|
3776
3856
|
this.latestReplicationInfoMessage.clear();
|
|
3777
3857
|
this._gidPeersHistory.clear();
|
|
3778
|
-
this._requestIPruneSent.clear();
|
|
3779
|
-
this._requestIPruneResponseReplicatorSet.clear();
|
|
3780
3858
|
// Cancel any pending debounced timers so they can't fire after we've torn down
|
|
3781
3859
|
// indexes/RPC state.
|
|
3782
3860
|
this.rebalanceParticipationDebounced?.close();
|
|
@@ -3827,7 +3905,7 @@ let SharedLog = (() => {
|
|
|
3827
3905
|
try {
|
|
3828
3906
|
await this.rpc
|
|
3829
3907
|
.send(new AllReplicatingSegmentsMessage({ segments: [] }), {
|
|
3830
|
-
priority:
|
|
3908
|
+
priority: CONVERGENCE_MESSAGE_PRIORITY,
|
|
3831
3909
|
signal: abort.signal,
|
|
3832
3910
|
})
|
|
3833
3911
|
.catch(() => { });
|
|
@@ -3871,7 +3949,7 @@ let SharedLog = (() => {
|
|
|
3871
3949
|
try {
|
|
3872
3950
|
await this.rpc
|
|
3873
3951
|
.send(new AllReplicatingSegmentsMessage({ segments: [] }), {
|
|
3874
|
-
priority:
|
|
3952
|
+
priority: CONVERGENCE_MESSAGE_PRIORITY,
|
|
3875
3953
|
signal: abort.signal,
|
|
3876
3954
|
})
|
|
3877
3955
|
.catch(() => { });
|
|
@@ -4014,7 +4092,7 @@ let SharedLog = (() => {
|
|
|
4014
4092
|
for (const entry of entries) {
|
|
4015
4093
|
this.pruneDebouncedFn.delete(entry.entry.hash);
|
|
4016
4094
|
this.removePruneRequestSent(entry.entry.hash);
|
|
4017
|
-
this.
|
|
4095
|
+
this._checkedPrune.clearConfirmedReplicators(entry.entry.hash);
|
|
4018
4096
|
if (fromIsLeader) {
|
|
4019
4097
|
this.addPeersToGidPeerHistory(gid, [
|
|
4020
4098
|
context.from.hashcode(),
|
|
@@ -4110,27 +4188,37 @@ let SharedLog = (() => {
|
|
|
4110
4188
|
this.removeEntriesKnownByPeer([hash], from);
|
|
4111
4189
|
// if we expect the remote to be owner of this entry because we are to prune ourselves, then we need to remove the remote
|
|
4112
4190
|
// this is due to that the remote has previously indicated to be a replicator to help us prune but now has changed their mind
|
|
4113
|
-
|
|
4114
|
-
if (outGoingPrunes) {
|
|
4115
|
-
outGoingPrunes.delete(from);
|
|
4116
|
-
}
|
|
4191
|
+
this._checkedPrune.removeConfirmedReplicator(hash, from);
|
|
4117
4192
|
const indexedEntry = await this.log.entryIndex.getShallow(hash);
|
|
4118
4193
|
let isLeader = false;
|
|
4119
|
-
if (indexedEntry &&
|
|
4120
|
-
|
|
4121
|
-
|
|
4122
|
-
|
|
4123
|
-
|
|
4124
|
-
|
|
4125
|
-
|
|
4126
|
-
|
|
4127
|
-
|
|
4128
|
-
|
|
4129
|
-
|
|
4130
|
-
|
|
4131
|
-
|
|
4132
|
-
|
|
4133
|
-
|
|
4194
|
+
if (indexedEntry && (await this.log.blocks.has(hash))) {
|
|
4195
|
+
const pendingDelete = this._checkedPrune.getPendingDelete(hash);
|
|
4196
|
+
if (pendingDelete) {
|
|
4197
|
+
const ownership = await this.revalidateCheckedPruneOwnership({
|
|
4198
|
+
hash,
|
|
4199
|
+
entry: indexedEntry.value,
|
|
4200
|
+
leaders: new Map(),
|
|
4201
|
+
});
|
|
4202
|
+
if (ownership.localLeader) {
|
|
4203
|
+
await this.cancelCheckedPruneForLocalLeader(hash);
|
|
4204
|
+
isLeader = true;
|
|
4205
|
+
}
|
|
4206
|
+
}
|
|
4207
|
+
else {
|
|
4208
|
+
this.removePeerFromGidPeerHistory(context.from.hashcode(), indexedEntry.value.meta.gid);
|
|
4209
|
+
await this._waitForReplicators(await this.createCoordinates(indexedEntry.value, decodeReplicas(indexedEntry.value).getValue(this)), indexedEntry.value, [
|
|
4210
|
+
{
|
|
4211
|
+
key: this.node.identity.publicKey.hashcode(),
|
|
4212
|
+
replicator: true,
|
|
4213
|
+
},
|
|
4214
|
+
], {
|
|
4215
|
+
onLeader: (key) => {
|
|
4216
|
+
isLeader =
|
|
4217
|
+
isLeader ||
|
|
4218
|
+
key === this.node.identity.publicKey.hashcode();
|
|
4219
|
+
},
|
|
4220
|
+
});
|
|
4221
|
+
}
|
|
4134
4222
|
}
|
|
4135
4223
|
if (isLeader) {
|
|
4136
4224
|
hasAndIsLeader.push(hash);
|
|
@@ -4193,8 +4281,18 @@ let SharedLog = (() => {
|
|
|
4193
4281
|
}
|
|
4194
4282
|
}
|
|
4195
4283
|
else if (msg instanceof ResponseIPrune) {
|
|
4284
|
+
const lateResponses = [];
|
|
4196
4285
|
for (const hash of msg.hashes) {
|
|
4197
|
-
this.
|
|
4286
|
+
const pendingDelete = this._checkedPrune.getPendingDelete(hash);
|
|
4287
|
+
if (pendingDelete) {
|
|
4288
|
+
void pendingDelete.resolve(context.from.hashcode());
|
|
4289
|
+
}
|
|
4290
|
+
else {
|
|
4291
|
+
lateResponses.push(hash);
|
|
4292
|
+
}
|
|
4293
|
+
}
|
|
4294
|
+
if (lateResponses.length > 0) {
|
|
4295
|
+
void this.recoverCheckedPruneFromLateResponses(lateResponses, context.from.hashcode()).catch((error) => logger.error(error.toString()));
|
|
4198
4296
|
}
|
|
4199
4297
|
}
|
|
4200
4298
|
else if (msg instanceof ConfirmEntriesMessage) {
|
|
@@ -4564,7 +4662,7 @@ let SharedLog = (() => {
|
|
|
4564
4662
|
}
|
|
4565
4663
|
if (messageToSend) {
|
|
4566
4664
|
await this.rpc.send(messageToSend, {
|
|
4567
|
-
priority:
|
|
4665
|
+
priority: CONVERGENCE_MESSAGE_PRIORITY,
|
|
4568
4666
|
});
|
|
4569
4667
|
}
|
|
4570
4668
|
}
|
|
@@ -5123,8 +5221,8 @@ let SharedLog = (() => {
|
|
|
5123
5221
|
};
|
|
5124
5222
|
this._replicationInfoRequestByPeer.set(peerHash, state);
|
|
5125
5223
|
const intervalMs = Math.max(50, this.waitForReplicatorRequestIntervalMs);
|
|
5126
|
-
const maxAttempts =
|
|
5127
|
-
WAIT_FOR_REPLICATOR_REQUEST_MIN_ATTEMPTS);
|
|
5224
|
+
const maxAttempts = this.waitForReplicatorRequestMaxAttempts ??
|
|
5225
|
+
Math.max(WAIT_FOR_REPLICATOR_REQUEST_MIN_ATTEMPTS, Math.ceil(this.waitForReplicatorTimeout / intervalMs));
|
|
5128
5226
|
const tick = () => {
|
|
5129
5227
|
if (this.closed || this._closeController.signal.aborted) {
|
|
5130
5228
|
this.cancelReplicationInfoRequests(peerHash);
|
|
@@ -5221,25 +5319,14 @@ let SharedLog = (() => {
|
|
|
5221
5319
|
return new AbsoluteReplicas(maxValue);
|
|
5222
5320
|
}
|
|
5223
5321
|
removePruneRequestSent(hash, to) {
|
|
5224
|
-
|
|
5225
|
-
this._requestIPruneSent.delete(hash);
|
|
5226
|
-
}
|
|
5227
|
-
else {
|
|
5228
|
-
let set = this._requestIPruneSent.get(hash);
|
|
5229
|
-
if (set) {
|
|
5230
|
-
set.delete(to);
|
|
5231
|
-
if (set.size === 0) {
|
|
5232
|
-
this._requestIPruneSent.delete(hash);
|
|
5233
|
-
}
|
|
5234
|
-
}
|
|
5235
|
-
}
|
|
5322
|
+
this._checkedPrune.removeRequestSent(hash, to);
|
|
5236
5323
|
}
|
|
5237
5324
|
prune(entries, options) {
|
|
5238
5325
|
if (options?.unchecked) {
|
|
5239
5326
|
return [...entries.values()].map((x) => {
|
|
5240
5327
|
this._gidPeersHistory.delete(x.entry.meta.gid);
|
|
5241
5328
|
this.removePruneRequestSent(x.entry.hash);
|
|
5242
|
-
this.
|
|
5329
|
+
this._checkedPrune.clearConfirmedReplicators(x.entry.hash);
|
|
5243
5330
|
return this.log.remove(x.entry, {
|
|
5244
5331
|
recursively: true,
|
|
5245
5332
|
});
|
|
@@ -5267,7 +5354,7 @@ let SharedLog = (() => {
|
|
|
5267
5354
|
}
|
|
5268
5355
|
set.push(entry.hash);
|
|
5269
5356
|
}
|
|
5270
|
-
const pendingPrev = this.
|
|
5357
|
+
const pendingPrev = this._checkedPrune.getPendingDelete(entry.hash);
|
|
5271
5358
|
if (pendingPrev) {
|
|
5272
5359
|
// If a background prune is already in-flight, an explicit prune request should
|
|
5273
5360
|
// still respect the caller's timeout. Otherwise, tests (and user calls) can
|
|
@@ -5296,45 +5383,62 @@ let SharedLog = (() => {
|
|
|
5296
5383
|
const minReplicas = decodeReplicas(entry);
|
|
5297
5384
|
const deferredPromise = pDefer();
|
|
5298
5385
|
const clear = () => {
|
|
5299
|
-
const pending = this.
|
|
5386
|
+
const pending = this._checkedPrune.getPendingDelete(entry.hash);
|
|
5300
5387
|
if (pending?.promise === deferredPromise) {
|
|
5301
|
-
this.
|
|
5388
|
+
this._checkedPrune.deletePendingDelete(entry.hash, pending);
|
|
5302
5389
|
}
|
|
5303
5390
|
clearTimeout(timeout);
|
|
5304
5391
|
};
|
|
5305
5392
|
const resolve = () => {
|
|
5306
|
-
|
|
5393
|
+
clearTimeout(timeout);
|
|
5307
5394
|
this.clearCheckedPruneRetry(entry.hash);
|
|
5308
5395
|
cleanupTimer.push(setTimeout(async () => {
|
|
5309
5396
|
this._gidPeersHistory.delete(entry.meta.gid);
|
|
5310
5397
|
this.removePruneRequestSent(entry.hash);
|
|
5311
|
-
this.
|
|
5312
|
-
|
|
5398
|
+
this._checkedPrune.clearConfirmedReplicators(entry.hash);
|
|
5399
|
+
const ownership = await this.revalidateCheckedPruneOwnership({
|
|
5400
|
+
hash: entry.hash,
|
|
5313
5401
|
entry,
|
|
5314
|
-
|
|
5315
|
-
|
|
5402
|
+
leaders: this.checkedPruneLeadersToMap(leaders),
|
|
5403
|
+
selfReplicating: true,
|
|
5404
|
+
});
|
|
5405
|
+
if (ownership.localLeader) {
|
|
5406
|
+
clear();
|
|
5407
|
+
if (!explicitTimeout) {
|
|
5408
|
+
this.scheduleCheckedPruneRetry({ entry, leaders });
|
|
5409
|
+
}
|
|
5316
5410
|
deferredPromise.reject(new Error("Failed to delete, is leader again"));
|
|
5317
5411
|
return;
|
|
5318
5412
|
}
|
|
5413
|
+
this._checkedPrune.markRemoving(entry.hash);
|
|
5319
5414
|
return this.log
|
|
5320
5415
|
.remove(entry, {
|
|
5321
5416
|
recursively: true,
|
|
5322
5417
|
})
|
|
5323
5418
|
.then(() => {
|
|
5419
|
+
clear();
|
|
5420
|
+
this._checkedPrune.markDone(entry.hash);
|
|
5324
5421
|
deferredPromise.resolve();
|
|
5325
5422
|
})
|
|
5326
5423
|
.catch((e) => {
|
|
5424
|
+
clear();
|
|
5425
|
+
this._checkedPrune.markCancelled(entry.hash, {
|
|
5426
|
+
preserveRetry: false,
|
|
5427
|
+
});
|
|
5327
5428
|
deferredPromise.reject(e);
|
|
5328
5429
|
})
|
|
5329
5430
|
.finally(async () => {
|
|
5330
5431
|
this._gidPeersHistory.delete(entry.meta.gid);
|
|
5331
5432
|
this.removePruneRequestSent(entry.hash);
|
|
5332
|
-
this.
|
|
5433
|
+
this._checkedPrune.clearConfirmedReplicators(entry.hash);
|
|
5333
5434
|
// TODO in the case we become leader again here we need to re-add the entry
|
|
5334
|
-
|
|
5435
|
+
const ownership = await this.revalidateCheckedPruneOwnership({
|
|
5436
|
+
hash: entry.hash,
|
|
5335
5437
|
entry,
|
|
5336
|
-
|
|
5337
|
-
|
|
5438
|
+
leaders: this.checkedPruneLeadersToMap(leaders),
|
|
5439
|
+
selfReplicating: true,
|
|
5440
|
+
});
|
|
5441
|
+
if (ownership.localLeader) {
|
|
5338
5442
|
logger.error("Unexpected: Is leader after delete");
|
|
5339
5443
|
}
|
|
5340
5444
|
});
|
|
@@ -5345,11 +5449,9 @@ let SharedLog = (() => {
|
|
|
5345
5449
|
const isCheckedPruneTimeout = e instanceof Error &&
|
|
5346
5450
|
typeof e.message === "string" &&
|
|
5347
5451
|
e.message.startsWith("Timeout for checked pruning");
|
|
5348
|
-
|
|
5349
|
-
|
|
5350
|
-
}
|
|
5351
|
-
this.removePruneRequestSent(entry.hash);
|
|
5352
|
-
this._requestIPruneResponseReplicatorSet.delete(entry.hash);
|
|
5452
|
+
this._checkedPrune.markCancelled(entry.hash, {
|
|
5453
|
+
preserveRetry: !explicitTimeout && isCheckedPruneTimeout,
|
|
5454
|
+
});
|
|
5353
5455
|
deferredPromise.reject(e);
|
|
5354
5456
|
};
|
|
5355
5457
|
let cursor = undefined;
|
|
@@ -5359,7 +5461,7 @@ let SharedLog = (() => {
|
|
|
5359
5461
|
// If we time out too early we can end up with permanently prunable heads that never
|
|
5360
5462
|
// get retried (a common CI flake in "prune before join" tests).
|
|
5361
5463
|
const checkedPruneTimeoutMs = options?.timeout ??
|
|
5362
|
-
Math.max(
|
|
5464
|
+
Math.max(CHECKED_PRUNE_BACKGROUND_TIMEOUT_MIN_MS, Number(this._respondToIHaveTimeout ?? 0) +
|
|
5363
5465
|
this.waitForReplicatorTimeout +
|
|
5364
5466
|
PRUNE_DEBOUNCE_INTERVAL * 2);
|
|
5365
5467
|
const timeout = setTimeout(() => {
|
|
@@ -5372,7 +5474,7 @@ let SharedLog = (() => {
|
|
|
5372
5474
|
reject(new Error(`Timeout for checked pruning after ${checkedPruneTimeoutMs}ms (closed=${this.closed})`));
|
|
5373
5475
|
}, checkedPruneTimeoutMs);
|
|
5374
5476
|
timeout.unref?.();
|
|
5375
|
-
this.
|
|
5477
|
+
this._checkedPrune.setPendingDelete(entry.hash, {
|
|
5376
5478
|
promise: deferredPromise,
|
|
5377
5479
|
clear,
|
|
5378
5480
|
reject,
|
|
@@ -5392,34 +5494,24 @@ let SharedLog = (() => {
|
|
|
5392
5494
|
}))) {
|
|
5393
5495
|
return;
|
|
5394
5496
|
}
|
|
5395
|
-
|
|
5396
|
-
if (!existCounter) {
|
|
5397
|
-
existCounter = new Set();
|
|
5398
|
-
this._requestIPruneResponseReplicatorSet.set(entry.hash, existCounter);
|
|
5399
|
-
}
|
|
5400
|
-
existCounter.add(publicKeyHash);
|
|
5497
|
+
const existCounter = this._checkedPrune.addConfirmedReplicator(entry.hash, publicKeyHash);
|
|
5401
5498
|
// Seed provider hints so future remote reads can avoid extra round-trips.
|
|
5402
5499
|
this.remoteBlocks.hintProviders(entry.hash, [publicKeyHash]);
|
|
5403
5500
|
if (minReplicasValue <= existCounter.size) {
|
|
5404
5501
|
resolve();
|
|
5405
5502
|
}
|
|
5406
5503
|
},
|
|
5407
|
-
});
|
|
5504
|
+
}, entry, leaders);
|
|
5408
5505
|
promises.push(deferredPromise.promise);
|
|
5409
5506
|
}
|
|
5410
5507
|
const emitMessages = async (entries, to) => {
|
|
5411
5508
|
const filteredSet = [];
|
|
5412
5509
|
for (const entry of entries) {
|
|
5413
|
-
let set = this._requestIPruneSent.get(entry);
|
|
5414
|
-
if (!set) {
|
|
5415
|
-
set = new Set();
|
|
5416
|
-
this._requestIPruneSent.set(entry, set);
|
|
5417
|
-
}
|
|
5418
5510
|
/* TODO why can we not have this statement?
|
|
5419
5511
|
if (set.has(to)) {
|
|
5420
5512
|
continue;
|
|
5421
5513
|
} */
|
|
5422
|
-
|
|
5514
|
+
this._checkedPrune.addRequestSent(entry, to);
|
|
5423
5515
|
filteredSet.push(entry);
|
|
5424
5516
|
}
|
|
5425
5517
|
if (filteredSet.length > 0) {
|
|
@@ -5430,7 +5522,7 @@ let SharedLog = (() => {
|
|
|
5430
5522
|
to: [to], // TODO group by peers?
|
|
5431
5523
|
redundancy: 1,
|
|
5432
5524
|
}),
|
|
5433
|
-
priority:
|
|
5525
|
+
priority: CONVERGENCE_MESSAGE_PRIORITY,
|
|
5434
5526
|
});
|
|
5435
5527
|
}
|
|
5436
5528
|
};
|
|
@@ -5451,7 +5543,7 @@ let SharedLog = (() => {
|
|
|
5451
5543
|
return;
|
|
5452
5544
|
const pendingByPeer = [];
|
|
5453
5545
|
for (const [peer, hashes] of peerToEntries) {
|
|
5454
|
-
const pending = hashes.filter((h) => this.
|
|
5546
|
+
const pending = hashes.filter((h) => this._checkedPrune.hasPendingDelete(h));
|
|
5455
5547
|
if (pending.length > 0) {
|
|
5456
5548
|
pendingByPeer.push([peer, pending]);
|
|
5457
5549
|
}
|
|
@@ -5777,12 +5869,21 @@ let SharedLog = (() => {
|
|
|
5777
5869
|
for (const target of [...uncheckedDeliver.keys()]) {
|
|
5778
5870
|
flushUncheckedDeliverTarget(target);
|
|
5779
5871
|
}
|
|
5780
|
-
if (this._isAdaptiveReplicating &&
|
|
5781
|
-
|
|
5782
|
-
|
|
5783
|
-
|
|
5784
|
-
|
|
5785
|
-
|
|
5872
|
+
if (this._isAdaptiveReplicating &&
|
|
5873
|
+
(hasSelfRangeRemoval ||
|
|
5874
|
+
changes.some((change) => change.type === "added" ||
|
|
5875
|
+
change.type === "removed" ||
|
|
5876
|
+
change.type === "replaced"))) {
|
|
5877
|
+
// Adaptive range changes can make already-indexed local heads prunable
|
|
5878
|
+
// even when the incremental rebalance scan misses them under churn or
|
|
5879
|
+
// timing pressure. Re-scan after repair dispatches are flushed using the
|
|
5880
|
+
// mature-role view, which matches the bounded pruning contract.
|
|
5881
|
+
await this.pruneIndexedEntriesNoLongerLed({
|
|
5882
|
+
useDefaultRoleAge: true,
|
|
5883
|
+
});
|
|
5884
|
+
await this.pruneCurrentHeadsNoLongerLed({
|
|
5885
|
+
useDefaultRoleAge: true,
|
|
5886
|
+
});
|
|
5786
5887
|
}
|
|
5787
5888
|
return changed;
|
|
5788
5889
|
}
|