@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/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,
|
|
@@ -72,6 +74,12 @@ import pDefer, { type DeferredPromise } from "p-defer";
|
|
|
72
74
|
import PQueue from "p-queue";
|
|
73
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
|
}
|
|
@@ -2163,7 +2169,7 @@ export class SharedLog<
|
|
|
2163
2169
|
this.node.identity.publicKey,
|
|
2164
2170
|
);
|
|
2165
2171
|
await this.rpc.send(new StoppedReplicating({ segmentIds }), {
|
|
2166
|
-
priority:
|
|
2172
|
+
priority: CONVERGENCE_MESSAGE_PRIORITY,
|
|
2167
2173
|
});
|
|
2168
2174
|
}
|
|
2169
2175
|
|
|
@@ -2189,7 +2195,7 @@ export class SharedLog<
|
|
|
2189
2195
|
// announce that we are no longer replicating
|
|
2190
2196
|
|
|
2191
2197
|
await this.rpc.send(new AllReplicatingSegmentsMessage({ segments: [] }), {
|
|
2192
|
-
priority:
|
|
2198
|
+
priority: CONVERGENCE_MESSAGE_PRIORITY,
|
|
2193
2199
|
});
|
|
2194
2200
|
}
|
|
2195
2201
|
|
|
@@ -2673,7 +2679,7 @@ export class SharedLog<
|
|
|
2673
2679
|
return options.announce(message);
|
|
2674
2680
|
} else {
|
|
2675
2681
|
await this.rpc.send(message, {
|
|
2676
|
-
priority:
|
|
2682
|
+
priority: CONVERGENCE_MESSAGE_PRIORITY,
|
|
2677
2683
|
});
|
|
2678
2684
|
}
|
|
2679
2685
|
}
|
|
@@ -2902,7 +2908,7 @@ export class SharedLog<
|
|
|
2902
2908
|
i + REPAIR_CONFIRMATION_HASH_BATCH_SIZE,
|
|
2903
2909
|
);
|
|
2904
2910
|
await this.rpc.send(new ConfirmEntriesMessage({ hashes: chunk }), {
|
|
2905
|
-
priority:
|
|
2911
|
+
priority: CONVERGENCE_MESSAGE_PRIORITY,
|
|
2906
2912
|
mode: new SilentDelivery({ to: [target], redundancy: 1 }),
|
|
2907
2913
|
});
|
|
2908
2914
|
}
|
|
@@ -2918,7 +2924,7 @@ export class SharedLog<
|
|
|
2918
2924
|
)) {
|
|
2919
2925
|
message.reserved[0] |= EXCHANGE_HEADS_REPAIR_HINT;
|
|
2920
2926
|
await this.rpc.send(message, {
|
|
2921
|
-
priority:
|
|
2927
|
+
priority: SYNC_MESSAGE_PRIORITY,
|
|
2922
2928
|
mode: new SilentDelivery({ to: [target], redundancy: 1 }),
|
|
2923
2929
|
});
|
|
2924
2930
|
}
|
|
@@ -3569,30 +3575,32 @@ export class SharedLog<
|
|
|
3569
3575
|
if (this.keep && (await this.keep(args.value.entry))) {
|
|
3570
3576
|
return false;
|
|
3571
3577
|
}
|
|
3578
|
+
this._checkedPrune.trackCandidate(
|
|
3579
|
+
args.key,
|
|
3580
|
+
args.value.entry,
|
|
3581
|
+
args.value.leaders,
|
|
3582
|
+
);
|
|
3572
3583
|
void this.pruneDebouncedFn.add(args);
|
|
3573
3584
|
return true;
|
|
3574
3585
|
}
|
|
3575
3586
|
|
|
3576
|
-
private async cancelCheckedPruneForLocalLeader(
|
|
3587
|
+
private async cancelCheckedPruneForLocalLeader(
|
|
3588
|
+
hash: string,
|
|
3589
|
+
options?: { preserveRetry?: boolean },
|
|
3590
|
+
) {
|
|
3577
3591
|
this.pruneDebouncedFn.delete(hash);
|
|
3578
|
-
this.
|
|
3579
|
-
this.
|
|
3580
|
-
|
|
3581
|
-
|
|
3582
|
-
|
|
3583
|
-
?.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"));
|
|
3584
3597
|
}
|
|
3585
3598
|
|
|
3586
3599
|
private hasActiveCheckedPruneWork(hash: string) {
|
|
3587
|
-
return (
|
|
3588
|
-
this._pendingDeletes.has(hash) ||
|
|
3589
|
-
this._requestIPruneSent.has(hash) ||
|
|
3590
|
-
this._requestIPruneResponseReplicatorSet.has(hash) ||
|
|
3591
|
-
this._checkedPruneRetries.has(hash)
|
|
3592
|
-
);
|
|
3600
|
+
return this._checkedPrune.hasActiveWork(hash);
|
|
3593
3601
|
}
|
|
3594
3602
|
|
|
3595
|
-
private async
|
|
3603
|
+
private async revalidateCheckedPruneOwnership(args: {
|
|
3596
3604
|
hash: string;
|
|
3597
3605
|
entry: CheckedPruneEntry<T, R>;
|
|
3598
3606
|
leaders: CheckedPruneLeaderMap;
|
|
@@ -3660,7 +3668,7 @@ export class SharedLog<
|
|
|
3660
3668
|
continue;
|
|
3661
3669
|
}
|
|
3662
3670
|
|
|
3663
|
-
if (this.
|
|
3671
|
+
if (this._checkedPrune.hasPendingDelete(entry.hash)) {
|
|
3664
3672
|
continue;
|
|
3665
3673
|
}
|
|
3666
3674
|
|
|
@@ -3676,7 +3684,9 @@ export class SharedLog<
|
|
|
3676
3684
|
}
|
|
3677
3685
|
}
|
|
3678
3686
|
|
|
3679
|
-
private async pruneIndexedEntriesNoLongerLed(
|
|
3687
|
+
private async pruneIndexedEntriesNoLongerLed(options?: {
|
|
3688
|
+
useDefaultRoleAge?: boolean;
|
|
3689
|
+
}) {
|
|
3680
3690
|
const selfHash = this.node.identity.publicKey.hashcode();
|
|
3681
3691
|
const iterator = this.entryCoordinatesIndex.iterate({});
|
|
3682
3692
|
let enqueuedPrune = false;
|
|
@@ -3692,7 +3702,7 @@ export class SharedLog<
|
|
|
3692
3702
|
const leaders = await this.findLeaders(
|
|
3693
3703
|
entryReplicated.coordinates,
|
|
3694
3704
|
entryReplicated,
|
|
3695
|
-
{ roleAge: 0 },
|
|
3705
|
+
options?.useDefaultRoleAge ? undefined : { roleAge: 0 },
|
|
3696
3706
|
);
|
|
3697
3707
|
|
|
3698
3708
|
if (leaders.has(selfHash)) {
|
|
@@ -3700,7 +3710,7 @@ export class SharedLog<
|
|
|
3700
3710
|
continue;
|
|
3701
3711
|
}
|
|
3702
3712
|
|
|
3703
|
-
if (this.
|
|
3713
|
+
if (this._checkedPrune.hasPendingDelete(entryReplicated.hash)) {
|
|
3704
3714
|
continue;
|
|
3705
3715
|
}
|
|
3706
3716
|
|
|
@@ -3724,12 +3734,65 @@ export class SharedLog<
|
|
|
3724
3734
|
}
|
|
3725
3735
|
}
|
|
3726
3736
|
|
|
3727
|
-
private
|
|
3728
|
-
|
|
3729
|
-
|
|
3730
|
-
|
|
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);
|
|
3774
|
+
}
|
|
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 });
|
|
3731
3790
|
}
|
|
3732
|
-
|
|
3791
|
+
return leadersMap;
|
|
3792
|
+
}
|
|
3793
|
+
|
|
3794
|
+
private clearCheckedPruneRetry(hash: string) {
|
|
3795
|
+
this._checkedPrune.clearRetry(hash);
|
|
3733
3796
|
}
|
|
3734
3797
|
|
|
3735
3798
|
private scheduleCheckedPruneRetry(args: {
|
|
@@ -3737,11 +3800,18 @@ export class SharedLog<
|
|
|
3737
3800
|
leaders: CheckedPruneLeaderMap | Set<string>;
|
|
3738
3801
|
}) {
|
|
3739
3802
|
if (this.closed) return;
|
|
3740
|
-
if (this.
|
|
3803
|
+
if (this._checkedPrune.hasPendingDelete(args.entry.hash)) return;
|
|
3741
3804
|
|
|
3742
3805
|
const hash = args.entry.hash;
|
|
3743
3806
|
const state =
|
|
3744
|
-
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;
|
|
3745
3815
|
|
|
3746
3816
|
if (state.timer) return;
|
|
3747
3817
|
if (state.attempts >= CHECKED_PRUNE_RETRY_MAX_ATTEMPTS) {
|
|
@@ -3759,15 +3829,17 @@ export class SharedLog<
|
|
|
3759
3829
|
|
|
3760
3830
|
state.attempts = attempt;
|
|
3761
3831
|
state.timer = setTimeout(async () => {
|
|
3762
|
-
const st = this.
|
|
3832
|
+
const st = this._checkedPrune.getRetry(hash);
|
|
3763
3833
|
if (st) st.timer = undefined;
|
|
3764
3834
|
if (this.closed) return;
|
|
3765
|
-
if (this.
|
|
3835
|
+
if (this._checkedPrune.hasPendingDelete(hash)) return;
|
|
3836
|
+
const retryEntry = st?.entry ?? args.entry;
|
|
3837
|
+
const retryLeaders = st?.leaders ?? args.leaders;
|
|
3766
3838
|
|
|
3767
3839
|
let leadersMap: CheckedPruneLeaderMap | undefined;
|
|
3768
3840
|
try {
|
|
3769
|
-
const replicas = decodeReplicas(
|
|
3770
|
-
leadersMap = await this.findLeadersFromEntry(
|
|
3841
|
+
const replicas = decodeReplicas(retryEntry).getValue(this);
|
|
3842
|
+
leadersMap = await this.findLeadersFromEntry(retryEntry, replicas, {
|
|
3771
3843
|
roleAge: 0,
|
|
3772
3844
|
});
|
|
3773
3845
|
} catch {
|
|
@@ -3775,14 +3847,7 @@ export class SharedLog<
|
|
|
3775
3847
|
}
|
|
3776
3848
|
|
|
3777
3849
|
if (!leadersMap || leadersMap.size === 0) {
|
|
3778
|
-
|
|
3779
|
-
leadersMap = args.leaders;
|
|
3780
|
-
} else {
|
|
3781
|
-
leadersMap = new Map<string, { intersecting: boolean }>();
|
|
3782
|
-
for (const k of args.leaders) {
|
|
3783
|
-
leadersMap.set(k, { intersecting: true });
|
|
3784
|
-
}
|
|
3785
|
-
}
|
|
3850
|
+
leadersMap = this.checkedPruneLeadersToMap(retryLeaders);
|
|
3786
3851
|
}
|
|
3787
3852
|
|
|
3788
3853
|
try {
|
|
@@ -3790,14 +3855,81 @@ export class SharedLog<
|
|
|
3790
3855
|
leadersMap ?? new Map<string, { intersecting: boolean }>();
|
|
3791
3856
|
await this.pruneDebouncedFnAddIfNotKeeping({
|
|
3792
3857
|
key: hash,
|
|
3793
|
-
value: { entry:
|
|
3858
|
+
value: { entry: retryEntry, leaders: leadersForRetry },
|
|
3794
3859
|
});
|
|
3795
3860
|
} catch {
|
|
3796
3861
|
// Best-effort only; pruning will be re-attempted on future changes.
|
|
3797
3862
|
}
|
|
3798
3863
|
}, delayMs);
|
|
3799
3864
|
state.timer.unref?.();
|
|
3800
|
-
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
|
+
}
|
|
3801
3933
|
}
|
|
3802
3934
|
|
|
3803
3935
|
async append(
|
|
@@ -3947,7 +4079,7 @@ export class SharedLog<
|
|
|
3947
4079
|
this.domain.resolution,
|
|
3948
4080
|
);
|
|
3949
4081
|
this._respondToIHaveTimeout = options?.respondToIHaveTimeout ?? 2e4;
|
|
3950
|
-
this.
|
|
4082
|
+
this._checkedPrune = new CheckedPruneCoordinator<T, R>();
|
|
3951
4083
|
this._pendingIHave = new Map();
|
|
3952
4084
|
this.latestReplicationInfoMessage = new Map();
|
|
3953
4085
|
this._replicationInfoBlockedPeers = new Set();
|
|
@@ -4139,10 +4271,6 @@ export class SharedLog<
|
|
|
4139
4271
|
})) > 0;
|
|
4140
4272
|
|
|
4141
4273
|
this._gidPeersHistory = new Map();
|
|
4142
|
-
this._requestIPruneSent = new Map();
|
|
4143
|
-
this._requestIPruneResponseReplicatorSet = new Map();
|
|
4144
|
-
this._checkedPruneRetries = new Map();
|
|
4145
|
-
|
|
4146
4274
|
this.replicationChangeDebounceFn = debounceAggregationChanges<
|
|
4147
4275
|
ReplicationRangeIndexable<R>
|
|
4148
4276
|
>(
|
|
@@ -4164,14 +4292,24 @@ export class SharedLog<
|
|
|
4164
4292
|
>();
|
|
4165
4293
|
const selfReplicating = await this.isReplicating();
|
|
4166
4294
|
for (const [hash, value] of map) {
|
|
4167
|
-
|
|
4168
|
-
|
|
4169
|
-
|
|
4170
|
-
|
|
4171
|
-
|
|
4172
|
-
|
|
4295
|
+
const checkedPruneLeaders =
|
|
4296
|
+
await this.revalidateCheckedPruneOwnership({
|
|
4297
|
+
hash,
|
|
4298
|
+
entry: value.entry,
|
|
4299
|
+
leaders: value.leaders,
|
|
4300
|
+
selfReplicating,
|
|
4301
|
+
});
|
|
4173
4302
|
if (checkedPruneLeaders.localLeader) {
|
|
4174
|
-
|
|
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
|
+
}
|
|
4175
4313
|
continue;
|
|
4176
4314
|
}
|
|
4177
4315
|
current.set(hash, {
|
|
@@ -4217,7 +4355,7 @@ export class SharedLog<
|
|
|
4217
4355
|
to: allRequestingPeers,
|
|
4218
4356
|
redundancy: 1,
|
|
4219
4357
|
}),
|
|
4220
|
-
priority:
|
|
4358
|
+
priority: CONVERGENCE_MESSAGE_PRIORITY,
|
|
4221
4359
|
});
|
|
4222
4360
|
},
|
|
4223
4361
|
() => {
|
|
@@ -4581,20 +4719,7 @@ export class SharedLog<
|
|
|
4581
4719
|
this.cancelReplicationInfoRequests(peerHash);
|
|
4582
4720
|
this._replicatorLivenessFailures.delete(peerHash);
|
|
4583
4721
|
this._replicatorLastActivityAt.delete(peerHash);
|
|
4584
|
-
|
|
4585
|
-
for (const [hash, peers] of this._requestIPruneSent) {
|
|
4586
|
-
peers.delete(peerHash);
|
|
4587
|
-
if (peers.size === 0) {
|
|
4588
|
-
this._requestIPruneSent.delete(hash);
|
|
4589
|
-
}
|
|
4590
|
-
}
|
|
4591
|
-
|
|
4592
|
-
for (const [hash, peers] of this._requestIPruneResponseReplicatorSet) {
|
|
4593
|
-
peers.delete(peerHash);
|
|
4594
|
-
if (peers.size === 0) {
|
|
4595
|
-
this._requestIPruneResponseReplicatorSet.delete(hash);
|
|
4596
|
-
}
|
|
4597
|
-
}
|
|
4722
|
+
this._checkedPrune.cleanupPeer(peerHash);
|
|
4598
4723
|
}
|
|
4599
4724
|
|
|
4600
4725
|
private markReplicatorActivity(peerHash: string, now = Date.now()) {
|
|
@@ -4658,7 +4783,7 @@ export class SharedLog<
|
|
|
4658
4783
|
const self = this.node.identity.publicKey.hashcode();
|
|
4659
4784
|
const seed = hashToSeed32(hash);
|
|
4660
4785
|
|
|
4661
|
-
const hinted = this.
|
|
4786
|
+
const hinted = this._checkedPrune.getConfirmedReplicators(hash);
|
|
4662
4787
|
if (hinted && hinted.size > 0) {
|
|
4663
4788
|
const peers = [...hinted].filter((p) => p !== self);
|
|
4664
4789
|
return peers.length > 0
|
|
@@ -4666,7 +4791,7 @@ export class SharedLog<
|
|
|
4666
4791
|
: undefined;
|
|
4667
4792
|
}
|
|
4668
4793
|
|
|
4669
|
-
const contacted = this.
|
|
4794
|
+
const contacted = this._checkedPrune.getContactedReplicators(hash);
|
|
4670
4795
|
if (contacted && contacted.size > 0) {
|
|
4671
4796
|
const peers = [...contacted].filter((p) => p !== self);
|
|
4672
4797
|
return peers.length > 0
|
|
@@ -5084,25 +5209,15 @@ export class SharedLog<
|
|
|
5084
5209
|
}
|
|
5085
5210
|
this._appendBackfillPendingByTarget.clear();
|
|
5086
5211
|
|
|
5087
|
-
for (const [_k, v] of this._pendingDeletes) {
|
|
5088
|
-
v.clear();
|
|
5089
|
-
v.promise.resolve(); // TODO or reject?
|
|
5090
|
-
}
|
|
5091
5212
|
for (const [_k, v] of this._pendingIHave) {
|
|
5092
5213
|
v.clear();
|
|
5093
5214
|
}
|
|
5094
|
-
|
|
5095
|
-
if (v.timer) clearTimeout(v.timer);
|
|
5096
|
-
}
|
|
5215
|
+
this._checkedPrune.close();
|
|
5097
5216
|
|
|
5098
5217
|
await this.remoteBlocks.stop();
|
|
5099
|
-
this._pendingDeletes.clear();
|
|
5100
5218
|
this._pendingIHave.clear();
|
|
5101
|
-
this._checkedPruneRetries.clear();
|
|
5102
5219
|
this.latestReplicationInfoMessage.clear();
|
|
5103
5220
|
this._gidPeersHistory.clear();
|
|
5104
|
-
this._requestIPruneSent.clear();
|
|
5105
|
-
this._requestIPruneResponseReplicatorSet.clear();
|
|
5106
5221
|
// Cancel any pending debounced timers so they can't fire after we've torn down
|
|
5107
5222
|
// indexes/RPC state.
|
|
5108
5223
|
this.rebalanceParticipationDebounced?.close();
|
|
@@ -5158,7 +5273,7 @@ export class SharedLog<
|
|
|
5158
5273
|
try {
|
|
5159
5274
|
await this.rpc
|
|
5160
5275
|
.send(new AllReplicatingSegmentsMessage({ segments: [] }), {
|
|
5161
|
-
priority:
|
|
5276
|
+
priority: CONVERGENCE_MESSAGE_PRIORITY,
|
|
5162
5277
|
signal: abort.signal,
|
|
5163
5278
|
})
|
|
5164
5279
|
.catch(() => {});
|
|
@@ -5205,7 +5320,7 @@ export class SharedLog<
|
|
|
5205
5320
|
try {
|
|
5206
5321
|
await this.rpc
|
|
5207
5322
|
.send(new AllReplicatingSegmentsMessage({ segments: [] }), {
|
|
5208
|
-
priority:
|
|
5323
|
+
priority: CONVERGENCE_MESSAGE_PRIORITY,
|
|
5209
5324
|
signal: abort.signal,
|
|
5210
5325
|
})
|
|
5211
5326
|
.catch(() => {});
|
|
@@ -5390,7 +5505,7 @@ export class SharedLog<
|
|
|
5390
5505
|
for (const entry of entries) {
|
|
5391
5506
|
this.pruneDebouncedFn.delete(entry.entry.hash);
|
|
5392
5507
|
this.removePruneRequestSent(entry.entry.hash);
|
|
5393
|
-
this.
|
|
5508
|
+
this._checkedPrune.clearConfirmedReplicators(
|
|
5394
5509
|
entry.entry.hash,
|
|
5395
5510
|
);
|
|
5396
5511
|
|
|
@@ -5513,44 +5628,51 @@ export class SharedLog<
|
|
|
5513
5628
|
|
|
5514
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
|
|
5515
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
|
|
5516
|
-
|
|
5517
|
-
this._requestIPruneResponseReplicatorSet.get(hash);
|
|
5518
|
-
if (outGoingPrunes) {
|
|
5519
|
-
outGoingPrunes.delete(from);
|
|
5520
|
-
}
|
|
5631
|
+
this._checkedPrune.removeConfirmedReplicator(hash, from);
|
|
5521
5632
|
|
|
5522
5633
|
const indexedEntry = await this.log.entryIndex.getShallow(hash);
|
|
5523
5634
|
let isLeader = false;
|
|
5524
5635
|
|
|
5525
|
-
if (
|
|
5526
|
-
|
|
5527
|
-
|
|
5528
|
-
|
|
5529
|
-
|
|
5530
|
-
|
|
5531
|
-
|
|
5532
|
-
|
|
5533
|
-
|
|
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
|
+
);
|
|
5534
5654
|
|
|
5535
|
-
|
|
5536
|
-
|
|
5655
|
+
await this._waitForReplicators(
|
|
5656
|
+
await this.createCoordinates(
|
|
5657
|
+
indexedEntry.value,
|
|
5658
|
+
decodeReplicas(indexedEntry.value).getValue(this),
|
|
5659
|
+
),
|
|
5537
5660
|
indexedEntry.value,
|
|
5538
|
-
|
|
5539
|
-
|
|
5540
|
-
|
|
5541
|
-
|
|
5661
|
+
[
|
|
5662
|
+
{
|
|
5663
|
+
key: this.node.identity.publicKey.hashcode(),
|
|
5664
|
+
replicator: true,
|
|
5665
|
+
},
|
|
5666
|
+
],
|
|
5542
5667
|
{
|
|
5543
|
-
|
|
5544
|
-
|
|
5545
|
-
|
|
5546
|
-
|
|
5547
|
-
|
|
5548
|
-
onLeader: (key) => {
|
|
5549
|
-
isLeader =
|
|
5550
|
-
isLeader || key === this.node.identity.publicKey.hashcode();
|
|
5668
|
+
onLeader: (key) => {
|
|
5669
|
+
isLeader =
|
|
5670
|
+
isLeader ||
|
|
5671
|
+
key === this.node.identity.publicKey.hashcode();
|
|
5672
|
+
},
|
|
5551
5673
|
},
|
|
5552
|
-
|
|
5553
|
-
|
|
5674
|
+
);
|
|
5675
|
+
}
|
|
5554
5676
|
}
|
|
5555
5677
|
|
|
5556
5678
|
if (isLeader) {
|
|
@@ -5625,8 +5747,20 @@ export class SharedLog<
|
|
|
5625
5747
|
}
|
|
5626
5748
|
}
|
|
5627
5749
|
} else if (msg instanceof ResponseIPrune) {
|
|
5750
|
+
const lateResponses: string[] = [];
|
|
5628
5751
|
for (const hash of msg.hashes) {
|
|
5629
|
-
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()));
|
|
5630
5764
|
}
|
|
5631
5765
|
} else if (msg instanceof ConfirmEntriesMessage) {
|
|
5632
5766
|
this.markEntriesKnownByPeer(msg.hashes, context.from.hashcode());
|
|
@@ -6105,7 +6239,7 @@ export class SharedLog<
|
|
|
6105
6239
|
|
|
6106
6240
|
if (messageToSend) {
|
|
6107
6241
|
await this.rpc.send(messageToSend, {
|
|
6108
|
-
priority:
|
|
6242
|
+
priority: CONVERGENCE_MESSAGE_PRIORITY,
|
|
6109
6243
|
});
|
|
6110
6244
|
}
|
|
6111
6245
|
}
|
|
@@ -6892,11 +7026,12 @@ export class SharedLog<
|
|
|
6892
7026
|
this._replicationInfoRequestByPeer.set(peerHash, state);
|
|
6893
7027
|
|
|
6894
7028
|
const intervalMs = Math.max(50, this.waitForReplicatorRequestIntervalMs);
|
|
6895
|
-
const maxAttempts =
|
|
6896
|
-
5,
|
|
7029
|
+
const maxAttempts =
|
|
6897
7030
|
this.waitForReplicatorRequestMaxAttempts ??
|
|
7031
|
+
Math.max(
|
|
6898
7032
|
WAIT_FOR_REPLICATOR_REQUEST_MIN_ATTEMPTS,
|
|
6899
|
-
|
|
7033
|
+
Math.ceil(this.waitForReplicatorTimeout / intervalMs),
|
|
7034
|
+
);
|
|
6900
7035
|
|
|
6901
7036
|
const tick = () => {
|
|
6902
7037
|
if (this.closed || this._closeController.signal.aborted) {
|
|
@@ -7022,17 +7157,7 @@ export class SharedLog<
|
|
|
7022
7157
|
}
|
|
7023
7158
|
|
|
7024
7159
|
private removePruneRequestSent(hash: string, to?: string) {
|
|
7025
|
-
|
|
7026
|
-
this._requestIPruneSent.delete(hash);
|
|
7027
|
-
} else {
|
|
7028
|
-
let set = this._requestIPruneSent.get(hash);
|
|
7029
|
-
if (set) {
|
|
7030
|
-
set.delete(to);
|
|
7031
|
-
if (set.size === 0) {
|
|
7032
|
-
this._requestIPruneSent.delete(hash);
|
|
7033
|
-
}
|
|
7034
|
-
}
|
|
7035
|
-
}
|
|
7160
|
+
this._checkedPrune.removeRequestSent(hash, to);
|
|
7036
7161
|
}
|
|
7037
7162
|
|
|
7038
7163
|
prune(
|
|
@@ -7049,7 +7174,7 @@ export class SharedLog<
|
|
|
7049
7174
|
return [...entries.values()].map((x) => {
|
|
7050
7175
|
this._gidPeersHistory.delete(x.entry.meta.gid);
|
|
7051
7176
|
this.removePruneRequestSent(x.entry.hash);
|
|
7052
|
-
this.
|
|
7177
|
+
this._checkedPrune.clearConfirmedReplicators(x.entry.hash);
|
|
7053
7178
|
return this.log.remove(x.entry, {
|
|
7054
7179
|
recursively: true,
|
|
7055
7180
|
});
|
|
@@ -7086,7 +7211,7 @@ export class SharedLog<
|
|
|
7086
7211
|
set.push(entry.hash);
|
|
7087
7212
|
}
|
|
7088
7213
|
|
|
7089
|
-
const pendingPrev = this.
|
|
7214
|
+
const pendingPrev = this._checkedPrune.getPendingDelete(entry.hash);
|
|
7090
7215
|
if (pendingPrev) {
|
|
7091
7216
|
// If a background prune is already in-flight, an explicit prune request should
|
|
7092
7217
|
// still respect the caller's timeout. Otherwise, tests (and user calls) can
|
|
@@ -7122,62 +7247,77 @@ export class SharedLog<
|
|
|
7122
7247
|
const deferredPromise: DeferredPromise<void> = pDefer();
|
|
7123
7248
|
|
|
7124
7249
|
const clear = () => {
|
|
7125
|
-
const pending = this.
|
|
7250
|
+
const pending = this._checkedPrune.getPendingDelete(entry.hash);
|
|
7126
7251
|
if (pending?.promise === deferredPromise) {
|
|
7127
|
-
this.
|
|
7252
|
+
this._checkedPrune.deletePendingDelete(entry.hash, pending);
|
|
7128
7253
|
}
|
|
7129
7254
|
clearTimeout(timeout);
|
|
7130
7255
|
};
|
|
7131
7256
|
|
|
7132
|
-
|
|
7133
|
-
|
|
7134
|
-
|
|
7135
|
-
|
|
7136
|
-
|
|
7137
|
-
this._gidPeersHistory.delete(entry.meta.gid);
|
|
7138
|
-
this.removePruneRequestSent(entry.hash);
|
|
7139
|
-
this._requestIPruneResponseReplicatorSet.delete(entry.hash);
|
|
7140
|
-
|
|
7141
|
-
if (
|
|
7142
|
-
await this.isLeader({
|
|
7143
|
-
entry,
|
|
7144
|
-
replicas: minReplicas.getValue(this),
|
|
7145
|
-
})
|
|
7146
|
-
) {
|
|
7147
|
-
deferredPromise.reject(
|
|
7148
|
-
new Error("Failed to delete, is leader again"),
|
|
7149
|
-
);
|
|
7150
|
-
return;
|
|
7151
|
-
}
|
|
7152
|
-
|
|
7153
|
-
return this.log
|
|
7154
|
-
.remove(entry, {
|
|
7155
|
-
recursively: true,
|
|
7156
|
-
})
|
|
7157
|
-
.then(() => {
|
|
7158
|
-
deferredPromise.resolve();
|
|
7159
|
-
})
|
|
7160
|
-
.catch((e) => {
|
|
7161
|
-
deferredPromise.reject(e);
|
|
7162
|
-
})
|
|
7163
|
-
.finally(async () => {
|
|
7257
|
+
const resolve = () => {
|
|
7258
|
+
clearTimeout(timeout);
|
|
7259
|
+
this.clearCheckedPruneRetry(entry.hash);
|
|
7260
|
+
cleanupTimer.push(
|
|
7261
|
+
setTimeout(async () => {
|
|
7164
7262
|
this._gidPeersHistory.delete(entry.meta.gid);
|
|
7165
7263
|
this.removePruneRequestSent(entry.hash);
|
|
7166
|
-
this.
|
|
7167
|
-
// TODO in the case we become leader again here we need to re-add the entry
|
|
7264
|
+
this._checkedPrune.clearConfirmedReplicators(entry.hash);
|
|
7168
7265
|
|
|
7169
|
-
|
|
7170
|
-
await this.
|
|
7266
|
+
const ownership =
|
|
7267
|
+
await this.revalidateCheckedPruneOwnership({
|
|
7268
|
+
hash: entry.hash,
|
|
7171
7269
|
entry,
|
|
7172
|
-
|
|
7173
|
-
|
|
7174
|
-
|
|
7175
|
-
|
|
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;
|
|
7176
7282
|
}
|
|
7177
|
-
|
|
7178
|
-
|
|
7179
|
-
|
|
7180
|
-
|
|
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
|
+
};
|
|
7181
7321
|
|
|
7182
7322
|
const reject = (e: any) => {
|
|
7183
7323
|
clear();
|
|
@@ -7185,11 +7325,9 @@ export class SharedLog<
|
|
|
7185
7325
|
e instanceof Error &&
|
|
7186
7326
|
typeof e.message === "string" &&
|
|
7187
7327
|
e.message.startsWith("Timeout for checked pruning");
|
|
7188
|
-
|
|
7189
|
-
|
|
7190
|
-
}
|
|
7191
|
-
this.removePruneRequestSent(entry.hash);
|
|
7192
|
-
this._requestIPruneResponseReplicatorSet.delete(entry.hash);
|
|
7328
|
+
this._checkedPrune.markCancelled(entry.hash, {
|
|
7329
|
+
preserveRetry: !explicitTimeout && isCheckedPruneTimeout,
|
|
7330
|
+
});
|
|
7193
7331
|
deferredPromise.reject(e);
|
|
7194
7332
|
};
|
|
7195
7333
|
|
|
@@ -7203,70 +7341,65 @@ export class SharedLog<
|
|
|
7203
7341
|
const checkedPruneTimeoutMs =
|
|
7204
7342
|
options?.timeout ??
|
|
7205
7343
|
Math.max(
|
|
7206
|
-
|
|
7344
|
+
CHECKED_PRUNE_BACKGROUND_TIMEOUT_MIN_MS,
|
|
7207
7345
|
Number(this._respondToIHaveTimeout ?? 0) +
|
|
7208
7346
|
this.waitForReplicatorTimeout +
|
|
7209
7347
|
PRUNE_DEBOUNCE_INTERVAL * 2,
|
|
7210
7348
|
);
|
|
7211
7349
|
|
|
7212
|
-
|
|
7213
|
-
|
|
7214
|
-
|
|
7215
|
-
|
|
7216
|
-
|
|
7217
|
-
|
|
7218
|
-
|
|
7219
|
-
|
|
7220
|
-
|
|
7221
|
-
|
|
7222
|
-
|
|
7223
|
-
|
|
7224
|
-
|
|
7225
|
-
|
|
7226
|
-
|
|
7227
|
-
this.
|
|
7228
|
-
|
|
7229
|
-
|
|
7230
|
-
|
|
7231
|
-
|
|
7232
|
-
|
|
7233
|
-
|
|
7234
|
-
|
|
7235
|
-
|
|
7236
|
-
|
|
7237
|
-
|
|
7238
|
-
|
|
7239
|
-
|
|
7240
|
-
|
|
7241
|
-
|
|
7242
|
-
|
|
7243
|
-
|
|
7244
|
-
|
|
7245
|
-
|
|
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
|
+
],
|
|
7246
7391
|
{
|
|
7247
|
-
|
|
7248
|
-
replicator: false,
|
|
7392
|
+
persist: false,
|
|
7249
7393
|
},
|
|
7250
|
-
|
|
7251
|
-
|
|
7252
|
-
|
|
7253
|
-
},
|
|
7254
|
-
))
|
|
7255
|
-
) {
|
|
7256
|
-
return;
|
|
7257
|
-
}
|
|
7258
|
-
|
|
7259
|
-
let existCounter = this._requestIPruneResponseReplicatorSet.get(
|
|
7260
|
-
entry.hash,
|
|
7261
|
-
);
|
|
7262
|
-
if (!existCounter) {
|
|
7263
|
-
existCounter = new Set();
|
|
7264
|
-
this._requestIPruneResponseReplicatorSet.set(
|
|
7265
|
-
entry.hash,
|
|
7266
|
-
existCounter,
|
|
7267
|
-
);
|
|
7394
|
+
))
|
|
7395
|
+
) {
|
|
7396
|
+
return;
|
|
7268
7397
|
}
|
|
7269
|
-
|
|
7398
|
+
|
|
7399
|
+
const existCounter = this._checkedPrune.addConfirmedReplicator(
|
|
7400
|
+
entry.hash,
|
|
7401
|
+
publicKeyHash,
|
|
7402
|
+
);
|
|
7270
7403
|
// Seed provider hints so future remote reads can avoid extra round-trips.
|
|
7271
7404
|
this.remoteBlocks.hintProviders(entry.hash, [publicKeyHash]);
|
|
7272
7405
|
|
|
@@ -7274,7 +7407,10 @@ export class SharedLog<
|
|
|
7274
7407
|
resolve();
|
|
7275
7408
|
}
|
|
7276
7409
|
},
|
|
7277
|
-
}
|
|
7410
|
+
},
|
|
7411
|
+
entry,
|
|
7412
|
+
leaders,
|
|
7413
|
+
);
|
|
7278
7414
|
|
|
7279
7415
|
promises.push(deferredPromise.promise);
|
|
7280
7416
|
}
|
|
@@ -7282,16 +7418,11 @@ export class SharedLog<
|
|
|
7282
7418
|
const emitMessages = async (entries: string[], to: string) => {
|
|
7283
7419
|
const filteredSet: string[] = [];
|
|
7284
7420
|
for (const entry of entries) {
|
|
7285
|
-
let set = this._requestIPruneSent.get(entry);
|
|
7286
|
-
if (!set) {
|
|
7287
|
-
set = new Set();
|
|
7288
|
-
this._requestIPruneSent.set(entry, set);
|
|
7289
|
-
}
|
|
7290
7421
|
/* TODO why can we not have this statement?
|
|
7291
7422
|
if (set.has(to)) {
|
|
7292
7423
|
continue;
|
|
7293
7424
|
} */
|
|
7294
|
-
|
|
7425
|
+
this._checkedPrune.addRequestSent(entry, to);
|
|
7295
7426
|
filteredSet.push(entry);
|
|
7296
7427
|
}
|
|
7297
7428
|
if (filteredSet.length > 0) {
|
|
@@ -7304,7 +7435,7 @@ export class SharedLog<
|
|
|
7304
7435
|
to: [to], // TODO group by peers?
|
|
7305
7436
|
redundancy: 1,
|
|
7306
7437
|
}),
|
|
7307
|
-
priority:
|
|
7438
|
+
priority: CONVERGENCE_MESSAGE_PRIORITY,
|
|
7308
7439
|
},
|
|
7309
7440
|
);
|
|
7310
7441
|
}
|
|
@@ -7333,7 +7464,9 @@ export class SharedLog<
|
|
|
7333
7464
|
|
|
7334
7465
|
const pendingByPeer: [string, string[]][] = [];
|
|
7335
7466
|
for (const [peer, hashes] of peerToEntries) {
|
|
7336
|
-
const pending = hashes.filter((h) =>
|
|
7467
|
+
const pending = hashes.filter((h) =>
|
|
7468
|
+
this._checkedPrune.hasPendingDelete(h),
|
|
7469
|
+
);
|
|
7337
7470
|
if (pending.length > 0) {
|
|
7338
7471
|
pendingByPeer.push([peer, pending]);
|
|
7339
7472
|
}
|
|
@@ -7764,12 +7897,26 @@ export class SharedLog<
|
|
|
7764
7897
|
flushUncheckedDeliverTarget(target);
|
|
7765
7898
|
}
|
|
7766
7899
|
|
|
7767
|
-
if (
|
|
7768
|
-
|
|
7769
|
-
|
|
7770
|
-
|
|
7771
|
-
|
|
7772
|
-
|
|
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
|
+
});
|
|
7773
7920
|
}
|
|
7774
7921
|
|
|
7775
7922
|
return changed;
|