@peerbit/shared-log 13.2.16 → 13.2.18
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/src/checked-prune.d.ts +7 -1
- package/dist/src/checked-prune.d.ts.map +1 -1
- package/dist/src/checked-prune.js +13 -3
- package/dist/src/checked-prune.js.map +1 -1
- package/dist/src/index.d.ts +4 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +137 -34
- package/dist/src/index.js.map +1 -1
- package/package.json +15 -15
- package/src/checked-prune.ts +22 -3
- package/src/index.ts +188 -39
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@peerbit/shared-log",
|
|
3
|
-
"version": "13.2.
|
|
3
|
+
"version": "13.2.18",
|
|
4
4
|
"description": "Shared log",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"type": "module",
|
|
@@ -62,25 +62,25 @@
|
|
|
62
62
|
"pino": "^9.4.0",
|
|
63
63
|
"uint8arrays": "^5.1.0",
|
|
64
64
|
"@peerbit/any-store": "2.2.13",
|
|
65
|
-
"@peerbit/blocks": "4.2.
|
|
65
|
+
"@peerbit/blocks": "4.2.8",
|
|
66
66
|
"@peerbit/crypto": "3.1.4",
|
|
67
|
-
"@peerbit/diagnostics": "0.0.1",
|
|
68
67
|
"@peerbit/cache": "3.1.1",
|
|
69
|
-
"@peerbit/
|
|
70
|
-
"@peerbit/
|
|
68
|
+
"@peerbit/blocks-interface": "2.1.4",
|
|
69
|
+
"@peerbit/diagnostics": "0.0.1",
|
|
71
70
|
"@peerbit/indexer-sqlite3": "3.0.11",
|
|
72
|
-
"@peerbit/
|
|
71
|
+
"@peerbit/indexer-interface": "3.0.8",
|
|
72
|
+
"@peerbit/log": "6.2.11",
|
|
73
73
|
"@peerbit/logger": "2.0.1",
|
|
74
|
-
"@peerbit/
|
|
75
|
-
"@peerbit/pubsub
|
|
74
|
+
"@peerbit/program": "6.0.42",
|
|
75
|
+
"@peerbit/pubsub": "5.3.7",
|
|
76
76
|
"@peerbit/riblt": "1.2.0",
|
|
77
|
-
"@peerbit/
|
|
77
|
+
"@peerbit/pubsub-interface": "5.1.8",
|
|
78
|
+
"@peerbit/rpc": "6.1.10",
|
|
78
79
|
"@peerbit/time": "3.0.1",
|
|
79
|
-
"@peerbit/stream-interface": "6.0.
|
|
80
|
-
"@peerbit/pubsub": "5.3.6"
|
|
80
|
+
"@peerbit/stream-interface": "6.0.14"
|
|
81
81
|
},
|
|
82
82
|
"optionalDependencies": {
|
|
83
|
-
"@peerbit/native-backbone": "0.2.
|
|
83
|
+
"@peerbit/native-backbone": "0.2.4",
|
|
84
84
|
"@peerbit/shared-log-rust": "0.1.3"
|
|
85
85
|
},
|
|
86
86
|
"devDependencies": {
|
|
@@ -88,9 +88,9 @@
|
|
|
88
88
|
"@types/pidusage": "^2.0.5",
|
|
89
89
|
"uuid": "^11.1.1",
|
|
90
90
|
"@peerbit/any-store-rust": "0.1.3",
|
|
91
|
-
"@peerbit/
|
|
92
|
-
"
|
|
93
|
-
"peerbit": "
|
|
91
|
+
"@peerbit/test-utils": "3.1.14",
|
|
92
|
+
"peerbit": "5.3.14",
|
|
93
|
+
"@peerbit/indexer-rust": "1.0.6"
|
|
94
94
|
},
|
|
95
95
|
"repository": {
|
|
96
96
|
"type": "git",
|
package/src/checked-prune.ts
CHANGED
|
@@ -26,11 +26,17 @@ export type CheckedPrunePendingDelete = {
|
|
|
26
26
|
|
|
27
27
|
export type CheckedPruneRetryState<T, R extends "u32" | "u64"> = {
|
|
28
28
|
attempts: number;
|
|
29
|
+
generation: number;
|
|
29
30
|
timer?: ReturnType<typeof setTimeout>;
|
|
30
31
|
entry: CheckedPruneEntry<T, R>;
|
|
31
32
|
leaders: CheckedPruneLeaderMap | Set<string>;
|
|
32
33
|
};
|
|
33
34
|
|
|
35
|
+
export type CheckedPruneRetryIdentity<T, R extends "u32" | "u64"> = {
|
|
36
|
+
state: CheckedPruneRetryState<T, R>;
|
|
37
|
+
generation: number;
|
|
38
|
+
};
|
|
39
|
+
|
|
34
40
|
export type CheckedPruneInvalidatedGeneration<T, R extends "u32" | "u64"> = {
|
|
35
41
|
hash: string;
|
|
36
42
|
pending: CheckedPrunePendingDelete;
|
|
@@ -109,6 +115,7 @@ export class CheckedPruneCoordinator<T, R extends "u32" | "u64"> {
|
|
|
109
115
|
this.requestIPruneSent.has(hash) ||
|
|
110
116
|
this.responseReplicatorSet.has(hash) ||
|
|
111
117
|
this.retries.has(hash) ||
|
|
118
|
+
this.restartReservations.has(hash) ||
|
|
112
119
|
this.candidateTokens.has(hash)
|
|
113
120
|
) {
|
|
114
121
|
return;
|
|
@@ -219,6 +226,11 @@ export class CheckedPruneCoordinator<T, R extends "u32" | "u64"> {
|
|
|
219
226
|
return this.retries.get(hash);
|
|
220
227
|
}
|
|
221
228
|
|
|
229
|
+
getRetryIdentity(hash: string): CheckedPruneRetryIdentity<T, R> | undefined {
|
|
230
|
+
const state = this.retries.get(hash);
|
|
231
|
+
return state ? { state, generation: state.generation } : undefined;
|
|
232
|
+
}
|
|
233
|
+
|
|
222
234
|
hasRetry(hash: string) {
|
|
223
235
|
return this.retries.has(hash);
|
|
224
236
|
}
|
|
@@ -232,10 +244,14 @@ export class CheckedPruneCoordinator<T, R extends "u32" | "u64"> {
|
|
|
232
244
|
session.phase = "retrying";
|
|
233
245
|
}
|
|
234
246
|
|
|
235
|
-
clearRetry(hash: string) {
|
|
236
|
-
this.restartReservations.delete(hash);
|
|
237
|
-
this.candidateTokens.delete(hash);
|
|
247
|
+
clearRetry(hash: string, expected?: CheckedPruneRetryIdentity<T, R>) {
|
|
238
248
|
const state = this.retries.get(hash);
|
|
249
|
+
if (
|
|
250
|
+
expected &&
|
|
251
|
+
(state !== expected.state || state.generation !== expected.generation)
|
|
252
|
+
) {
|
|
253
|
+
return false;
|
|
254
|
+
}
|
|
239
255
|
if (state?.timer) {
|
|
240
256
|
clearTimeout(state.timer);
|
|
241
257
|
}
|
|
@@ -245,6 +261,7 @@ export class CheckedPruneCoordinator<T, R extends "u32" | "u64"> {
|
|
|
245
261
|
session.retry = undefined;
|
|
246
262
|
}
|
|
247
263
|
this.deleteSessionIfIdle(hash);
|
|
264
|
+
return true;
|
|
248
265
|
}
|
|
249
266
|
|
|
250
267
|
clearRetryTimer(hash: string) {
|
|
@@ -477,12 +494,14 @@ export class CheckedPruneCoordinator<T, R extends "u32" | "u64"> {
|
|
|
477
494
|
return false;
|
|
478
495
|
}
|
|
479
496
|
this.restartReservations.delete(hash);
|
|
497
|
+
this.deleteSessionIfIdle(hash);
|
|
480
498
|
return true;
|
|
481
499
|
}
|
|
482
500
|
|
|
483
501
|
cancelRestartReservation(hash: string, reservation: object) {
|
|
484
502
|
if (this.restartReservations.get(hash) === reservation) {
|
|
485
503
|
this.restartReservations.delete(hash);
|
|
504
|
+
this.deleteSessionIfIdle(hash);
|
|
486
505
|
}
|
|
487
506
|
}
|
|
488
507
|
|
package/src/index.ts
CHANGED
|
@@ -132,6 +132,7 @@ import {
|
|
|
132
132
|
type CheckedPruneLeaderMap,
|
|
133
133
|
type CheckedPrunePendingDelete,
|
|
134
134
|
type CheckedPruneRestartCandidate,
|
|
135
|
+
type CheckedPruneRetryIdentity,
|
|
135
136
|
type CheckedPruneRetryState,
|
|
136
137
|
} from "./checked-prune.js";
|
|
137
138
|
import { type CPUUsage, CPUUsageIntervalLag } from "./cpu.js";
|
|
@@ -3349,6 +3350,13 @@ export class SharedLog<
|
|
|
3349
3350
|
// before a liveness eviction but reach the per-peer apply lane after it. Unlike
|
|
3350
3351
|
// message timestamps, these tokens never compare clocks from different peers.
|
|
3351
3352
|
private _replicationInfoReceiveEpochByPeer!: Map<string, object>;
|
|
3353
|
+
// Receive-side ownership plans may span lower-log joins that invoke user code.
|
|
3354
|
+
// Increment synchronously with leader-cache invalidation so the handler can
|
|
3355
|
+
// detect whether its pre-join plan needs one fresh post-persist audit.
|
|
3356
|
+
private _receiveOwnershipRevision = 0;
|
|
3357
|
+
// Count ownership-changing range mutations from queue admission through
|
|
3358
|
+
// settlement, including mutations already pending when a receive starts.
|
|
3359
|
+
private _receiveOwnershipMutationAdmissions = 0;
|
|
3352
3360
|
// Subscription callbacks can overlap because removing a replicator mutates the
|
|
3353
3361
|
// replication index asynchronously. Keep that lifecycle separate from message
|
|
3354
3362
|
// timestamps so a reconnect can synchronously revoke an older unsubscribe.
|
|
@@ -5921,9 +5929,17 @@ export class SharedLog<
|
|
|
5921
5929
|
}
|
|
5922
5930
|
|
|
5923
5931
|
private invalidateLeaderSelectionContextCache() {
|
|
5932
|
+
this._receiveOwnershipRevision++;
|
|
5924
5933
|
this._leaderSelectionContextCache = undefined;
|
|
5925
5934
|
}
|
|
5926
5935
|
|
|
5936
|
+
private isReceiveOwnershipSnapshotStable(revision: number): boolean {
|
|
5937
|
+
return (
|
|
5938
|
+
this._receiveOwnershipMutationAdmissions === 0 &&
|
|
5939
|
+
this._receiveOwnershipRevision === revision
|
|
5940
|
+
);
|
|
5941
|
+
}
|
|
5942
|
+
|
|
5927
5943
|
private canCacheLeaderSelectionContext(options?: {
|
|
5928
5944
|
roleAge?: number;
|
|
5929
5945
|
candidates?: Iterable<string>;
|
|
@@ -7706,7 +7722,7 @@ export class SharedLog<
|
|
|
7706
7722
|
let deleted: ReplicationRangeIndexable<R>[] = [];
|
|
7707
7723
|
let ownerHasRanges = false;
|
|
7708
7724
|
let mutationError: unknown;
|
|
7709
|
-
const mutationCommitted = await this.
|
|
7725
|
+
const mutationCommitted = await this.withReceiveOwnershipMutationQueue(
|
|
7710
7726
|
async () => {
|
|
7711
7727
|
if (
|
|
7712
7728
|
!ownsReplicationOwnershipLifecycle() ||
|
|
@@ -7934,7 +7950,7 @@ export class SharedLog<
|
|
|
7934
7950
|
this.throwIfReplicationOwnershipLifecycleInactive(
|
|
7935
7951
|
replicationOwnershipLifecycleController,
|
|
7936
7952
|
);
|
|
7937
|
-
return this.
|
|
7953
|
+
return this.withReceiveOwnershipMutationQueue(
|
|
7938
7954
|
() => this.removeReplicationRangesUnlocked(ranges, from, options),
|
|
7939
7955
|
replicationOwnershipLifecycleController,
|
|
7940
7956
|
);
|
|
@@ -8106,7 +8122,7 @@ export class SharedLog<
|
|
|
8106
8122
|
this.throwIfReplicationOwnershipLifecycleInactive(
|
|
8107
8123
|
replicationOwnershipLifecycleController,
|
|
8108
8124
|
);
|
|
8109
|
-
return this.
|
|
8125
|
+
return this.withReceiveOwnershipMutationQueue(
|
|
8110
8126
|
() =>
|
|
8111
8127
|
this.addReplicationRangeUnlocked(
|
|
8112
8128
|
ranges,
|
|
@@ -11005,6 +11021,8 @@ export class SharedLog<
|
|
|
11005
11021
|
entries: ShallowOrFullEntry<T>[],
|
|
11006
11022
|
options?: {
|
|
11007
11023
|
decodedReplicaCounts?: DecodedReplicaCountMap;
|
|
11024
|
+
freshReceiveOwnerAudit?: boolean;
|
|
11025
|
+
preserveExistingPruneOnLocalResult?: boolean;
|
|
11008
11026
|
reusableLeaderPlans?: ReadonlyMap<
|
|
11009
11027
|
string,
|
|
11010
11028
|
Pick<ReusableReceiveCoordinatePlan<R>, "plan" | "replicas">
|
|
@@ -11020,11 +11038,17 @@ export class SharedLog<
|
|
|
11020
11038
|
return;
|
|
11021
11039
|
}
|
|
11022
11040
|
const selfHash = this.node.identity.publicKey.hashcode();
|
|
11041
|
+
const freshReceiveRoleAge = options?.freshReceiveOwnerAudit
|
|
11042
|
+
? await this.getDefaultMinRoleAge()
|
|
11043
|
+
: undefined;
|
|
11044
|
+
this.throwIfReplicationOwnershipLifecycleInactive(
|
|
11045
|
+
ownershipLifecycleController,
|
|
11046
|
+
);
|
|
11023
11047
|
const plans = new Array<EntryLeaderPlan<R> | undefined>(entries.length);
|
|
11024
11048
|
const leaderItems: Array<{
|
|
11025
11049
|
entry: ShallowOrFullEntry<T>;
|
|
11026
11050
|
replicas: number;
|
|
11027
|
-
options:
|
|
11051
|
+
options: LeaderSelectionOptions<R>;
|
|
11028
11052
|
}> = [];
|
|
11029
11053
|
const leaderItemIndexes: number[] = [];
|
|
11030
11054
|
let reusableLeaderPlanHits = 0;
|
|
@@ -11033,7 +11057,9 @@ export class SharedLog<
|
|
|
11033
11057
|
const replicas =
|
|
11034
11058
|
options?.decodedReplicaCounts?.get(entry.hash) ??
|
|
11035
11059
|
decodeReplicas(entry).getValue(this);
|
|
11036
|
-
const reusablePlan = options?.
|
|
11060
|
+
const reusablePlan = options?.freshReceiveOwnerAudit
|
|
11061
|
+
? undefined
|
|
11062
|
+
: options?.reusableLeaderPlans?.get(entry.hash);
|
|
11037
11063
|
if (reusablePlan && reusablePlan.replicas === replicas) {
|
|
11038
11064
|
plans[i] = reusablePlan.plan;
|
|
11039
11065
|
reusableLeaderPlanHits++;
|
|
@@ -11042,7 +11068,9 @@ export class SharedLog<
|
|
|
11042
11068
|
leaderItems.push({
|
|
11043
11069
|
entry,
|
|
11044
11070
|
replicas,
|
|
11045
|
-
options:
|
|
11071
|
+
options: options?.freshReceiveOwnerAudit
|
|
11072
|
+
? { roleAge: freshReceiveRoleAge!, persist: false }
|
|
11073
|
+
: { roleAge: 0, persist: false },
|
|
11046
11074
|
});
|
|
11047
11075
|
leaderItemIndexes.push(i);
|
|
11048
11076
|
}
|
|
@@ -11116,6 +11144,7 @@ export class SharedLog<
|
|
|
11116
11144
|
const loopStartedAt = syncProfileStart(options?.profile);
|
|
11117
11145
|
let enqueuedPrune = 0;
|
|
11118
11146
|
let cancelledLocalLeader = 0;
|
|
11147
|
+
let localLeaderResults = 0;
|
|
11119
11148
|
for (let i = 0; i < entries.length; i++) {
|
|
11120
11149
|
if (!this.isRepairLifecycleActive(ownershipLifecycleController)) {
|
|
11121
11150
|
continue;
|
|
@@ -11124,11 +11153,14 @@ export class SharedLog<
|
|
|
11124
11153
|
const leaders = plans[i]?.leaders ?? new Map();
|
|
11125
11154
|
|
|
11126
11155
|
if (leaders.has(selfHash)) {
|
|
11127
|
-
|
|
11128
|
-
|
|
11129
|
-
|
|
11130
|
-
|
|
11131
|
-
|
|
11156
|
+
if (!options?.preserveExistingPruneOnLocalResult) {
|
|
11157
|
+
await this.cancelCheckedPruneForLocalLeader(entry.hash);
|
|
11158
|
+
this.throwIfReplicationOwnershipLifecycleInactive(
|
|
11159
|
+
ownershipLifecycleController,
|
|
11160
|
+
);
|
|
11161
|
+
cancelledLocalLeader++;
|
|
11162
|
+
}
|
|
11163
|
+
localLeaderResults++;
|
|
11132
11164
|
continue;
|
|
11133
11165
|
}
|
|
11134
11166
|
|
|
@@ -11162,7 +11194,7 @@ export class SharedLog<
|
|
|
11162
11194
|
entries: entries.length,
|
|
11163
11195
|
count: enqueuedPrune,
|
|
11164
11196
|
messages: 1,
|
|
11165
|
-
details: { cancelledLocalLeader },
|
|
11197
|
+
details: { cancelledLocalLeader, localLeaderResults },
|
|
11166
11198
|
});
|
|
11167
11199
|
}
|
|
11168
11200
|
|
|
@@ -11482,9 +11514,11 @@ export class SharedLog<
|
|
|
11482
11514
|
checkedPruneCoordinator.getRetry(hash) ??
|
|
11483
11515
|
({
|
|
11484
11516
|
attempts: 0,
|
|
11517
|
+
generation: 0,
|
|
11485
11518
|
entry: args.entry,
|
|
11486
11519
|
leaders: args.leaders,
|
|
11487
11520
|
} satisfies CheckedPruneRetryState<T, R>);
|
|
11521
|
+
state.generation++;
|
|
11488
11522
|
state.entry = args.entry;
|
|
11489
11523
|
state.leaders = args.leaders;
|
|
11490
11524
|
|
|
@@ -11507,6 +11541,7 @@ export class SharedLog<
|
|
|
11507
11541
|
|
|
11508
11542
|
state.attempts = attempt;
|
|
11509
11543
|
const timer = setTimeout(() => {
|
|
11544
|
+
const generation = state.generation;
|
|
11510
11545
|
const run = async () => {
|
|
11511
11546
|
const st = checkedPruneCoordinator.getRetry(hash);
|
|
11512
11547
|
if (st !== state || state.timer !== timer || !isCurrent()) {
|
|
@@ -11516,6 +11551,7 @@ export class SharedLog<
|
|
|
11516
11551
|
const isExactAttempt = () =>
|
|
11517
11552
|
isCurrent() &&
|
|
11518
11553
|
checkedPruneCoordinator.getRetry(hash) === state &&
|
|
11554
|
+
state.generation === generation &&
|
|
11519
11555
|
state.timer === undefined;
|
|
11520
11556
|
if (checkedPruneCoordinator.hasPendingDelete(hash)) return;
|
|
11521
11557
|
const retryEntry = state.entry;
|
|
@@ -11567,25 +11603,31 @@ export class SharedLog<
|
|
|
11567
11603
|
) {
|
|
11568
11604
|
// A keep policy is terminal for this background prune. Do not
|
|
11569
11605
|
// retain a retry record with neither a timer nor pending work.
|
|
11570
|
-
checkedPruneCoordinator.clearRetry(hash
|
|
11606
|
+
checkedPruneCoordinator.clearRetry(hash, {
|
|
11607
|
+
state,
|
|
11608
|
+
generation,
|
|
11609
|
+
});
|
|
11571
11610
|
}
|
|
11572
11611
|
};
|
|
11573
11612
|
void run().catch((error) => {
|
|
11574
11613
|
if (isCurrent() && !isNotStartedError(error as Error)) {
|
|
11575
11614
|
logger.error(error);
|
|
11576
|
-
const retry = checkedPruneCoordinator.getRetry(hash);
|
|
11577
11615
|
if (
|
|
11578
|
-
|
|
11579
|
-
|
|
11616
|
+
checkedPruneCoordinator.getRetry(hash) === state &&
|
|
11617
|
+
state.generation === generation &&
|
|
11618
|
+
!state.timer &&
|
|
11580
11619
|
!checkedPruneCoordinator.hasPendingDelete(hash)
|
|
11581
11620
|
) {
|
|
11582
11621
|
try {
|
|
11583
11622
|
this.scheduleCheckedPruneRetry(
|
|
11584
|
-
{ entry:
|
|
11623
|
+
{ entry: state.entry, leaders: state.leaders },
|
|
11585
11624
|
ownershipLifecycleController,
|
|
11586
11625
|
);
|
|
11587
11626
|
} catch {
|
|
11588
|
-
checkedPruneCoordinator.clearRetry(hash
|
|
11627
|
+
checkedPruneCoordinator.clearRetry(hash, {
|
|
11628
|
+
state,
|
|
11629
|
+
generation,
|
|
11630
|
+
});
|
|
11589
11631
|
}
|
|
11590
11632
|
}
|
|
11591
11633
|
}
|
|
@@ -15689,6 +15731,8 @@ export class SharedLog<
|
|
|
15689
15731
|
this._checkedPruneRemoveBlocksLocalRangeMutationAdmission = 0;
|
|
15690
15732
|
this._checkedPruneRemovalCallbackInvocationDepth = 0;
|
|
15691
15733
|
this._localReplicationRoleGeneration = 0;
|
|
15734
|
+
this._receiveOwnershipRevision = 0;
|
|
15735
|
+
this._receiveOwnershipMutationAdmissions = 0;
|
|
15692
15736
|
this._pruneRemovesClosing = false;
|
|
15693
15737
|
this._replicationRangeMutationFailure = undefined;
|
|
15694
15738
|
this.startRepairLifecycle();
|
|
@@ -16123,7 +16167,9 @@ export class SharedLog<
|
|
|
16123
16167
|
continue;
|
|
16124
16168
|
}
|
|
16125
16169
|
if (checkedPruneLeaders.localLeader) {
|
|
16126
|
-
const
|
|
16170
|
+
const retryIdentity =
|
|
16171
|
+
checkedPruneCoordinator.getRetryIdentity(hash);
|
|
16172
|
+
const retry = retryIdentity?.state;
|
|
16127
16173
|
await this.cancelCheckedPruneForLocalLeader(hash, {
|
|
16128
16174
|
preserveRetry: retry != null,
|
|
16129
16175
|
});
|
|
@@ -16133,7 +16179,7 @@ export class SharedLog<
|
|
|
16133
16179
|
if (retry) {
|
|
16134
16180
|
// A delayed confirmation of local ownership is terminal.
|
|
16135
16181
|
// Future ownership mutations will enqueue fresh work.
|
|
16136
|
-
checkedPruneCoordinator.clearRetry(hash);
|
|
16182
|
+
checkedPruneCoordinator.clearRetry(hash, retryIdentity);
|
|
16137
16183
|
} else {
|
|
16138
16184
|
// A first positive view can be a transient membership
|
|
16139
16185
|
// snapshot. Confirm it once after the normal retry delay
|
|
@@ -16182,6 +16228,15 @@ export class SharedLog<
|
|
|
16182
16228
|
pruneOwnershipLifecycleController,
|
|
16183
16229
|
);
|
|
16184
16230
|
} catch {
|
|
16231
|
+
if (
|
|
16232
|
+
value.workToken != null &&
|
|
16233
|
+
checkedPruneCoordinator.isCandidateTokenCurrent(
|
|
16234
|
+
hash,
|
|
16235
|
+
value.workToken,
|
|
16236
|
+
)
|
|
16237
|
+
) {
|
|
16238
|
+
checkedPruneCoordinator.invalidateCandidateToken(hash);
|
|
16239
|
+
}
|
|
16185
16240
|
checkedPruneCoordinator.clearRetry(hash);
|
|
16186
16241
|
}
|
|
16187
16242
|
}
|
|
@@ -16982,7 +17037,7 @@ export class SharedLog<
|
|
|
16982
17037
|
) {
|
|
16983
17038
|
const ownershipLifecycleController =
|
|
16984
17039
|
this.captureReplicationOwnershipLifecycle();
|
|
16985
|
-
return this.
|
|
17040
|
+
return this.withReceiveOwnershipMutationQueue(async () => {
|
|
16986
17041
|
this.throwIfReplicationOwnershipLifecycleInactive(
|
|
16987
17042
|
ownershipLifecycleController,
|
|
16988
17043
|
);
|
|
@@ -19157,6 +19212,9 @@ export class SharedLog<
|
|
|
19157
19212
|
if (!releasePeerReceiveLease) {
|
|
19158
19213
|
return;
|
|
19159
19214
|
}
|
|
19215
|
+
const receiveOwnershipRevision = this._receiveOwnershipRevision;
|
|
19216
|
+
const receiveOwnershipLifecycleController =
|
|
19217
|
+
this.captureReplicationOwnershipLifecycle();
|
|
19160
19218
|
const receiveReplicationInfoReceiveEpoch =
|
|
19161
19219
|
this.getReplicationInfoReceiveEpoch(receiveFromHash);
|
|
19162
19220
|
if (msg instanceof ResponseRoleMessage) {
|
|
@@ -19440,6 +19498,7 @@ export class SharedLog<
|
|
|
19440
19498
|
heads,
|
|
19441
19499
|
hashes,
|
|
19442
19500
|
),
|
|
19501
|
+
receiveOwnershipRevision,
|
|
19443
19502
|
}),
|
|
19444
19503
|
selectPreparedRawReceiveHashes: rawIsRepairHint
|
|
19445
19504
|
? undefined
|
|
@@ -19454,6 +19513,7 @@ export class SharedLog<
|
|
|
19454
19513
|
heads,
|
|
19455
19514
|
hashes,
|
|
19456
19515
|
),
|
|
19516
|
+
receiveOwnershipRevision,
|
|
19457
19517
|
}),
|
|
19458
19518
|
},
|
|
19459
19519
|
);
|
|
@@ -20150,6 +20210,7 @@ export class SharedLog<
|
|
|
20150
20210
|
!isReplicating &&
|
|
20151
20211
|
!this.keep &&
|
|
20152
20212
|
!isRepairHint &&
|
|
20213
|
+
this.isReceiveOwnershipSnapshotStable(receiveOwnershipRevision) &&
|
|
20153
20214
|
receiveGroups.length > 0 &&
|
|
20154
20215
|
receiveGroups.every(
|
|
20155
20216
|
(group) =>
|
|
@@ -20894,11 +20955,60 @@ export class SharedLog<
|
|
|
20894
20955
|
confirmedHashes.add(hash);
|
|
20895
20956
|
}
|
|
20896
20957
|
const checkedPruneStartedAt = syncProfileStart(syncProfile);
|
|
20897
|
-
|
|
20898
|
-
|
|
20899
|
-
|
|
20900
|
-
|
|
20901
|
-
|
|
20958
|
+
const ownershipChangedDuringReceive =
|
|
20959
|
+
!this.isReceiveOwnershipSnapshotStable(
|
|
20960
|
+
receiveOwnershipRevision,
|
|
20961
|
+
);
|
|
20962
|
+
if (ownershipChangedDuringReceive) {
|
|
20963
|
+
const freshAuditRevision = this._receiveOwnershipRevision;
|
|
20964
|
+
const armFreshAuditRetry = () => {
|
|
20965
|
+
for (const entry of admittedShallowEntries) {
|
|
20966
|
+
this.scheduleCheckedPruneRetry(
|
|
20967
|
+
{ entry, leaders: new Map() },
|
|
20968
|
+
receiveOwnershipLifecycleController,
|
|
20969
|
+
);
|
|
20970
|
+
}
|
|
20971
|
+
};
|
|
20972
|
+
try {
|
|
20973
|
+
await this.pruneJoinedEntriesNoLongerLed(
|
|
20974
|
+
admittedShallowEntries,
|
|
20975
|
+
{
|
|
20976
|
+
decodedReplicaCounts: receiveReplicaCounts,
|
|
20977
|
+
freshReceiveOwnerAudit: true,
|
|
20978
|
+
preserveExistingPruneOnLocalResult: true,
|
|
20979
|
+
profile: syncProfile,
|
|
20980
|
+
},
|
|
20981
|
+
receiveOwnershipLifecycleController,
|
|
20982
|
+
);
|
|
20983
|
+
this.throwIfReplicationOwnershipLifecycleInactive(
|
|
20984
|
+
receiveOwnershipLifecycleController,
|
|
20985
|
+
);
|
|
20986
|
+
if (
|
|
20987
|
+
!this.isReceiveOwnershipSnapshotStable(freshAuditRevision)
|
|
20988
|
+
) {
|
|
20989
|
+
armFreshAuditRetry();
|
|
20990
|
+
}
|
|
20991
|
+
} catch {
|
|
20992
|
+
// The lower-log and coordinate commits are already durable. A
|
|
20993
|
+
// sender retry will filter these hashes as present, so retain a
|
|
20994
|
+
// bounded local obligation instead of failing the admitted receive.
|
|
20995
|
+
this.throwIfReplicationOwnershipLifecycleInactive(
|
|
20996
|
+
receiveOwnershipLifecycleController,
|
|
20997
|
+
);
|
|
20998
|
+
armFreshAuditRetry();
|
|
20999
|
+
}
|
|
21000
|
+
} else {
|
|
21001
|
+
await this.pruneJoinedEntriesNoLongerLed(
|
|
21002
|
+
admittedShallowEntries,
|
|
21003
|
+
{
|
|
21004
|
+
decodedReplicaCounts: receiveReplicaCounts,
|
|
21005
|
+
preserveExistingPruneOnLocalResult: true,
|
|
21006
|
+
reusableLeaderPlans: reusableCoordinatePlans,
|
|
21007
|
+
profile: syncProfile,
|
|
21008
|
+
},
|
|
21009
|
+
receiveOwnershipLifecycleController,
|
|
21010
|
+
);
|
|
21011
|
+
}
|
|
20902
21012
|
if (syncProfile) {
|
|
20903
21013
|
emitSyncProfileDuration(syncProfile, checkedPruneStartedAt, {
|
|
20904
21014
|
name: "sharedLog.receive.checkedPrune",
|
|
@@ -24490,8 +24600,10 @@ export class SharedLog<
|
|
|
24490
24600
|
fromIsSelf: boolean;
|
|
24491
24601
|
syncProfile?: SyncProfileFn;
|
|
24492
24602
|
selection?: NativeBackboneRawReceiveSelectionPlan;
|
|
24603
|
+
receiveOwnershipRevision: number;
|
|
24493
24604
|
}): Promise<boolean> {
|
|
24494
24605
|
const backbone = this._nativeBackbone;
|
|
24606
|
+
const ownershipRevision = properties.receiveOwnershipRevision;
|
|
24495
24607
|
if (!backbone || !this.syncronizer.onReceivedEntryHashes) {
|
|
24496
24608
|
return false;
|
|
24497
24609
|
}
|
|
@@ -24499,7 +24611,11 @@ export class SharedLog<
|
|
|
24499
24611
|
const selection =
|
|
24500
24612
|
properties.selection ??
|
|
24501
24613
|
(await this.planNativePreparedRawReceiveSelection(properties));
|
|
24502
|
-
if (
|
|
24614
|
+
if (
|
|
24615
|
+
!selection ||
|
|
24616
|
+
selection.retainedHashes.length > 0 ||
|
|
24617
|
+
!this.isReceiveOwnershipSnapshotStable(ownershipRevision)
|
|
24618
|
+
) {
|
|
24503
24619
|
return false;
|
|
24504
24620
|
}
|
|
24505
24621
|
|
|
@@ -24534,6 +24650,9 @@ export class SharedLog<
|
|
|
24534
24650
|
hashes: selection.droppedHashes,
|
|
24535
24651
|
from: properties.from,
|
|
24536
24652
|
});
|
|
24653
|
+
if (!this.isReceiveOwnershipSnapshotStable(ownershipRevision)) {
|
|
24654
|
+
return false;
|
|
24655
|
+
}
|
|
24537
24656
|
if (properties.syncProfile) {
|
|
24538
24657
|
emitSyncProfileDuration(properties.syncProfile, notifyStartedAt, {
|
|
24539
24658
|
name: "sharedLog.receive.notifySynchronizer",
|
|
@@ -24572,7 +24691,9 @@ export class SharedLog<
|
|
|
24572
24691
|
fromIsSelf: boolean;
|
|
24573
24692
|
syncProfile?: SyncProfileFn;
|
|
24574
24693
|
selection?: NativeBackboneRawReceiveSelectionPlan;
|
|
24694
|
+
receiveOwnershipRevision: number;
|
|
24575
24695
|
}): Promise<RawReceiveHashSelection | undefined> {
|
|
24696
|
+
const ownershipRevision = properties.receiveOwnershipRevision;
|
|
24576
24697
|
if (!this.syncronizer.onReceivedEntryHashes) {
|
|
24577
24698
|
return undefined;
|
|
24578
24699
|
}
|
|
@@ -24580,7 +24701,10 @@ export class SharedLog<
|
|
|
24580
24701
|
const selection =
|
|
24581
24702
|
properties.selection ??
|
|
24582
24703
|
(await this.planNativePreparedRawReceiveSelection(properties));
|
|
24583
|
-
if (
|
|
24704
|
+
if (
|
|
24705
|
+
!selection ||
|
|
24706
|
+
!this.isReceiveOwnershipSnapshotStable(ownershipRevision)
|
|
24707
|
+
) {
|
|
24584
24708
|
return undefined;
|
|
24585
24709
|
}
|
|
24586
24710
|
if (selection.droppedHashes.length === 0) {
|
|
@@ -24598,6 +24722,9 @@ export class SharedLog<
|
|
|
24598
24722
|
hashes: selection.droppedHashes,
|
|
24599
24723
|
from: properties.from,
|
|
24600
24724
|
});
|
|
24725
|
+
if (!this.isReceiveOwnershipSnapshotStable(ownershipRevision)) {
|
|
24726
|
+
return undefined;
|
|
24727
|
+
}
|
|
24601
24728
|
emitSyncProfileDuration(properties.syncProfile, notifyStartedAt, {
|
|
24602
24729
|
name: "sharedLog.receive.notifySynchronizer",
|
|
24603
24730
|
component: "shared-log",
|
|
@@ -25465,6 +25592,7 @@ export class SharedLog<
|
|
|
25465
25592
|
private withReplicationRangeMutationQueue<T>(
|
|
25466
25593
|
fn: () => Promise<T>,
|
|
25467
25594
|
replicationOwnershipLifecycleController = this.captureReplicationOwnershipLifecycle(),
|
|
25595
|
+
options?: { affectsReceiveOwnership?: boolean },
|
|
25468
25596
|
): Promise<T> {
|
|
25469
25597
|
try {
|
|
25470
25598
|
this.throwIfReplicationOwnershipLifecycleInactive(
|
|
@@ -25480,6 +25608,15 @@ export class SharedLog<
|
|
|
25480
25608
|
),
|
|
25481
25609
|
);
|
|
25482
25610
|
}
|
|
25611
|
+
let releaseReceiveOwnershipMutationAdmission: (() => void) | undefined;
|
|
25612
|
+
if (options?.affectsReceiveOwnership) {
|
|
25613
|
+
this._receiveOwnershipMutationAdmissions++;
|
|
25614
|
+
this.invalidateLeaderSelectionContextCache();
|
|
25615
|
+
releaseReceiveOwnershipMutationAdmission = () => {
|
|
25616
|
+
this._receiveOwnershipMutationAdmissions--;
|
|
25617
|
+
this.invalidateLeaderSelectionContextCache();
|
|
25618
|
+
};
|
|
25619
|
+
}
|
|
25483
25620
|
const run = (this._replicationRangeMutationTail ?? Promise.resolve())
|
|
25484
25621
|
.catch(() => {
|
|
25485
25622
|
// A failed predecessor must not leave the queue permanently rejected.
|
|
@@ -25493,11 +25630,25 @@ export class SharedLog<
|
|
|
25493
25630
|
);
|
|
25494
25631
|
return fn();
|
|
25495
25632
|
});
|
|
25496
|
-
|
|
25633
|
+
const fencedRun = releaseReceiveOwnershipMutationAdmission
|
|
25634
|
+
? run.finally(releaseReceiveOwnershipMutationAdmission)
|
|
25635
|
+
: run;
|
|
25636
|
+
this._replicationRangeMutationTail = fencedRun.then(
|
|
25497
25637
|
() => undefined,
|
|
25498
25638
|
() => undefined,
|
|
25499
25639
|
);
|
|
25500
|
-
return
|
|
25640
|
+
return fencedRun;
|
|
25641
|
+
}
|
|
25642
|
+
|
|
25643
|
+
private withReceiveOwnershipMutationQueue<T>(
|
|
25644
|
+
fn: () => Promise<T>,
|
|
25645
|
+
replicationOwnershipLifecycleController = this.captureReplicationOwnershipLifecycle(),
|
|
25646
|
+
): Promise<T> {
|
|
25647
|
+
return this.withReplicationRangeMutationQueue(
|
|
25648
|
+
fn,
|
|
25649
|
+
replicationOwnershipLifecycleController,
|
|
25650
|
+
{ affectsReceiveOwnership: true },
|
|
25651
|
+
);
|
|
25501
25652
|
}
|
|
25502
25653
|
|
|
25503
25654
|
private acquireReplicationRangeMutationTerminalFence(): {
|
|
@@ -26428,12 +26579,13 @@ export class SharedLog<
|
|
|
26428
26579
|
const restartCandidates: Array<{
|
|
26429
26580
|
candidate: CheckedPruneRestartCandidate<T, R>;
|
|
26430
26581
|
reservation: object;
|
|
26431
|
-
|
|
26582
|
+
retryIdentity?: CheckedPruneRetryIdentity<T, R>;
|
|
26432
26583
|
}> = [];
|
|
26433
26584
|
for (const { hash } of admitted) {
|
|
26434
26585
|
const candidate = checkedPruneCoordinator.getRestartCandidate(hash);
|
|
26435
26586
|
const pending = checkedPruneCoordinator.getPendingDelete(hash);
|
|
26436
|
-
const
|
|
26587
|
+
const retryIdentity = checkedPruneCoordinator.getRetryIdentity(hash);
|
|
26588
|
+
const retry = retryIdentity?.state;
|
|
26437
26589
|
const hadQueuedCandidate = this.pruneDebouncedFn.has(hash);
|
|
26438
26590
|
const hadInFlightCandidate = checkedPruneCoordinator.hasCandidate(hash);
|
|
26439
26591
|
const shouldRestart =
|
|
@@ -26476,7 +26628,7 @@ export class SharedLog<
|
|
|
26476
26628
|
restartCandidates.push({
|
|
26477
26629
|
candidate,
|
|
26478
26630
|
reservation: checkedPruneCoordinator.reserveRestart(hash),
|
|
26479
|
-
|
|
26631
|
+
retryIdentity,
|
|
26480
26632
|
});
|
|
26481
26633
|
}
|
|
26482
26634
|
}
|
|
@@ -26512,7 +26664,7 @@ export class SharedLog<
|
|
|
26512
26664
|
for (const {
|
|
26513
26665
|
candidate,
|
|
26514
26666
|
reservation,
|
|
26515
|
-
|
|
26667
|
+
retryIdentity,
|
|
26516
26668
|
} of admission.restartCandidates) {
|
|
26517
26669
|
if (
|
|
26518
26670
|
this._checkedPrune !== checkedPruneCoordinator ||
|
|
@@ -26522,11 +26674,8 @@ export class SharedLog<
|
|
|
26522
26674
|
candidate.hash,
|
|
26523
26675
|
reservation,
|
|
26524
26676
|
);
|
|
26525
|
-
if (
|
|
26526
|
-
|
|
26527
|
-
checkedPruneCoordinator.getRetry(candidate.hash) === retry
|
|
26528
|
-
) {
|
|
26529
|
-
checkedPruneCoordinator.clearRetry(candidate.hash);
|
|
26677
|
+
if (retryIdentity) {
|
|
26678
|
+
checkedPruneCoordinator.clearRetry(candidate.hash, retryIdentity);
|
|
26530
26679
|
}
|
|
26531
26680
|
continue;
|
|
26532
26681
|
}
|