@peerbit/shared-log 13.1.15 → 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 +285 -186
- 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 +16 -12
- package/src/checked-prune.ts +331 -0
- package/src/index.ts +451 -302
- 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
|
-
import { concat } from "uint8arrays";
|
|
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;
|
|
@@ -1374,12 +1385,18 @@ let SharedLog = (() => {
|
|
|
1374
1385
|
this.setupRebalanceDebounceFunction(options?.limits?.interval);
|
|
1375
1386
|
}
|
|
1376
1387
|
async replicate(rangeOrEntry, options) {
|
|
1388
|
+
const entryRangeId = (entry) => sha256Sync(concat([
|
|
1389
|
+
this.log.id,
|
|
1390
|
+
fromString(entry.hash),
|
|
1391
|
+
fromString(this.node.identity.publicKey.hashcode()),
|
|
1392
|
+
]));
|
|
1377
1393
|
let range = undefined;
|
|
1378
1394
|
if (rangeOrEntry instanceof ReplicationRangeMessage) {
|
|
1379
1395
|
range = rangeOrEntry;
|
|
1380
1396
|
}
|
|
1381
1397
|
else if (rangeOrEntry instanceof Entry) {
|
|
1382
1398
|
range = {
|
|
1399
|
+
id: entryRangeId(rangeOrEntry),
|
|
1383
1400
|
factor: 1,
|
|
1384
1401
|
offset: await this.domain.fromEntry(rangeOrEntry),
|
|
1385
1402
|
normalized: false,
|
|
@@ -1390,6 +1407,7 @@ let SharedLog = (() => {
|
|
|
1390
1407
|
for (const entry of rangeOrEntry) {
|
|
1391
1408
|
if (entry instanceof Entry) {
|
|
1392
1409
|
ranges.push({
|
|
1410
|
+
id: entryRangeId(entry),
|
|
1393
1411
|
factor: 1,
|
|
1394
1412
|
offset: await this.domain.fromEntry(entry),
|
|
1395
1413
|
normalized: false,
|
|
@@ -1452,7 +1470,7 @@ let SharedLog = (() => {
|
|
|
1452
1470
|
const rangesToRemove = await this.resolveReplicationRangesFromIdsAndKey(segmentIds, this.node.identity.publicKey);
|
|
1453
1471
|
await this.removeReplicationRanges(rangesToRemove, this.node.identity.publicKey);
|
|
1454
1472
|
await this.rpc.send(new StoppedReplicating({ segmentIds }), {
|
|
1455
|
-
priority:
|
|
1473
|
+
priority: CONVERGENCE_MESSAGE_PRIORITY,
|
|
1456
1474
|
});
|
|
1457
1475
|
}
|
|
1458
1476
|
async removeReplicator(key, options) {
|
|
@@ -1470,7 +1488,7 @@ let SharedLog = (() => {
|
|
|
1470
1488
|
if (isMe) {
|
|
1471
1489
|
// announce that we are no longer replicating
|
|
1472
1490
|
await this.rpc.send(new AllReplicatingSegmentsMessage({ segments: [] }), {
|
|
1473
|
-
priority:
|
|
1491
|
+
priority: CONVERGENCE_MESSAGE_PRIORITY,
|
|
1474
1492
|
});
|
|
1475
1493
|
}
|
|
1476
1494
|
if (options?.noEvent !== true) {
|
|
@@ -1833,7 +1851,7 @@ let SharedLog = (() => {
|
|
|
1833
1851
|
}
|
|
1834
1852
|
else {
|
|
1835
1853
|
await this.rpc.send(message, {
|
|
1836
|
-
priority:
|
|
1854
|
+
priority: CONVERGENCE_MESSAGE_PRIORITY,
|
|
1837
1855
|
});
|
|
1838
1856
|
}
|
|
1839
1857
|
}
|
|
@@ -2020,7 +2038,7 @@ let SharedLog = (() => {
|
|
|
2020
2038
|
for (let i = 0; i < uniqueHashes.length; i += REPAIR_CONFIRMATION_HASH_BATCH_SIZE) {
|
|
2021
2039
|
const chunk = uniqueHashes.slice(i, i + REPAIR_CONFIRMATION_HASH_BATCH_SIZE);
|
|
2022
2040
|
await this.rpc.send(new ConfirmEntriesMessage({ hashes: chunk }), {
|
|
2023
|
-
priority:
|
|
2041
|
+
priority: CONVERGENCE_MESSAGE_PRIORITY,
|
|
2024
2042
|
mode: new SilentDelivery({ to: [target], redundancy: 1 }),
|
|
2025
2043
|
});
|
|
2026
2044
|
}
|
|
@@ -2029,7 +2047,7 @@ let SharedLog = (() => {
|
|
|
2029
2047
|
for await (const message of createExchangeHeadsMessages(this.log, [...entries.keys()])) {
|
|
2030
2048
|
message.reserved[0] |= EXCHANGE_HEADS_REPAIR_HINT;
|
|
2031
2049
|
await this.rpc.send(message, {
|
|
2032
|
-
priority:
|
|
2050
|
+
priority: SYNC_MESSAGE_PRIORITY,
|
|
2033
2051
|
mode: new SilentDelivery({ to: [target], redundancy: 1 }),
|
|
2034
2052
|
});
|
|
2035
2053
|
}
|
|
@@ -2504,28 +2522,19 @@ let SharedLog = (() => {
|
|
|
2504
2522
|
const frontierTargets = this._repairFrontierByMode.get(mode);
|
|
2505
2523
|
for (const target of pendingPeersByMode.get(mode) ?? []) {
|
|
2506
2524
|
const replacement = nextTargets.get(target);
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
existing.set(hash, entry);
|
|
2516
|
-
}
|
|
2517
|
-
}
|
|
2518
|
-
else {
|
|
2519
|
-
frontierTargets?.set(target, replacement);
|
|
2525
|
+
// These repairs are receipt-driven: a later sweep can have a narrower
|
|
2526
|
+
// transient leader view, but it must not forget unconfirmed hashes
|
|
2527
|
+
// that were already queued for this target.
|
|
2528
|
+
if (replacement && replacement.size > 0) {
|
|
2529
|
+
const existing = frontierTargets?.get(target);
|
|
2530
|
+
if (existing && existing.size > 0) {
|
|
2531
|
+
for (const [hash, entry] of replacement) {
|
|
2532
|
+
existing.set(hash, entry);
|
|
2520
2533
|
}
|
|
2521
2534
|
}
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
frontierTargets?.set(target, replacement);
|
|
2526
|
-
}
|
|
2527
|
-
else {
|
|
2528
|
-
frontierTargets?.delete(target);
|
|
2535
|
+
else {
|
|
2536
|
+
frontierTargets?.set(target, replacement);
|
|
2537
|
+
}
|
|
2529
2538
|
}
|
|
2530
2539
|
}
|
|
2531
2540
|
}
|
|
@@ -2553,25 +2562,22 @@ let SharedLog = (() => {
|
|
|
2553
2562
|
if (this.keep && (await this.keep(args.value.entry))) {
|
|
2554
2563
|
return false;
|
|
2555
2564
|
}
|
|
2565
|
+
this._checkedPrune.trackCandidate(args.key, args.value.entry, args.value.leaders);
|
|
2556
2566
|
void this.pruneDebouncedFn.add(args);
|
|
2557
2567
|
return true;
|
|
2558
2568
|
}
|
|
2559
|
-
async cancelCheckedPruneForLocalLeader(hash) {
|
|
2569
|
+
async cancelCheckedPruneForLocalLeader(hash, options) {
|
|
2560
2570
|
this.pruneDebouncedFn.delete(hash);
|
|
2561
|
-
this.
|
|
2562
|
-
this.
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
?.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"));
|
|
2567
2576
|
}
|
|
2568
2577
|
hasActiveCheckedPruneWork(hash) {
|
|
2569
|
-
return
|
|
2570
|
-
this._requestIPruneSent.has(hash) ||
|
|
2571
|
-
this._requestIPruneResponseReplicatorSet.has(hash) ||
|
|
2572
|
-
this._checkedPruneRetries.has(hash));
|
|
2578
|
+
return this._checkedPrune.hasActiveWork(hash);
|
|
2573
2579
|
}
|
|
2574
|
-
async
|
|
2580
|
+
async revalidateCheckedPruneOwnership(args) {
|
|
2575
2581
|
const selfHash = this.node.identity.publicKey.hashcode();
|
|
2576
2582
|
if (args.leaders.has(selfHash)) {
|
|
2577
2583
|
if (args.selfReplicating === false) {
|
|
@@ -2617,7 +2623,7 @@ let SharedLog = (() => {
|
|
|
2617
2623
|
await this.cancelCheckedPruneForLocalLeader(entry.hash);
|
|
2618
2624
|
continue;
|
|
2619
2625
|
}
|
|
2620
|
-
if (this.
|
|
2626
|
+
if (this._checkedPrune.hasPendingDelete(entry.hash)) {
|
|
2621
2627
|
continue;
|
|
2622
2628
|
}
|
|
2623
2629
|
if (leaders.size === 0) {
|
|
@@ -2630,7 +2636,7 @@ let SharedLog = (() => {
|
|
|
2630
2636
|
this.responseToPruneDebouncedFn.delete(entry.hash);
|
|
2631
2637
|
}
|
|
2632
2638
|
}
|
|
2633
|
-
async pruneIndexedEntriesNoLongerLed() {
|
|
2639
|
+
async pruneIndexedEntriesNoLongerLed(options) {
|
|
2634
2640
|
const selfHash = this.node.identity.publicKey.hashcode();
|
|
2635
2641
|
const iterator = this.entryCoordinatesIndex.iterate({});
|
|
2636
2642
|
let enqueuedPrune = false;
|
|
@@ -2642,12 +2648,12 @@ let SharedLog = (() => {
|
|
|
2642
2648
|
if (this.closed) {
|
|
2643
2649
|
continue;
|
|
2644
2650
|
}
|
|
2645
|
-
const leaders = await this.findLeaders(entryReplicated.coordinates, entryReplicated, { roleAge: 0 });
|
|
2651
|
+
const leaders = await this.findLeaders(entryReplicated.coordinates, entryReplicated, options?.useDefaultRoleAge ? undefined : { roleAge: 0 });
|
|
2646
2652
|
if (leaders.has(selfHash)) {
|
|
2647
2653
|
await this.cancelCheckedPruneForLocalLeader(entryReplicated.hash);
|
|
2648
2654
|
continue;
|
|
2649
2655
|
}
|
|
2650
|
-
if (this.
|
|
2656
|
+
if (this._checkedPrune.hasPendingDelete(entryReplicated.hash)) {
|
|
2651
2657
|
continue;
|
|
2652
2658
|
}
|
|
2653
2659
|
if (leaders.size === 0) {
|
|
@@ -2669,20 +2675,63 @@ let SharedLog = (() => {
|
|
|
2669
2675
|
await this.pruneDebouncedFn.flush();
|
|
2670
2676
|
}
|
|
2671
2677
|
}
|
|
2672
|
-
|
|
2673
|
-
const
|
|
2674
|
-
|
|
2675
|
-
|
|
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 });
|
|
2676
2715
|
}
|
|
2677
|
-
|
|
2716
|
+
return leadersMap;
|
|
2717
|
+
}
|
|
2718
|
+
clearCheckedPruneRetry(hash) {
|
|
2719
|
+
this._checkedPrune.clearRetry(hash);
|
|
2678
2720
|
}
|
|
2679
2721
|
scheduleCheckedPruneRetry(args) {
|
|
2680
2722
|
if (this.closed)
|
|
2681
2723
|
return;
|
|
2682
|
-
if (this.
|
|
2724
|
+
if (this._checkedPrune.hasPendingDelete(args.entry.hash))
|
|
2683
2725
|
return;
|
|
2684
2726
|
const hash = args.entry.hash;
|
|
2685
|
-
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;
|
|
2686
2735
|
if (state.timer)
|
|
2687
2736
|
return;
|
|
2688
2737
|
if (state.attempts >= CHECKED_PRUNE_RETRY_MAX_ATTEMPTS) {
|
|
@@ -2695,17 +2744,19 @@ let SharedLog = (() => {
|
|
|
2695
2744
|
const delayMs = Math.min(CHECKED_PRUNE_RETRY_MAX_DELAY_MS, 1_000 * 2 ** (attempt - 1) + jitterMs);
|
|
2696
2745
|
state.attempts = attempt;
|
|
2697
2746
|
state.timer = setTimeout(async () => {
|
|
2698
|
-
const st = this.
|
|
2747
|
+
const st = this._checkedPrune.getRetry(hash);
|
|
2699
2748
|
if (st)
|
|
2700
2749
|
st.timer = undefined;
|
|
2701
2750
|
if (this.closed)
|
|
2702
2751
|
return;
|
|
2703
|
-
if (this.
|
|
2752
|
+
if (this._checkedPrune.hasPendingDelete(hash))
|
|
2704
2753
|
return;
|
|
2754
|
+
const retryEntry = st?.entry ?? args.entry;
|
|
2755
|
+
const retryLeaders = st?.leaders ?? args.leaders;
|
|
2705
2756
|
let leadersMap;
|
|
2706
2757
|
try {
|
|
2707
|
-
const replicas = decodeReplicas(
|
|
2708
|
-
leadersMap = await this.findLeadersFromEntry(
|
|
2758
|
+
const replicas = decodeReplicas(retryEntry).getValue(this);
|
|
2759
|
+
leadersMap = await this.findLeadersFromEntry(retryEntry, replicas, {
|
|
2709
2760
|
roleAge: 0,
|
|
2710
2761
|
});
|
|
2711
2762
|
}
|
|
@@ -2713,21 +2764,13 @@ let SharedLog = (() => {
|
|
|
2713
2764
|
// Best-effort only.
|
|
2714
2765
|
}
|
|
2715
2766
|
if (!leadersMap || leadersMap.size === 0) {
|
|
2716
|
-
|
|
2717
|
-
leadersMap = args.leaders;
|
|
2718
|
-
}
|
|
2719
|
-
else {
|
|
2720
|
-
leadersMap = new Map();
|
|
2721
|
-
for (const k of args.leaders) {
|
|
2722
|
-
leadersMap.set(k, { intersecting: true });
|
|
2723
|
-
}
|
|
2724
|
-
}
|
|
2767
|
+
leadersMap = this.checkedPruneLeadersToMap(retryLeaders);
|
|
2725
2768
|
}
|
|
2726
2769
|
try {
|
|
2727
2770
|
const leadersForRetry = leadersMap ?? new Map();
|
|
2728
2771
|
await this.pruneDebouncedFnAddIfNotKeeping({
|
|
2729
2772
|
key: hash,
|
|
2730
|
-
value: { entry:
|
|
2773
|
+
value: { entry: retryEntry, leaders: leadersForRetry },
|
|
2731
2774
|
});
|
|
2732
2775
|
}
|
|
2733
2776
|
catch {
|
|
@@ -2735,7 +2778,56 @@ let SharedLog = (() => {
|
|
|
2735
2778
|
}
|
|
2736
2779
|
}, delayMs);
|
|
2737
2780
|
state.timer.unref?.();
|
|
2738
|
-
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
|
+
}
|
|
2739
2831
|
}
|
|
2740
2832
|
async append(data, options) {
|
|
2741
2833
|
if (this._isAdaptiveReplicating) {
|
|
@@ -2843,7 +2935,7 @@ let SharedLog = (() => {
|
|
|
2843
2935
|
: createReplicationDomainHash(options?.compatibility && options?.compatibility < 10 ? "u32" : "u64")(this);
|
|
2844
2936
|
this.indexableDomain = createIndexableDomainFromResolution(this.domain.resolution);
|
|
2845
2937
|
this._respondToIHaveTimeout = options?.respondToIHaveTimeout ?? 2e4;
|
|
2846
|
-
this.
|
|
2938
|
+
this._checkedPrune = new CheckedPruneCoordinator();
|
|
2847
2939
|
this._pendingIHave = new Map();
|
|
2848
2940
|
this.latestReplicationInfoMessage = new Map();
|
|
2849
2941
|
this._replicationInfoBlockedPeers = new Set();
|
|
@@ -3001,22 +3093,28 @@ let SharedLog = (() => {
|
|
|
3001
3093
|
],
|
|
3002
3094
|
})) > 0;
|
|
3003
3095
|
this._gidPeersHistory = new Map();
|
|
3004
|
-
this._requestIPruneSent = new Map();
|
|
3005
|
-
this._requestIPruneResponseReplicatorSet = new Map();
|
|
3006
|
-
this._checkedPruneRetries = new Map();
|
|
3007
3096
|
this.replicationChangeDebounceFn = debounceAggregationChanges((change) => this.onReplicationChange(change).then(() => this.rebalanceParticipationDebounced?.call()), this.distributionDebounceTime);
|
|
3008
3097
|
this.pruneDebouncedFn = debouncedAccumulatorMap(async (map) => {
|
|
3009
3098
|
const current = new Map();
|
|
3010
3099
|
const selfReplicating = await this.isReplicating();
|
|
3011
3100
|
for (const [hash, value] of map) {
|
|
3012
|
-
const checkedPruneLeaders = await this.
|
|
3101
|
+
const checkedPruneLeaders = await this.revalidateCheckedPruneOwnership({
|
|
3013
3102
|
hash,
|
|
3014
3103
|
entry: value.entry,
|
|
3015
3104
|
leaders: value.leaders,
|
|
3016
3105
|
selfReplicating,
|
|
3017
3106
|
});
|
|
3018
3107
|
if (checkedPruneLeaders.localLeader) {
|
|
3019
|
-
|
|
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
|
+
}
|
|
3020
3118
|
continue;
|
|
3021
3119
|
}
|
|
3022
3120
|
current.set(hash, {
|
|
@@ -3050,7 +3148,7 @@ let SharedLog = (() => {
|
|
|
3050
3148
|
to: allRequestingPeers,
|
|
3051
3149
|
redundancy: 1,
|
|
3052
3150
|
}),
|
|
3053
|
-
priority:
|
|
3151
|
+
priority: CONVERGENCE_MESSAGE_PRIORITY,
|
|
3054
3152
|
});
|
|
3055
3153
|
}, () => {
|
|
3056
3154
|
let accumulator = new Map();
|
|
@@ -3339,18 +3437,7 @@ let SharedLog = (() => {
|
|
|
3339
3437
|
this.cancelReplicationInfoRequests(peerHash);
|
|
3340
3438
|
this._replicatorLivenessFailures.delete(peerHash);
|
|
3341
3439
|
this._replicatorLastActivityAt.delete(peerHash);
|
|
3342
|
-
|
|
3343
|
-
peers.delete(peerHash);
|
|
3344
|
-
if (peers.size === 0) {
|
|
3345
|
-
this._requestIPruneSent.delete(hash);
|
|
3346
|
-
}
|
|
3347
|
-
}
|
|
3348
|
-
for (const [hash, peers] of this._requestIPruneResponseReplicatorSet) {
|
|
3349
|
-
peers.delete(peerHash);
|
|
3350
|
-
if (peers.size === 0) {
|
|
3351
|
-
this._requestIPruneResponseReplicatorSet.delete(hash);
|
|
3352
|
-
}
|
|
3353
|
-
}
|
|
3440
|
+
this._checkedPrune.cleanupPeer(peerHash);
|
|
3354
3441
|
}
|
|
3355
3442
|
markReplicatorActivity(peerHash, now = Date.now()) {
|
|
3356
3443
|
this._replicatorLastActivityAt.set(peerHash, now);
|
|
@@ -3396,14 +3483,14 @@ let SharedLog = (() => {
|
|
|
3396
3483
|
const maxPeers = options?.maxPeers ?? 8;
|
|
3397
3484
|
const self = this.node.identity.publicKey.hashcode();
|
|
3398
3485
|
const seed = hashToSeed32(hash);
|
|
3399
|
-
const hinted = this.
|
|
3486
|
+
const hinted = this._checkedPrune.getConfirmedReplicators(hash);
|
|
3400
3487
|
if (hinted && hinted.size > 0) {
|
|
3401
3488
|
const peers = [...hinted].filter((p) => p !== self);
|
|
3402
3489
|
return peers.length > 0
|
|
3403
3490
|
? pickDeterministicSubset(peers, seed, maxPeers)
|
|
3404
3491
|
: undefined;
|
|
3405
3492
|
}
|
|
3406
|
-
const contacted = this.
|
|
3493
|
+
const contacted = this._checkedPrune.getContactedReplicators(hash);
|
|
3407
3494
|
if (contacted && contacted.size > 0) {
|
|
3408
3495
|
const peers = [...contacted].filter((p) => p !== self);
|
|
3409
3496
|
return peers.length > 0
|
|
@@ -3760,25 +3847,14 @@ let SharedLog = (() => {
|
|
|
3760
3847
|
this._appendBackfillTimer = undefined;
|
|
3761
3848
|
}
|
|
3762
3849
|
this._appendBackfillPendingByTarget.clear();
|
|
3763
|
-
for (const [_k, v] of this._pendingDeletes) {
|
|
3764
|
-
v.clear();
|
|
3765
|
-
v.promise.resolve(); // TODO or reject?
|
|
3766
|
-
}
|
|
3767
3850
|
for (const [_k, v] of this._pendingIHave) {
|
|
3768
3851
|
v.clear();
|
|
3769
3852
|
}
|
|
3770
|
-
|
|
3771
|
-
if (v.timer)
|
|
3772
|
-
clearTimeout(v.timer);
|
|
3773
|
-
}
|
|
3853
|
+
this._checkedPrune.close();
|
|
3774
3854
|
await this.remoteBlocks.stop();
|
|
3775
|
-
this._pendingDeletes.clear();
|
|
3776
3855
|
this._pendingIHave.clear();
|
|
3777
|
-
this._checkedPruneRetries.clear();
|
|
3778
3856
|
this.latestReplicationInfoMessage.clear();
|
|
3779
3857
|
this._gidPeersHistory.clear();
|
|
3780
|
-
this._requestIPruneSent.clear();
|
|
3781
|
-
this._requestIPruneResponseReplicatorSet.clear();
|
|
3782
3858
|
// Cancel any pending debounced timers so they can't fire after we've torn down
|
|
3783
3859
|
// indexes/RPC state.
|
|
3784
3860
|
this.rebalanceParticipationDebounced?.close();
|
|
@@ -3829,7 +3905,7 @@ let SharedLog = (() => {
|
|
|
3829
3905
|
try {
|
|
3830
3906
|
await this.rpc
|
|
3831
3907
|
.send(new AllReplicatingSegmentsMessage({ segments: [] }), {
|
|
3832
|
-
priority:
|
|
3908
|
+
priority: CONVERGENCE_MESSAGE_PRIORITY,
|
|
3833
3909
|
signal: abort.signal,
|
|
3834
3910
|
})
|
|
3835
3911
|
.catch(() => { });
|
|
@@ -3873,7 +3949,7 @@ let SharedLog = (() => {
|
|
|
3873
3949
|
try {
|
|
3874
3950
|
await this.rpc
|
|
3875
3951
|
.send(new AllReplicatingSegmentsMessage({ segments: [] }), {
|
|
3876
|
-
priority:
|
|
3952
|
+
priority: CONVERGENCE_MESSAGE_PRIORITY,
|
|
3877
3953
|
signal: abort.signal,
|
|
3878
3954
|
})
|
|
3879
3955
|
.catch(() => { });
|
|
@@ -4016,7 +4092,7 @@ let SharedLog = (() => {
|
|
|
4016
4092
|
for (const entry of entries) {
|
|
4017
4093
|
this.pruneDebouncedFn.delete(entry.entry.hash);
|
|
4018
4094
|
this.removePruneRequestSent(entry.entry.hash);
|
|
4019
|
-
this.
|
|
4095
|
+
this._checkedPrune.clearConfirmedReplicators(entry.entry.hash);
|
|
4020
4096
|
if (fromIsLeader) {
|
|
4021
4097
|
this.addPeersToGidPeerHistory(gid, [
|
|
4022
4098
|
context.from.hashcode(),
|
|
@@ -4112,27 +4188,37 @@ let SharedLog = (() => {
|
|
|
4112
4188
|
this.removeEntriesKnownByPeer([hash], from);
|
|
4113
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
|
|
4114
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
|
|
4115
|
-
|
|
4116
|
-
if (outGoingPrunes) {
|
|
4117
|
-
outGoingPrunes.delete(from);
|
|
4118
|
-
}
|
|
4191
|
+
this._checkedPrune.removeConfirmedReplicator(hash, from);
|
|
4119
4192
|
const indexedEntry = await this.log.entryIndex.getShallow(hash);
|
|
4120
4193
|
let isLeader = false;
|
|
4121
|
-
if (indexedEntry &&
|
|
4122
|
-
|
|
4123
|
-
|
|
4124
|
-
|
|
4125
|
-
|
|
4126
|
-
|
|
4127
|
-
|
|
4128
|
-
|
|
4129
|
-
|
|
4130
|
-
|
|
4131
|
-
|
|
4132
|
-
|
|
4133
|
-
|
|
4134
|
-
|
|
4135
|
-
|
|
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
|
+
}
|
|
4136
4222
|
}
|
|
4137
4223
|
if (isLeader) {
|
|
4138
4224
|
hasAndIsLeader.push(hash);
|
|
@@ -4195,8 +4281,18 @@ let SharedLog = (() => {
|
|
|
4195
4281
|
}
|
|
4196
4282
|
}
|
|
4197
4283
|
else if (msg instanceof ResponseIPrune) {
|
|
4284
|
+
const lateResponses = [];
|
|
4198
4285
|
for (const hash of msg.hashes) {
|
|
4199
|
-
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()));
|
|
4200
4296
|
}
|
|
4201
4297
|
}
|
|
4202
4298
|
else if (msg instanceof ConfirmEntriesMessage) {
|
|
@@ -4539,7 +4635,7 @@ let SharedLog = (() => {
|
|
|
4539
4635
|
}
|
|
4540
4636
|
await this.replicate(entriesToReplicate, {
|
|
4541
4637
|
rebalance: assumeSynced ? false : true,
|
|
4542
|
-
checkDuplicates: true,
|
|
4638
|
+
checkDuplicates: assumeSynced ? false : true,
|
|
4543
4639
|
mergeSegments: typeof options.replicate !== "boolean" && options.replicate
|
|
4544
4640
|
? options.replicate.mergeSegments
|
|
4545
4641
|
: false,
|
|
@@ -4566,7 +4662,7 @@ let SharedLog = (() => {
|
|
|
4566
4662
|
}
|
|
4567
4663
|
if (messageToSend) {
|
|
4568
4664
|
await this.rpc.send(messageToSend, {
|
|
4569
|
-
priority:
|
|
4665
|
+
priority: CONVERGENCE_MESSAGE_PRIORITY,
|
|
4570
4666
|
});
|
|
4571
4667
|
}
|
|
4572
4668
|
}
|
|
@@ -5125,8 +5221,8 @@ let SharedLog = (() => {
|
|
|
5125
5221
|
};
|
|
5126
5222
|
this._replicationInfoRequestByPeer.set(peerHash, state);
|
|
5127
5223
|
const intervalMs = Math.max(50, this.waitForReplicatorRequestIntervalMs);
|
|
5128
|
-
const maxAttempts =
|
|
5129
|
-
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));
|
|
5130
5226
|
const tick = () => {
|
|
5131
5227
|
if (this.closed || this._closeController.signal.aborted) {
|
|
5132
5228
|
this.cancelReplicationInfoRequests(peerHash);
|
|
@@ -5223,25 +5319,14 @@ let SharedLog = (() => {
|
|
|
5223
5319
|
return new AbsoluteReplicas(maxValue);
|
|
5224
5320
|
}
|
|
5225
5321
|
removePruneRequestSent(hash, to) {
|
|
5226
|
-
|
|
5227
|
-
this._requestIPruneSent.delete(hash);
|
|
5228
|
-
}
|
|
5229
|
-
else {
|
|
5230
|
-
let set = this._requestIPruneSent.get(hash);
|
|
5231
|
-
if (set) {
|
|
5232
|
-
set.delete(to);
|
|
5233
|
-
if (set.size === 0) {
|
|
5234
|
-
this._requestIPruneSent.delete(hash);
|
|
5235
|
-
}
|
|
5236
|
-
}
|
|
5237
|
-
}
|
|
5322
|
+
this._checkedPrune.removeRequestSent(hash, to);
|
|
5238
5323
|
}
|
|
5239
5324
|
prune(entries, options) {
|
|
5240
5325
|
if (options?.unchecked) {
|
|
5241
5326
|
return [...entries.values()].map((x) => {
|
|
5242
5327
|
this._gidPeersHistory.delete(x.entry.meta.gid);
|
|
5243
5328
|
this.removePruneRequestSent(x.entry.hash);
|
|
5244
|
-
this.
|
|
5329
|
+
this._checkedPrune.clearConfirmedReplicators(x.entry.hash);
|
|
5245
5330
|
return this.log.remove(x.entry, {
|
|
5246
5331
|
recursively: true,
|
|
5247
5332
|
});
|
|
@@ -5269,7 +5354,7 @@ let SharedLog = (() => {
|
|
|
5269
5354
|
}
|
|
5270
5355
|
set.push(entry.hash);
|
|
5271
5356
|
}
|
|
5272
|
-
const pendingPrev = this.
|
|
5357
|
+
const pendingPrev = this._checkedPrune.getPendingDelete(entry.hash);
|
|
5273
5358
|
if (pendingPrev) {
|
|
5274
5359
|
// If a background prune is already in-flight, an explicit prune request should
|
|
5275
5360
|
// still respect the caller's timeout. Otherwise, tests (and user calls) can
|
|
@@ -5298,45 +5383,62 @@ let SharedLog = (() => {
|
|
|
5298
5383
|
const minReplicas = decodeReplicas(entry);
|
|
5299
5384
|
const deferredPromise = pDefer();
|
|
5300
5385
|
const clear = () => {
|
|
5301
|
-
const pending = this.
|
|
5386
|
+
const pending = this._checkedPrune.getPendingDelete(entry.hash);
|
|
5302
5387
|
if (pending?.promise === deferredPromise) {
|
|
5303
|
-
this.
|
|
5388
|
+
this._checkedPrune.deletePendingDelete(entry.hash, pending);
|
|
5304
5389
|
}
|
|
5305
5390
|
clearTimeout(timeout);
|
|
5306
5391
|
};
|
|
5307
5392
|
const resolve = () => {
|
|
5308
|
-
|
|
5393
|
+
clearTimeout(timeout);
|
|
5309
5394
|
this.clearCheckedPruneRetry(entry.hash);
|
|
5310
5395
|
cleanupTimer.push(setTimeout(async () => {
|
|
5311
5396
|
this._gidPeersHistory.delete(entry.meta.gid);
|
|
5312
5397
|
this.removePruneRequestSent(entry.hash);
|
|
5313
|
-
this.
|
|
5314
|
-
|
|
5398
|
+
this._checkedPrune.clearConfirmedReplicators(entry.hash);
|
|
5399
|
+
const ownership = await this.revalidateCheckedPruneOwnership({
|
|
5400
|
+
hash: entry.hash,
|
|
5315
5401
|
entry,
|
|
5316
|
-
|
|
5317
|
-
|
|
5402
|
+
leaders: this.checkedPruneLeadersToMap(leaders),
|
|
5403
|
+
selfReplicating: true,
|
|
5404
|
+
});
|
|
5405
|
+
if (ownership.localLeader) {
|
|
5406
|
+
clear();
|
|
5407
|
+
if (!explicitTimeout) {
|
|
5408
|
+
this.scheduleCheckedPruneRetry({ entry, leaders });
|
|
5409
|
+
}
|
|
5318
5410
|
deferredPromise.reject(new Error("Failed to delete, is leader again"));
|
|
5319
5411
|
return;
|
|
5320
5412
|
}
|
|
5413
|
+
this._checkedPrune.markRemoving(entry.hash);
|
|
5321
5414
|
return this.log
|
|
5322
5415
|
.remove(entry, {
|
|
5323
5416
|
recursively: true,
|
|
5324
5417
|
})
|
|
5325
5418
|
.then(() => {
|
|
5419
|
+
clear();
|
|
5420
|
+
this._checkedPrune.markDone(entry.hash);
|
|
5326
5421
|
deferredPromise.resolve();
|
|
5327
5422
|
})
|
|
5328
5423
|
.catch((e) => {
|
|
5424
|
+
clear();
|
|
5425
|
+
this._checkedPrune.markCancelled(entry.hash, {
|
|
5426
|
+
preserveRetry: false,
|
|
5427
|
+
});
|
|
5329
5428
|
deferredPromise.reject(e);
|
|
5330
5429
|
})
|
|
5331
5430
|
.finally(async () => {
|
|
5332
5431
|
this._gidPeersHistory.delete(entry.meta.gid);
|
|
5333
5432
|
this.removePruneRequestSent(entry.hash);
|
|
5334
|
-
this.
|
|
5433
|
+
this._checkedPrune.clearConfirmedReplicators(entry.hash);
|
|
5335
5434
|
// TODO in the case we become leader again here we need to re-add the entry
|
|
5336
|
-
|
|
5435
|
+
const ownership = await this.revalidateCheckedPruneOwnership({
|
|
5436
|
+
hash: entry.hash,
|
|
5337
5437
|
entry,
|
|
5338
|
-
|
|
5339
|
-
|
|
5438
|
+
leaders: this.checkedPruneLeadersToMap(leaders),
|
|
5439
|
+
selfReplicating: true,
|
|
5440
|
+
});
|
|
5441
|
+
if (ownership.localLeader) {
|
|
5340
5442
|
logger.error("Unexpected: Is leader after delete");
|
|
5341
5443
|
}
|
|
5342
5444
|
});
|
|
@@ -5347,11 +5449,9 @@ let SharedLog = (() => {
|
|
|
5347
5449
|
const isCheckedPruneTimeout = e instanceof Error &&
|
|
5348
5450
|
typeof e.message === "string" &&
|
|
5349
5451
|
e.message.startsWith("Timeout for checked pruning");
|
|
5350
|
-
|
|
5351
|
-
|
|
5352
|
-
}
|
|
5353
|
-
this.removePruneRequestSent(entry.hash);
|
|
5354
|
-
this._requestIPruneResponseReplicatorSet.delete(entry.hash);
|
|
5452
|
+
this._checkedPrune.markCancelled(entry.hash, {
|
|
5453
|
+
preserveRetry: !explicitTimeout && isCheckedPruneTimeout,
|
|
5454
|
+
});
|
|
5355
5455
|
deferredPromise.reject(e);
|
|
5356
5456
|
};
|
|
5357
5457
|
let cursor = undefined;
|
|
@@ -5361,7 +5461,7 @@ let SharedLog = (() => {
|
|
|
5361
5461
|
// If we time out too early we can end up with permanently prunable heads that never
|
|
5362
5462
|
// get retried (a common CI flake in "prune before join" tests).
|
|
5363
5463
|
const checkedPruneTimeoutMs = options?.timeout ??
|
|
5364
|
-
Math.max(
|
|
5464
|
+
Math.max(CHECKED_PRUNE_BACKGROUND_TIMEOUT_MIN_MS, Number(this._respondToIHaveTimeout ?? 0) +
|
|
5365
5465
|
this.waitForReplicatorTimeout +
|
|
5366
5466
|
PRUNE_DEBOUNCE_INTERVAL * 2);
|
|
5367
5467
|
const timeout = setTimeout(() => {
|
|
@@ -5374,7 +5474,7 @@ let SharedLog = (() => {
|
|
|
5374
5474
|
reject(new Error(`Timeout for checked pruning after ${checkedPruneTimeoutMs}ms (closed=${this.closed})`));
|
|
5375
5475
|
}, checkedPruneTimeoutMs);
|
|
5376
5476
|
timeout.unref?.();
|
|
5377
|
-
this.
|
|
5477
|
+
this._checkedPrune.setPendingDelete(entry.hash, {
|
|
5378
5478
|
promise: deferredPromise,
|
|
5379
5479
|
clear,
|
|
5380
5480
|
reject,
|
|
@@ -5394,34 +5494,24 @@ let SharedLog = (() => {
|
|
|
5394
5494
|
}))) {
|
|
5395
5495
|
return;
|
|
5396
5496
|
}
|
|
5397
|
-
|
|
5398
|
-
if (!existCounter) {
|
|
5399
|
-
existCounter = new Set();
|
|
5400
|
-
this._requestIPruneResponseReplicatorSet.set(entry.hash, existCounter);
|
|
5401
|
-
}
|
|
5402
|
-
existCounter.add(publicKeyHash);
|
|
5497
|
+
const existCounter = this._checkedPrune.addConfirmedReplicator(entry.hash, publicKeyHash);
|
|
5403
5498
|
// Seed provider hints so future remote reads can avoid extra round-trips.
|
|
5404
5499
|
this.remoteBlocks.hintProviders(entry.hash, [publicKeyHash]);
|
|
5405
5500
|
if (minReplicasValue <= existCounter.size) {
|
|
5406
5501
|
resolve();
|
|
5407
5502
|
}
|
|
5408
5503
|
},
|
|
5409
|
-
});
|
|
5504
|
+
}, entry, leaders);
|
|
5410
5505
|
promises.push(deferredPromise.promise);
|
|
5411
5506
|
}
|
|
5412
5507
|
const emitMessages = async (entries, to) => {
|
|
5413
5508
|
const filteredSet = [];
|
|
5414
5509
|
for (const entry of entries) {
|
|
5415
|
-
let set = this._requestIPruneSent.get(entry);
|
|
5416
|
-
if (!set) {
|
|
5417
|
-
set = new Set();
|
|
5418
|
-
this._requestIPruneSent.set(entry, set);
|
|
5419
|
-
}
|
|
5420
5510
|
/* TODO why can we not have this statement?
|
|
5421
5511
|
if (set.has(to)) {
|
|
5422
5512
|
continue;
|
|
5423
5513
|
} */
|
|
5424
|
-
|
|
5514
|
+
this._checkedPrune.addRequestSent(entry, to);
|
|
5425
5515
|
filteredSet.push(entry);
|
|
5426
5516
|
}
|
|
5427
5517
|
if (filteredSet.length > 0) {
|
|
@@ -5432,7 +5522,7 @@ let SharedLog = (() => {
|
|
|
5432
5522
|
to: [to], // TODO group by peers?
|
|
5433
5523
|
redundancy: 1,
|
|
5434
5524
|
}),
|
|
5435
|
-
priority:
|
|
5525
|
+
priority: CONVERGENCE_MESSAGE_PRIORITY,
|
|
5436
5526
|
});
|
|
5437
5527
|
}
|
|
5438
5528
|
};
|
|
@@ -5453,7 +5543,7 @@ let SharedLog = (() => {
|
|
|
5453
5543
|
return;
|
|
5454
5544
|
const pendingByPeer = [];
|
|
5455
5545
|
for (const [peer, hashes] of peerToEntries) {
|
|
5456
|
-
const pending = hashes.filter((h) => this.
|
|
5546
|
+
const pending = hashes.filter((h) => this._checkedPrune.hasPendingDelete(h));
|
|
5457
5547
|
if (pending.length > 0) {
|
|
5458
5548
|
pendingByPeer.push([peer, pending]);
|
|
5459
5549
|
}
|
|
@@ -5779,12 +5869,21 @@ let SharedLog = (() => {
|
|
|
5779
5869
|
for (const target of [...uncheckedDeliver.keys()]) {
|
|
5780
5870
|
flushUncheckedDeliverTarget(target);
|
|
5781
5871
|
}
|
|
5782
|
-
if (this._isAdaptiveReplicating &&
|
|
5783
|
-
|
|
5784
|
-
|
|
5785
|
-
|
|
5786
|
-
|
|
5787
|
-
|
|
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
|
+
});
|
|
5788
5887
|
}
|
|
5789
5888
|
return changed;
|
|
5790
5889
|
}
|