@peerbit/shared-log 15.0.0 → 16.0.1
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.map +1 -1
- package/dist/src/checked-prune.js +12 -0
- package/dist/src/checked-prune.js.map +1 -1
- package/dist/src/index.d.ts +7 -13
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +81 -598
- package/dist/src/index.js.map +1 -1
- package/dist/src/instance-lifecycle.d.ts.map +1 -1
- package/dist/src/instance-lifecycle.js +9 -0
- package/dist/src/instance-lifecycle.js.map +1 -1
- package/dist/src/peer-session.d.ts.map +1 -1
- package/dist/src/peer-session.js +9 -0
- package/dist/src/peer-session.js.map +1 -1
- package/dist/src/pid.d.ts +0 -2
- package/dist/src/pid.d.ts.map +1 -1
- package/dist/src/pid.js +0 -5
- package/dist/src/pid.js.map +1 -1
- package/dist/src/replication-announcement.d.ts +3 -106
- package/dist/src/replication-announcement.d.ts.map +1 -1
- package/dist/src/replication-announcement.js +0 -521
- package/dist/src/replication-announcement.js.map +1 -1
- package/dist/src/replication-info-v2-receive.d.ts +14 -43
- package/dist/src/replication-info-v2-receive.d.ts.map +1 -1
- package/dist/src/replication-info-v2-receive.js +24 -199
- package/dist/src/replication-info-v2-receive.js.map +1 -1
- package/dist/src/replication-info-v2-send.d.ts +0 -1
- package/dist/src/replication-info-v2-send.d.ts.map +1 -1
- package/dist/src/replication-info-v2-send.js +0 -6
- package/dist/src/replication-info-v2-send.js.map +1 -1
- package/dist/src/replication.d.ts +0 -1
- package/dist/src/replication.d.ts.map +1 -1
- package/dist/src/replication.js +2 -17
- package/dist/src/replication.js.map +1 -1
- package/dist/src/role.d.ts +0 -4
- package/dist/src/role.d.ts.map +1 -1
- package/dist/src/role.js +0 -9
- package/dist/src/role.js.map +1 -1
- package/dist/src/sync/factory.d.ts +0 -1
- package/dist/src/sync/factory.d.ts.map +1 -1
- package/dist/src/sync/factory.js +20 -36
- package/dist/src/sync/factory.js.map +1 -1
- package/package.json +8 -8
- package/src/checked-prune.ts +12 -0
- package/src/index.ts +104 -817
- package/src/instance-lifecycle.ts +9 -0
- package/src/peer-session.ts +9 -0
- package/src/pid.ts +0 -6
- package/src/replication-announcement.ts +8 -739
- package/src/replication-info-v2-receive.ts +25 -271
- package/src/replication-info-v2-send.ts +0 -7
- package/src/replication.ts +1 -20
- package/src/role.ts +0 -13
- package/src/sync/factory.ts +24 -38
package/dist/src/index.js
CHANGED
|
@@ -66,7 +66,7 @@ import { NativeBackboneWriteThroughBlockStore } from "./native-write-through-blo
|
|
|
66
66
|
import { PeerSessionRegistry } from "./peer-session.js";
|
|
67
67
|
import { PIDReplicationController } from "./pid.js";
|
|
68
68
|
import { EntryReplicatedU32, EntryReplicatedU64, ReplicationIntent, ReplicationRangeIndexableU32, ReplicationRangeIndexableU64, ReplicationRangeMessage, appromixateCoverage, calculateCoverage, countCoveringRangesSameOwner, createAssignedRangesQuery, debounceAggregationChanges, getAllMergeCandiates, getCoverSet, getSamples, isEntryReplicated, isMatured, isReplicationRangeMessage, mergeRanges, minimumWidthToCover, shouldAssigneToRangeBoundary as shouldAssignToRangeBoundary, toRebalance, } from "./ranges.js";
|
|
69
|
-
import { ReplicationAnnouncementCoordinator
|
|
69
|
+
import { ReplicationAnnouncementCoordinator } from "./replication-announcement.js";
|
|
70
70
|
import { createReplicationDomainHash, } from "./replication-domain-hash.js";
|
|
71
71
|
import { createReplicationDomainTime, } from "./replication-domain-time.js";
|
|
72
72
|
import {} from "./replication-domain.js";
|
|
@@ -74,7 +74,6 @@ import { ReplicationInfoV2ReceiveCoordinator } from "./replication-info-v2-recei
|
|
|
74
74
|
import { ReplicationInfoV2SendCoordinator } from "./replication-info-v2-send.js";
|
|
75
75
|
import { AbsoluteReplicas, AddedReplicationInfoV2Message, AddedReplicationSegmentMessage, AllReplicatingSegmentsMessage, FullReplicationInfoV2Message, MinReplicas, ReplicationError, ReplicationPingMessage, RequestReplicationInfoMessage, RequestReplicationInfoV2Message, ResponseRoleMessage, StoppedReplicating, StoppedReplicationInfoV2Message, decodeReplicas, encodeReplicas, isReplicationInfoV2Message, maxReplicas, } from "./replication.js";
|
|
76
76
|
import { ReplicatorLivenessMonitor } from "./replicator-liveness.js";
|
|
77
|
-
import { Observer, Replicator } from "./role.js";
|
|
78
77
|
import { createSyncronizer } from "./sync/factory.js";
|
|
79
78
|
import { emitSyncProfileDuration, emitSyncProfileEvent, syncProfileStart, } from "./sync/profile.js";
|
|
80
79
|
import { ConfirmEntriesMessage, SYNC_MESSAGE_PRIORITY, SimpleSyncronizer, } from "./sync/simple.js";
|
|
@@ -645,7 +644,6 @@ let SharedLog = (() => {
|
|
|
645
644
|
recentlyRebalanced;
|
|
646
645
|
uniqueReplicators;
|
|
647
646
|
_replicatorJoinEmitted;
|
|
648
|
-
_replicatorsReconciled;
|
|
649
647
|
/* private _totalParticipation!: number; */
|
|
650
648
|
// gid -> coordinate -> publicKeyHash list (of owners)
|
|
651
649
|
_gidPeersHistory;
|
|
@@ -682,24 +680,17 @@ let SharedLog = (() => {
|
|
|
682
680
|
_pendingIHave;
|
|
683
681
|
// public key hash to range id to range
|
|
684
682
|
pendingMaturity; // map of peerId to timeout
|
|
685
|
-
//
|
|
686
|
-
// FENCING role
|
|
687
|
-
//
|
|
688
|
-
// the
|
|
689
|
-
//
|
|
690
|
-
//
|
|
691
|
-
// NOT subsumed: within one (lifecycle, session, epoch, unblocked) regime
|
|
692
|
-
// the epoch token is constant across every message from the peer, so only
|
|
693
|
-
// the two apply-lane timestamp comparisons can drop an older reset
|
|
694
|
-
// delivered after a newer add (unordered pubsub / retransmits) — an
|
|
695
|
-
// identity token carries no order. Deletion is blocked until
|
|
696
|
-
// replication-info messages carry sender-authoritative sequence numbers
|
|
697
|
-
// (stage-5 schema change); the `receive admission replication-info
|
|
698
|
-
// ordering watermark` pins fail if the read sites are removed before then.
|
|
699
|
-
latestReplicationInfoMessage;
|
|
683
|
+
// The legacy replication-info ordering watermark (fence B8) is deleted:
|
|
684
|
+
// its FENCING role was subsumed by the per-peer receive epoch, blocked set
|
|
685
|
+
// and session identity, and its intra-epoch ORDERING role existed only for
|
|
686
|
+
// the legacy apply lanes. The V2 lane orders by sender-authoritative
|
|
687
|
+
// sequence numbers, and legacy frames are dropped unconditionally at the
|
|
688
|
+
// B1 gate before any side effect.
|
|
700
689
|
// The replication-info blocked set (fence B5) lives on the peer-session
|
|
701
690
|
// registry: unsubscribed peers whose replication-info is ignored until a
|
|
702
691
|
// reconnect barrier commits. See PeerSessionRegistry._replicationInfoBlockedPeers.
|
|
692
|
+
// V2 recovery scheduler state, one row per open peer session (B12: the
|
|
693
|
+
// legacy request scheduler that shared this map is deleted).
|
|
703
694
|
_replicationInfoRequestByPeer;
|
|
704
695
|
_replicationInfoApplyQueueByPeer;
|
|
705
696
|
// One in-flight targeted subscriber-snapshot request per session-less peer.
|
|
@@ -841,7 +832,6 @@ let SharedLog = (() => {
|
|
|
841
832
|
for (const hash of this._checkedPrune?.retries.keys() ?? []) {
|
|
842
833
|
this._checkedPrune.clearRetry(hash);
|
|
843
834
|
}
|
|
844
|
-
this._announcements.cancelCurrentReplicationStateAnnouncementRetry();
|
|
845
835
|
this.joinWarmup.cancelAllJoinWarmupTargets();
|
|
846
836
|
for (const timer of this._repairRetryTimers) {
|
|
847
837
|
clearTimeout(timer);
|
|
@@ -1707,25 +1697,9 @@ let SharedLog = (() => {
|
|
|
1707
1697
|
}
|
|
1708
1698
|
createReplicationAnnouncementCoordinator() {
|
|
1709
1699
|
return new ReplicationAnnouncementCoordinator({
|
|
1710
|
-
// Route re-entrant queueing through the owner so coordinator spies keep
|
|
1711
|
-
// observing it (the poison guard assertions in events.spec.ts depend on
|
|
1712
|
-
// this).
|
|
1713
|
-
queueCurrentReplicationStateAnnouncementRepair: () => this._announcements.queueCurrentReplicationStateAnnouncementRepair(),
|
|
1714
|
-
queueCurrentReplicationStateAnnouncementRetry: (error) => this._announcements.queueCurrentReplicationStateAnnouncementRetry(error),
|
|
1715
1700
|
enqueueReplicationInfoV2: (message) => this._v2Send.enqueue(message),
|
|
1716
|
-
isLegacyReplicationInfoEnabled: () => this.legacyReplicationInfoEnabled,
|
|
1717
|
-
isClosed: () => this.closed,
|
|
1718
|
-
getCloseSignal: () => this._closeController.signal,
|
|
1719
|
-
getMyReplicationSegments: () => this.getMyReplicationSegments(),
|
|
1720
|
-
validatePersistedReplicationRangeSnapshot: (ranges) => this.validatePersistedReplicationRangeSnapshot(ranges),
|
|
1721
|
-
getSubscribers: () => this.node.services.pubsub.getSubscribers(this.topic),
|
|
1722
|
-
getSelfHash: () => this.node.identity.publicKey.hashcode(),
|
|
1723
|
-
isBlockedPeer: (hash) => this._peerSessions.isReplicationInfoBlocked(hash),
|
|
1724
|
-
getRpc: () => this.rpc,
|
|
1725
1701
|
captureReplicationOwnershipLifecycle: () => this.captureReplicationOwnershipLifecycle(),
|
|
1726
1702
|
throwIfReplicationOwnershipLifecycleInactive: (controller) => this.throwIfReplicationOwnershipLifecycleInactive(controller),
|
|
1727
|
-
isAdaptiveReplicating: () => this._isAdaptiveReplicating,
|
|
1728
|
-
callRebalanceParticipationDebounced: () => this.rebalanceParticipationDebounced?.call(),
|
|
1729
1703
|
});
|
|
1730
1704
|
}
|
|
1731
1705
|
createReplicationInfoV2SendCoordinator() {
|
|
@@ -1885,7 +1859,6 @@ let SharedLog = (() => {
|
|
|
1885
1859
|
this._admittedPruneRemoves = new Set();
|
|
1886
1860
|
this._pendingIHave = new Map();
|
|
1887
1861
|
this._pendingIHaveCallbacks = new Set();
|
|
1888
|
-
this.latestReplicationInfoMessage = new Map();
|
|
1889
1862
|
this._replicationInfoRequestByPeer = new Map();
|
|
1890
1863
|
this._subscriberSnapshotRequestsByPeer = new Map();
|
|
1891
1864
|
this._replicationInfoApplyQueueByPeer = new Map();
|
|
@@ -1925,7 +1898,6 @@ let SharedLog = (() => {
|
|
|
1925
1898
|
this.recentlyRebalanced = new Cache({ max: 1e4, ttl: 1e5 });
|
|
1926
1899
|
this.uniqueReplicators = new Set();
|
|
1927
1900
|
this._replicatorJoinEmitted = new Set();
|
|
1928
|
-
this._replicatorsReconciled = false;
|
|
1929
1901
|
this._liveness = this.createReplicatorLivenessMonitor();
|
|
1930
1902
|
this._v2Receive = this.createReplicationInfoV2ReceiveCoordinator();
|
|
1931
1903
|
this._v2Send = this.createReplicationInfoV2SendCoordinator();
|
|
@@ -1985,25 +1957,9 @@ let SharedLog = (() => {
|
|
|
1985
1957
|
this._nativeStrictDurableDocumentRecoveryDeferred ??= false;
|
|
1986
1958
|
this._nativeStrictDurableTransactionsClosing ??= false;
|
|
1987
1959
|
}
|
|
1988
|
-
get compatibility() {
|
|
1989
|
-
// B12: the open option was removed and any defined value rejects at
|
|
1990
|
-
// open(); this is permanently undefined and dies with the residual
|
|
1991
|
-
// gates in a later cleanup stage.
|
|
1992
|
-
return this._logProperties?.compatibility;
|
|
1993
|
-
}
|
|
1994
|
-
/**
|
|
1995
|
-
* Legacy replication-info is an explicit compatibility fallback. Current
|
|
1996
|
-
* logs never infer or re-enable it from a remote peer's capabilities.
|
|
1997
|
-
*/
|
|
1998
|
-
get legacyReplicationInfoEnabled() {
|
|
1999
|
-
return this.compatibility !== undefined && this.compatibility < 10;
|
|
2000
|
-
}
|
|
2001
1960
|
get isAdaptiveReplicating() {
|
|
2002
1961
|
return this._isAdaptiveReplicating;
|
|
2003
1962
|
}
|
|
2004
|
-
get v8Behaviour() {
|
|
2005
|
-
return (this.compatibility ?? Number.MAX_VALUE) < 9;
|
|
2006
|
-
}
|
|
2007
1963
|
getFanoutChannelOptions(options) {
|
|
2008
1964
|
return {
|
|
2009
1965
|
...DEFAULT_SHARED_LOG_FANOUT_CHANNEL_OPTIONS,
|
|
@@ -2692,8 +2648,7 @@ let SharedLog = (() => {
|
|
|
2692
2648
|
!delivery &&
|
|
2693
2649
|
!requireRecipients &&
|
|
2694
2650
|
(leaders.size === 0 || (leaders.size === 1 && leaders.has(selfHash)))) {
|
|
2695
|
-
const allowSubscriberFallback = this.syncronizer instanceof SimpleSyncronizer
|
|
2696
|
-
(this.compatibility ?? Number.MAX_VALUE) < 10;
|
|
2651
|
+
const allowSubscriberFallback = this.syncronizer instanceof SimpleSyncronizer;
|
|
2697
2652
|
if (!allowSubscriberFallback) {
|
|
2698
2653
|
return;
|
|
2699
2654
|
}
|
|
@@ -2816,8 +2771,7 @@ let SharedLog = (() => {
|
|
|
2816
2771
|
// sweeps can still backfill peers that missed the initial delivery.
|
|
2817
2772
|
const set = new Set(leaders.keys());
|
|
2818
2773
|
let hasRemotePeers = set.has(selfHash) ? set.size > 1 : set.size > 0;
|
|
2819
|
-
const allowSubscriberFallback = this.syncronizer instanceof SimpleSyncronizer
|
|
2820
|
-
(this.compatibility ?? Number.MAX_VALUE) < 10;
|
|
2774
|
+
const allowSubscriberFallback = this.syncronizer instanceof SimpleSyncronizer;
|
|
2821
2775
|
if (!hasRemotePeers && allowSubscriberFallback) {
|
|
2822
2776
|
try {
|
|
2823
2777
|
const subscribers = await this._getTopicSubscribers(this.topic);
|
|
@@ -3185,21 +3139,6 @@ let SharedLog = (() => {
|
|
|
3185
3139
|
context: this.cloneLeaderSelectionContext(context),
|
|
3186
3140
|
};
|
|
3187
3141
|
}
|
|
3188
|
-
// @deprecated
|
|
3189
|
-
getRoleFromReplicationSegments(segments) {
|
|
3190
|
-
if (segments.length > 1) {
|
|
3191
|
-
throw new Error("More than one replication segment found. Can only use one segment for compatbility with v8");
|
|
3192
|
-
}
|
|
3193
|
-
if (segments.length > 0) {
|
|
3194
|
-
const segment = segments[0].toReplicationRange();
|
|
3195
|
-
return new Replicator({
|
|
3196
|
-
factor: segment.factor / MAX_U32,
|
|
3197
|
-
offset: segment.offset / MAX_U32,
|
|
3198
|
-
});
|
|
3199
|
-
}
|
|
3200
|
-
// TODO this is not accurate but might be good enough
|
|
3201
|
-
return new Observer();
|
|
3202
|
-
}
|
|
3203
3142
|
isTerminating() {
|
|
3204
3143
|
return (this.acceptsParentAttachments === false ||
|
|
3205
3144
|
this.closed ||
|
|
@@ -3398,10 +3337,7 @@ let SharedLog = (() => {
|
|
|
3398
3337
|
this.rebalanceParticipationDebounced = rebalanceParticipationDebounced;
|
|
3399
3338
|
}
|
|
3400
3339
|
onRebalanceParticipationError(error) {
|
|
3401
|
-
if (this.closed ||
|
|
3402
|
-
isNotStartedError(error) ||
|
|
3403
|
-
(isTransientReplicationAnnouncementError(error) &&
|
|
3404
|
-
this._announcements._replicationAnnouncementRetryPending)) {
|
|
3340
|
+
if (this.closed || isNotStartedError(error)) {
|
|
3405
3341
|
return;
|
|
3406
3342
|
}
|
|
3407
3343
|
// Debounced invocations run from an un-awaited timer. Throwing here would
|
|
@@ -4274,7 +4210,7 @@ let SharedLog = (() => {
|
|
|
4274
4210
|
this.throwIfReplicationOwnershipLifecycleInactive(replicationOwnershipLifecycleController);
|
|
4275
4211
|
return this.withReceiveOwnershipMutationQueue(() => this.addReplicationRangeUnlocked(ranges, from, options, replicationOwnershipLifecycleController), replicationOwnershipLifecycleController);
|
|
4276
4212
|
}
|
|
4277
|
-
async addReplicationRangeUnlocked(ranges, from, { reset, checkDuplicates, timestamp: ts, rebalance,
|
|
4213
|
+
async addReplicationRangeUnlocked(ranges, from, { reset, checkDuplicates, timestamp: ts, rebalance, allowOrderedReplacementPairs, onConfirmedDurableStateChanged, onDurableApplyCommitted, shouldApply, } = {}, replicationOwnershipLifecycleController = this.captureReplicationOwnershipLifecycle()) {
|
|
4278
4214
|
this.validateReplicationRangeAnnouncement(ranges);
|
|
4279
4215
|
this.throwIfReplicationOwnershipLifecycleInactive(replicationOwnershipLifecycleController);
|
|
4280
4216
|
// This predicate is intentionally checked inside the global ownership lane.
|
|
@@ -4299,14 +4235,16 @@ let SharedLog = (() => {
|
|
|
4299
4235
|
const count = (incomingRangeCountsById.get(range.idString) ?? 0) + 1;
|
|
4300
4236
|
incomingRangeCountsById.set(range.idString, count);
|
|
4301
4237
|
if (count > 1 &&
|
|
4302
|
-
(!
|
|
4238
|
+
(!allowOrderedReplacementPairs || reset === true || count > 2)) {
|
|
4303
4239
|
throw new Error(`Duplicate replication range id in announcement: ${range.idString}`);
|
|
4304
4240
|
}
|
|
4305
|
-
//
|
|
4306
|
-
//
|
|
4307
|
-
//
|
|
4308
|
-
//
|
|
4309
|
-
//
|
|
4241
|
+
// Rolling-upgrade relaxation on the live V2 Added path: a peer may
|
|
4242
|
+
// still express a non-reset replacement as the retired geometry
|
|
4243
|
+
// followed by the current geometry under one id. The sender is already
|
|
4244
|
+
// authorized to replace that id with the final item, so collapsing an
|
|
4245
|
+
// EXACT two-item incremental pair to its last item accepts the older
|
|
4246
|
+
// wire shape without broadening authority. Deliberately narrow — reset
|
|
4247
|
+
// announcements and any run longer than two still fail as duplicates.
|
|
4310
4248
|
incomingRangesById.set(range.idString, range);
|
|
4311
4249
|
}
|
|
4312
4250
|
const incomingRanges = [...incomingRangesById.values()];
|
|
@@ -9175,10 +9113,7 @@ let SharedLog = (() => {
|
|
|
9175
9113
|
this._logProperties = options;
|
|
9176
9114
|
this.domain = options?.domain
|
|
9177
9115
|
? options.domain(this)
|
|
9178
|
-
: createReplicationDomainHash(
|
|
9179
|
-
options.compatibility < 10
|
|
9180
|
-
? "u32"
|
|
9181
|
-
: "u64")(this);
|
|
9116
|
+
: createReplicationDomainHash("u64")(this);
|
|
9182
9117
|
this.indexableDomain = createIndexableDomainFromResolution(this.domain.resolution);
|
|
9183
9118
|
this._respondToIHaveTimeout = options?.respondToIHaveTimeout ?? 2e4;
|
|
9184
9119
|
this._checkedPrune = new CheckedPruneCoordinator();
|
|
@@ -9186,7 +9121,6 @@ let SharedLog = (() => {
|
|
|
9186
9121
|
this._admittedPruneRemoves = new Set();
|
|
9187
9122
|
this._pendingIHave = new Map();
|
|
9188
9123
|
this._pendingIHaveCallbacks = new Set();
|
|
9189
|
-
this.latestReplicationInfoMessage = new Map();
|
|
9190
9124
|
this._replicationInfoRequestByPeer = new Map();
|
|
9191
9125
|
this._subscriberSnapshotRequestsByPeer = new Map();
|
|
9192
9126
|
// Terminal close/drop drains the previous lifecycle before another open can
|
|
@@ -9247,7 +9181,6 @@ let SharedLog = (() => {
|
|
|
9247
9181
|
this.recentlyRebalanced = new Cache({ max: 1e4, ttl: 1e5 });
|
|
9248
9182
|
this.uniqueReplicators = new Set();
|
|
9249
9183
|
this._replicatorJoinEmitted = new Set();
|
|
9250
|
-
this._replicatorsReconciled = false;
|
|
9251
9184
|
// Deserialized instances never ran the constructor; create the monitor and
|
|
9252
9185
|
// coordinator lazily on first open. Reopens keep the SAME instances so
|
|
9253
9186
|
// stale async continuations observe resets via property lookup.
|
|
@@ -9255,7 +9188,6 @@ let SharedLog = (() => {
|
|
|
9255
9188
|
this._liveness.resetForOpen();
|
|
9256
9189
|
this._lastLocalAppendAt = 0;
|
|
9257
9190
|
this._announcements ??= this.createReplicationAnnouncementCoordinator();
|
|
9258
|
-
this._announcements.resetForOpen();
|
|
9259
9191
|
this._v2Receive ??= this.createReplicationInfoV2ReceiveCoordinator();
|
|
9260
9192
|
this._v2Receive.resetForOpen();
|
|
9261
9193
|
this._v2Send ??= this.createReplicationInfoV2SendCoordinator();
|
|
@@ -9297,13 +9229,6 @@ let SharedLog = (() => {
|
|
|
9297
9229
|
throw new Error("waitForReplicatorRequestMaxAttempts must be a positive number");
|
|
9298
9230
|
}
|
|
9299
9231
|
this._closeController = new AbortController();
|
|
9300
|
-
if (this.legacyReplicationInfoEnabled) {
|
|
9301
|
-
this._announcements.setupReplicationAnnouncementRetryFunction();
|
|
9302
|
-
this._announcements.setupReplicationAnnouncementRepairFunction();
|
|
9303
|
-
}
|
|
9304
|
-
else {
|
|
9305
|
-
this._announcements.cancelCurrentReplicationStateAnnouncementRetry();
|
|
9306
|
-
}
|
|
9307
9232
|
this._closeController.signal.addEventListener("abort", () => {
|
|
9308
9233
|
for (const [_peer, state] of this._replicationInfoRequestByPeer) {
|
|
9309
9234
|
if (state.timer)
|
|
@@ -9658,7 +9583,6 @@ let SharedLog = (() => {
|
|
|
9658
9583
|
peerSupportsRawExchangeHeads: (peer) => this.peerSupportsRawExchangeHeads(peer),
|
|
9659
9584
|
sendRawExchangeHeads: (hashes, to, sendOptions) => this.trySendFusedRawExchangeHeads(hashes, to, sendOptions),
|
|
9660
9585
|
warn,
|
|
9661
|
-
compatibility: this._logProperties?.compatibility,
|
|
9662
9586
|
resolution: this.domain.resolution,
|
|
9663
9587
|
sync: options?.sync,
|
|
9664
9588
|
syncronizer: options?.syncronizer,
|
|
@@ -10228,14 +10152,8 @@ let SharedLog = (() => {
|
|
|
10228
10152
|
// pubsub snapshot below may create subscription fallback sessions.
|
|
10229
10153
|
const subscriberDiscoveryPromise = this._getTopicSubscribers(this.topic);
|
|
10230
10154
|
const existingSubscribersPromise = this.node.services.pubsub.getSubscribers(this.topic);
|
|
10231
|
-
const replicationLifecycleController = this._instanceLifecycle?.membershipLifecycleController;
|
|
10232
10155
|
// We do this here, because these calls requires this.closed == false
|
|
10233
10156
|
void this.pruneOfflineReplicators()
|
|
10234
|
-
.then(() => {
|
|
10235
|
-
if (this.isReplicationLifecycleActive(replicationLifecycleController)) {
|
|
10236
|
-
this._replicatorsReconciled = true;
|
|
10237
|
-
}
|
|
10238
|
-
})
|
|
10239
10157
|
.catch((error) => {
|
|
10240
10158
|
if (isNotStartedError(error)) {
|
|
10241
10159
|
return;
|
|
@@ -10399,7 +10317,7 @@ let SharedLog = (() => {
|
|
|
10399
10317
|
cleanupPeerDisconnectTracking(peerHash, ownershipLifecycleController = this.captureReplicationOwnershipLifecycle()) {
|
|
10400
10318
|
const peerSession = this._peerSessions.current(peerHash);
|
|
10401
10319
|
const preserveV2Session = peerSession?.phase === "open" && peerSession.isActive();
|
|
10402
|
-
if (
|
|
10320
|
+
if (!preserveV2Session) {
|
|
10403
10321
|
this.cancelReplicationInfoRequests(peerHash);
|
|
10404
10322
|
}
|
|
10405
10323
|
this._liveness._replicatorLivenessFailures.delete(peerHash);
|
|
@@ -10425,11 +10343,8 @@ let SharedLog = (() => {
|
|
|
10425
10343
|
}
|
|
10426
10344
|
advanceReplicationInfoRecoveryEpoch(peerHash) {
|
|
10427
10345
|
// Handlers admitted before a successful peer removal must not restore state
|
|
10428
|
-
// when they eventually reach the apply lane.
|
|
10429
|
-
// ordering watermark with the local epoch so a later arrival can be
|
|
10430
|
-
// accepted without comparing its clock to this receiver's clock.
|
|
10346
|
+
// when they eventually reach the apply lane.
|
|
10431
10347
|
const receiveEpoch = this._peerSessions.advanceReceiveEpoch(peerHash);
|
|
10432
|
-
this.latestReplicationInfoMessage.delete(peerHash);
|
|
10433
10348
|
const peerSession = this._peerSessions.current(peerHash);
|
|
10434
10349
|
if (peerSession?.phase === "open") {
|
|
10435
10350
|
this._v2Receive.advanceRecovery({
|
|
@@ -11138,13 +11053,6 @@ let SharedLog = (() => {
|
|
|
11138
11053
|
firstError ??= error;
|
|
11139
11054
|
}
|
|
11140
11055
|
};
|
|
11141
|
-
// A borsh-deserialized instance that is closed before ever being opened
|
|
11142
|
-
// has no coordinators (they are created in open()); the old inline code
|
|
11143
|
-
// only touched plain fields here and never threw.
|
|
11144
|
-
captureSync(() => this._announcements?.cancelCurrentReplicationStateAnnouncementRetry());
|
|
11145
|
-
if (this._announcements) {
|
|
11146
|
-
this._announcements.replicationAnnouncementRetryDebounced = undefined;
|
|
11147
|
-
}
|
|
11148
11056
|
captureSync(() => {
|
|
11149
11057
|
if (this._wireSyncSession) {
|
|
11150
11058
|
this._wireSyncSession.unregisterTopic(this.topic);
|
|
@@ -11227,7 +11135,6 @@ let SharedLog = (() => {
|
|
|
11227
11135
|
captureSync(() => {
|
|
11228
11136
|
this._pendingIHave?.clear();
|
|
11229
11137
|
this._pendingIHaveCallbacks?.clear();
|
|
11230
|
-
this.latestReplicationInfoMessage?.clear();
|
|
11231
11138
|
this._peerSessions?.clearReceiveEpochsForClose();
|
|
11232
11139
|
this._peerSessions?.clearCleanupGatesForClose();
|
|
11233
11140
|
this._activeReceiveHandlersByPeer?.clear();
|
|
@@ -11317,7 +11224,6 @@ let SharedLog = (() => {
|
|
|
11317
11224
|
await pruneRemoveTerminalFence.drained;
|
|
11318
11225
|
await this.drainPendingIHaveCallbacks();
|
|
11319
11226
|
this.ensureNativeDurabilityRuntimeState();
|
|
11320
|
-
this._announcements.cancelCurrentReplicationStateAnnouncementRetry();
|
|
11321
11227
|
}
|
|
11322
11228
|
catch (error) {
|
|
11323
11229
|
// The terminal preamble has already disabled parent attachments and the
|
|
@@ -11360,17 +11266,7 @@ let SharedLog = (() => {
|
|
|
11360
11266
|
}
|
|
11361
11267
|
}, 2_000);
|
|
11362
11268
|
try {
|
|
11363
|
-
|
|
11364
|
-
const resets = [this._v2Send.sendTerminalReset(abort.signal)];
|
|
11365
|
-
if (this.legacyReplicationInfoEnabled) {
|
|
11366
|
-
resets.push(this.rpc
|
|
11367
|
-
.send(reset, {
|
|
11368
|
-
priority: CONVERGENCE_MESSAGE_PRIORITY,
|
|
11369
|
-
signal: abort.signal,
|
|
11370
|
-
})
|
|
11371
|
-
.catch(() => { }));
|
|
11372
|
-
}
|
|
11373
|
-
await Promise.all(resets);
|
|
11269
|
+
await this._v2Send.sendTerminalReset(abort.signal);
|
|
11374
11270
|
}
|
|
11375
11271
|
finally {
|
|
11376
11272
|
clearTimeout(abortTimer);
|
|
@@ -11462,7 +11358,6 @@ let SharedLog = (() => {
|
|
|
11462
11358
|
await replicationRangeTerminalFence.drained;
|
|
11463
11359
|
await pruneRemoveTerminalFence.drained;
|
|
11464
11360
|
await this.drainPendingIHaveCallbacks();
|
|
11465
|
-
this._announcements.cancelCurrentReplicationStateAnnouncementRetry();
|
|
11466
11361
|
}
|
|
11467
11362
|
catch (error) {
|
|
11468
11363
|
// The terminal preamble is not safely reversible. Preserve the fence until
|
|
@@ -11489,17 +11384,7 @@ let SharedLog = (() => {
|
|
|
11489
11384
|
}
|
|
11490
11385
|
}, 2_000);
|
|
11491
11386
|
try {
|
|
11492
|
-
|
|
11493
|
-
const resets = [this._v2Send.sendTerminalReset(abort.signal)];
|
|
11494
|
-
if (this.legacyReplicationInfoEnabled) {
|
|
11495
|
-
resets.push(this.rpc
|
|
11496
|
-
.send(reset, {
|
|
11497
|
-
priority: CONVERGENCE_MESSAGE_PRIORITY,
|
|
11498
|
-
signal: abort.signal,
|
|
11499
|
-
})
|
|
11500
|
-
.catch(() => { }));
|
|
11501
|
-
}
|
|
11502
|
-
await Promise.all(resets);
|
|
11387
|
+
await this._v2Send.sendTerminalReset(abort.signal);
|
|
11503
11388
|
}
|
|
11504
11389
|
finally {
|
|
11505
11390
|
clearTimeout(abortTimer);
|
|
@@ -11923,15 +11808,15 @@ let SharedLog = (() => {
|
|
|
11923
11808
|
if (!context.from) {
|
|
11924
11809
|
throw new Error("Missing from in update role message");
|
|
11925
11810
|
}
|
|
11926
|
-
if (
|
|
11927
|
-
|
|
11928
|
-
|
|
11929
|
-
|
|
11930
|
-
|
|
11931
|
-
|
|
11932
|
-
//
|
|
11933
|
-
//
|
|
11934
|
-
//
|
|
11811
|
+
if (msg instanceof RequestReplicationInfoMessage ||
|
|
11812
|
+
msg instanceof ResponseRoleMessage ||
|
|
11813
|
+
msg instanceof AllReplicatingSegmentsMessage ||
|
|
11814
|
+
msg instanceof AddedReplicationSegmentMessage ||
|
|
11815
|
+
msg instanceof StoppedReplicating) {
|
|
11816
|
+
// These variants remain registered decode tombstones, but logs fail
|
|
11817
|
+
// closed unconditionally before leases, synchronizer work, liveness,
|
|
11818
|
+
// watermarks or mutations (B12: the compatibility opens that once
|
|
11819
|
+
// admitted them reject at open()).
|
|
11935
11820
|
return;
|
|
11936
11821
|
}
|
|
11937
11822
|
// Snapshot receive ownership before any async handler gets a chance to
|
|
@@ -11961,29 +11846,20 @@ let SharedLog = (() => {
|
|
|
11961
11846
|
const receiveOwnershipRevision = this._instanceLifecycle?._receiveOwnershipRevision ?? 0;
|
|
11962
11847
|
const receiveOwnershipLifecycleController = this.captureReplicationOwnershipLifecycle();
|
|
11963
11848
|
const receiveReplicationInfoReceiveEpoch = this._peerSessions.receiveEpoch(receiveFromHash);
|
|
11964
|
-
if (msg instanceof
|
|
11965
|
-
msg = msg.toReplicationInfoMessage(); // migration
|
|
11966
|
-
}
|
|
11967
|
-
if (msg instanceof AllReplicatingSegmentsMessage ||
|
|
11968
|
-
msg instanceof AddedReplicationSegmentMessage ||
|
|
11969
|
-
msg instanceof FullReplicationInfoV2Message ||
|
|
11849
|
+
if (msg instanceof FullReplicationInfoV2Message ||
|
|
11970
11850
|
msg instanceof AddedReplicationInfoV2Message) {
|
|
11971
11851
|
// Bound decoded untrusted vectors before per-peer/global mutation
|
|
11972
11852
|
// queues, trusted-replicator authorization, or liveness side effects.
|
|
11973
11853
|
this.validateReplicationRangeAnnouncement(msg.segments);
|
|
11974
11854
|
}
|
|
11975
|
-
else if (msg instanceof
|
|
11976
|
-
msg instanceof StoppedReplicationInfoV2Message) {
|
|
11855
|
+
else if (msg instanceof StoppedReplicationInfoV2Message) {
|
|
11977
11856
|
// Bound the raw decoded vector before deduplication can hide the
|
|
11978
11857
|
// allocation cost, and before liveness or apply-queue side effects.
|
|
11979
11858
|
this.validateStoppedReplicationAnnouncement(msg.segmentIds);
|
|
11980
11859
|
}
|
|
11981
11860
|
if (!context.from.equals(this.node.identity.publicKey) &&
|
|
11982
11861
|
!(msg instanceof RequestReplicationInfoV2Message) &&
|
|
11983
|
-
!isReplicationInfoV2Message(msg)
|
|
11984
|
-
!(msg instanceof AllReplicatingSegmentsMessage) &&
|
|
11985
|
-
!(msg instanceof AddedReplicationSegmentMessage) &&
|
|
11986
|
-
!(msg instanceof StoppedReplicating)) {
|
|
11862
|
+
!isReplicationInfoV2Message(msg)) {
|
|
11987
11863
|
this._liveness.markReplicatorActivity(receiveFromHash);
|
|
11988
11864
|
}
|
|
11989
11865
|
const syncProfile = this._logProperties?.sync?.profile;
|
|
@@ -13405,16 +13281,6 @@ let SharedLog = (() => {
|
|
|
13405
13281
|
else if (msg instanceof ReplicationPingMessage) {
|
|
13406
13282
|
// No-op: used as an ACKed unicast liveness probe.
|
|
13407
13283
|
}
|
|
13408
|
-
else if (msg instanceof RequestReplicationInfoMessage) {
|
|
13409
|
-
await this.handleRequestReplicationInfo(msg, laneRequestContext, lane);
|
|
13410
|
-
}
|
|
13411
|
-
else if (msg instanceof AllReplicatingSegmentsMessage ||
|
|
13412
|
-
msg instanceof AddedReplicationSegmentMessage) {
|
|
13413
|
-
await this.handleReplicationInfoAnnouncement(msg, laneRequestContext, lane);
|
|
13414
|
-
}
|
|
13415
|
-
else if (msg instanceof StoppedReplicating) {
|
|
13416
|
-
await this.handleStoppedReplicating(msg, laneRequestContext, lane);
|
|
13417
|
-
}
|
|
13418
13284
|
else {
|
|
13419
13285
|
throw new Error("Unexpected message");
|
|
13420
13286
|
}
|
|
@@ -13600,68 +13466,6 @@ let SharedLog = (() => {
|
|
|
13600
13466
|
}
|
|
13601
13467
|
}
|
|
13602
13468
|
}
|
|
13603
|
-
async handleRequestReplicationInfo(_msg, context, lane) {
|
|
13604
|
-
const receiveFromHash = lane.fromHash;
|
|
13605
|
-
const receiveSession = lane.session;
|
|
13606
|
-
const receiveReplicationLifecycleController = lane.lifecycleController;
|
|
13607
|
-
if (context.from.equals(this.node.identity.publicKey)) {
|
|
13608
|
-
return;
|
|
13609
|
-
}
|
|
13610
|
-
const replicationLifecycleController = receiveReplicationLifecycleController;
|
|
13611
|
-
if (!replicationLifecycleController ||
|
|
13612
|
-
!this._peerSessions.isReceiveAdmissionOpen(receiveFromHash, receiveSession, replicationLifecycleController)) {
|
|
13613
|
-
return;
|
|
13614
|
-
}
|
|
13615
|
-
let replicationSegments;
|
|
13616
|
-
try {
|
|
13617
|
-
replicationSegments = await this.getMyReplicationSegments();
|
|
13618
|
-
}
|
|
13619
|
-
catch (error) {
|
|
13620
|
-
if (!this._peerSessions.isReceiveAdmissionOpen(receiveFromHash, receiveSession, replicationLifecycleController) &&
|
|
13621
|
-
isNotStartedError(error)) {
|
|
13622
|
-
return;
|
|
13623
|
-
}
|
|
13624
|
-
throw error;
|
|
13625
|
-
}
|
|
13626
|
-
if (!this._peerSessions.isReceiveAdmissionOpen(receiveFromHash, receiveSession, replicationLifecycleController)) {
|
|
13627
|
-
return;
|
|
13628
|
-
}
|
|
13629
|
-
const segments = replicationSegments.map((x) => x.toReplicationRange());
|
|
13630
|
-
this.validatePersistedReplicationRangeSnapshot(segments);
|
|
13631
|
-
this._v2Send.enqueueSnapshotForPeer(receiveFromHash);
|
|
13632
|
-
await this.rpc
|
|
13633
|
-
.send(new AllReplicatingSegmentsMessage({ segments }), {
|
|
13634
|
-
mode: new AcknowledgeDelivery({
|
|
13635
|
-
to: [context.from],
|
|
13636
|
-
redundancy: 1,
|
|
13637
|
-
}),
|
|
13638
|
-
signal: replicationLifecycleController.signal,
|
|
13639
|
-
})
|
|
13640
|
-
.catch((error) => this.handleReplicationLifecycleSendError(error, replicationLifecycleController));
|
|
13641
|
-
if (!this._peerSessions.isReceiveAdmissionOpen(receiveFromHash, receiveSession, replicationLifecycleController)) {
|
|
13642
|
-
return;
|
|
13643
|
-
}
|
|
13644
|
-
// for backwards compatibility (v8) remove this when we are sure that all nodes are v9+
|
|
13645
|
-
if (this.v8Behaviour) {
|
|
13646
|
-
const role = this.getRoleFromReplicationSegments(replicationSegments);
|
|
13647
|
-
if (role instanceof Replicator) {
|
|
13648
|
-
const fixedSettings = !this._isAdaptiveReplicating;
|
|
13649
|
-
if (fixedSettings) {
|
|
13650
|
-
await this.rpc
|
|
13651
|
-
.send(new ResponseRoleMessage({
|
|
13652
|
-
role,
|
|
13653
|
-
}), {
|
|
13654
|
-
mode: new SilentDelivery({
|
|
13655
|
-
to: [context.from],
|
|
13656
|
-
redundancy: 1,
|
|
13657
|
-
}),
|
|
13658
|
-
signal: replicationLifecycleController.signal,
|
|
13659
|
-
})
|
|
13660
|
-
.catch((error) => this.handleReplicationLifecycleSendError(error, replicationLifecycleController));
|
|
13661
|
-
}
|
|
13662
|
-
}
|
|
13663
|
-
}
|
|
13664
|
-
}
|
|
13665
13469
|
async handleReplicationInfoV2Announcement(msg, context, lane) {
|
|
13666
13470
|
const from = context.from;
|
|
13667
13471
|
const fromHash = lane.fromHash;
|
|
@@ -13713,7 +13517,7 @@ let SharedLog = (() => {
|
|
|
13713
13517
|
reset: msg instanceof FullReplicationInfoV2Message,
|
|
13714
13518
|
checkDuplicates: true,
|
|
13715
13519
|
timestamp: Number(context.message.header.timestamp),
|
|
13716
|
-
|
|
13520
|
+
allowOrderedReplacementPairs: msg instanceof AddedReplicationInfoV2Message,
|
|
13717
13521
|
shouldApply: () => {
|
|
13718
13522
|
mutationGateChecked = true;
|
|
13719
13523
|
mutationGateAdmitted = exactGate();
|
|
@@ -13797,187 +13601,12 @@ let SharedLog = (() => {
|
|
|
13797
13601
|
// A committed V2 announcement is applied progress: the peer answers,
|
|
13798
13602
|
// so recovery re-solicitation may restart from the base interval.
|
|
13799
13603
|
this.resetReplicationInfoV2RecoveryEscalation(fromHash);
|
|
13800
|
-
if (msg instanceof FullReplicationInfoV2Message &&
|
|
13801
|
-
this.legacyReplicationInfoEnabled) {
|
|
13802
|
-
this.cancelReplicationInfoRequests(fromHash);
|
|
13803
|
-
}
|
|
13804
13604
|
});
|
|
13805
13605
|
}
|
|
13806
13606
|
finally {
|
|
13807
13607
|
this._v2Receive.release(admission);
|
|
13808
13608
|
}
|
|
13809
13609
|
}
|
|
13810
|
-
async handleReplicationInfoAnnouncement(msg, context, lane) {
|
|
13811
|
-
const receiveFromHash = lane.fromHash;
|
|
13812
|
-
const receiveSession = lane.session;
|
|
13813
|
-
const receiveReplicationLifecycleController = lane.lifecycleController;
|
|
13814
|
-
const receiveReplicationInfoReceiveEpoch = lane.receiveEpoch;
|
|
13815
|
-
const peerReceiveLease = lane.lease;
|
|
13816
|
-
if (context.from.equals(this.node.identity.publicKey)) {
|
|
13817
|
-
return;
|
|
13818
|
-
}
|
|
13819
|
-
const replicationInfoMessage = msg;
|
|
13820
|
-
// Process replication updates even if the sender isn't yet considered "ready" by
|
|
13821
|
-
// `Program.waitFor()`. Dropping these messages can lead to missing replicator info
|
|
13822
|
-
// (and downstream `waitForReplicator()` timeouts) under timing-sensitive joins.
|
|
13823
|
-
const from = context.from;
|
|
13824
|
-
const fromHash = from.hashcode();
|
|
13825
|
-
if (this._v2Receive.isLegacyCutover(receiveSession)) {
|
|
13826
|
-
if (receiveSession) {
|
|
13827
|
-
this._v2Receive.noteLegacyAnnouncement({
|
|
13828
|
-
peerHash: fromHash,
|
|
13829
|
-
peerSession: receiveSession,
|
|
13830
|
-
receiveEpoch: receiveReplicationInfoReceiveEpoch,
|
|
13831
|
-
senderTransportSession: context.message.header.session,
|
|
13832
|
-
transportTimestamp: context.message.header.timestamp,
|
|
13833
|
-
message: msg,
|
|
13834
|
-
});
|
|
13835
|
-
}
|
|
13836
|
-
return;
|
|
13837
|
-
}
|
|
13838
|
-
// Pre-lane gate: lifecycle -> receive-epoch -> blocked, exactly the
|
|
13839
|
-
// legacy order. isMembershipActiveFor is the unit-pinned fold of
|
|
13840
|
-
// isReplicationLifecycleActive; isReceiveEpochCurrent is the
|
|
13841
|
-
// relocated `===`-with-`?? null`. The DELIBERATE absence of a
|
|
13842
|
-
// subscription-epoch term is preserved: the lease already validated
|
|
13843
|
-
// it, and the in-lane recheck owns post-await staleness.
|
|
13844
|
-
if (!this._instanceLifecycle.isMembershipActiveFor(receiveReplicationLifecycleController) ||
|
|
13845
|
-
!this._peerSessions.isReceiveEpochCurrent(receiveFromHash, receiveReplicationInfoReceiveEpoch) ||
|
|
13846
|
-
this._peerSessions.isReplicationInfoBlocked(fromHash)) {
|
|
13847
|
-
return;
|
|
13848
|
-
}
|
|
13849
|
-
const messageTimestamp = context.message.header.timestamp;
|
|
13850
|
-
peerReceiveLease.release();
|
|
13851
|
-
await this.withReplicationInfoApplyQueue(fromHash, async () => {
|
|
13852
|
-
try {
|
|
13853
|
-
// The peer may have unsubscribed after this message was queued.
|
|
13854
|
-
// In-lane gate: lifecycle -> subscription-epoch -> receive-epoch
|
|
13855
|
-
// -> blocked, term for term as before the session migration.
|
|
13856
|
-
if (!this._instanceLifecycle.isMembershipActiveFor(receiveReplicationLifecycleController) ||
|
|
13857
|
-
!this._peerSessions.isCurrent(fromHash, receiveSession) ||
|
|
13858
|
-
!this._peerSessions.isReceiveEpochCurrent(fromHash, receiveReplicationInfoReceiveEpoch) ||
|
|
13859
|
-
this._peerSessions.isReplicationInfoBlocked(fromHash)) {
|
|
13860
|
-
return;
|
|
13861
|
-
}
|
|
13862
|
-
if (receiveSession && this._v2Receive.isLegacyCutover(receiveSession)) {
|
|
13863
|
-
this._v2Receive.noteLegacyAnnouncement({
|
|
13864
|
-
peerHash: fromHash,
|
|
13865
|
-
peerSession: receiveSession,
|
|
13866
|
-
receiveEpoch: receiveReplicationInfoReceiveEpoch,
|
|
13867
|
-
senderTransportSession: context.message.header.session,
|
|
13868
|
-
transportTimestamp: context.message.header.timestamp,
|
|
13869
|
-
message: msg,
|
|
13870
|
-
});
|
|
13871
|
-
return;
|
|
13872
|
-
}
|
|
13873
|
-
// Process in-order to avoid races where repeated reset messages arrive
|
|
13874
|
-
// concurrently and trigger spurious "added" diffs / rebalancing.
|
|
13875
|
-
const prev = this.latestReplicationInfoMessage.get(fromHash);
|
|
13876
|
-
if (prev && prev > messageTimestamp) {
|
|
13877
|
-
return;
|
|
13878
|
-
}
|
|
13879
|
-
this.latestReplicationInfoMessage.set(fromHash, messageTimestamp);
|
|
13880
|
-
if (this.closed) {
|
|
13881
|
-
return;
|
|
13882
|
-
}
|
|
13883
|
-
const reset = msg instanceof AllReplicatingSegmentsMessage;
|
|
13884
|
-
const result = await this.addReplicationRange(replicationInfoMessage.segments.map((x) => x.toReplicationRangeIndexable(from)), from, {
|
|
13885
|
-
reset,
|
|
13886
|
-
checkDuplicates: true,
|
|
13887
|
-
timestamp: Number(messageTimestamp),
|
|
13888
|
-
allowLegacyOrderedReplacementPairs: msg instanceof AddedReplicationSegmentMessage,
|
|
13889
|
-
});
|
|
13890
|
-
if (result === undefined) {
|
|
13891
|
-
return;
|
|
13892
|
-
}
|
|
13893
|
-
this._liveness.markReplicatorActivity(fromHash);
|
|
13894
|
-
// If the peer reports any replication segments, stop re-requesting.
|
|
13895
|
-
// (Empty reports can be transient during startup.)
|
|
13896
|
-
if (replicationInfoMessage.segments.length > 0) {
|
|
13897
|
-
this.cancelReplicationInfoRequests(fromHash);
|
|
13898
|
-
}
|
|
13899
|
-
}
|
|
13900
|
-
catch (e) {
|
|
13901
|
-
if (isNotStartedError(e)) {
|
|
13902
|
-
return;
|
|
13903
|
-
}
|
|
13904
|
-
logger.error(`Failed to apply replication settings from '${fromHash}': ${e?.message ?? e}`);
|
|
13905
|
-
}
|
|
13906
|
-
});
|
|
13907
|
-
}
|
|
13908
|
-
async handleStoppedReplicating(msg, context, lane) {
|
|
13909
|
-
const receiveFromHash = lane.fromHash;
|
|
13910
|
-
const receiveSession = lane.session;
|
|
13911
|
-
const receiveReplicationLifecycleController = lane.lifecycleController;
|
|
13912
|
-
const receiveReplicationInfoReceiveEpoch = lane.receiveEpoch;
|
|
13913
|
-
const peerReceiveLease = lane.lease;
|
|
13914
|
-
const from = context.from;
|
|
13915
|
-
const segmentIds = msg.segmentIds;
|
|
13916
|
-
if (from.equals(this.node.identity.publicKey)) {
|
|
13917
|
-
return;
|
|
13918
|
-
}
|
|
13919
|
-
const fromHash = from.hashcode();
|
|
13920
|
-
if (this._v2Receive.isLegacyCutover(receiveSession)) {
|
|
13921
|
-
if (receiveSession) {
|
|
13922
|
-
this._v2Receive.noteLegacyAnnouncement({
|
|
13923
|
-
peerHash: fromHash,
|
|
13924
|
-
peerSession: receiveSession,
|
|
13925
|
-
receiveEpoch: receiveReplicationInfoReceiveEpoch,
|
|
13926
|
-
senderTransportSession: context.message.header.session,
|
|
13927
|
-
transportTimestamp: context.message.header.timestamp,
|
|
13928
|
-
message: msg,
|
|
13929
|
-
});
|
|
13930
|
-
}
|
|
13931
|
-
return;
|
|
13932
|
-
}
|
|
13933
|
-
// Same pre-lane gate shape as Added/All above (and the same
|
|
13934
|
-
// intentional absence of a subscription-epoch term).
|
|
13935
|
-
if (!this._instanceLifecycle.isMembershipActiveFor(receiveReplicationLifecycleController) ||
|
|
13936
|
-
!this._peerSessions.isReceiveEpochCurrent(receiveFromHash, receiveReplicationInfoReceiveEpoch) ||
|
|
13937
|
-
this._peerSessions.isReplicationInfoBlocked(fromHash)) {
|
|
13938
|
-
return;
|
|
13939
|
-
}
|
|
13940
|
-
const messageTimestamp = context.message.header.timestamp;
|
|
13941
|
-
peerReceiveLease.release();
|
|
13942
|
-
await this.withReplicationInfoApplyQueue(fromHash, async () => {
|
|
13943
|
-
if (!this._instanceLifecycle.isMembershipActiveFor(receiveReplicationLifecycleController) ||
|
|
13944
|
-
!this._peerSessions.isCurrent(fromHash, receiveSession) ||
|
|
13945
|
-
!this._peerSessions.isReceiveEpochCurrent(fromHash, receiveReplicationInfoReceiveEpoch) ||
|
|
13946
|
-
this._peerSessions.isReplicationInfoBlocked(fromHash)) {
|
|
13947
|
-
return;
|
|
13948
|
-
}
|
|
13949
|
-
if (receiveSession && this._v2Receive.isLegacyCutover(receiveSession)) {
|
|
13950
|
-
this._v2Receive.noteLegacyAnnouncement({
|
|
13951
|
-
peerHash: fromHash,
|
|
13952
|
-
peerSession: receiveSession,
|
|
13953
|
-
receiveEpoch: receiveReplicationInfoReceiveEpoch,
|
|
13954
|
-
senderTransportSession: context.message.header.session,
|
|
13955
|
-
transportTimestamp: context.message.header.timestamp,
|
|
13956
|
-
message: msg,
|
|
13957
|
-
});
|
|
13958
|
-
return;
|
|
13959
|
-
}
|
|
13960
|
-
const previousTimestamp = this.latestReplicationInfoMessage.get(fromHash);
|
|
13961
|
-
if (previousTimestamp && previousTimestamp > messageTimestamp) {
|
|
13962
|
-
return;
|
|
13963
|
-
}
|
|
13964
|
-
this.latestReplicationInfoMessage.set(fromHash, messageTimestamp);
|
|
13965
|
-
if (this.closed) {
|
|
13966
|
-
return;
|
|
13967
|
-
}
|
|
13968
|
-
const rangesToRemove = await this.resolveReplicationRangesFromIdsAndKey(segmentIds, from);
|
|
13969
|
-
await this.removeReplicationRanges(rangesToRemove, from);
|
|
13970
|
-
this._liveness.markReplicatorActivity(fromHash);
|
|
13971
|
-
const timestamp = BigInt(+new Date());
|
|
13972
|
-
for (const range of rangesToRemove) {
|
|
13973
|
-
this.replicationChangeDebounceFn.add({
|
|
13974
|
-
range,
|
|
13975
|
-
type: "removed",
|
|
13976
|
-
timestamp,
|
|
13977
|
-
});
|
|
13978
|
-
}
|
|
13979
|
-
});
|
|
13980
|
-
}
|
|
13981
13610
|
async calculateTotalParticipation(options) {
|
|
13982
13611
|
if (options?.sum) {
|
|
13983
13612
|
const ranges = await this.replicationIndex.iterate().all();
|
|
@@ -14334,36 +13963,21 @@ let SharedLog = (() => {
|
|
|
14334
13963
|
return;
|
|
14335
13964
|
}
|
|
14336
13965
|
requestAttempts++;
|
|
14337
|
-
|
|
14338
|
-
|
|
14339
|
-
|
|
14340
|
-
|
|
14341
|
-
|
|
14342
|
-
|
|
14343
|
-
|
|
14344
|
-
if (isNotStartedError(e)) {
|
|
14345
|
-
return;
|
|
14346
|
-
}
|
|
14347
|
-
logger.error(e?.toString?.() ?? String(e));
|
|
13966
|
+
const peerHash = key.hashcode();
|
|
13967
|
+
const peerSession = this._peerSessions.current(peerHash);
|
|
13968
|
+
if (peerSession?.phase === "open") {
|
|
13969
|
+
this._v2Receive.resumeParkedRequest({
|
|
13970
|
+
peerHash,
|
|
13971
|
+
peerSession,
|
|
13972
|
+
receiveEpoch: this._peerSessions.receiveEpoch(peerHash),
|
|
14348
13973
|
});
|
|
14349
13974
|
}
|
|
14350
|
-
else {
|
|
14351
|
-
|
|
14352
|
-
|
|
14353
|
-
|
|
14354
|
-
|
|
14355
|
-
|
|
14356
|
-
peerSession,
|
|
14357
|
-
receiveEpoch: this._peerSessions.receiveEpoch(peerHash),
|
|
14358
|
-
});
|
|
14359
|
-
}
|
|
14360
|
-
else if (peerSession === null || peerSession.phase === "departing") {
|
|
14361
|
-
// A peer can be known to routing before its SharedLog topic
|
|
14362
|
-
// subscription has been observed. Legacy requests used to bootstrap
|
|
14363
|
-
// that case directly; V2 needs an authoritative Subscribe snapshot
|
|
14364
|
-
// before it can create a fenced PeerSession and request a Full.
|
|
14365
|
-
requestSubscriberSnapshot();
|
|
14366
|
-
}
|
|
13975
|
+
else if (peerSession === null || peerSession.phase === "departing") {
|
|
13976
|
+
// A peer can be known to routing before its SharedLog topic
|
|
13977
|
+
// subscription has been observed. Legacy requests used to bootstrap
|
|
13978
|
+
// that case directly; V2 needs an authoritative Subscribe snapshot
|
|
13979
|
+
// before it can create a fenced PeerSession and request a Full.
|
|
13980
|
+
requestSubscriberSnapshot();
|
|
14367
13981
|
}
|
|
14368
13982
|
if (requestAttempts < maxRequestAttempts) {
|
|
14369
13983
|
requestTimer = setTimeout(requestReplicationInfo, requestIntervalMs);
|
|
@@ -16185,7 +15799,7 @@ let SharedLog = (() => {
|
|
|
16185
15799
|
*/
|
|
16186
15800
|
resetReplicationInfoV2RecoveryEscalation(peerHash) {
|
|
16187
15801
|
const state = this._replicationInfoRequestByPeer.get(peerHash);
|
|
16188
|
-
if (!state
|
|
15802
|
+
if (!state) {
|
|
16189
15803
|
return;
|
|
16190
15804
|
}
|
|
16191
15805
|
state.attempts = 0;
|
|
@@ -16244,7 +15858,11 @@ let SharedLog = (() => {
|
|
|
16244
15858
|
return;
|
|
16245
15859
|
}
|
|
16246
15860
|
const receiveEpoch = this._peerSessions.receiveEpoch(peerHash);
|
|
16247
|
-
if (!this._v2Receive.isRequestParked({
|
|
15861
|
+
if (!this._v2Receive.isRequestParked({
|
|
15862
|
+
peerHash,
|
|
15863
|
+
peerSession,
|
|
15864
|
+
receiveEpoch,
|
|
15865
|
+
})) {
|
|
16248
15866
|
// Active, or a bounded request cycle is still running its own
|
|
16249
15867
|
// exponential retries. Keep polling for the next park.
|
|
16250
15868
|
state.parkedSinceMs = undefined;
|
|
@@ -16274,65 +15892,20 @@ let SharedLog = (() => {
|
|
|
16274
15892
|
};
|
|
16275
15893
|
tick();
|
|
16276
15894
|
}
|
|
15895
|
+
/**
|
|
15896
|
+
* Collapsed B12 shell: the legacy request-polling body (bounded
|
|
15897
|
+
* RequestReplicationInfoMessage ticks) is deleted; V2 recovery is the
|
|
15898
|
+
* only scheduler. Retained as a named seam rather than inlined at the
|
|
15899
|
+
* callers because the liveness monitor wiring and several suites
|
|
15900
|
+
* stub/spy it by name.
|
|
15901
|
+
*/
|
|
16277
15902
|
scheduleReplicationInfoRequests(peer, replicationLifecycleController = this._instanceLifecycle
|
|
16278
15903
|
?.membershipLifecycleController) {
|
|
16279
15904
|
if (!replicationLifecycleController ||
|
|
16280
15905
|
!this.isReplicationLifecycleActive(replicationLifecycleController)) {
|
|
16281
15906
|
return;
|
|
16282
15907
|
}
|
|
16283
|
-
|
|
16284
|
-
this.scheduleReplicationInfoV2Recovery(peer, replicationLifecycleController);
|
|
16285
|
-
return;
|
|
16286
|
-
}
|
|
16287
|
-
const peerHash = peer.hashcode();
|
|
16288
|
-
const requestStates = this._replicationInfoRequestByPeer;
|
|
16289
|
-
if (requestStates.has(peerHash)) {
|
|
16290
|
-
return;
|
|
16291
|
-
}
|
|
16292
|
-
const state = {
|
|
16293
|
-
attempts: 0,
|
|
16294
|
-
};
|
|
16295
|
-
requestStates.set(peerHash, state);
|
|
16296
|
-
const cancel = () => {
|
|
16297
|
-
if (requestStates.get(peerHash) !== state) {
|
|
16298
|
-
return;
|
|
16299
|
-
}
|
|
16300
|
-
if (state.timer) {
|
|
16301
|
-
clearTimeout(state.timer);
|
|
16302
|
-
}
|
|
16303
|
-
requestStates.delete(peerHash);
|
|
16304
|
-
};
|
|
16305
|
-
const intervalMs = Math.max(50, this.waitForReplicatorRequestIntervalMs);
|
|
16306
|
-
const maxAttempts = this.waitForReplicatorRequestMaxAttempts ??
|
|
16307
|
-
Math.max(WAIT_FOR_REPLICATOR_REQUEST_MIN_ATTEMPTS, Math.ceil(this.waitForReplicatorTimeout / intervalMs));
|
|
16308
|
-
const tick = () => {
|
|
16309
|
-
if (!this.isReplicationLifecycleActive(replicationLifecycleController)) {
|
|
16310
|
-
cancel();
|
|
16311
|
-
return;
|
|
16312
|
-
}
|
|
16313
|
-
state.attempts++;
|
|
16314
|
-
this.rpc
|
|
16315
|
-
.send(new RequestReplicationInfoMessage(), {
|
|
16316
|
-
mode: new AcknowledgeDelivery({ redundancy: 1, to: [peer] }),
|
|
16317
|
-
signal: replicationLifecycleController.signal,
|
|
16318
|
-
})
|
|
16319
|
-
.catch((e) => {
|
|
16320
|
-
// Best-effort: missing peers / unopened RPC should not fail join flows.
|
|
16321
|
-
if (isNotStartedError(e) ||
|
|
16322
|
-
(replicationLifecycleController.signal.aborted &&
|
|
16323
|
-
e instanceof AbortError)) {
|
|
16324
|
-
return;
|
|
16325
|
-
}
|
|
16326
|
-
logger.error(e?.toString?.() ?? String(e));
|
|
16327
|
-
});
|
|
16328
|
-
if (state.attempts >= maxAttempts) {
|
|
16329
|
-
cancel();
|
|
16330
|
-
return;
|
|
16331
|
-
}
|
|
16332
|
-
state.timer = setTimeout(tick, intervalMs);
|
|
16333
|
-
state.timer.unref?.();
|
|
16334
|
-
};
|
|
16335
|
-
tick();
|
|
15908
|
+
this.scheduleReplicationInfoV2Recovery(peer, replicationLifecycleController);
|
|
16336
15909
|
}
|
|
16337
15910
|
async handleSubscriptionChange(publicKey, topics, subscribed, subscriptionEpoch, subscriptionTransportSession) {
|
|
16338
15911
|
if (!topics.includes(this.topic)) {
|
|
@@ -16395,10 +15968,6 @@ let SharedLog = (() => {
|
|
|
16395
15968
|
!ownsSubscriptionEpoch()) {
|
|
16396
15969
|
return;
|
|
16397
15970
|
}
|
|
16398
|
-
// The timestamp watermark belongs to the previous subscription epoch.
|
|
16399
|
-
// Sender clocks are not synchronized, so carrying a local unsubscribe
|
|
16400
|
-
// timestamp forward could reject every valid announcement after reconnect.
|
|
16401
|
-
this.latestReplicationInfoMessage.delete(peerHash);
|
|
16402
15971
|
this._pendingReplicatorLeaveByPeer.delete(peerHash);
|
|
16403
15972
|
const openingCapabilities = this._openingSyncCapabilitiesByPeer.get(peerHash);
|
|
16404
15973
|
if (openingCapabilities?.epoch === expectedSubscriptionEpoch) {
|
|
@@ -16441,11 +16010,6 @@ let SharedLog = (() => {
|
|
|
16441
16010
|
this._peerSessions.blockReplicationInfo(peerHash);
|
|
16442
16011
|
const disconnectedWarmupSession = this.joinWarmup._warmupSessionsByTarget.get(peerHash) ?? null;
|
|
16443
16012
|
this.joinWarmup.cancelJoinWarmupTarget(peerHash);
|
|
16444
|
-
const now = BigInt(+new Date());
|
|
16445
|
-
const previous = this.latestReplicationInfoMessage.get(peerHash);
|
|
16446
|
-
if (!previous || previous < now) {
|
|
16447
|
-
this.latestReplicationInfoMessage.set(peerHash, now);
|
|
16448
|
-
}
|
|
16449
16013
|
let removed = false;
|
|
16450
16014
|
try {
|
|
16451
16015
|
// Unsubscribe can race with the peer's final replication reset message.
|
|
@@ -16487,84 +16051,20 @@ let SharedLog = (() => {
|
|
|
16487
16051
|
this.promoteReplicationInfoV2ReceiveCapability(publicKey, expectedSubscriptionEpoch);
|
|
16488
16052
|
// Decode, sender and authenticated apply readiness are separate bits. An
|
|
16489
16053
|
// ACKed capability advert is the local half of receiver-led negotiation;
|
|
16490
|
-
// readiness is promoted
|
|
16491
|
-
// This
|
|
16492
|
-
//
|
|
16054
|
+
// readiness is promoted through the coordinator once the ACK arrives.
|
|
16055
|
+
// This keeps capability ACK latency off the subscription callback's
|
|
16056
|
+
// critical path.
|
|
16493
16057
|
const receiveEpoch = this._peerSessions.receiveEpoch(peerHash);
|
|
16494
|
-
|
|
16058
|
+
// B12: there is no legacy startup work to order ahead of RequestV2, so
|
|
16059
|
+
// the two-phase legacy barrier is gone — an ACKed advert promotes
|
|
16060
|
+
// readiness through the coordinator as soon as it lands.
|
|
16061
|
+
this._v2Receive.advertiseLocalCapability({
|
|
16495
16062
|
target: publicKey,
|
|
16496
16063
|
peerSession: expectedSubscriptionEpoch,
|
|
16497
16064
|
receiveEpoch,
|
|
16498
16065
|
signal: replicationLifecycleController.signal,
|
|
16499
16066
|
});
|
|
16500
|
-
|
|
16501
|
-
// Current logs have no legacy startup work to order ahead of RequestV2.
|
|
16502
|
-
// Releasing is synchronous and exact-session fenced; the ACK may still
|
|
16503
|
-
// arrive later and promote readiness through the coordinator.
|
|
16504
|
-
localCapabilityAdvertisement.releaseLegacyBarrier();
|
|
16505
|
-
this.scheduleReplicationInfoV2Recovery(publicKey, replicationLifecycleController);
|
|
16506
|
-
return;
|
|
16507
|
-
}
|
|
16508
|
-
try {
|
|
16509
|
-
let replicationSegments;
|
|
16510
|
-
try {
|
|
16511
|
-
replicationSegments = await this.getMyReplicationSegments();
|
|
16512
|
-
}
|
|
16513
|
-
catch (error) {
|
|
16514
|
-
if (!this.isReplicationLifecycleActive(replicationLifecycleController) &&
|
|
16515
|
-
isNotStartedError(error)) {
|
|
16516
|
-
return;
|
|
16517
|
-
}
|
|
16518
|
-
throw error;
|
|
16519
|
-
}
|
|
16520
|
-
if (!this.isReplicationLifecycleActive(replicationLifecycleController) ||
|
|
16521
|
-
!ownsSubscriptionEpoch()) {
|
|
16522
|
-
return;
|
|
16523
|
-
}
|
|
16524
|
-
if (replicationSegments.length > 0) {
|
|
16525
|
-
const segments = replicationSegments.map((x) => x.toReplicationRange());
|
|
16526
|
-
this.validatePersistedReplicationRangeSnapshot(segments);
|
|
16527
|
-
await this.rpc
|
|
16528
|
-
.send(new AllReplicatingSegmentsMessage({
|
|
16529
|
-
segments,
|
|
16530
|
-
}), {
|
|
16531
|
-
mode: new AcknowledgeDelivery({
|
|
16532
|
-
redundancy: 1,
|
|
16533
|
-
to: [publicKey],
|
|
16534
|
-
}),
|
|
16535
|
-
signal: replicationLifecycleController.signal,
|
|
16536
|
-
})
|
|
16537
|
-
.catch((error) => this.handleReplicationLifecycleSendError(error, replicationLifecycleController));
|
|
16538
|
-
if (!this.isReplicationLifecycleActive(replicationLifecycleController) ||
|
|
16539
|
-
!ownsSubscriptionEpoch()) {
|
|
16540
|
-
return;
|
|
16541
|
-
}
|
|
16542
|
-
if (this.v8Behaviour) {
|
|
16543
|
-
// for backwards compatibility
|
|
16544
|
-
await this.rpc
|
|
16545
|
-
.send(new ResponseRoleMessage({
|
|
16546
|
-
role: this.getRoleFromReplicationSegments(replicationSegments),
|
|
16547
|
-
}), {
|
|
16548
|
-
mode: new AcknowledgeDelivery({
|
|
16549
|
-
redundancy: 1,
|
|
16550
|
-
to: [publicKey],
|
|
16551
|
-
}),
|
|
16552
|
-
signal: replicationLifecycleController.signal,
|
|
16553
|
-
})
|
|
16554
|
-
.catch((error) => this.handleReplicationLifecycleSendError(error, replicationLifecycleController));
|
|
16555
|
-
}
|
|
16556
|
-
}
|
|
16557
|
-
// Keep legacy request-based discovery independent of the capability ACK.
|
|
16558
|
-
// This makes mixed-version joins resilient to timing-sensitive delivery/order
|
|
16559
|
-
// issues where we may miss the remote peer's initial announcement.
|
|
16560
|
-
if (this.isReplicationLifecycleActive(replicationLifecycleController) &&
|
|
16561
|
-
ownsSubscriptionEpoch()) {
|
|
16562
|
-
this.scheduleReplicationInfoRequests(publicKey, replicationLifecycleController);
|
|
16563
|
-
}
|
|
16564
|
-
}
|
|
16565
|
-
finally {
|
|
16566
|
-
localCapabilityAdvertisement.releaseLegacyBarrier();
|
|
16567
|
-
}
|
|
16067
|
+
this.scheduleReplicationInfoV2Recovery(publicKey, replicationLifecycleController);
|
|
16568
16068
|
}
|
|
16569
16069
|
getClampedReplicas(customValue) {
|
|
16570
16070
|
if (!customValue) {
|
|
@@ -17540,14 +17040,6 @@ let SharedLog = (() => {
|
|
|
17540
17040
|
const subscriptionEpoch = this._peerSessions.rotate(fromHash, "departing");
|
|
17541
17041
|
this._peerSessions.blockReplicationInfo(fromHash);
|
|
17542
17042
|
this._recentRepairDispatch.delete(fromHash);
|
|
17543
|
-
// Keep a per-peer timestamp watermark when we observe an unsubscribe. This
|
|
17544
|
-
// prevents late/out-of-order replication-info messages from re-introducing
|
|
17545
|
-
// stale segments for a peer that has already left the topic.
|
|
17546
|
-
const now = BigInt(+new Date());
|
|
17547
|
-
const prev = this.latestReplicationInfoMessage.get(fromHash);
|
|
17548
|
-
if (!prev || prev < now) {
|
|
17549
|
-
this.latestReplicationInfoMessage.set(fromHash, now);
|
|
17550
|
-
}
|
|
17551
17043
|
this.invalidateSharedLogTopicSubscribersCache();
|
|
17552
17044
|
return this.handleSubscriptionChange(evt.detail.from, evt.detail.topics, false, subscriptionEpoch);
|
|
17553
17045
|
}
|
|
@@ -17663,22 +17155,13 @@ let SharedLog = (() => {
|
|
|
17663
17155
|
if (!canReplicate) {
|
|
17664
17156
|
return false;
|
|
17665
17157
|
}
|
|
17666
|
-
|
|
17667
|
-
|
|
17668
|
-
|
|
17669
|
-
|
|
17670
|
-
|
|
17671
|
-
|
|
17672
|
-
|
|
17673
|
-
return false;
|
|
17674
|
-
}
|
|
17675
|
-
catch (error) {
|
|
17676
|
-
if (isTransientReplicationAnnouncementError(error) &&
|
|
17677
|
-
this._announcements._replicationAnnouncementRetryPending) {
|
|
17678
|
-
return false;
|
|
17679
|
-
}
|
|
17680
|
-
throw error;
|
|
17681
|
-
}
|
|
17158
|
+
await this.startAnnounceReplicating([dynamicRange], {
|
|
17159
|
+
checkDuplicates: false,
|
|
17160
|
+
reset: false,
|
|
17161
|
+
shouldApply: isCurrent,
|
|
17162
|
+
}, ownershipLifecycleController);
|
|
17163
|
+
if (!isCurrent())
|
|
17164
|
+
return false;
|
|
17682
17165
|
/* await this._updateRole(newRole, onRoleChange); */
|
|
17683
17166
|
if (isCurrent()) {
|
|
17684
17167
|
void rebalanceParticipationDebounced?.call();
|