@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/src/index.ts
CHANGED
|
@@ -54,6 +54,8 @@ import {
|
|
|
54
54
|
ACK_CONTROL_PRIORITY,
|
|
55
55
|
AcknowledgeDelivery,
|
|
56
56
|
AnyWhere,
|
|
57
|
+
BACKGROUND_MESSAGE_PRIORITY,
|
|
58
|
+
CONVERGENCE_MESSAGE_PRIORITY,
|
|
57
59
|
createRequestTransportContext,
|
|
58
60
|
DataMessage,
|
|
59
61
|
MessageHeader,
|
|
@@ -70,8 +72,14 @@ import {
|
|
|
70
72
|
} from "@peerbit/time";
|
|
71
73
|
import pDefer, { type DeferredPromise } from "p-defer";
|
|
72
74
|
import PQueue from "p-queue";
|
|
73
|
-
import { concat } from "uint8arrays";
|
|
75
|
+
import { concat, fromString } from "uint8arrays";
|
|
74
76
|
import { BlocksMessage } from "./blocks.js";
|
|
77
|
+
import {
|
|
78
|
+
CheckedPruneCoordinator,
|
|
79
|
+
type CheckedPruneEntry,
|
|
80
|
+
type CheckedPruneLeaderMap,
|
|
81
|
+
type CheckedPruneRetryState,
|
|
82
|
+
} from "./checked-prune.js";
|
|
75
83
|
import { type CPUUsage, CPUUsageIntervalLag } from "./cpu.js";
|
|
76
84
|
import {
|
|
77
85
|
type DebouncedAccumulatorMap,
|
|
@@ -169,7 +177,11 @@ import type {
|
|
|
169
177
|
Syncronizer,
|
|
170
178
|
} from "./sync/index.js";
|
|
171
179
|
import { RatelessIBLTSynchronizer } from "./sync/rateless-iblt.js";
|
|
172
|
-
import {
|
|
180
|
+
import {
|
|
181
|
+
ConfirmEntriesMessage,
|
|
182
|
+
SYNC_MESSAGE_PRIORITY,
|
|
183
|
+
SimpleSyncronizer,
|
|
184
|
+
} from "./sync/simple.js";
|
|
173
185
|
import { groupByGid } from "./utils.js";
|
|
174
186
|
|
|
175
187
|
const toLocalPublicSignKey = (
|
|
@@ -241,12 +253,6 @@ export { MAX_U32, MAX_U64, type NumberFromType };
|
|
|
241
253
|
export const logger = loggerFn("peerbit:shared-log");
|
|
242
254
|
const warn = logger.newScope("warn");
|
|
243
255
|
|
|
244
|
-
type CheckedPruneLeaderMap = Map<string, { intersecting: boolean }>;
|
|
245
|
-
type CheckedPruneEntry<T, R extends "u32" | "u64"> =
|
|
246
|
-
| Entry<T>
|
|
247
|
-
| ShallowEntry
|
|
248
|
-
| EntryReplicated<R>;
|
|
249
|
-
|
|
250
256
|
const getLatestEntry = (
|
|
251
257
|
entries: (ShallowOrFullEntry<any> | EntryWithRefs<any>)[],
|
|
252
258
|
) => {
|
|
@@ -494,6 +500,7 @@ export const WAIT_FOR_PRUNE_DELAY = 0;
|
|
|
494
500
|
const PRUNE_DEBOUNCE_INTERVAL = 500;
|
|
495
501
|
const CHECKED_PRUNE_RESEND_INTERVAL_MIN_MS = 250;
|
|
496
502
|
const CHECKED_PRUNE_RESEND_INTERVAL_MAX_MS = 5_000;
|
|
503
|
+
const CHECKED_PRUNE_BACKGROUND_TIMEOUT_MIN_MS = 120_000;
|
|
497
504
|
const CHECKED_PRUNE_RETRY_MAX_ATTEMPTS = 3;
|
|
498
505
|
const CHECKED_PRUNE_RETRY_MAX_DELAY_MS = 30_000;
|
|
499
506
|
|
|
@@ -799,15 +806,11 @@ export class SharedLog<
|
|
|
799
806
|
SharedLogOptions<T, D, R>;
|
|
800
807
|
private _closeController!: AbortController;
|
|
801
808
|
private _respondToIHaveTimeout!: any;
|
|
802
|
-
private
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
resolve: (publicKeyHash: string) => Promise<void> | void;
|
|
808
|
-
reject(reason: any): Promise<void> | void;
|
|
809
|
-
}
|
|
810
|
-
>;
|
|
809
|
+
private _checkedPrune!: CheckedPruneCoordinator<T, R>;
|
|
810
|
+
|
|
811
|
+
private get _pendingDeletes() {
|
|
812
|
+
return this._checkedPrune.pendingDeletes;
|
|
813
|
+
}
|
|
811
814
|
|
|
812
815
|
private _pendingIHave!: Map<
|
|
813
816
|
string,
|
|
@@ -879,12 +882,15 @@ export class SharedLog<
|
|
|
879
882
|
>
|
|
880
883
|
>;
|
|
881
884
|
|
|
882
|
-
private _requestIPruneSent
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
885
|
+
private get _requestIPruneSent() {
|
|
886
|
+
return this._checkedPrune.requestIPruneSent;
|
|
887
|
+
}
|
|
888
|
+
private get _requestIPruneResponseReplicatorSet() {
|
|
889
|
+
return this._checkedPrune.responseReplicatorSet;
|
|
890
|
+
}
|
|
891
|
+
private get _checkedPruneRetries() {
|
|
892
|
+
return this._checkedPrune.retries;
|
|
893
|
+
}
|
|
888
894
|
|
|
889
895
|
private replicationChangeDebounceFn!: ReturnType<
|
|
890
896
|
typeof debounceAggregationChanges<ReplicationRangeIndexable<R>>
|
|
@@ -1081,7 +1087,7 @@ export class SharedLog<
|
|
|
1081
1087
|
header: new MessageHeader({
|
|
1082
1088
|
session: 0,
|
|
1083
1089
|
mode: new AnyWhere(),
|
|
1084
|
-
priority:
|
|
1090
|
+
priority: BACKGROUND_MESSAGE_PRIORITY,
|
|
1085
1091
|
}),
|
|
1086
1092
|
});
|
|
1087
1093
|
contextMessage.header.timestamp = envelope.timestamp;
|
|
@@ -1113,7 +1119,7 @@ export class SharedLog<
|
|
|
1113
1119
|
header: new MessageHeader({
|
|
1114
1120
|
session: 0,
|
|
1115
1121
|
mode: new AnyWhere(),
|
|
1116
|
-
priority:
|
|
1122
|
+
priority: BACKGROUND_MESSAGE_PRIORITY,
|
|
1117
1123
|
}),
|
|
1118
1124
|
});
|
|
1119
1125
|
contextMessage.header.timestamp = detail.timestamp;
|
|
@@ -2015,7 +2021,7 @@ export class SharedLog<
|
|
|
2015
2021
|
segmentIds: rangesToUnreplicate.map((x) => x.id),
|
|
2016
2022
|
}),
|
|
2017
2023
|
{
|
|
2018
|
-
priority:
|
|
2024
|
+
priority: CONVERGENCE_MESSAGE_PRIORITY,
|
|
2019
2025
|
},
|
|
2020
2026
|
);
|
|
2021
2027
|
}
|
|
@@ -2065,6 +2071,14 @@ export class SharedLog<
|
|
|
2065
2071
|
) => void;
|
|
2066
2072
|
},
|
|
2067
2073
|
) {
|
|
2074
|
+
const entryRangeId = (entry: Entry<T>) =>
|
|
2075
|
+
sha256Sync(
|
|
2076
|
+
concat([
|
|
2077
|
+
this.log.id,
|
|
2078
|
+
fromString(entry.hash),
|
|
2079
|
+
fromString(this.node.identity.publicKey.hashcode()),
|
|
2080
|
+
]),
|
|
2081
|
+
);
|
|
2068
2082
|
let range:
|
|
2069
2083
|
| ReplicationRangeMessage<any>[]
|
|
2070
2084
|
| ReplicationOptions<R>
|
|
@@ -2074,6 +2088,7 @@ export class SharedLog<
|
|
|
2074
2088
|
range = rangeOrEntry;
|
|
2075
2089
|
} else if (rangeOrEntry instanceof Entry) {
|
|
2076
2090
|
range = {
|
|
2091
|
+
id: entryRangeId(rangeOrEntry),
|
|
2077
2092
|
factor: 1,
|
|
2078
2093
|
offset: await this.domain.fromEntry(rangeOrEntry),
|
|
2079
2094
|
normalized: false,
|
|
@@ -2084,6 +2099,7 @@ export class SharedLog<
|
|
|
2084
2099
|
for (const entry of rangeOrEntry) {
|
|
2085
2100
|
if (entry instanceof Entry) {
|
|
2086
2101
|
ranges.push({
|
|
2102
|
+
id: entryRangeId(entry),
|
|
2087
2103
|
factor: 1,
|
|
2088
2104
|
offset: await this.domain.fromEntry(entry),
|
|
2089
2105
|
normalized: false,
|
|
@@ -2153,7 +2169,7 @@ export class SharedLog<
|
|
|
2153
2169
|
this.node.identity.publicKey,
|
|
2154
2170
|
);
|
|
2155
2171
|
await this.rpc.send(new StoppedReplicating({ segmentIds }), {
|
|
2156
|
-
priority:
|
|
2172
|
+
priority: CONVERGENCE_MESSAGE_PRIORITY,
|
|
2157
2173
|
});
|
|
2158
2174
|
}
|
|
2159
2175
|
|
|
@@ -2179,7 +2195,7 @@ export class SharedLog<
|
|
|
2179
2195
|
// announce that we are no longer replicating
|
|
2180
2196
|
|
|
2181
2197
|
await this.rpc.send(new AllReplicatingSegmentsMessage({ segments: [] }), {
|
|
2182
|
-
priority:
|
|
2198
|
+
priority: CONVERGENCE_MESSAGE_PRIORITY,
|
|
2183
2199
|
});
|
|
2184
2200
|
}
|
|
2185
2201
|
|
|
@@ -2663,7 +2679,7 @@ export class SharedLog<
|
|
|
2663
2679
|
return options.announce(message);
|
|
2664
2680
|
} else {
|
|
2665
2681
|
await this.rpc.send(message, {
|
|
2666
|
-
priority:
|
|
2682
|
+
priority: CONVERGENCE_MESSAGE_PRIORITY,
|
|
2667
2683
|
});
|
|
2668
2684
|
}
|
|
2669
2685
|
}
|
|
@@ -2892,7 +2908,7 @@ export class SharedLog<
|
|
|
2892
2908
|
i + REPAIR_CONFIRMATION_HASH_BATCH_SIZE,
|
|
2893
2909
|
);
|
|
2894
2910
|
await this.rpc.send(new ConfirmEntriesMessage({ hashes: chunk }), {
|
|
2895
|
-
priority:
|
|
2911
|
+
priority: CONVERGENCE_MESSAGE_PRIORITY,
|
|
2896
2912
|
mode: new SilentDelivery({ to: [target], redundancy: 1 }),
|
|
2897
2913
|
});
|
|
2898
2914
|
}
|
|
@@ -2908,7 +2924,7 @@ export class SharedLog<
|
|
|
2908
2924
|
)) {
|
|
2909
2925
|
message.reserved[0] |= EXCHANGE_HEADS_REPAIR_HINT;
|
|
2910
2926
|
await this.rpc.send(message, {
|
|
2911
|
-
priority:
|
|
2927
|
+
priority: SYNC_MESSAGE_PRIORITY,
|
|
2912
2928
|
mode: new SilentDelivery({ to: [target], redundancy: 1 }),
|
|
2913
2929
|
});
|
|
2914
2930
|
}
|
|
@@ -3514,26 +3530,18 @@ export class SharedLog<
|
|
|
3514
3530
|
const frontierTargets = this._repairFrontierByMode.get(mode);
|
|
3515
3531
|
for (const target of pendingPeersByMode.get(mode) ?? []) {
|
|
3516
3532
|
const replacement = nextTargets.get(target);
|
|
3517
|
-
|
|
3518
|
-
|
|
3519
|
-
|
|
3520
|
-
|
|
3521
|
-
|
|
3522
|
-
|
|
3523
|
-
|
|
3524
|
-
|
|
3525
|
-
existing.set(hash, entry);
|
|
3526
|
-
}
|
|
3527
|
-
} else {
|
|
3528
|
-
frontierTargets?.set(target, replacement);
|
|
3533
|
+
// These repairs are receipt-driven: a later sweep can have a narrower
|
|
3534
|
+
// transient leader view, but it must not forget unconfirmed hashes
|
|
3535
|
+
// that were already queued for this target.
|
|
3536
|
+
if (replacement && replacement.size > 0) {
|
|
3537
|
+
const existing = frontierTargets?.get(target);
|
|
3538
|
+
if (existing && existing.size > 0) {
|
|
3539
|
+
for (const [hash, entry] of replacement) {
|
|
3540
|
+
existing.set(hash, entry);
|
|
3529
3541
|
}
|
|
3542
|
+
} else {
|
|
3543
|
+
frontierTargets?.set(target, replacement);
|
|
3530
3544
|
}
|
|
3531
|
-
continue;
|
|
3532
|
-
}
|
|
3533
|
-
if (replacement && replacement.size > 0) {
|
|
3534
|
-
frontierTargets?.set(target, replacement);
|
|
3535
|
-
} else {
|
|
3536
|
-
frontierTargets?.delete(target);
|
|
3537
3545
|
}
|
|
3538
3546
|
}
|
|
3539
3547
|
}
|
|
@@ -3567,30 +3575,32 @@ export class SharedLog<
|
|
|
3567
3575
|
if (this.keep && (await this.keep(args.value.entry))) {
|
|
3568
3576
|
return false;
|
|
3569
3577
|
}
|
|
3578
|
+
this._checkedPrune.trackCandidate(
|
|
3579
|
+
args.key,
|
|
3580
|
+
args.value.entry,
|
|
3581
|
+
args.value.leaders,
|
|
3582
|
+
);
|
|
3570
3583
|
void this.pruneDebouncedFn.add(args);
|
|
3571
3584
|
return true;
|
|
3572
3585
|
}
|
|
3573
3586
|
|
|
3574
|
-
private async cancelCheckedPruneForLocalLeader(
|
|
3587
|
+
private async cancelCheckedPruneForLocalLeader(
|
|
3588
|
+
hash: string,
|
|
3589
|
+
options?: { preserveRetry?: boolean },
|
|
3590
|
+
) {
|
|
3575
3591
|
this.pruneDebouncedFn.delete(hash);
|
|
3576
|
-
this.
|
|
3577
|
-
this.
|
|
3578
|
-
|
|
3579
|
-
|
|
3580
|
-
|
|
3581
|
-
?.reject(new Error("Failed to delete, is leader again"));
|
|
3592
|
+
const pendingDelete = this._checkedPrune.getPendingDelete(hash);
|
|
3593
|
+
this._checkedPrune.markCancelled(hash, {
|
|
3594
|
+
preserveRetry: options?.preserveRetry,
|
|
3595
|
+
});
|
|
3596
|
+
await pendingDelete?.reject(new Error("Failed to delete, is leader again"));
|
|
3582
3597
|
}
|
|
3583
3598
|
|
|
3584
3599
|
private hasActiveCheckedPruneWork(hash: string) {
|
|
3585
|
-
return (
|
|
3586
|
-
this._pendingDeletes.has(hash) ||
|
|
3587
|
-
this._requestIPruneSent.has(hash) ||
|
|
3588
|
-
this._requestIPruneResponseReplicatorSet.has(hash) ||
|
|
3589
|
-
this._checkedPruneRetries.has(hash)
|
|
3590
|
-
);
|
|
3600
|
+
return this._checkedPrune.hasActiveWork(hash);
|
|
3591
3601
|
}
|
|
3592
3602
|
|
|
3593
|
-
private async
|
|
3603
|
+
private async revalidateCheckedPruneOwnership(args: {
|
|
3594
3604
|
hash: string;
|
|
3595
3605
|
entry: CheckedPruneEntry<T, R>;
|
|
3596
3606
|
leaders: CheckedPruneLeaderMap;
|
|
@@ -3658,7 +3668,7 @@ export class SharedLog<
|
|
|
3658
3668
|
continue;
|
|
3659
3669
|
}
|
|
3660
3670
|
|
|
3661
|
-
if (this.
|
|
3671
|
+
if (this._checkedPrune.hasPendingDelete(entry.hash)) {
|
|
3662
3672
|
continue;
|
|
3663
3673
|
}
|
|
3664
3674
|
|
|
@@ -3674,7 +3684,9 @@ export class SharedLog<
|
|
|
3674
3684
|
}
|
|
3675
3685
|
}
|
|
3676
3686
|
|
|
3677
|
-
private async pruneIndexedEntriesNoLongerLed(
|
|
3687
|
+
private async pruneIndexedEntriesNoLongerLed(options?: {
|
|
3688
|
+
useDefaultRoleAge?: boolean;
|
|
3689
|
+
}) {
|
|
3678
3690
|
const selfHash = this.node.identity.publicKey.hashcode();
|
|
3679
3691
|
const iterator = this.entryCoordinatesIndex.iterate({});
|
|
3680
3692
|
let enqueuedPrune = false;
|
|
@@ -3690,7 +3702,7 @@ export class SharedLog<
|
|
|
3690
3702
|
const leaders = await this.findLeaders(
|
|
3691
3703
|
entryReplicated.coordinates,
|
|
3692
3704
|
entryReplicated,
|
|
3693
|
-
{ roleAge: 0 },
|
|
3705
|
+
options?.useDefaultRoleAge ? undefined : { roleAge: 0 },
|
|
3694
3706
|
);
|
|
3695
3707
|
|
|
3696
3708
|
if (leaders.has(selfHash)) {
|
|
@@ -3698,7 +3710,7 @@ export class SharedLog<
|
|
|
3698
3710
|
continue;
|
|
3699
3711
|
}
|
|
3700
3712
|
|
|
3701
|
-
if (this.
|
|
3713
|
+
if (this._checkedPrune.hasPendingDelete(entryReplicated.hash)) {
|
|
3702
3714
|
continue;
|
|
3703
3715
|
}
|
|
3704
3716
|
|
|
@@ -3722,12 +3734,65 @@ export class SharedLog<
|
|
|
3722
3734
|
}
|
|
3723
3735
|
}
|
|
3724
3736
|
|
|
3725
|
-
private
|
|
3726
|
-
|
|
3727
|
-
|
|
3728
|
-
|
|
3737
|
+
private async pruneCurrentHeadsNoLongerLed(options?: {
|
|
3738
|
+
useDefaultRoleAge?: boolean;
|
|
3739
|
+
}) {
|
|
3740
|
+
const selfHash = this.node.identity.publicKey.hashcode();
|
|
3741
|
+
const heads = await this.log.getHeads(true).all();
|
|
3742
|
+
let enqueuedPrune = false;
|
|
3743
|
+
|
|
3744
|
+
for (const head of heads) {
|
|
3745
|
+
if (this.closed) {
|
|
3746
|
+
break;
|
|
3747
|
+
}
|
|
3748
|
+
|
|
3749
|
+
const leaders = await this.findLeadersFromEntry(
|
|
3750
|
+
head,
|
|
3751
|
+
maxReplicas(this, [head]),
|
|
3752
|
+
options?.useDefaultRoleAge ? undefined : { roleAge: 0 },
|
|
3753
|
+
);
|
|
3754
|
+
|
|
3755
|
+
if (leaders.has(selfHash)) {
|
|
3756
|
+
await this.cancelCheckedPruneForLocalLeader(head.hash);
|
|
3757
|
+
continue;
|
|
3758
|
+
}
|
|
3759
|
+
|
|
3760
|
+
if (this._checkedPrune.hasPendingDelete(head.hash)) {
|
|
3761
|
+
continue;
|
|
3762
|
+
}
|
|
3763
|
+
|
|
3764
|
+
if (leaders.size === 0) {
|
|
3765
|
+
continue;
|
|
3766
|
+
}
|
|
3767
|
+
|
|
3768
|
+
enqueuedPrune =
|
|
3769
|
+
(await this.pruneDebouncedFnAddIfNotKeeping({
|
|
3770
|
+
key: head.hash,
|
|
3771
|
+
value: { entry: head, leaders },
|
|
3772
|
+
})) || enqueuedPrune;
|
|
3773
|
+
this.responseToPruneDebouncedFn.delete(head.hash);
|
|
3729
3774
|
}
|
|
3730
|
-
|
|
3775
|
+
|
|
3776
|
+
if (enqueuedPrune && !this.closed) {
|
|
3777
|
+
await this.pruneDebouncedFn.flush();
|
|
3778
|
+
}
|
|
3779
|
+
}
|
|
3780
|
+
|
|
3781
|
+
private checkedPruneLeadersToMap(
|
|
3782
|
+
leaders: CheckedPruneLeaderMap | Set<string>,
|
|
3783
|
+
): CheckedPruneLeaderMap {
|
|
3784
|
+
if (leaders instanceof Map) {
|
|
3785
|
+
return new Map(leaders);
|
|
3786
|
+
}
|
|
3787
|
+
const leadersMap: CheckedPruneLeaderMap = new Map();
|
|
3788
|
+
for (const leader of leaders) {
|
|
3789
|
+
leadersMap.set(leader, { intersecting: true });
|
|
3790
|
+
}
|
|
3791
|
+
return leadersMap;
|
|
3792
|
+
}
|
|
3793
|
+
|
|
3794
|
+
private clearCheckedPruneRetry(hash: string) {
|
|
3795
|
+
this._checkedPrune.clearRetry(hash);
|
|
3731
3796
|
}
|
|
3732
3797
|
|
|
3733
3798
|
private scheduleCheckedPruneRetry(args: {
|
|
@@ -3735,11 +3800,18 @@ export class SharedLog<
|
|
|
3735
3800
|
leaders: CheckedPruneLeaderMap | Set<string>;
|
|
3736
3801
|
}) {
|
|
3737
3802
|
if (this.closed) return;
|
|
3738
|
-
if (this.
|
|
3803
|
+
if (this._checkedPrune.hasPendingDelete(args.entry.hash)) return;
|
|
3739
3804
|
|
|
3740
3805
|
const hash = args.entry.hash;
|
|
3741
3806
|
const state =
|
|
3742
|
-
this.
|
|
3807
|
+
this._checkedPrune.getRetry(hash) ??
|
|
3808
|
+
({
|
|
3809
|
+
attempts: 0,
|
|
3810
|
+
entry: args.entry,
|
|
3811
|
+
leaders: args.leaders,
|
|
3812
|
+
} satisfies CheckedPruneRetryState<T, R>);
|
|
3813
|
+
state.entry = args.entry;
|
|
3814
|
+
state.leaders = args.leaders;
|
|
3743
3815
|
|
|
3744
3816
|
if (state.timer) return;
|
|
3745
3817
|
if (state.attempts >= CHECKED_PRUNE_RETRY_MAX_ATTEMPTS) {
|
|
@@ -3757,15 +3829,17 @@ export class SharedLog<
|
|
|
3757
3829
|
|
|
3758
3830
|
state.attempts = attempt;
|
|
3759
3831
|
state.timer = setTimeout(async () => {
|
|
3760
|
-
const st = this.
|
|
3832
|
+
const st = this._checkedPrune.getRetry(hash);
|
|
3761
3833
|
if (st) st.timer = undefined;
|
|
3762
3834
|
if (this.closed) return;
|
|
3763
|
-
if (this.
|
|
3835
|
+
if (this._checkedPrune.hasPendingDelete(hash)) return;
|
|
3836
|
+
const retryEntry = st?.entry ?? args.entry;
|
|
3837
|
+
const retryLeaders = st?.leaders ?? args.leaders;
|
|
3764
3838
|
|
|
3765
3839
|
let leadersMap: CheckedPruneLeaderMap | undefined;
|
|
3766
3840
|
try {
|
|
3767
|
-
const replicas = decodeReplicas(
|
|
3768
|
-
leadersMap = await this.findLeadersFromEntry(
|
|
3841
|
+
const replicas = decodeReplicas(retryEntry).getValue(this);
|
|
3842
|
+
leadersMap = await this.findLeadersFromEntry(retryEntry, replicas, {
|
|
3769
3843
|
roleAge: 0,
|
|
3770
3844
|
});
|
|
3771
3845
|
} catch {
|
|
@@ -3773,14 +3847,7 @@ export class SharedLog<
|
|
|
3773
3847
|
}
|
|
3774
3848
|
|
|
3775
3849
|
if (!leadersMap || leadersMap.size === 0) {
|
|
3776
|
-
|
|
3777
|
-
leadersMap = args.leaders;
|
|
3778
|
-
} else {
|
|
3779
|
-
leadersMap = new Map<string, { intersecting: boolean }>();
|
|
3780
|
-
for (const k of args.leaders) {
|
|
3781
|
-
leadersMap.set(k, { intersecting: true });
|
|
3782
|
-
}
|
|
3783
|
-
}
|
|
3850
|
+
leadersMap = this.checkedPruneLeadersToMap(retryLeaders);
|
|
3784
3851
|
}
|
|
3785
3852
|
|
|
3786
3853
|
try {
|
|
@@ -3788,14 +3855,81 @@ export class SharedLog<
|
|
|
3788
3855
|
leadersMap ?? new Map<string, { intersecting: boolean }>();
|
|
3789
3856
|
await this.pruneDebouncedFnAddIfNotKeeping({
|
|
3790
3857
|
key: hash,
|
|
3791
|
-
value: { entry:
|
|
3858
|
+
value: { entry: retryEntry, leaders: leadersForRetry },
|
|
3792
3859
|
});
|
|
3793
3860
|
} catch {
|
|
3794
3861
|
// Best-effort only; pruning will be re-attempted on future changes.
|
|
3795
3862
|
}
|
|
3796
3863
|
}, delayMs);
|
|
3797
3864
|
state.timer.unref?.();
|
|
3798
|
-
this.
|
|
3865
|
+
this._checkedPrune.setRetry(hash, state);
|
|
3866
|
+
}
|
|
3867
|
+
|
|
3868
|
+
private async recoverCheckedPruneFromLateResponses(
|
|
3869
|
+
hashes: string[],
|
|
3870
|
+
publicKeyHash: string,
|
|
3871
|
+
) {
|
|
3872
|
+
if (this.closed) return;
|
|
3873
|
+
const selfHash = this.node.identity.publicKey.hashcode();
|
|
3874
|
+
const toPrune = new Map<
|
|
3875
|
+
string,
|
|
3876
|
+
{
|
|
3877
|
+
entry: CheckedPruneEntry<T, R>;
|
|
3878
|
+
leaders: CheckedPruneLeaderMap;
|
|
3879
|
+
}
|
|
3880
|
+
>();
|
|
3881
|
+
const responseStillApplies: string[] = [];
|
|
3882
|
+
|
|
3883
|
+
for (const hash of hashes) {
|
|
3884
|
+
if (this.closed) {
|
|
3885
|
+
break;
|
|
3886
|
+
}
|
|
3887
|
+
if (this._checkedPrune.hasPendingDelete(hash)) {
|
|
3888
|
+
continue;
|
|
3889
|
+
}
|
|
3890
|
+
const retry = this._checkedPrune.clearRetryTimer(hash);
|
|
3891
|
+
if (!retry) {
|
|
3892
|
+
continue;
|
|
3893
|
+
}
|
|
3894
|
+
|
|
3895
|
+
const entry = retry.entry;
|
|
3896
|
+
let leaders = this.checkedPruneLeadersToMap(retry.leaders);
|
|
3897
|
+
try {
|
|
3898
|
+
const currentLeaders = await this.findLeadersFromEntry(
|
|
3899
|
+
entry,
|
|
3900
|
+
decodeReplicas(entry).getValue(this),
|
|
3901
|
+
{ roleAge: 0 },
|
|
3902
|
+
);
|
|
3903
|
+
if (currentLeaders.size > 0) {
|
|
3904
|
+
leaders = currentLeaders;
|
|
3905
|
+
}
|
|
3906
|
+
} catch {
|
|
3907
|
+
// Best-effort only; the stored retry leaders came from a previous
|
|
3908
|
+
// checked-prune decision for this exact entry.
|
|
3909
|
+
}
|
|
3910
|
+
|
|
3911
|
+
if (leaders.has(selfHash)) {
|
|
3912
|
+
await this.cancelCheckedPruneForLocalLeader(hash);
|
|
3913
|
+
continue;
|
|
3914
|
+
}
|
|
3915
|
+
if (leaders.size === 0) {
|
|
3916
|
+
continue;
|
|
3917
|
+
}
|
|
3918
|
+
|
|
3919
|
+
toPrune.set(hash, { entry, leaders });
|
|
3920
|
+
if (leaders.has(publicKeyHash)) {
|
|
3921
|
+
responseStillApplies.push(hash);
|
|
3922
|
+
}
|
|
3923
|
+
}
|
|
3924
|
+
|
|
3925
|
+
if (toPrune.size === 0) {
|
|
3926
|
+
return;
|
|
3927
|
+
}
|
|
3928
|
+
|
|
3929
|
+
void Promise.allSettled(this.prune(toPrune));
|
|
3930
|
+
for (const hash of responseStillApplies) {
|
|
3931
|
+
void this._checkedPrune.getPendingDelete(hash)?.resolve(publicKeyHash);
|
|
3932
|
+
}
|
|
3799
3933
|
}
|
|
3800
3934
|
|
|
3801
3935
|
async append(
|
|
@@ -3945,7 +4079,7 @@ export class SharedLog<
|
|
|
3945
4079
|
this.domain.resolution,
|
|
3946
4080
|
);
|
|
3947
4081
|
this._respondToIHaveTimeout = options?.respondToIHaveTimeout ?? 2e4;
|
|
3948
|
-
this.
|
|
4082
|
+
this._checkedPrune = new CheckedPruneCoordinator<T, R>();
|
|
3949
4083
|
this._pendingIHave = new Map();
|
|
3950
4084
|
this.latestReplicationInfoMessage = new Map();
|
|
3951
4085
|
this._replicationInfoBlockedPeers = new Set();
|
|
@@ -4137,10 +4271,6 @@ export class SharedLog<
|
|
|
4137
4271
|
})) > 0;
|
|
4138
4272
|
|
|
4139
4273
|
this._gidPeersHistory = new Map();
|
|
4140
|
-
this._requestIPruneSent = new Map();
|
|
4141
|
-
this._requestIPruneResponseReplicatorSet = new Map();
|
|
4142
|
-
this._checkedPruneRetries = new Map();
|
|
4143
|
-
|
|
4144
4274
|
this.replicationChangeDebounceFn = debounceAggregationChanges<
|
|
4145
4275
|
ReplicationRangeIndexable<R>
|
|
4146
4276
|
>(
|
|
@@ -4162,14 +4292,24 @@ export class SharedLog<
|
|
|
4162
4292
|
>();
|
|
4163
4293
|
const selfReplicating = await this.isReplicating();
|
|
4164
4294
|
for (const [hash, value] of map) {
|
|
4165
|
-
|
|
4166
|
-
|
|
4167
|
-
|
|
4168
|
-
|
|
4169
|
-
|
|
4170
|
-
|
|
4295
|
+
const checkedPruneLeaders =
|
|
4296
|
+
await this.revalidateCheckedPruneOwnership({
|
|
4297
|
+
hash,
|
|
4298
|
+
entry: value.entry,
|
|
4299
|
+
leaders: value.leaders,
|
|
4300
|
+
selfReplicating,
|
|
4301
|
+
});
|
|
4171
4302
|
if (checkedPruneLeaders.localLeader) {
|
|
4172
|
-
|
|
4303
|
+
const preserveRetry = this._checkedPrune.hasRetry(hash);
|
|
4304
|
+
await this.cancelCheckedPruneForLocalLeader(hash, {
|
|
4305
|
+
preserveRetry,
|
|
4306
|
+
});
|
|
4307
|
+
if (preserveRetry) {
|
|
4308
|
+
this.scheduleCheckedPruneRetry({
|
|
4309
|
+
entry: value.entry,
|
|
4310
|
+
leaders: checkedPruneLeaders.leaders,
|
|
4311
|
+
});
|
|
4312
|
+
}
|
|
4173
4313
|
continue;
|
|
4174
4314
|
}
|
|
4175
4315
|
current.set(hash, {
|
|
@@ -4215,7 +4355,7 @@ export class SharedLog<
|
|
|
4215
4355
|
to: allRequestingPeers,
|
|
4216
4356
|
redundancy: 1,
|
|
4217
4357
|
}),
|
|
4218
|
-
priority:
|
|
4358
|
+
priority: CONVERGENCE_MESSAGE_PRIORITY,
|
|
4219
4359
|
});
|
|
4220
4360
|
},
|
|
4221
4361
|
() => {
|
|
@@ -4579,20 +4719,7 @@ export class SharedLog<
|
|
|
4579
4719
|
this.cancelReplicationInfoRequests(peerHash);
|
|
4580
4720
|
this._replicatorLivenessFailures.delete(peerHash);
|
|
4581
4721
|
this._replicatorLastActivityAt.delete(peerHash);
|
|
4582
|
-
|
|
4583
|
-
for (const [hash, peers] of this._requestIPruneSent) {
|
|
4584
|
-
peers.delete(peerHash);
|
|
4585
|
-
if (peers.size === 0) {
|
|
4586
|
-
this._requestIPruneSent.delete(hash);
|
|
4587
|
-
}
|
|
4588
|
-
}
|
|
4589
|
-
|
|
4590
|
-
for (const [hash, peers] of this._requestIPruneResponseReplicatorSet) {
|
|
4591
|
-
peers.delete(peerHash);
|
|
4592
|
-
if (peers.size === 0) {
|
|
4593
|
-
this._requestIPruneResponseReplicatorSet.delete(hash);
|
|
4594
|
-
}
|
|
4595
|
-
}
|
|
4722
|
+
this._checkedPrune.cleanupPeer(peerHash);
|
|
4596
4723
|
}
|
|
4597
4724
|
|
|
4598
4725
|
private markReplicatorActivity(peerHash: string, now = Date.now()) {
|
|
@@ -4656,7 +4783,7 @@ export class SharedLog<
|
|
|
4656
4783
|
const self = this.node.identity.publicKey.hashcode();
|
|
4657
4784
|
const seed = hashToSeed32(hash);
|
|
4658
4785
|
|
|
4659
|
-
const hinted = this.
|
|
4786
|
+
const hinted = this._checkedPrune.getConfirmedReplicators(hash);
|
|
4660
4787
|
if (hinted && hinted.size > 0) {
|
|
4661
4788
|
const peers = [...hinted].filter((p) => p !== self);
|
|
4662
4789
|
return peers.length > 0
|
|
@@ -4664,7 +4791,7 @@ export class SharedLog<
|
|
|
4664
4791
|
: undefined;
|
|
4665
4792
|
}
|
|
4666
4793
|
|
|
4667
|
-
const contacted = this.
|
|
4794
|
+
const contacted = this._checkedPrune.getContactedReplicators(hash);
|
|
4668
4795
|
if (contacted && contacted.size > 0) {
|
|
4669
4796
|
const peers = [...contacted].filter((p) => p !== self);
|
|
4670
4797
|
return peers.length > 0
|
|
@@ -5082,25 +5209,15 @@ export class SharedLog<
|
|
|
5082
5209
|
}
|
|
5083
5210
|
this._appendBackfillPendingByTarget.clear();
|
|
5084
5211
|
|
|
5085
|
-
for (const [_k, v] of this._pendingDeletes) {
|
|
5086
|
-
v.clear();
|
|
5087
|
-
v.promise.resolve(); // TODO or reject?
|
|
5088
|
-
}
|
|
5089
5212
|
for (const [_k, v] of this._pendingIHave) {
|
|
5090
5213
|
v.clear();
|
|
5091
5214
|
}
|
|
5092
|
-
|
|
5093
|
-
if (v.timer) clearTimeout(v.timer);
|
|
5094
|
-
}
|
|
5215
|
+
this._checkedPrune.close();
|
|
5095
5216
|
|
|
5096
5217
|
await this.remoteBlocks.stop();
|
|
5097
|
-
this._pendingDeletes.clear();
|
|
5098
5218
|
this._pendingIHave.clear();
|
|
5099
|
-
this._checkedPruneRetries.clear();
|
|
5100
5219
|
this.latestReplicationInfoMessage.clear();
|
|
5101
5220
|
this._gidPeersHistory.clear();
|
|
5102
|
-
this._requestIPruneSent.clear();
|
|
5103
|
-
this._requestIPruneResponseReplicatorSet.clear();
|
|
5104
5221
|
// Cancel any pending debounced timers so they can't fire after we've torn down
|
|
5105
5222
|
// indexes/RPC state.
|
|
5106
5223
|
this.rebalanceParticipationDebounced?.close();
|
|
@@ -5156,7 +5273,7 @@ export class SharedLog<
|
|
|
5156
5273
|
try {
|
|
5157
5274
|
await this.rpc
|
|
5158
5275
|
.send(new AllReplicatingSegmentsMessage({ segments: [] }), {
|
|
5159
|
-
priority:
|
|
5276
|
+
priority: CONVERGENCE_MESSAGE_PRIORITY,
|
|
5160
5277
|
signal: abort.signal,
|
|
5161
5278
|
})
|
|
5162
5279
|
.catch(() => {});
|
|
@@ -5203,7 +5320,7 @@ export class SharedLog<
|
|
|
5203
5320
|
try {
|
|
5204
5321
|
await this.rpc
|
|
5205
5322
|
.send(new AllReplicatingSegmentsMessage({ segments: [] }), {
|
|
5206
|
-
priority:
|
|
5323
|
+
priority: CONVERGENCE_MESSAGE_PRIORITY,
|
|
5207
5324
|
signal: abort.signal,
|
|
5208
5325
|
})
|
|
5209
5326
|
.catch(() => {});
|
|
@@ -5388,7 +5505,7 @@ export class SharedLog<
|
|
|
5388
5505
|
for (const entry of entries) {
|
|
5389
5506
|
this.pruneDebouncedFn.delete(entry.entry.hash);
|
|
5390
5507
|
this.removePruneRequestSent(entry.entry.hash);
|
|
5391
|
-
this.
|
|
5508
|
+
this._checkedPrune.clearConfirmedReplicators(
|
|
5392
5509
|
entry.entry.hash,
|
|
5393
5510
|
);
|
|
5394
5511
|
|
|
@@ -5511,44 +5628,51 @@ export class SharedLog<
|
|
|
5511
5628
|
|
|
5512
5629
|
// if we expect the remote to be owner of this entry because we are to prune ourselves, then we need to remove the remote
|
|
5513
5630
|
// this is due to that the remote has previously indicated to be a replicator to help us prune but now has changed their mind
|
|
5514
|
-
|
|
5515
|
-
this._requestIPruneResponseReplicatorSet.get(hash);
|
|
5516
|
-
if (outGoingPrunes) {
|
|
5517
|
-
outGoingPrunes.delete(from);
|
|
5518
|
-
}
|
|
5631
|
+
this._checkedPrune.removeConfirmedReplicator(hash, from);
|
|
5519
5632
|
|
|
5520
5633
|
const indexedEntry = await this.log.entryIndex.getShallow(hash);
|
|
5521
5634
|
let isLeader = false;
|
|
5522
5635
|
|
|
5523
|
-
if (
|
|
5524
|
-
|
|
5525
|
-
|
|
5526
|
-
|
|
5527
|
-
|
|
5528
|
-
|
|
5529
|
-
|
|
5530
|
-
|
|
5531
|
-
|
|
5636
|
+
if (indexedEntry && (await this.log.blocks.has(hash))) {
|
|
5637
|
+
const pendingDelete = this._checkedPrune.getPendingDelete(hash);
|
|
5638
|
+
if (pendingDelete) {
|
|
5639
|
+
const ownership =
|
|
5640
|
+
await this.revalidateCheckedPruneOwnership({
|
|
5641
|
+
hash,
|
|
5642
|
+
entry: indexedEntry.value,
|
|
5643
|
+
leaders: new Map(),
|
|
5644
|
+
});
|
|
5645
|
+
if (ownership.localLeader) {
|
|
5646
|
+
await this.cancelCheckedPruneForLocalLeader(hash);
|
|
5647
|
+
isLeader = true;
|
|
5648
|
+
}
|
|
5649
|
+
} else {
|
|
5650
|
+
this.removePeerFromGidPeerHistory(
|
|
5651
|
+
context.from!.hashcode(),
|
|
5652
|
+
indexedEntry!.value.meta.gid,
|
|
5653
|
+
);
|
|
5532
5654
|
|
|
5533
|
-
|
|
5534
|
-
|
|
5655
|
+
await this._waitForReplicators(
|
|
5656
|
+
await this.createCoordinates(
|
|
5657
|
+
indexedEntry.value,
|
|
5658
|
+
decodeReplicas(indexedEntry.value).getValue(this),
|
|
5659
|
+
),
|
|
5535
5660
|
indexedEntry.value,
|
|
5536
|
-
|
|
5537
|
-
|
|
5538
|
-
|
|
5539
|
-
|
|
5661
|
+
[
|
|
5662
|
+
{
|
|
5663
|
+
key: this.node.identity.publicKey.hashcode(),
|
|
5664
|
+
replicator: true,
|
|
5665
|
+
},
|
|
5666
|
+
],
|
|
5540
5667
|
{
|
|
5541
|
-
|
|
5542
|
-
|
|
5543
|
-
|
|
5544
|
-
|
|
5545
|
-
|
|
5546
|
-
onLeader: (key) => {
|
|
5547
|
-
isLeader =
|
|
5548
|
-
isLeader || key === this.node.identity.publicKey.hashcode();
|
|
5668
|
+
onLeader: (key) => {
|
|
5669
|
+
isLeader =
|
|
5670
|
+
isLeader ||
|
|
5671
|
+
key === this.node.identity.publicKey.hashcode();
|
|
5672
|
+
},
|
|
5549
5673
|
},
|
|
5550
|
-
|
|
5551
|
-
|
|
5674
|
+
);
|
|
5675
|
+
}
|
|
5552
5676
|
}
|
|
5553
5677
|
|
|
5554
5678
|
if (isLeader) {
|
|
@@ -5623,8 +5747,20 @@ export class SharedLog<
|
|
|
5623
5747
|
}
|
|
5624
5748
|
}
|
|
5625
5749
|
} else if (msg instanceof ResponseIPrune) {
|
|
5750
|
+
const lateResponses: string[] = [];
|
|
5626
5751
|
for (const hash of msg.hashes) {
|
|
5627
|
-
this.
|
|
5752
|
+
const pendingDelete = this._checkedPrune.getPendingDelete(hash);
|
|
5753
|
+
if (pendingDelete) {
|
|
5754
|
+
void pendingDelete.resolve(context.from.hashcode());
|
|
5755
|
+
} else {
|
|
5756
|
+
lateResponses.push(hash);
|
|
5757
|
+
}
|
|
5758
|
+
}
|
|
5759
|
+
if (lateResponses.length > 0) {
|
|
5760
|
+
void this.recoverCheckedPruneFromLateResponses(
|
|
5761
|
+
lateResponses,
|
|
5762
|
+
context.from.hashcode(),
|
|
5763
|
+
).catch((error) => logger.error(error.toString()));
|
|
5628
5764
|
}
|
|
5629
5765
|
} else if (msg instanceof ConfirmEntriesMessage) {
|
|
5630
5766
|
this.markEntriesKnownByPeer(msg.hashes, context.from.hashcode());
|
|
@@ -6072,7 +6208,7 @@ export class SharedLog<
|
|
|
6072
6208
|
|
|
6073
6209
|
await this.replicate(entriesToReplicate, {
|
|
6074
6210
|
rebalance: assumeSynced ? false : true,
|
|
6075
|
-
checkDuplicates: true,
|
|
6211
|
+
checkDuplicates: assumeSynced ? false : true,
|
|
6076
6212
|
mergeSegments:
|
|
6077
6213
|
typeof options.replicate !== "boolean" && options.replicate
|
|
6078
6214
|
? options.replicate.mergeSegments
|
|
@@ -6103,7 +6239,7 @@ export class SharedLog<
|
|
|
6103
6239
|
|
|
6104
6240
|
if (messageToSend) {
|
|
6105
6241
|
await this.rpc.send(messageToSend, {
|
|
6106
|
-
priority:
|
|
6242
|
+
priority: CONVERGENCE_MESSAGE_PRIORITY,
|
|
6107
6243
|
});
|
|
6108
6244
|
}
|
|
6109
6245
|
}
|
|
@@ -6890,11 +7026,12 @@ export class SharedLog<
|
|
|
6890
7026
|
this._replicationInfoRequestByPeer.set(peerHash, state);
|
|
6891
7027
|
|
|
6892
7028
|
const intervalMs = Math.max(50, this.waitForReplicatorRequestIntervalMs);
|
|
6893
|
-
const maxAttempts =
|
|
6894
|
-
5,
|
|
7029
|
+
const maxAttempts =
|
|
6895
7030
|
this.waitForReplicatorRequestMaxAttempts ??
|
|
7031
|
+
Math.max(
|
|
6896
7032
|
WAIT_FOR_REPLICATOR_REQUEST_MIN_ATTEMPTS,
|
|
6897
|
-
|
|
7033
|
+
Math.ceil(this.waitForReplicatorTimeout / intervalMs),
|
|
7034
|
+
);
|
|
6898
7035
|
|
|
6899
7036
|
const tick = () => {
|
|
6900
7037
|
if (this.closed || this._closeController.signal.aborted) {
|
|
@@ -7020,17 +7157,7 @@ export class SharedLog<
|
|
|
7020
7157
|
}
|
|
7021
7158
|
|
|
7022
7159
|
private removePruneRequestSent(hash: string, to?: string) {
|
|
7023
|
-
|
|
7024
|
-
this._requestIPruneSent.delete(hash);
|
|
7025
|
-
} else {
|
|
7026
|
-
let set = this._requestIPruneSent.get(hash);
|
|
7027
|
-
if (set) {
|
|
7028
|
-
set.delete(to);
|
|
7029
|
-
if (set.size === 0) {
|
|
7030
|
-
this._requestIPruneSent.delete(hash);
|
|
7031
|
-
}
|
|
7032
|
-
}
|
|
7033
|
-
}
|
|
7160
|
+
this._checkedPrune.removeRequestSent(hash, to);
|
|
7034
7161
|
}
|
|
7035
7162
|
|
|
7036
7163
|
prune(
|
|
@@ -7047,7 +7174,7 @@ export class SharedLog<
|
|
|
7047
7174
|
return [...entries.values()].map((x) => {
|
|
7048
7175
|
this._gidPeersHistory.delete(x.entry.meta.gid);
|
|
7049
7176
|
this.removePruneRequestSent(x.entry.hash);
|
|
7050
|
-
this.
|
|
7177
|
+
this._checkedPrune.clearConfirmedReplicators(x.entry.hash);
|
|
7051
7178
|
return this.log.remove(x.entry, {
|
|
7052
7179
|
recursively: true,
|
|
7053
7180
|
});
|
|
@@ -7084,7 +7211,7 @@ export class SharedLog<
|
|
|
7084
7211
|
set.push(entry.hash);
|
|
7085
7212
|
}
|
|
7086
7213
|
|
|
7087
|
-
const pendingPrev = this.
|
|
7214
|
+
const pendingPrev = this._checkedPrune.getPendingDelete(entry.hash);
|
|
7088
7215
|
if (pendingPrev) {
|
|
7089
7216
|
// If a background prune is already in-flight, an explicit prune request should
|
|
7090
7217
|
// still respect the caller's timeout. Otherwise, tests (and user calls) can
|
|
@@ -7120,62 +7247,77 @@ export class SharedLog<
|
|
|
7120
7247
|
const deferredPromise: DeferredPromise<void> = pDefer();
|
|
7121
7248
|
|
|
7122
7249
|
const clear = () => {
|
|
7123
|
-
const pending = this.
|
|
7250
|
+
const pending = this._checkedPrune.getPendingDelete(entry.hash);
|
|
7124
7251
|
if (pending?.promise === deferredPromise) {
|
|
7125
|
-
this.
|
|
7252
|
+
this._checkedPrune.deletePendingDelete(entry.hash, pending);
|
|
7126
7253
|
}
|
|
7127
7254
|
clearTimeout(timeout);
|
|
7128
7255
|
};
|
|
7129
7256
|
|
|
7130
|
-
|
|
7131
|
-
|
|
7132
|
-
|
|
7133
|
-
|
|
7134
|
-
|
|
7135
|
-
this._gidPeersHistory.delete(entry.meta.gid);
|
|
7136
|
-
this.removePruneRequestSent(entry.hash);
|
|
7137
|
-
this._requestIPruneResponseReplicatorSet.delete(entry.hash);
|
|
7138
|
-
|
|
7139
|
-
if (
|
|
7140
|
-
await this.isLeader({
|
|
7141
|
-
entry,
|
|
7142
|
-
replicas: minReplicas.getValue(this),
|
|
7143
|
-
})
|
|
7144
|
-
) {
|
|
7145
|
-
deferredPromise.reject(
|
|
7146
|
-
new Error("Failed to delete, is leader again"),
|
|
7147
|
-
);
|
|
7148
|
-
return;
|
|
7149
|
-
}
|
|
7150
|
-
|
|
7151
|
-
return this.log
|
|
7152
|
-
.remove(entry, {
|
|
7153
|
-
recursively: true,
|
|
7154
|
-
})
|
|
7155
|
-
.then(() => {
|
|
7156
|
-
deferredPromise.resolve();
|
|
7157
|
-
})
|
|
7158
|
-
.catch((e) => {
|
|
7159
|
-
deferredPromise.reject(e);
|
|
7160
|
-
})
|
|
7161
|
-
.finally(async () => {
|
|
7257
|
+
const resolve = () => {
|
|
7258
|
+
clearTimeout(timeout);
|
|
7259
|
+
this.clearCheckedPruneRetry(entry.hash);
|
|
7260
|
+
cleanupTimer.push(
|
|
7261
|
+
setTimeout(async () => {
|
|
7162
7262
|
this._gidPeersHistory.delete(entry.meta.gid);
|
|
7163
7263
|
this.removePruneRequestSent(entry.hash);
|
|
7164
|
-
this.
|
|
7165
|
-
// TODO in the case we become leader again here we need to re-add the entry
|
|
7264
|
+
this._checkedPrune.clearConfirmedReplicators(entry.hash);
|
|
7166
7265
|
|
|
7167
|
-
|
|
7168
|
-
await this.
|
|
7266
|
+
const ownership =
|
|
7267
|
+
await this.revalidateCheckedPruneOwnership({
|
|
7268
|
+
hash: entry.hash,
|
|
7169
7269
|
entry,
|
|
7170
|
-
|
|
7171
|
-
|
|
7172
|
-
|
|
7173
|
-
|
|
7270
|
+
leaders: this.checkedPruneLeadersToMap(leaders),
|
|
7271
|
+
selfReplicating: true,
|
|
7272
|
+
});
|
|
7273
|
+
if (ownership.localLeader) {
|
|
7274
|
+
clear();
|
|
7275
|
+
if (!explicitTimeout) {
|
|
7276
|
+
this.scheduleCheckedPruneRetry({ entry, leaders });
|
|
7277
|
+
}
|
|
7278
|
+
deferredPromise.reject(
|
|
7279
|
+
new Error("Failed to delete, is leader again"),
|
|
7280
|
+
);
|
|
7281
|
+
return;
|
|
7174
7282
|
}
|
|
7175
|
-
|
|
7176
|
-
|
|
7177
|
-
|
|
7178
|
-
|
|
7283
|
+
|
|
7284
|
+
this._checkedPrune.markRemoving(entry.hash);
|
|
7285
|
+
return this.log
|
|
7286
|
+
.remove(entry, {
|
|
7287
|
+
recursively: true,
|
|
7288
|
+
})
|
|
7289
|
+
.then(() => {
|
|
7290
|
+
clear();
|
|
7291
|
+
this._checkedPrune.markDone(entry.hash);
|
|
7292
|
+
deferredPromise.resolve();
|
|
7293
|
+
})
|
|
7294
|
+
.catch((e) => {
|
|
7295
|
+
clear();
|
|
7296
|
+
this._checkedPrune.markCancelled(entry.hash, {
|
|
7297
|
+
preserveRetry: false,
|
|
7298
|
+
});
|
|
7299
|
+
deferredPromise.reject(e);
|
|
7300
|
+
})
|
|
7301
|
+
.finally(async () => {
|
|
7302
|
+
this._gidPeersHistory.delete(entry.meta.gid);
|
|
7303
|
+
this.removePruneRequestSent(entry.hash);
|
|
7304
|
+
this._checkedPrune.clearConfirmedReplicators(entry.hash);
|
|
7305
|
+
// TODO in the case we become leader again here we need to re-add the entry
|
|
7306
|
+
|
|
7307
|
+
const ownership =
|
|
7308
|
+
await this.revalidateCheckedPruneOwnership({
|
|
7309
|
+
hash: entry.hash,
|
|
7310
|
+
entry,
|
|
7311
|
+
leaders: this.checkedPruneLeadersToMap(leaders),
|
|
7312
|
+
selfReplicating: true,
|
|
7313
|
+
});
|
|
7314
|
+
if (ownership.localLeader) {
|
|
7315
|
+
logger.error("Unexpected: Is leader after delete");
|
|
7316
|
+
}
|
|
7317
|
+
});
|
|
7318
|
+
}, this.waitForPruneDelay),
|
|
7319
|
+
);
|
|
7320
|
+
};
|
|
7179
7321
|
|
|
7180
7322
|
const reject = (e: any) => {
|
|
7181
7323
|
clear();
|
|
@@ -7183,11 +7325,9 @@ export class SharedLog<
|
|
|
7183
7325
|
e instanceof Error &&
|
|
7184
7326
|
typeof e.message === "string" &&
|
|
7185
7327
|
e.message.startsWith("Timeout for checked pruning");
|
|
7186
|
-
|
|
7187
|
-
|
|
7188
|
-
}
|
|
7189
|
-
this.removePruneRequestSent(entry.hash);
|
|
7190
|
-
this._requestIPruneResponseReplicatorSet.delete(entry.hash);
|
|
7328
|
+
this._checkedPrune.markCancelled(entry.hash, {
|
|
7329
|
+
preserveRetry: !explicitTimeout && isCheckedPruneTimeout,
|
|
7330
|
+
});
|
|
7191
7331
|
deferredPromise.reject(e);
|
|
7192
7332
|
};
|
|
7193
7333
|
|
|
@@ -7201,70 +7341,65 @@ export class SharedLog<
|
|
|
7201
7341
|
const checkedPruneTimeoutMs =
|
|
7202
7342
|
options?.timeout ??
|
|
7203
7343
|
Math.max(
|
|
7204
|
-
|
|
7344
|
+
CHECKED_PRUNE_BACKGROUND_TIMEOUT_MIN_MS,
|
|
7205
7345
|
Number(this._respondToIHaveTimeout ?? 0) +
|
|
7206
7346
|
this.waitForReplicatorTimeout +
|
|
7207
7347
|
PRUNE_DEBOUNCE_INTERVAL * 2,
|
|
7208
7348
|
);
|
|
7209
7349
|
|
|
7210
|
-
|
|
7211
|
-
|
|
7212
|
-
|
|
7213
|
-
|
|
7214
|
-
|
|
7215
|
-
|
|
7216
|
-
|
|
7217
|
-
|
|
7218
|
-
|
|
7219
|
-
|
|
7220
|
-
|
|
7221
|
-
|
|
7222
|
-
|
|
7223
|
-
|
|
7224
|
-
|
|
7225
|
-
this.
|
|
7226
|
-
|
|
7227
|
-
|
|
7228
|
-
|
|
7229
|
-
|
|
7230
|
-
|
|
7231
|
-
|
|
7232
|
-
|
|
7233
|
-
|
|
7234
|
-
|
|
7235
|
-
|
|
7236
|
-
|
|
7237
|
-
|
|
7238
|
-
|
|
7239
|
-
|
|
7240
|
-
|
|
7241
|
-
|
|
7242
|
-
|
|
7243
|
-
|
|
7350
|
+
const timeout = setTimeout(() => {
|
|
7351
|
+
// For internal/background prune flows (no explicit timeout), retry a few times
|
|
7352
|
+
// to avoid "permanently prunable" entries when `_pendingIHave` expires under
|
|
7353
|
+
// heavy load.
|
|
7354
|
+
if (!explicitTimeout) {
|
|
7355
|
+
this.scheduleCheckedPruneRetry({ entry, leaders });
|
|
7356
|
+
}
|
|
7357
|
+
reject(
|
|
7358
|
+
new Error(
|
|
7359
|
+
`Timeout for checked pruning after ${checkedPruneTimeoutMs}ms (closed=${this.closed})`,
|
|
7360
|
+
),
|
|
7361
|
+
);
|
|
7362
|
+
}, checkedPruneTimeoutMs);
|
|
7363
|
+
timeout.unref?.();
|
|
7364
|
+
|
|
7365
|
+
this._checkedPrune.setPendingDelete(
|
|
7366
|
+
entry.hash,
|
|
7367
|
+
{
|
|
7368
|
+
promise: deferredPromise,
|
|
7369
|
+
clear,
|
|
7370
|
+
reject,
|
|
7371
|
+
resolve: async (publicKeyHash: string) => {
|
|
7372
|
+
const minReplicasObj = this.getClampedReplicas(minReplicas);
|
|
7373
|
+
const minReplicasValue = minReplicasObj.getValue(this);
|
|
7374
|
+
|
|
7375
|
+
// TODO is this check necessary
|
|
7376
|
+
if (
|
|
7377
|
+
!(await this._waitForReplicators(
|
|
7378
|
+
cursor ??
|
|
7379
|
+
(cursor = await this.createCoordinates(
|
|
7380
|
+
entry,
|
|
7381
|
+
minReplicasValue,
|
|
7382
|
+
)),
|
|
7383
|
+
entry,
|
|
7384
|
+
[
|
|
7385
|
+
{ key: publicKeyHash, replicator: true },
|
|
7386
|
+
{
|
|
7387
|
+
key: this.node.identity.publicKey.hashcode(),
|
|
7388
|
+
replicator: false,
|
|
7389
|
+
},
|
|
7390
|
+
],
|
|
7244
7391
|
{
|
|
7245
|
-
|
|
7246
|
-
replicator: false,
|
|
7392
|
+
persist: false,
|
|
7247
7393
|
},
|
|
7248
|
-
|
|
7249
|
-
|
|
7250
|
-
|
|
7251
|
-
},
|
|
7252
|
-
))
|
|
7253
|
-
) {
|
|
7254
|
-
return;
|
|
7255
|
-
}
|
|
7256
|
-
|
|
7257
|
-
let existCounter = this._requestIPruneResponseReplicatorSet.get(
|
|
7258
|
-
entry.hash,
|
|
7259
|
-
);
|
|
7260
|
-
if (!existCounter) {
|
|
7261
|
-
existCounter = new Set();
|
|
7262
|
-
this._requestIPruneResponseReplicatorSet.set(
|
|
7263
|
-
entry.hash,
|
|
7264
|
-
existCounter,
|
|
7265
|
-
);
|
|
7394
|
+
))
|
|
7395
|
+
) {
|
|
7396
|
+
return;
|
|
7266
7397
|
}
|
|
7267
|
-
|
|
7398
|
+
|
|
7399
|
+
const existCounter = this._checkedPrune.addConfirmedReplicator(
|
|
7400
|
+
entry.hash,
|
|
7401
|
+
publicKeyHash,
|
|
7402
|
+
);
|
|
7268
7403
|
// Seed provider hints so future remote reads can avoid extra round-trips.
|
|
7269
7404
|
this.remoteBlocks.hintProviders(entry.hash, [publicKeyHash]);
|
|
7270
7405
|
|
|
@@ -7272,7 +7407,10 @@ export class SharedLog<
|
|
|
7272
7407
|
resolve();
|
|
7273
7408
|
}
|
|
7274
7409
|
},
|
|
7275
|
-
}
|
|
7410
|
+
},
|
|
7411
|
+
entry,
|
|
7412
|
+
leaders,
|
|
7413
|
+
);
|
|
7276
7414
|
|
|
7277
7415
|
promises.push(deferredPromise.promise);
|
|
7278
7416
|
}
|
|
@@ -7280,16 +7418,11 @@ export class SharedLog<
|
|
|
7280
7418
|
const emitMessages = async (entries: string[], to: string) => {
|
|
7281
7419
|
const filteredSet: string[] = [];
|
|
7282
7420
|
for (const entry of entries) {
|
|
7283
|
-
let set = this._requestIPruneSent.get(entry);
|
|
7284
|
-
if (!set) {
|
|
7285
|
-
set = new Set();
|
|
7286
|
-
this._requestIPruneSent.set(entry, set);
|
|
7287
|
-
}
|
|
7288
7421
|
/* TODO why can we not have this statement?
|
|
7289
7422
|
if (set.has(to)) {
|
|
7290
7423
|
continue;
|
|
7291
7424
|
} */
|
|
7292
|
-
|
|
7425
|
+
this._checkedPrune.addRequestSent(entry, to);
|
|
7293
7426
|
filteredSet.push(entry);
|
|
7294
7427
|
}
|
|
7295
7428
|
if (filteredSet.length > 0) {
|
|
@@ -7302,7 +7435,7 @@ export class SharedLog<
|
|
|
7302
7435
|
to: [to], // TODO group by peers?
|
|
7303
7436
|
redundancy: 1,
|
|
7304
7437
|
}),
|
|
7305
|
-
priority:
|
|
7438
|
+
priority: CONVERGENCE_MESSAGE_PRIORITY,
|
|
7306
7439
|
},
|
|
7307
7440
|
);
|
|
7308
7441
|
}
|
|
@@ -7331,7 +7464,9 @@ export class SharedLog<
|
|
|
7331
7464
|
|
|
7332
7465
|
const pendingByPeer: [string, string[]][] = [];
|
|
7333
7466
|
for (const [peer, hashes] of peerToEntries) {
|
|
7334
|
-
const pending = hashes.filter((h) =>
|
|
7467
|
+
const pending = hashes.filter((h) =>
|
|
7468
|
+
this._checkedPrune.hasPendingDelete(h),
|
|
7469
|
+
);
|
|
7335
7470
|
if (pending.length > 0) {
|
|
7336
7471
|
pendingByPeer.push([peer, pending]);
|
|
7337
7472
|
}
|
|
@@ -7762,12 +7897,26 @@ export class SharedLog<
|
|
|
7762
7897
|
flushUncheckedDeliverTarget(target);
|
|
7763
7898
|
}
|
|
7764
7899
|
|
|
7765
|
-
if (
|
|
7766
|
-
|
|
7767
|
-
|
|
7768
|
-
|
|
7769
|
-
|
|
7770
|
-
|
|
7900
|
+
if (
|
|
7901
|
+
this._isAdaptiveReplicating &&
|
|
7902
|
+
(hasSelfRangeRemoval ||
|
|
7903
|
+
changes.some(
|
|
7904
|
+
(change) =>
|
|
7905
|
+
change.type === "added" ||
|
|
7906
|
+
change.type === "removed" ||
|
|
7907
|
+
change.type === "replaced",
|
|
7908
|
+
))
|
|
7909
|
+
) {
|
|
7910
|
+
// Adaptive range changes can make already-indexed local heads prunable
|
|
7911
|
+
// even when the incremental rebalance scan misses them under churn or
|
|
7912
|
+
// timing pressure. Re-scan after repair dispatches are flushed using the
|
|
7913
|
+
// mature-role view, which matches the bounded pruning contract.
|
|
7914
|
+
await this.pruneIndexedEntriesNoLongerLed({
|
|
7915
|
+
useDefaultRoleAge: true,
|
|
7916
|
+
});
|
|
7917
|
+
await this.pruneCurrentHeadsNoLongerLed({
|
|
7918
|
+
useDefaultRoleAge: true,
|
|
7919
|
+
});
|
|
7771
7920
|
}
|
|
7772
7921
|
|
|
7773
7922
|
return changed;
|