@peerbit/shared-log 16.0.10 → 16.0.12
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/index.d.ts +6 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +390 -87
- package/dist/src/index.js.map +1 -1
- package/package.json +15 -15
- package/src/index.ts +469 -92
package/dist/src/index.js
CHANGED
|
@@ -405,6 +405,8 @@ const RECALCULATE_PARTICIPATION_MIN_RELATIVE_CHANGE_WITH_CPU_LIMIT = 0.005;
|
|
|
405
405
|
const RECALCULATE_PARTICIPATION_MIN_RELATIVE_CHANGE_WITH_MEMORY_LIMIT = 0.001;
|
|
406
406
|
const RECALCULATE_PARTICIPATION_RELATIVE_DENOMINATOR_FLOOR = 1e-3;
|
|
407
407
|
const TOPIC_SUBSCRIBERS_CACHE_TTL_MS = 250;
|
|
408
|
+
const LOCAL_REACHABLE_PEERS_CACHE_TTL_MS = 250;
|
|
409
|
+
const LOCAL_REACHABLE_PEERS_MAX = 64;
|
|
408
410
|
const LEADER_SELECTION_CONTEXT_CACHE_TTL_MS = 50;
|
|
409
411
|
// Leader-plan results are invalidated structurally (replication:change,
|
|
410
412
|
// replicator:mature, subscriber changes). The TTL bounds wall-clock maturity
|
|
@@ -427,6 +429,8 @@ const ADAPTIVE_REBALANCE_MIN_IDLE_AFTER_LOCAL_APPEND_MS = 10_000;
|
|
|
427
429
|
// raw exchange message size the receive path is tuned for.
|
|
428
430
|
const LIVE_RAW_GOSSIP_MAX_ENTRIES = 256;
|
|
429
431
|
const LIVE_RAW_GOSSIP_MAX_BYTES = 128 * 1024;
|
|
432
|
+
const GID_PEER_HISTORY_CLEANUP_BATCH_SIZE = 256;
|
|
433
|
+
const GID_PEER_HISTORY_CLEANUP_PENDING_CAPACITY = 4096;
|
|
430
434
|
const DEFAULT_DISTRIBUTION_DEBOUNCE_TIME = 500;
|
|
431
435
|
const RECENT_REPAIR_DISPATCH_TTL_MS = 5_000;
|
|
432
436
|
const REPAIR_SWEEP_ENTRY_BATCH_SIZE = 1_000;
|
|
@@ -678,17 +682,19 @@ let SharedLog = (() => {
|
|
|
678
682
|
// GROWTH SHAPE. Rows are released by `deleteGidPeerHistory` on the two prune
|
|
679
683
|
// paths, by `removePeerFromGidPeerHistory` once a gid's last peer drops (the
|
|
680
684
|
// routine disconnect outcome), by `rebalanceAll({ clearCache: true })`, and
|
|
681
|
-
// wholesale on close/reset.
|
|
682
|
-
//
|
|
683
|
-
//
|
|
685
|
+
// wholesale on close/reset. Trim now carries compact distinct removed-gid
|
|
686
|
+
// facts beside its hash-only result. After the lower mutation commits, a
|
|
687
|
+
// coalesced cleanup checks authoritative local-head liveness and drops a row
|
|
688
|
+
// only when no sibling remains. A gid names a graph, not an entry: an
|
|
684
689
|
// entry with `meta.next` inherits `min(next.meta.gid)` (see
|
|
685
690
|
// packages/log/src/entry-v0.ts), so document updates fold into the gid of
|
|
686
691
|
// the first put and the row count tracks distinct chain roots -- distinct
|
|
687
692
|
// document ids -- rather than entry count. Insert-only workloads mint a
|
|
688
|
-
// fresh gid per append and so do grow one row per entry.
|
|
689
|
-
//
|
|
690
|
-
//
|
|
691
|
-
//
|
|
693
|
+
// fresh gid per append and so do grow one row per entry. Merge-shadowed gids
|
|
694
|
+
// are a separate, smaller source that still needs a fast-path-compatible
|
|
695
|
+
// post-commit metadata seam. Registering the lower log's `onGidRemoved` hook
|
|
696
|
+
// here would disable native prepared, commit-only, and batch append paths, so
|
|
697
|
+
// this bounded slice deliberately addresses trim only.
|
|
692
698
|
//
|
|
693
699
|
// WHY TRIM DOES NOT SIMPLY CALL `deleteGidPeerHistory` AS WELL. Both prune
|
|
694
700
|
// callers delete a whole row from a single entry's gid, and under the
|
|
@@ -706,10 +712,11 @@ let SharedLog = (() => {
|
|
|
706
712
|
// on the common path, paying for the freed memory in repeated re-delivery of
|
|
707
713
|
// entries that are still here. That trade is not worth it.
|
|
708
714
|
//
|
|
709
|
-
//
|
|
710
|
-
//
|
|
711
|
-
//
|
|
712
|
-
//
|
|
715
|
+
// Cleanup work is coalesced, checked in bounded batches, and capped at 4096
|
|
716
|
+
// pending gids. Saturation forgets the overflowing suppression row
|
|
717
|
+
// immediately. That bounded fallback can cause redundant delivery for a live
|
|
718
|
+
// sibling, but missing history is deliberately correctness-safe as described
|
|
719
|
+
// above; it cannot affect ownership, quorum, or retained data.
|
|
713
720
|
//
|
|
714
721
|
// Existing, deliberate imprecision: under the time domain the coordinate is
|
|
715
722
|
// `meta.clock.timestamp.wallTime` (replication-domain-time.ts) and is
|
|
@@ -717,6 +724,7 @@ let SharedLog = (() => {
|
|
|
717
724
|
// and prune's whole-row delete is already over-eager there. The cost is the
|
|
718
725
|
// same bounded extra traffic, never a wrong prune.
|
|
719
726
|
_gidPeersHistory;
|
|
727
|
+
_gidPeerHistoryCleanupState;
|
|
720
728
|
_onSubscriptionFn;
|
|
721
729
|
_onUnsubscriptionFn;
|
|
722
730
|
_subscriptionChangeCallbacks;
|
|
@@ -1729,6 +1737,7 @@ let SharedLog = (() => {
|
|
|
1729
1737
|
_appendBackfillPendingByTarget;
|
|
1730
1738
|
_repairMetrics;
|
|
1731
1739
|
_topicSubscribersCache;
|
|
1740
|
+
_localReachablePeerHashesCache;
|
|
1732
1741
|
_leaderSelectionContextCache;
|
|
1733
1742
|
_leaderPlanCache;
|
|
1734
1743
|
// Sync capability bits advertised by peers (SyncCapabilitiesMessage), keyed
|
|
@@ -1960,6 +1969,7 @@ let SharedLog = (() => {
|
|
|
1960
1969
|
this._receiveHandlerDrainByPeer = new Map();
|
|
1961
1970
|
this._openingSyncCapabilitiesByPeer = new Map();
|
|
1962
1971
|
this._gidPeersHistory = new Map();
|
|
1972
|
+
this.resetGidPeerHistoryCleanupState();
|
|
1963
1973
|
this._repairRetryTimers = new Set();
|
|
1964
1974
|
this._recentRepairDispatch = new Map();
|
|
1965
1975
|
this._repairSweepRunning = false;
|
|
@@ -1979,6 +1989,7 @@ let SharedLog = (() => {
|
|
|
1979
1989
|
this._joinAuthoritativeRepairPeersByDelay = new Map();
|
|
1980
1990
|
this._appendBackfillPendingByTarget = new Map();
|
|
1981
1991
|
this._topicSubscribersCache = new Map();
|
|
1992
|
+
this._localReachablePeerHashesCache = new Map();
|
|
1982
1993
|
this._peerSyncCapabilities = new Map();
|
|
1983
1994
|
this._peerSyncCapabilitySessions = new Map();
|
|
1984
1995
|
this._peerSyncCapabilityTimestamps = new Map();
|
|
@@ -3045,6 +3056,88 @@ let SharedLog = (() => {
|
|
|
3045
3056
|
return (fanoutService?.getPublicKey?.(hash) ??
|
|
3046
3057
|
this.node.services.pubsub.getPublicKey(hash));
|
|
3047
3058
|
}
|
|
3059
|
+
async _getLocalReachablePeerHashes(topic) {
|
|
3060
|
+
const cache = (this._localReachablePeerHashesCache ??= new Map());
|
|
3061
|
+
const cached = cache.get(topic);
|
|
3062
|
+
if (cached?.hashes && cached.expiresAt > Date.now()) {
|
|
3063
|
+
return cached.hashes.slice();
|
|
3064
|
+
}
|
|
3065
|
+
if (cached?.inFlight) {
|
|
3066
|
+
return (await cached.inFlight).slice();
|
|
3067
|
+
}
|
|
3068
|
+
const entry = { expiresAt: 0 };
|
|
3069
|
+
const inFlight = (async () => {
|
|
3070
|
+
const selfHash = this.node.identity.publicKey.hashcode();
|
|
3071
|
+
const hashes = [];
|
|
3072
|
+
const seen = new Set();
|
|
3073
|
+
const addHash = (hash) => {
|
|
3074
|
+
if (typeof hash !== "string" ||
|
|
3075
|
+
hash.length === 0 ||
|
|
3076
|
+
hash === selfHash ||
|
|
3077
|
+
seen.has(hash) ||
|
|
3078
|
+
hashes.length >= LOCAL_REACHABLE_PEERS_MAX) {
|
|
3079
|
+
return;
|
|
3080
|
+
}
|
|
3081
|
+
seen.add(hash);
|
|
3082
|
+
hashes.push(hash);
|
|
3083
|
+
};
|
|
3084
|
+
const pubsub = this.node.services.pubsub;
|
|
3085
|
+
const peerMap = pubsub?.peers;
|
|
3086
|
+
if (peerMap?.entries) {
|
|
3087
|
+
for (const [hash, peer] of peerMap.entries()) {
|
|
3088
|
+
addHash(peer?.publicKey?.hashcode?.());
|
|
3089
|
+
addHash(hash);
|
|
3090
|
+
if (hashes.length >= LOCAL_REACHABLE_PEERS_MAX)
|
|
3091
|
+
break;
|
|
3092
|
+
}
|
|
3093
|
+
}
|
|
3094
|
+
if (hashes.length < LOCAL_REACHABLE_PEERS_MAX) {
|
|
3095
|
+
const connectionManager = pubsub?.components?.connectionManager;
|
|
3096
|
+
for (const conn of connectionManager?.getConnections?.() ?? []) {
|
|
3097
|
+
const peerId = conn?.remotePeer;
|
|
3098
|
+
if (!peerId)
|
|
3099
|
+
continue;
|
|
3100
|
+
try {
|
|
3101
|
+
addHash(getPublicKeyFromPeerId(peerId).hashcode());
|
|
3102
|
+
}
|
|
3103
|
+
catch {
|
|
3104
|
+
// Best-effort only.
|
|
3105
|
+
}
|
|
3106
|
+
if (hashes.length >= LOCAL_REACHABLE_PEERS_MAX)
|
|
3107
|
+
break;
|
|
3108
|
+
}
|
|
3109
|
+
}
|
|
3110
|
+
if (hashes.length < LOCAL_REACHABLE_PEERS_MAX) {
|
|
3111
|
+
try {
|
|
3112
|
+
for (const subscriber of (await pubsub.getSubscribers(topic)) ?? []) {
|
|
3113
|
+
addHash(subscriber?.hashcode?.());
|
|
3114
|
+
if (hashes.length >= LOCAL_REACHABLE_PEERS_MAX)
|
|
3115
|
+
break;
|
|
3116
|
+
}
|
|
3117
|
+
}
|
|
3118
|
+
catch {
|
|
3119
|
+
// Local reachability is best-effort.
|
|
3120
|
+
}
|
|
3121
|
+
}
|
|
3122
|
+
return hashes;
|
|
3123
|
+
})();
|
|
3124
|
+
entry.inFlight = inFlight;
|
|
3125
|
+
cache.set(topic, entry);
|
|
3126
|
+
try {
|
|
3127
|
+
const hashes = await inFlight;
|
|
3128
|
+
if (cache.get(topic) === entry) {
|
|
3129
|
+
entry.hashes = hashes;
|
|
3130
|
+
entry.expiresAt = Date.now() + LOCAL_REACHABLE_PEERS_CACHE_TTL_MS;
|
|
3131
|
+
entry.inFlight = undefined;
|
|
3132
|
+
}
|
|
3133
|
+
return hashes.slice();
|
|
3134
|
+
}
|
|
3135
|
+
catch (error) {
|
|
3136
|
+
if (cache.get(topic) === entry)
|
|
3137
|
+
cache.delete(topic);
|
|
3138
|
+
throw error;
|
|
3139
|
+
}
|
|
3140
|
+
}
|
|
3048
3141
|
async _getTopicSubscribers(topic) {
|
|
3049
3142
|
const cached = this._topicSubscribersCache.get(topic);
|
|
3050
3143
|
if (cached && cached.expiresAt > Date.now()) {
|
|
@@ -3158,6 +3251,7 @@ let SharedLog = (() => {
|
|
|
3158
3251
|
if (!topic)
|
|
3159
3252
|
continue;
|
|
3160
3253
|
this._topicSubscribersCache.delete(topic);
|
|
3254
|
+
this._localReachablePeerHashesCache?.delete(topic);
|
|
3161
3255
|
}
|
|
3162
3256
|
this.invalidateLeaderSelectionContextCache();
|
|
3163
3257
|
}
|
|
@@ -6728,6 +6822,8 @@ let SharedLog = (() => {
|
|
|
6728
6822
|
return {
|
|
6729
6823
|
entry: result.entry,
|
|
6730
6824
|
removed: result.removed,
|
|
6825
|
+
removedHashes: result.removedHashes,
|
|
6826
|
+
removedGids: result.removedGids,
|
|
6731
6827
|
appendCommit: nativePreparedCommit,
|
|
6732
6828
|
};
|
|
6733
6829
|
}
|
|
@@ -6777,6 +6873,8 @@ let SharedLog = (() => {
|
|
|
6777
6873
|
return {
|
|
6778
6874
|
entry: result.entry,
|
|
6779
6875
|
removed: result.removed,
|
|
6876
|
+
removedHashes: result.removedHashes,
|
|
6877
|
+
removedGids: result.removedGids,
|
|
6780
6878
|
appendCommit: this.createPreparedLocalAppendCommitFromFacts(result.appendFacts, nativeAppendPlan),
|
|
6781
6879
|
};
|
|
6782
6880
|
}
|
|
@@ -6792,6 +6890,7 @@ let SharedLog = (() => {
|
|
|
6792
6890
|
return undefined;
|
|
6793
6891
|
}
|
|
6794
6892
|
const plannedCoordinateDeleteHashes = result.change?.removed.map((entry) => entry.hash) ??
|
|
6893
|
+
result.removedHashes ??
|
|
6795
6894
|
result.removed.map((entry) => entry.hash);
|
|
6796
6895
|
const nativeAppendPlan = await this.planNativeLocalAppendFacts(result.appendFacts, properties.minReplicasValue, {
|
|
6797
6896
|
deleteHashes: plannedCoordinateDeleteHashes.length > 0
|
|
@@ -6811,6 +6910,8 @@ let SharedLog = (() => {
|
|
|
6811
6910
|
})
|
|
6812
6911
|
: this.applyPreparedAppendFactsWithDeferredCoordinateDeletes(result.appendFacts, result.removed, () => this.materializePreparedAppendResultEntry(result), {
|
|
6813
6912
|
forgetNativeCoordinates: !nativeAppendPlan.committedNativeCoordinateDeletes,
|
|
6913
|
+
removedHashes: result.removedHashes,
|
|
6914
|
+
removedGids: result.removedGids,
|
|
6814
6915
|
ownershipLifecycleController,
|
|
6815
6916
|
});
|
|
6816
6917
|
await this._coordinates.persistPreparedCoordinate({
|
|
@@ -7286,6 +7387,7 @@ let SharedLog = (() => {
|
|
|
7286
7387
|
},
|
|
7287
7388
|
removed: prepared.removed,
|
|
7288
7389
|
removedHashes: prepared.removedHashes,
|
|
7390
|
+
removedGids: prepared.removedGids,
|
|
7289
7391
|
appendCommit,
|
|
7290
7392
|
};
|
|
7291
7393
|
};
|
|
@@ -7308,7 +7410,10 @@ let SharedLog = (() => {
|
|
|
7308
7410
|
// `rollback` can no longer fire. Nothing downstream rolls back
|
|
7309
7411
|
// (the retire below only warns), so the token is terminal here.
|
|
7310
7412
|
this._coordinates.settleResidentCoordinateSnapshot(lowerPublicationRollback?.coordinateEntries);
|
|
7311
|
-
this.applyPreparedAppendFactsWithDeferredCoordinateDeletes(prepared.appendFacts, prepared.removed, prepared.materializeEntry, {
|
|
7413
|
+
this.applyPreparedAppendFactsWithDeferredCoordinateDeletes(prepared.appendFacts, prepared.removed, prepared.materializeEntry, {
|
|
7414
|
+
removedHashes: prepared.removedHashes,
|
|
7415
|
+
removedGids: prepared.removedGids,
|
|
7416
|
+
});
|
|
7312
7417
|
try {
|
|
7313
7418
|
await this.completeNativeStrictDurableTransaction(nativeStrictTransaction);
|
|
7314
7419
|
}
|
|
@@ -7451,6 +7556,10 @@ let SharedLog = (() => {
|
|
|
7451
7556
|
const useTrimmedHashesOnly = properties?.resolveTrimmedEntries === false;
|
|
7452
7557
|
const nativeTrimmedHashes = backboneAppend.trimmedHashes ??
|
|
7453
7558
|
backboneAppend.trimmed.map((entry) => entry.hash);
|
|
7559
|
+
const nativeTrimmedGids = backboneAppend.trimmedGids ??
|
|
7560
|
+
(backboneAppend.trimmed.length > 0
|
|
7561
|
+
? backboneAppend.trimmed.map((entry) => entry.gid)
|
|
7562
|
+
: undefined);
|
|
7454
7563
|
const committedHash = backboneAppend.entry.cid ?? backboneAppend.entry.hash;
|
|
7455
7564
|
const committedNext = backboneAppend.entry.next ?? next;
|
|
7456
7565
|
nativeCoordinateRollback =
|
|
@@ -7511,6 +7620,9 @@ let SharedLog = (() => {
|
|
|
7511
7620
|
trimmedEntryHashes: useTrimmedHashesOnly
|
|
7512
7621
|
? backboneAppend.trimmedHashes
|
|
7513
7622
|
: undefined,
|
|
7623
|
+
trimmedEntryGids: useTrimmedHashesOnly
|
|
7624
|
+
? nativeTrimmedGids
|
|
7625
|
+
: undefined,
|
|
7514
7626
|
nativeBlocksDeleted: commitBlocksInBackbone,
|
|
7515
7627
|
nativeDeleteCleanupToken,
|
|
7516
7628
|
documentPreviousContext: backboneAppend.documentPreviousContext,
|
|
@@ -7659,6 +7771,7 @@ let SharedLog = (() => {
|
|
|
7659
7771
|
},
|
|
7660
7772
|
removed: prepared.removed,
|
|
7661
7773
|
removedHashes: prepared.removedHashes,
|
|
7774
|
+
removedGids: prepared.removedGids,
|
|
7662
7775
|
appendCommit,
|
|
7663
7776
|
};
|
|
7664
7777
|
};
|
|
@@ -7751,6 +7864,7 @@ let SharedLog = (() => {
|
|
|
7751
7864
|
this.applyPreparedAppendFactsWithDeferredCoordinateDeletes(prepared.appendFacts, prepared.removed, prepared.materializeEntry, {
|
|
7752
7865
|
forgetNativeCoordinates: false,
|
|
7753
7866
|
removedHashes: prepared.removedHashes,
|
|
7867
|
+
removedGids: prepared.removedGids,
|
|
7754
7868
|
});
|
|
7755
7869
|
try {
|
|
7756
7870
|
await this.completeNativeStrictDurableTransaction(nativeStrictTransaction);
|
|
@@ -7789,6 +7903,7 @@ let SharedLog = (() => {
|
|
|
7789
7903
|
if (this.shouldDeferHeadCoordinatePersistence(options)) {
|
|
7790
7904
|
const deferredCoordinateDeleteHashes = this.applyPreparedAppendFactsWithDeferredCoordinateDeletes(result.appendFacts, result.removed, result.materializeEntry, {
|
|
7791
7905
|
removedHashes: result.removedHashes,
|
|
7906
|
+
removedGids: result.removedGids,
|
|
7792
7907
|
ownershipLifecycleController,
|
|
7793
7908
|
});
|
|
7794
7909
|
const deleteHashes = deferredCoordinateDeleteHashes &&
|
|
@@ -7809,6 +7924,7 @@ let SharedLog = (() => {
|
|
|
7809
7924
|
},
|
|
7810
7925
|
removed: result.removed,
|
|
7811
7926
|
removedHashes: result.removedHashes,
|
|
7927
|
+
removedGids: result.removedGids,
|
|
7812
7928
|
appendCommit: this.createPreparedLocalAppendCommitFromFacts(result.appendFacts),
|
|
7813
7929
|
};
|
|
7814
7930
|
});
|
|
@@ -7819,6 +7935,7 @@ let SharedLog = (() => {
|
|
|
7819
7935
|
},
|
|
7820
7936
|
removed: result.removed,
|
|
7821
7937
|
removedHashes: result.removedHashes,
|
|
7938
|
+
removedGids: result.removedGids,
|
|
7822
7939
|
appendCommit: this.createPreparedLocalAppendCommitFromFacts(result.appendFacts),
|
|
7823
7940
|
};
|
|
7824
7941
|
}
|
|
@@ -7867,6 +7984,7 @@ let SharedLog = (() => {
|
|
|
7867
7984
|
},
|
|
7868
7985
|
removed: result.removed,
|
|
7869
7986
|
removedHashes: result.removedHashes,
|
|
7987
|
+
removedGids: result.removedGids,
|
|
7870
7988
|
appendCommit: nativePreparedCommit,
|
|
7871
7989
|
};
|
|
7872
7990
|
}
|
|
@@ -7894,6 +8012,7 @@ let SharedLog = (() => {
|
|
|
7894
8012
|
: this.applyPreparedAppendFactsWithDeferredCoordinateDeletes(result.appendFacts, result.removed, () => this.materializePreparedAppendResultEntry(result), {
|
|
7895
8013
|
forgetNativeCoordinates: !nativeAppendPlan.committedNativeCoordinateDeletes,
|
|
7896
8014
|
removedHashes: result.removedHashes,
|
|
8015
|
+
removedGids: result.removedGids,
|
|
7897
8016
|
ownershipLifecycleController,
|
|
7898
8017
|
});
|
|
7899
8018
|
await this._coordinates.persistPreparedCoordinateNativeTransaction({
|
|
@@ -7949,6 +8068,8 @@ let SharedLog = (() => {
|
|
|
7949
8068
|
return result.entry;
|
|
7950
8069
|
},
|
|
7951
8070
|
removed: result.removed,
|
|
8071
|
+
removedHashes: result.removedHashes,
|
|
8072
|
+
removedGids: result.removedGids,
|
|
7952
8073
|
appendCommit: nativePreparedCommit,
|
|
7953
8074
|
};
|
|
7954
8075
|
}
|
|
@@ -7958,6 +8079,7 @@ let SharedLog = (() => {
|
|
|
7958
8079
|
deferredCoordinateDeleteHashes =
|
|
7959
8080
|
this.applyPreparedAppendFactsWithDeferredCoordinateDeletes(result.appendFacts, result.removed, result.materializeEntry, {
|
|
7960
8081
|
removedHashes: result.removedHashes,
|
|
8082
|
+
removedGids: result.removedGids,
|
|
7961
8083
|
ownershipLifecycleController,
|
|
7962
8084
|
});
|
|
7963
8085
|
nativeAppendPlan = await this.planNativeLocalAppendFacts(result.appendFacts, minReplicasValue, undefined, ownershipLifecycleController);
|
|
@@ -8000,6 +8122,7 @@ let SharedLog = (() => {
|
|
|
8000
8122
|
},
|
|
8001
8123
|
removed: result.removed,
|
|
8002
8124
|
removedHashes: result.removedHashes,
|
|
8125
|
+
removedGids: result.removedGids,
|
|
8003
8126
|
appendCommit: this.createPreparedLocalAppendCommitFromFacts(result.appendFacts, nativeAppendPlan),
|
|
8004
8127
|
};
|
|
8005
8128
|
}
|
|
@@ -8198,6 +8321,10 @@ let SharedLog = (() => {
|
|
|
8198
8321
|
getBytes: (hash) => backbone.blocks.get(hash),
|
|
8199
8322
|
nativeIndexMutationLockOwner: nativeStrictTransaction?.lowerHashMutationLockOwner,
|
|
8200
8323
|
trimmedEntryHashes: append.trimmedHashes,
|
|
8324
|
+
trimmedEntryGids: append.trimmedGids ??
|
|
8325
|
+
(append.trimmed.length > 0
|
|
8326
|
+
? append.trimmed.map((entry) => entry.gid)
|
|
8327
|
+
: undefined),
|
|
8201
8328
|
nativeBlocksDeleted: true,
|
|
8202
8329
|
nativeDeleteCleanupToken,
|
|
8203
8330
|
documentTrimmedHeadsProcessed: append.documentTrimmedHeadsProcessed,
|
|
@@ -8428,6 +8555,10 @@ let SharedLog = (() => {
|
|
|
8428
8555
|
this.applyPreparedAppendFactsWithDeferredCoordinateDeletes(facts, [], appended.materializeEntries[i], {
|
|
8429
8556
|
forgetNativeCoordinates: false,
|
|
8430
8557
|
removedHashes: plannedCoordinateDeleteHashes,
|
|
8558
|
+
removedGids: backboneAppend.trimmedGids ??
|
|
8559
|
+
(backboneAppend.trimmed.length > 0
|
|
8560
|
+
? backboneAppend.trimmed.map((entry) => entry.gid)
|
|
8561
|
+
: undefined),
|
|
8431
8562
|
});
|
|
8432
8563
|
if (!runtimeOnlyCoordinates && this.remoteBlocks.hasNotifyStoredHook()) {
|
|
8433
8564
|
this.remoteBlocks.notifyStoredDeferred(facts.hash);
|
|
@@ -9362,6 +9493,7 @@ let SharedLog = (() => {
|
|
|
9362
9493
|
this._appendBackfillPendingByTarget = new Map();
|
|
9363
9494
|
this._repairMetrics = createRepairMetrics();
|
|
9364
9495
|
this._topicSubscribersCache = new Map();
|
|
9496
|
+
this._localReachablePeerHashesCache = new Map();
|
|
9365
9497
|
this._leaderSelectionContextCache = undefined;
|
|
9366
9498
|
this._leaderPlanCache = new LeaderPlanCache({
|
|
9367
9499
|
max: LEADER_PLAN_CACHE_MAX,
|
|
@@ -9515,27 +9647,57 @@ let SharedLog = (() => {
|
|
|
9515
9647
|
// compatible eager path with bounded validation and storage budgets.
|
|
9516
9648
|
eagerBlocks: options?.eagerBlocks ?? false,
|
|
9517
9649
|
resolveProviders: async (cid, opts) => {
|
|
9518
|
-
|
|
9650
|
+
const maxPeers = 8;
|
|
9651
|
+
const localCandidates = (await this.resolveCandidatePeersForHash(cid, {
|
|
9652
|
+
signal: opts?.signal,
|
|
9653
|
+
maxPeers,
|
|
9654
|
+
})) ?? [];
|
|
9655
|
+
if (opts?.signal?.aborted)
|
|
9656
|
+
return [];
|
|
9657
|
+
const locallyReachable = new Set(await this._getLocalReachablePeerHashes(this.topic));
|
|
9658
|
+
const confirmed = this._checkedPrune.getConfirmedReplicators(cid);
|
|
9659
|
+
const contacted = this._checkedPrune.getContactedReplicators(cid);
|
|
9660
|
+
const hasLiveCandidate = localCandidates.some((peer) => locallyReachable.has(peer) &&
|
|
9661
|
+
(confirmed?.has(peer) ||
|
|
9662
|
+
contacted?.has(peer) ||
|
|
9663
|
+
this.uniqueReplicators.has(peer)));
|
|
9664
|
+
// Only reachability corroborated by provider/replicator evidence may
|
|
9665
|
+
// bypass the initial CID lookup. Arbitrary bootstrap connections are
|
|
9666
|
+
// useful fallbacks, but are not evidence that they hold this block.
|
|
9667
|
+
if (hasLiveCandidate && !opts?.refresh) {
|
|
9668
|
+
return localCandidates;
|
|
9669
|
+
}
|
|
9670
|
+
let directoryProviders = [];
|
|
9519
9671
|
try {
|
|
9520
|
-
|
|
9521
|
-
|
|
9522
|
-
|
|
9523
|
-
|
|
9524
|
-
|
|
9525
|
-
|
|
9526
|
-
|
|
9527
|
-
|
|
9528
|
-
return providers;
|
|
9672
|
+
directoryProviders =
|
|
9673
|
+
(await fanoutService?.queryProviders(blockProviderNamespace(cid), {
|
|
9674
|
+
want: maxPeers,
|
|
9675
|
+
timeoutMs: 2_000,
|
|
9676
|
+
queryTimeoutMs: 500,
|
|
9677
|
+
bootstrapMaxPeers: 2,
|
|
9678
|
+
signal: opts?.signal,
|
|
9679
|
+
})) ?? [];
|
|
9529
9680
|
}
|
|
9530
9681
|
catch {
|
|
9531
|
-
//
|
|
9682
|
+
// Ignore discovery failures; local evidence remains usable.
|
|
9683
|
+
}
|
|
9684
|
+
const selected = [];
|
|
9685
|
+
const selectedSet = new Set();
|
|
9686
|
+
const add = (peer) => {
|
|
9687
|
+
if (!peer ||
|
|
9688
|
+
selectedSet.has(peer) ||
|
|
9689
|
+
selected.length >= maxPeers) {
|
|
9690
|
+
return;
|
|
9691
|
+
}
|
|
9692
|
+
selectedSet.add(peer);
|
|
9693
|
+
selected.push(peer);
|
|
9694
|
+
};
|
|
9695
|
+
for (let index = 0; selected.length < maxPeers &&
|
|
9696
|
+
(index < localCandidates.length || index < directoryProviders.length); index++) {
|
|
9697
|
+
add(localCandidates[index]);
|
|
9698
|
+
add(directoryProviders[index]);
|
|
9532
9699
|
}
|
|
9533
|
-
|
|
9534
|
-
// entry loads so block retries can widen beyond stale explicit hints.
|
|
9535
|
-
return ((await this.resolveCandidatePeersForHash(cid, {
|
|
9536
|
-
signal: opts?.signal,
|
|
9537
|
-
maxPeers: 8,
|
|
9538
|
-
})) ?? []);
|
|
9700
|
+
return selected;
|
|
9539
9701
|
},
|
|
9540
9702
|
watchProviders: fanoutService
|
|
9541
9703
|
? (cid, opts) => fanoutService.watchProviders(blockProviderNamespace(cid), {
|
|
@@ -9572,6 +9734,7 @@ let SharedLog = (() => {
|
|
|
9572
9734
|
],
|
|
9573
9735
|
})) > 0;
|
|
9574
9736
|
this._gidPeersHistory = new Map();
|
|
9737
|
+
this.resetGidPeerHistoryCleanupState();
|
|
9575
9738
|
const replicationChangeOwnershipLifecycleController = this.captureReplicationOwnershipLifecycle();
|
|
9576
9739
|
let replicationChangeDebounceFn;
|
|
9577
9740
|
replicationChangeDebounceFn = debounceAggregationChanges(async (change) => {
|
|
@@ -10566,70 +10729,82 @@ let SharedLog = (() => {
|
|
|
10566
10729
|
if (options?.signal?.aborted)
|
|
10567
10730
|
return undefined;
|
|
10568
10731
|
const maxPeers = options?.maxPeers ?? 8;
|
|
10732
|
+
if (maxPeers <= 0)
|
|
10733
|
+
return undefined;
|
|
10569
10734
|
const self = this.node.identity.publicKey.hashcode();
|
|
10570
10735
|
const seed = hashToSeed32(hash);
|
|
10571
|
-
|
|
10572
|
-
|
|
10573
|
-
|
|
10574
|
-
|
|
10575
|
-
|
|
10576
|
-
|
|
10577
|
-
|
|
10736
|
+
// Replication/provider knowledge deliberately outlives a transport session.
|
|
10737
|
+
// That is useful for repair, but it must not fill the bounded fetch target set
|
|
10738
|
+
// with departed peers while a reachable holder is available. The local
|
|
10739
|
+
// snapshot performs no tracker/provider discovery or key resolution.
|
|
10740
|
+
const reachable = await this._getLocalReachablePeerHashes(this.topic);
|
|
10741
|
+
if (options?.signal?.aborted)
|
|
10742
|
+
return undefined;
|
|
10743
|
+
const reachableSet = new Set(reachable);
|
|
10744
|
+
const confirmed = this._checkedPrune.getConfirmedReplicators(hash);
|
|
10578
10745
|
const contacted = this._checkedPrune.getContactedReplicators(hash);
|
|
10579
|
-
|
|
10580
|
-
|
|
10581
|
-
|
|
10582
|
-
|
|
10583
|
-
|
|
10584
|
-
|
|
10585
|
-
|
|
10586
|
-
|
|
10587
|
-
|
|
10588
|
-
|
|
10746
|
+
const evidenceTiers = [
|
|
10747
|
+
confirmed ?? [],
|
|
10748
|
+
contacted ?? [],
|
|
10749
|
+
this.uniqueReplicators,
|
|
10750
|
+
];
|
|
10751
|
+
// Peers supported by both current transport state and provider history are
|
|
10752
|
+
// the strongest signal. Afterwards alternate live-only and historical-only
|
|
10753
|
+
// evidence so either source can widen a saturated bounded set. Derive the
|
|
10754
|
+
// live intersection from the bounded reachability snapshot; never clone or
|
|
10755
|
+
// scan an unbounded historical replicator set per missing block.
|
|
10756
|
+
const liveEvidenceTiers = [[], [], []];
|
|
10757
|
+
for (const peer of reachable) {
|
|
10758
|
+
if (confirmed?.has(peer))
|
|
10759
|
+
liveEvidenceTiers[0].push(peer);
|
|
10760
|
+
else if (contacted?.has(peer))
|
|
10761
|
+
liveEvidenceTiers[1].push(peer);
|
|
10762
|
+
else if (this.uniqueReplicators.has(peer))
|
|
10763
|
+
liveEvidenceTiers[2].push(peer);
|
|
10764
|
+
}
|
|
10765
|
+
const liveKnown = [];
|
|
10766
|
+
for (let tier = 0; tier < liveEvidenceTiers.length; tier++) {
|
|
10767
|
+
liveKnown.push(...pickDeterministicSubset(liveEvidenceTiers[tier], (seed ^ Math.imul(tier + 1, 0x9e3779b1)) >>> 0, maxPeers - liveKnown.length));
|
|
10768
|
+
if (liveKnown.length >= maxPeers)
|
|
10769
|
+
break;
|
|
10589
10770
|
}
|
|
10590
|
-
|
|
10591
|
-
|
|
10592
|
-
|
|
10593
|
-
|
|
10594
|
-
|
|
10595
|
-
|
|
10596
|
-
|
|
10597
|
-
|
|
10598
|
-
|
|
10599
|
-
|
|
10600
|
-
|
|
10601
|
-
|
|
10602
|
-
|
|
10603
|
-
|
|
10771
|
+
const liveKnownSet = new Set(liveKnown);
|
|
10772
|
+
const liveOnly = pickDeterministicSubset(reachable.filter((peer) => !liveKnownSet.has(peer)), (seed ^ 0x85ebca6b) >>> 0, maxPeers);
|
|
10773
|
+
const historicalOnly = [];
|
|
10774
|
+
const historicalSeen = new Set();
|
|
10775
|
+
for (let tier = 0; tier < evidenceTiers.length; tier++) {
|
|
10776
|
+
const available = [];
|
|
10777
|
+
let inspected = 0;
|
|
10778
|
+
for (const peer of evidenceTiers[tier]) {
|
|
10779
|
+
if (inspected++ >= LOCAL_REACHABLE_PEERS_MAX)
|
|
10780
|
+
break;
|
|
10781
|
+
if (!peer ||
|
|
10782
|
+
peer === self ||
|
|
10783
|
+
reachableSet.has(peer) ||
|
|
10784
|
+
historicalSeen.has(peer)) {
|
|
10785
|
+
continue;
|
|
10604
10786
|
}
|
|
10787
|
+
historicalSeen.add(peer);
|
|
10788
|
+
available.push(peer);
|
|
10605
10789
|
}
|
|
10606
|
-
|
|
10607
|
-
|
|
10608
|
-
|
|
10609
|
-
|
|
10610
|
-
|
|
10611
|
-
|
|
10612
|
-
|
|
10613
|
-
|
|
10614
|
-
|
|
10615
|
-
|
|
10616
|
-
|
|
10617
|
-
|
|
10618
|
-
|
|
10619
|
-
|
|
10620
|
-
|
|
10621
|
-
}
|
|
10622
|
-
if (connectionHashes.length > 0) {
|
|
10623
|
-
candidates = connectionHashes;
|
|
10624
|
-
}
|
|
10790
|
+
historicalOnly.push(...pickDeterministicSubset(available, (seed ^ Math.imul(tier + 5, 0x9e3779b1)) >>> 0, maxPeers - historicalOnly.length));
|
|
10791
|
+
if (historicalOnly.length >= maxPeers)
|
|
10792
|
+
break;
|
|
10793
|
+
}
|
|
10794
|
+
const selected = liveKnown.slice(0, maxPeers);
|
|
10795
|
+
let liveIndex = 0;
|
|
10796
|
+
let historicalIndex = 0;
|
|
10797
|
+
while (selected.length < maxPeers &&
|
|
10798
|
+
(liveIndex < liveOnly.length || historicalIndex < historicalOnly.length)) {
|
|
10799
|
+
if (liveIndex < liveOnly.length) {
|
|
10800
|
+
selected.push(liveOnly[liveIndex++]);
|
|
10801
|
+
}
|
|
10802
|
+
if (selected.length < maxPeers &&
|
|
10803
|
+
historicalIndex < historicalOnly.length) {
|
|
10804
|
+
selected.push(historicalOnly[historicalIndex++]);
|
|
10625
10805
|
}
|
|
10626
10806
|
}
|
|
10627
|
-
|
|
10628
|
-
return undefined;
|
|
10629
|
-
const peers = candidates.filter((p) => p !== self);
|
|
10630
|
-
if (peers.length === 0)
|
|
10631
|
-
return undefined;
|
|
10632
|
-
return pickDeterministicSubset(peers, seed, maxPeers);
|
|
10807
|
+
return selected.length > 0 ? selected : undefined;
|
|
10633
10808
|
}
|
|
10634
10809
|
async getMemoryUsage() {
|
|
10635
10810
|
return this.log.blocks.size();
|
|
@@ -10953,6 +11128,7 @@ let SharedLog = (() => {
|
|
|
10953
11128
|
}
|
|
10954
11129
|
const deferredCoordinateDeleteHashes = change.removed.map((removed) => removed.hash);
|
|
10955
11130
|
this.onEntryRemovedHashes(deferredCoordinateDeleteHashes);
|
|
11131
|
+
this.scheduleDeadGidPeerHistoryReclaim(change.removed.map((removed) => removed.meta.gid));
|
|
10956
11132
|
if (options?.forgetNativeCoordinates === false) {
|
|
10957
11133
|
this._coordinates.forgetResidentCoordinateStateForHashes(deferredCoordinateDeleteHashes);
|
|
10958
11134
|
}
|
|
@@ -10982,6 +11158,9 @@ let SharedLog = (() => {
|
|
|
10982
11158
|
? normalizedHashValues(removedHashes)
|
|
10983
11159
|
: removed.map((entry) => entry.hash);
|
|
10984
11160
|
this.onEntryRemovedHashes(deferredCoordinateDeleteHashes);
|
|
11161
|
+
this.scheduleDeadGidPeerHistoryReclaim(removed.length > 0
|
|
11162
|
+
? removed.map((entry) => entry.meta.gid)
|
|
11163
|
+
: options?.removedGids);
|
|
10985
11164
|
if (options?.forgetNativeCoordinates === false) {
|
|
10986
11165
|
this._coordinates.forgetResidentCoordinateStateForHashes(deferredCoordinateDeleteHashes);
|
|
10987
11166
|
}
|
|
@@ -11001,8 +11180,128 @@ let SharedLog = (() => {
|
|
|
11001
11180
|
}
|
|
11002
11181
|
this.onEntryRemoved(removed.hash);
|
|
11003
11182
|
}
|
|
11183
|
+
this.scheduleDeadGidPeerHistoryReclaim(removedEntries.map((removed) => removed.meta.gid));
|
|
11004
11184
|
return undefined;
|
|
11005
11185
|
}
|
|
11186
|
+
async reclaimDeadGidPeerHistory(gids, history = this._gidPeersHistory) {
|
|
11187
|
+
if (!gids)
|
|
11188
|
+
return;
|
|
11189
|
+
const candidates = [...new Set(gids)].filter((gid) => history === this._gidPeersHistory && !!gid && history.has(gid));
|
|
11190
|
+
if (candidates.length === 0)
|
|
11191
|
+
return;
|
|
11192
|
+
const hasHeads = await this.hasAnyHeadForGidSets(candidates.map((gid) => [gid]));
|
|
11193
|
+
if (history !== this._gidPeersHistory)
|
|
11194
|
+
return;
|
|
11195
|
+
for (let index = 0; index < candidates.length; index++) {
|
|
11196
|
+
const gid = candidates[index];
|
|
11197
|
+
if (!hasHeads[index] && history.has(gid)) {
|
|
11198
|
+
this.deleteGidPeerHistory(gid);
|
|
11199
|
+
}
|
|
11200
|
+
}
|
|
11201
|
+
}
|
|
11202
|
+
resetGidPeerHistoryCleanupState() {
|
|
11203
|
+
this._gidPeerHistoryCleanupState = {
|
|
11204
|
+
history: this._gidPeersHistory,
|
|
11205
|
+
tail: Promise.resolve(),
|
|
11206
|
+
pending: new Set(),
|
|
11207
|
+
draining: false,
|
|
11208
|
+
highWater: 0,
|
|
11209
|
+
};
|
|
11210
|
+
}
|
|
11211
|
+
scheduleDeadGidPeerHistoryReclaim(gids) {
|
|
11212
|
+
if (!gids)
|
|
11213
|
+
return;
|
|
11214
|
+
const state = this._gidPeerHistoryCleanupState;
|
|
11215
|
+
const history = state.history;
|
|
11216
|
+
for (const gid of gids) {
|
|
11217
|
+
if (!gid || !history.has(gid))
|
|
11218
|
+
continue;
|
|
11219
|
+
if (state.pending.has(gid))
|
|
11220
|
+
continue;
|
|
11221
|
+
if (state.pending.size >= GID_PEER_HISTORY_CLEANUP_PENDING_CAPACITY) {
|
|
11222
|
+
// History is only a suppression memo. Under sustained synchronous
|
|
11223
|
+
// production, forgetting an overflow row is the bounded, safe fallback:
|
|
11224
|
+
// it can cause redundant delivery but cannot affect ownership or data.
|
|
11225
|
+
this.deleteGidPeerHistory(gid);
|
|
11226
|
+
continue;
|
|
11227
|
+
}
|
|
11228
|
+
state.pending.add(gid);
|
|
11229
|
+
}
|
|
11230
|
+
state.highWater = Math.max(state.highWater, state.pending.size);
|
|
11231
|
+
if (state.pending.size === 0)
|
|
11232
|
+
return;
|
|
11233
|
+
// A macrotask gate lets sequential awaited appends coalesce; a Promise
|
|
11234
|
+
// microtask runs before the caller can enqueue the next trimmed gid. The
|
|
11235
|
+
// fixed early-wake threshold also amortizes latest-only (retain=1) logs,
|
|
11236
|
+
// while the separate pending cap remains the absolute memory bound.
|
|
11237
|
+
const wakeThreshold = GID_PEER_HISTORY_CLEANUP_BATCH_SIZE;
|
|
11238
|
+
if (state.draining) {
|
|
11239
|
+
if (state.pending.size >= wakeThreshold)
|
|
11240
|
+
state.wake?.();
|
|
11241
|
+
return;
|
|
11242
|
+
}
|
|
11243
|
+
state.draining = true;
|
|
11244
|
+
let wake;
|
|
11245
|
+
const batchReady = new Promise((resolve) => {
|
|
11246
|
+
let settled = false;
|
|
11247
|
+
let cancel = () => { };
|
|
11248
|
+
wake = () => {
|
|
11249
|
+
if (settled)
|
|
11250
|
+
return;
|
|
11251
|
+
settled = true;
|
|
11252
|
+
cancel();
|
|
11253
|
+
if (state.wake === wake)
|
|
11254
|
+
state.wake = undefined;
|
|
11255
|
+
resolve();
|
|
11256
|
+
};
|
|
11257
|
+
state.wake = wake;
|
|
11258
|
+
if (typeof setImmediate === "function") {
|
|
11259
|
+
const handle = setImmediate(wake);
|
|
11260
|
+
cancel = () => clearImmediate(handle);
|
|
11261
|
+
}
|
|
11262
|
+
else {
|
|
11263
|
+
const handle = setTimeout(wake, 0);
|
|
11264
|
+
cancel = () => clearTimeout(handle);
|
|
11265
|
+
}
|
|
11266
|
+
});
|
|
11267
|
+
if (state.pending.size >= wakeThreshold)
|
|
11268
|
+
wake();
|
|
11269
|
+
const drain = state.tail
|
|
11270
|
+
.then(async () => {
|
|
11271
|
+
await batchReady;
|
|
11272
|
+
while (history === this._gidPeersHistory) {
|
|
11273
|
+
const batch = [...state.pending].slice(0, GID_PEER_HISTORY_CLEANUP_BATCH_SIZE);
|
|
11274
|
+
if (batch.length === 0)
|
|
11275
|
+
break;
|
|
11276
|
+
for (const gid of batch)
|
|
11277
|
+
state.pending.delete(gid);
|
|
11278
|
+
try {
|
|
11279
|
+
await this.reclaimDeadGidPeerHistory(batch, history);
|
|
11280
|
+
}
|
|
11281
|
+
catch (error) {
|
|
11282
|
+
warn(`Failed to reclaim gid peer history: ${String(error)}`);
|
|
11283
|
+
if (history === this._gidPeersHistory) {
|
|
11284
|
+
for (const gid of batch)
|
|
11285
|
+
this.deleteGidPeerHistory(gid);
|
|
11286
|
+
}
|
|
11287
|
+
}
|
|
11288
|
+
}
|
|
11289
|
+
})
|
|
11290
|
+
.catch((error) => {
|
|
11291
|
+
warn(`Failed to reclaim gid peer history: ${String(error)}`);
|
|
11292
|
+
});
|
|
11293
|
+
state.tail = drain.finally(() => {
|
|
11294
|
+
state.draining = false;
|
|
11295
|
+
if (history !== this._gidPeersHistory) {
|
|
11296
|
+
state.pending.clear();
|
|
11297
|
+
}
|
|
11298
|
+
if (history === this._gidPeersHistory &&
|
|
11299
|
+
state.pending.size > 0 &&
|
|
11300
|
+
state === this._gidPeerHistoryCleanupState) {
|
|
11301
|
+
this.scheduleDeadGidPeerHistoryReclaim([]);
|
|
11302
|
+
}
|
|
11303
|
+
});
|
|
11304
|
+
}
|
|
11006
11305
|
async canAppend(entry) {
|
|
11007
11306
|
try {
|
|
11008
11307
|
if (!entry.meta.data) {
|
|
@@ -11460,6 +11759,7 @@ let SharedLog = (() => {
|
|
|
11460
11759
|
this.recentlyRebalanced?.clear();
|
|
11461
11760
|
this.uniqueReplicators?.clear();
|
|
11462
11761
|
this._topicSubscribersCache?.clear();
|
|
11762
|
+
this._localReachablePeerHashesCache?.clear();
|
|
11463
11763
|
this._closeController.abort();
|
|
11464
11764
|
clearInterval(this.interval);
|
|
11465
11765
|
this._liveness?.stopReplicatorLivenessSweep();
|
|
@@ -17033,7 +17333,8 @@ let SharedLog = (() => {
|
|
|
17033
17333
|
}
|
|
17034
17334
|
async rebalanceAll(options) {
|
|
17035
17335
|
if (options?.clearCache) {
|
|
17036
|
-
this._gidPeersHistory
|
|
17336
|
+
this._gidPeersHistory = new Map();
|
|
17337
|
+
this.resetGidPeerHistoryCleanupState();
|
|
17037
17338
|
this._nativeSharedLogState?.clearGidPeers();
|
|
17038
17339
|
this._nativeBackbone?.clearGidPeers();
|
|
17039
17340
|
}
|
|
@@ -17446,9 +17747,11 @@ let SharedLog = (() => {
|
|
|
17446
17747
|
}
|
|
17447
17748
|
const fromHash = evt.detail.from.hashcode();
|
|
17448
17749
|
const subscriptionEpoch = this._peerSessions.rotate(fromHash, "opening");
|
|
17449
|
-
this.remoteBlocks.onReachable(evt.detail.from);
|
|
17450
17750
|
this._peerSessions.blockReplicationInfo(fromHash);
|
|
17451
17751
|
this.invalidateSharedLogTopicSubscribersCache();
|
|
17752
|
+
// Invalidate the local reachability snapshot before waking block reads;
|
|
17753
|
+
// their forced resolver runs synchronously from the reachable event.
|
|
17754
|
+
this.remoteBlocks.onReachable(evt.detail.from);
|
|
17452
17755
|
await this.handleSubscriptionChange(evt.detail.from, evt.detail.topics, true, subscriptionEpoch, evt.detail.session);
|
|
17453
17756
|
}
|
|
17454
17757
|
async rebalanceParticipation(ownershipLifecycleController = this.captureReplicationOwnershipLifecycle(), rebalanceParticipationDebounced = this.rebalanceParticipationDebounced) {
|