@peerbit/shared-log 14.0.0 → 16.0.0
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 +11 -15
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +84 -596
- package/dist/src/index.js.map +1 -1
- package/dist/src/replication-announcement.d.ts +6 -110
- package/dist/src/replication-announcement.d.ts.map +1 -1
- package/dist/src/replication-announcement.js +2 -504
- package/dist/src/replication-announcement.js.map +1 -1
- package/dist/src/replication-info-mutation.d.ts +34 -0
- package/dist/src/replication-info-mutation.d.ts.map +1 -0
- package/dist/src/replication-info-mutation.js +2 -0
- package/dist/src/replication-info-mutation.js.map +1 -0
- package/dist/src/replication-info-v2-receive.d.ts +10 -41
- package/dist/src/replication-info-v2-receive.d.ts.map +1 -1
- package/dist/src/replication-info-v2-receive.js +11 -192
- package/dist/src/replication-info-v2-receive.js.map +1 -1
- package/dist/src/replication-info-v2-send.d.ts +5 -6
- package/dist/src/replication-info-v2-send.d.ts.map +1 -1
- package/dist/src/replication-info-v2-send.js +9 -14
- 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/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 +10 -10
- package/src/index.ts +115 -816
- package/src/replication-announcement.ts +12 -722
- package/src/replication-info-mutation.ts +34 -0
- package/src/replication-info-v2-receive.ts +17 -263
- package/src/replication-info-v2-send.ts +10 -23
- package/src/replication.ts +1 -20
- 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";
|
|
@@ -682,24 +681,17 @@ let SharedLog = (() => {
|
|
|
682
681
|
_pendingIHave;
|
|
683
682
|
// public key hash to range id to range
|
|
684
683
|
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;
|
|
684
|
+
// The legacy replication-info ordering watermark (fence B8) is deleted:
|
|
685
|
+
// its FENCING role was subsumed by the per-peer receive epoch, blocked set
|
|
686
|
+
// and session identity, and its intra-epoch ORDERING role existed only for
|
|
687
|
+
// the legacy apply lanes. The V2 lane orders by sender-authoritative
|
|
688
|
+
// sequence numbers, and legacy frames are dropped unconditionally at the
|
|
689
|
+
// B1 gate before any side effect.
|
|
700
690
|
// The replication-info blocked set (fence B5) lives on the peer-session
|
|
701
691
|
// registry: unsubscribed peers whose replication-info is ignored until a
|
|
702
692
|
// reconnect barrier commits. See PeerSessionRegistry._replicationInfoBlockedPeers.
|
|
693
|
+
// V2 recovery scheduler state, one row per open peer session (B12: the
|
|
694
|
+
// legacy request scheduler that shared this map is deleted).
|
|
703
695
|
_replicationInfoRequestByPeer;
|
|
704
696
|
_replicationInfoApplyQueueByPeer;
|
|
705
697
|
// One in-flight targeted subscriber-snapshot request per session-less peer.
|
|
@@ -841,7 +833,6 @@ let SharedLog = (() => {
|
|
|
841
833
|
for (const hash of this._checkedPrune?.retries.keys() ?? []) {
|
|
842
834
|
this._checkedPrune.clearRetry(hash);
|
|
843
835
|
}
|
|
844
|
-
this._announcements.cancelCurrentReplicationStateAnnouncementRetry();
|
|
845
836
|
this.joinWarmup.cancelAllJoinWarmupTargets();
|
|
846
837
|
for (const timer of this._repairRetryTimers) {
|
|
847
838
|
clearTimeout(timer);
|
|
@@ -1707,25 +1698,9 @@ let SharedLog = (() => {
|
|
|
1707
1698
|
}
|
|
1708
1699
|
createReplicationAnnouncementCoordinator() {
|
|
1709
1700
|
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
1701
|
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
1702
|
captureReplicationOwnershipLifecycle: () => this.captureReplicationOwnershipLifecycle(),
|
|
1726
1703
|
throwIfReplicationOwnershipLifecycleInactive: (controller) => this.throwIfReplicationOwnershipLifecycleInactive(controller),
|
|
1727
|
-
isAdaptiveReplicating: () => this._isAdaptiveReplicating,
|
|
1728
|
-
callRebalanceParticipationDebounced: () => this.rebalanceParticipationDebounced?.call(),
|
|
1729
1704
|
});
|
|
1730
1705
|
}
|
|
1731
1706
|
createReplicationInfoV2SendCoordinator() {
|
|
@@ -1885,7 +1860,6 @@ let SharedLog = (() => {
|
|
|
1885
1860
|
this._admittedPruneRemoves = new Set();
|
|
1886
1861
|
this._pendingIHave = new Map();
|
|
1887
1862
|
this._pendingIHaveCallbacks = new Set();
|
|
1888
|
-
this.latestReplicationInfoMessage = new Map();
|
|
1889
1863
|
this._replicationInfoRequestByPeer = new Map();
|
|
1890
1864
|
this._subscriberSnapshotRequestsByPeer = new Map();
|
|
1891
1865
|
this._replicationInfoApplyQueueByPeer = new Map();
|
|
@@ -1985,25 +1959,9 @@ let SharedLog = (() => {
|
|
|
1985
1959
|
this._nativeStrictDurableDocumentRecoveryDeferred ??= false;
|
|
1986
1960
|
this._nativeStrictDurableTransactionsClosing ??= false;
|
|
1987
1961
|
}
|
|
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
1962
|
get isAdaptiveReplicating() {
|
|
2002
1963
|
return this._isAdaptiveReplicating;
|
|
2003
1964
|
}
|
|
2004
|
-
get v8Behaviour() {
|
|
2005
|
-
return (this.compatibility ?? Number.MAX_VALUE) < 9;
|
|
2006
|
-
}
|
|
2007
1965
|
getFanoutChannelOptions(options) {
|
|
2008
1966
|
return {
|
|
2009
1967
|
...DEFAULT_SHARED_LOG_FANOUT_CHANNEL_OPTIONS,
|
|
@@ -2692,8 +2650,7 @@ let SharedLog = (() => {
|
|
|
2692
2650
|
!delivery &&
|
|
2693
2651
|
!requireRecipients &&
|
|
2694
2652
|
(leaders.size === 0 || (leaders.size === 1 && leaders.has(selfHash)))) {
|
|
2695
|
-
const allowSubscriberFallback = this.syncronizer instanceof SimpleSyncronizer
|
|
2696
|
-
(this.compatibility ?? Number.MAX_VALUE) < 10;
|
|
2653
|
+
const allowSubscriberFallback = this.syncronizer instanceof SimpleSyncronizer;
|
|
2697
2654
|
if (!allowSubscriberFallback) {
|
|
2698
2655
|
return;
|
|
2699
2656
|
}
|
|
@@ -2816,8 +2773,7 @@ let SharedLog = (() => {
|
|
|
2816
2773
|
// sweeps can still backfill peers that missed the initial delivery.
|
|
2817
2774
|
const set = new Set(leaders.keys());
|
|
2818
2775
|
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;
|
|
2776
|
+
const allowSubscriberFallback = this.syncronizer instanceof SimpleSyncronizer;
|
|
2821
2777
|
if (!hasRemotePeers && allowSubscriberFallback) {
|
|
2822
2778
|
try {
|
|
2823
2779
|
const subscribers = await this._getTopicSubscribers(this.topic);
|
|
@@ -3185,21 +3141,6 @@ let SharedLog = (() => {
|
|
|
3185
3141
|
context: this.cloneLeaderSelectionContext(context),
|
|
3186
3142
|
};
|
|
3187
3143
|
}
|
|
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
3144
|
isTerminating() {
|
|
3204
3145
|
return (this.acceptsParentAttachments === false ||
|
|
3205
3146
|
this.closed ||
|
|
@@ -3398,10 +3339,7 @@ let SharedLog = (() => {
|
|
|
3398
3339
|
this.rebalanceParticipationDebounced = rebalanceParticipationDebounced;
|
|
3399
3340
|
}
|
|
3400
3341
|
onRebalanceParticipationError(error) {
|
|
3401
|
-
if (this.closed ||
|
|
3402
|
-
isNotStartedError(error) ||
|
|
3403
|
-
(isTransientReplicationAnnouncementError(error) &&
|
|
3404
|
-
this._announcements._replicationAnnouncementRetryPending)) {
|
|
3342
|
+
if (this.closed || isNotStartedError(error)) {
|
|
3405
3343
|
return;
|
|
3406
3344
|
}
|
|
3407
3345
|
// Debounced invocations run from an un-awaited timer. Throwing here would
|
|
@@ -3630,9 +3568,11 @@ let SharedLog = (() => {
|
|
|
3630
3568
|
}, replicationOwnershipLifecycleController);
|
|
3631
3569
|
this.throwIfReplicationOwnershipLifecycleInactive(replicationOwnershipLifecycleController);
|
|
3632
3570
|
if (confirmedPreliminaryRemovals.length > 0) {
|
|
3633
|
-
await this._announcements.sendReplicationAnnouncement(
|
|
3634
|
-
|
|
3635
|
-
|
|
3571
|
+
await this._announcements.sendReplicationAnnouncement({
|
|
3572
|
+
stopped: {
|
|
3573
|
+
segmentIds: confirmedPreliminaryRemovals.map((x) => x.id),
|
|
3574
|
+
},
|
|
3575
|
+
}, replicationOwnershipLifecycleController);
|
|
3636
3576
|
this.throwIfReplicationOwnershipLifecycleInactive(replicationOwnershipLifecycleController);
|
|
3637
3577
|
}
|
|
3638
3578
|
}
|
|
@@ -3646,7 +3586,7 @@ let SharedLog = (() => {
|
|
|
3646
3586
|
const segments = (await this.getMyReplicationSegments()).map((range) => range.toReplicationRange());
|
|
3647
3587
|
this.throwIfReplicationOwnershipLifecycleInactive(replicationOwnershipLifecycleController);
|
|
3648
3588
|
this.validatePersistedReplicationRangeSnapshot(segments);
|
|
3649
|
-
await this._announcements.sendReplicationAnnouncement(
|
|
3589
|
+
await this._announcements.sendReplicationAnnouncement({ full: { segments } }, replicationOwnershipLifecycleController);
|
|
3650
3590
|
}
|
|
3651
3591
|
catch (error) {
|
|
3652
3592
|
announcementError = error;
|
|
@@ -3856,7 +3796,7 @@ let SharedLog = (() => {
|
|
|
3856
3796
|
return;
|
|
3857
3797
|
}
|
|
3858
3798
|
try {
|
|
3859
|
-
await this._announcements.sendReplicationAnnouncement(
|
|
3799
|
+
await this._announcements.sendReplicationAnnouncement({ stopped: { segmentIds: removedSegmentIds } }, replicationOwnershipLifecycleController);
|
|
3860
3800
|
this.throwIfReplicationOwnershipLifecycleInactive(replicationOwnershipLifecycleController);
|
|
3861
3801
|
}
|
|
3862
3802
|
catch (announcementError) {
|
|
@@ -4070,7 +4010,7 @@ let SharedLog = (() => {
|
|
|
4070
4010
|
let announcementError;
|
|
4071
4011
|
if (isMe && !ownerHasRanges) {
|
|
4072
4012
|
try {
|
|
4073
|
-
await this._announcements.sendReplicationAnnouncement(
|
|
4013
|
+
await this._announcements.sendReplicationAnnouncement({ full: { segments: [] } }, replicationOwnershipLifecycleController);
|
|
4074
4014
|
}
|
|
4075
4015
|
catch (error) {
|
|
4076
4016
|
announcementError = error;
|
|
@@ -4743,7 +4683,7 @@ let SharedLog = (() => {
|
|
|
4743
4683
|
const segments = (await this.getMyReplicationSegments()).map((range) => range.toReplicationRange());
|
|
4744
4684
|
this.throwIfReplicationOwnershipLifecycleInactive(replicationOwnershipLifecycleController);
|
|
4745
4685
|
this.validatePersistedReplicationRangeSnapshot(segments);
|
|
4746
|
-
await this._announcements.sendReplicationAnnouncement(
|
|
4686
|
+
await this._announcements.sendReplicationAnnouncement({ full: { segments } }, replicationOwnershipLifecycleController);
|
|
4747
4687
|
}
|
|
4748
4688
|
catch (error) {
|
|
4749
4689
|
announcementError = error;
|
|
@@ -4784,14 +4724,14 @@ let SharedLog = (() => {
|
|
|
4784
4724
|
}
|
|
4785
4725
|
let message = undefined;
|
|
4786
4726
|
if (options.reset) {
|
|
4787
|
-
message =
|
|
4788
|
-
segments: added.map((x) => x.range.toReplicationRange()),
|
|
4789
|
-
}
|
|
4727
|
+
message = {
|
|
4728
|
+
full: { segments: added.map((x) => x.range.toReplicationRange()) },
|
|
4729
|
+
};
|
|
4790
4730
|
}
|
|
4791
4731
|
else {
|
|
4792
|
-
message =
|
|
4793
|
-
segments: added.map((x) => x.range.toReplicationRange()),
|
|
4794
|
-
}
|
|
4732
|
+
message = {
|
|
4733
|
+
added: { segments: added.map((x) => x.range.toReplicationRange()) },
|
|
4734
|
+
};
|
|
4795
4735
|
}
|
|
4796
4736
|
if (options.announce) {
|
|
4797
4737
|
return options.announce(message);
|
|
@@ -9173,10 +9113,7 @@ let SharedLog = (() => {
|
|
|
9173
9113
|
this._logProperties = options;
|
|
9174
9114
|
this.domain = options?.domain
|
|
9175
9115
|
? options.domain(this)
|
|
9176
|
-
: createReplicationDomainHash(
|
|
9177
|
-
options.compatibility < 10
|
|
9178
|
-
? "u32"
|
|
9179
|
-
: "u64")(this);
|
|
9116
|
+
: createReplicationDomainHash("u64")(this);
|
|
9180
9117
|
this.indexableDomain = createIndexableDomainFromResolution(this.domain.resolution);
|
|
9181
9118
|
this._respondToIHaveTimeout = options?.respondToIHaveTimeout ?? 2e4;
|
|
9182
9119
|
this._checkedPrune = new CheckedPruneCoordinator();
|
|
@@ -9184,7 +9121,6 @@ let SharedLog = (() => {
|
|
|
9184
9121
|
this._admittedPruneRemoves = new Set();
|
|
9185
9122
|
this._pendingIHave = new Map();
|
|
9186
9123
|
this._pendingIHaveCallbacks = new Set();
|
|
9187
|
-
this.latestReplicationInfoMessage = new Map();
|
|
9188
9124
|
this._replicationInfoRequestByPeer = new Map();
|
|
9189
9125
|
this._subscriberSnapshotRequestsByPeer = new Map();
|
|
9190
9126
|
// Terminal close/drop drains the previous lifecycle before another open can
|
|
@@ -9253,7 +9189,6 @@ let SharedLog = (() => {
|
|
|
9253
9189
|
this._liveness.resetForOpen();
|
|
9254
9190
|
this._lastLocalAppendAt = 0;
|
|
9255
9191
|
this._announcements ??= this.createReplicationAnnouncementCoordinator();
|
|
9256
|
-
this._announcements.resetForOpen();
|
|
9257
9192
|
this._v2Receive ??= this.createReplicationInfoV2ReceiveCoordinator();
|
|
9258
9193
|
this._v2Receive.resetForOpen();
|
|
9259
9194
|
this._v2Send ??= this.createReplicationInfoV2SendCoordinator();
|
|
@@ -9295,13 +9230,6 @@ let SharedLog = (() => {
|
|
|
9295
9230
|
throw new Error("waitForReplicatorRequestMaxAttempts must be a positive number");
|
|
9296
9231
|
}
|
|
9297
9232
|
this._closeController = new AbortController();
|
|
9298
|
-
if (this.legacyReplicationInfoEnabled) {
|
|
9299
|
-
this._announcements.setupReplicationAnnouncementRetryFunction();
|
|
9300
|
-
this._announcements.setupReplicationAnnouncementRepairFunction();
|
|
9301
|
-
}
|
|
9302
|
-
else {
|
|
9303
|
-
this._announcements.cancelCurrentReplicationStateAnnouncementRetry();
|
|
9304
|
-
}
|
|
9305
9233
|
this._closeController.signal.addEventListener("abort", () => {
|
|
9306
9234
|
for (const [_peer, state] of this._replicationInfoRequestByPeer) {
|
|
9307
9235
|
if (state.timer)
|
|
@@ -9656,7 +9584,6 @@ let SharedLog = (() => {
|
|
|
9656
9584
|
peerSupportsRawExchangeHeads: (peer) => this.peerSupportsRawExchangeHeads(peer),
|
|
9657
9585
|
sendRawExchangeHeads: (hashes, to, sendOptions) => this.trySendFusedRawExchangeHeads(hashes, to, sendOptions),
|
|
9658
9586
|
warn,
|
|
9659
|
-
compatibility: this._logProperties?.compatibility,
|
|
9660
9587
|
resolution: this.domain.resolution,
|
|
9661
9588
|
sync: options?.sync,
|
|
9662
9589
|
syncronizer: options?.syncronizer,
|
|
@@ -10397,7 +10324,7 @@ let SharedLog = (() => {
|
|
|
10397
10324
|
cleanupPeerDisconnectTracking(peerHash, ownershipLifecycleController = this.captureReplicationOwnershipLifecycle()) {
|
|
10398
10325
|
const peerSession = this._peerSessions.current(peerHash);
|
|
10399
10326
|
const preserveV2Session = peerSession?.phase === "open" && peerSession.isActive();
|
|
10400
|
-
if (
|
|
10327
|
+
if (!preserveV2Session) {
|
|
10401
10328
|
this.cancelReplicationInfoRequests(peerHash);
|
|
10402
10329
|
}
|
|
10403
10330
|
this._liveness._replicatorLivenessFailures.delete(peerHash);
|
|
@@ -10423,11 +10350,8 @@ let SharedLog = (() => {
|
|
|
10423
10350
|
}
|
|
10424
10351
|
advanceReplicationInfoRecoveryEpoch(peerHash) {
|
|
10425
10352
|
// Handlers admitted before a successful peer removal must not restore state
|
|
10426
|
-
// when they eventually reach the apply lane.
|
|
10427
|
-
// ordering watermark with the local epoch so a later arrival can be
|
|
10428
|
-
// accepted without comparing its clock to this receiver's clock.
|
|
10353
|
+
// when they eventually reach the apply lane.
|
|
10429
10354
|
const receiveEpoch = this._peerSessions.advanceReceiveEpoch(peerHash);
|
|
10430
|
-
this.latestReplicationInfoMessage.delete(peerHash);
|
|
10431
10355
|
const peerSession = this._peerSessions.current(peerHash);
|
|
10432
10356
|
if (peerSession?.phase === "open") {
|
|
10433
10357
|
this._v2Receive.advanceRecovery({
|
|
@@ -11136,13 +11060,6 @@ let SharedLog = (() => {
|
|
|
11136
11060
|
firstError ??= error;
|
|
11137
11061
|
}
|
|
11138
11062
|
};
|
|
11139
|
-
// A borsh-deserialized instance that is closed before ever being opened
|
|
11140
|
-
// has no coordinators (they are created in open()); the old inline code
|
|
11141
|
-
// only touched plain fields here and never threw.
|
|
11142
|
-
captureSync(() => this._announcements?.cancelCurrentReplicationStateAnnouncementRetry());
|
|
11143
|
-
if (this._announcements) {
|
|
11144
|
-
this._announcements.replicationAnnouncementRetryDebounced = undefined;
|
|
11145
|
-
}
|
|
11146
11063
|
captureSync(() => {
|
|
11147
11064
|
if (this._wireSyncSession) {
|
|
11148
11065
|
this._wireSyncSession.unregisterTopic(this.topic);
|
|
@@ -11225,7 +11142,6 @@ let SharedLog = (() => {
|
|
|
11225
11142
|
captureSync(() => {
|
|
11226
11143
|
this._pendingIHave?.clear();
|
|
11227
11144
|
this._pendingIHaveCallbacks?.clear();
|
|
11228
|
-
this.latestReplicationInfoMessage?.clear();
|
|
11229
11145
|
this._peerSessions?.clearReceiveEpochsForClose();
|
|
11230
11146
|
this._peerSessions?.clearCleanupGatesForClose();
|
|
11231
11147
|
this._activeReceiveHandlersByPeer?.clear();
|
|
@@ -11315,7 +11231,6 @@ let SharedLog = (() => {
|
|
|
11315
11231
|
await pruneRemoveTerminalFence.drained;
|
|
11316
11232
|
await this.drainPendingIHaveCallbacks();
|
|
11317
11233
|
this.ensureNativeDurabilityRuntimeState();
|
|
11318
|
-
this._announcements.cancelCurrentReplicationStateAnnouncementRetry();
|
|
11319
11234
|
}
|
|
11320
11235
|
catch (error) {
|
|
11321
11236
|
// The terminal preamble has already disabled parent attachments and the
|
|
@@ -11358,17 +11273,7 @@ let SharedLog = (() => {
|
|
|
11358
11273
|
}
|
|
11359
11274
|
}, 2_000);
|
|
11360
11275
|
try {
|
|
11361
|
-
|
|
11362
|
-
const resets = [this._v2Send.sendTerminalReset(abort.signal)];
|
|
11363
|
-
if (this.legacyReplicationInfoEnabled) {
|
|
11364
|
-
resets.push(this.rpc
|
|
11365
|
-
.send(reset, {
|
|
11366
|
-
priority: CONVERGENCE_MESSAGE_PRIORITY,
|
|
11367
|
-
signal: abort.signal,
|
|
11368
|
-
})
|
|
11369
|
-
.catch(() => { }));
|
|
11370
|
-
}
|
|
11371
|
-
await Promise.all(resets);
|
|
11276
|
+
await this._v2Send.sendTerminalReset(abort.signal);
|
|
11372
11277
|
}
|
|
11373
11278
|
finally {
|
|
11374
11279
|
clearTimeout(abortTimer);
|
|
@@ -11460,7 +11365,6 @@ let SharedLog = (() => {
|
|
|
11460
11365
|
await replicationRangeTerminalFence.drained;
|
|
11461
11366
|
await pruneRemoveTerminalFence.drained;
|
|
11462
11367
|
await this.drainPendingIHaveCallbacks();
|
|
11463
|
-
this._announcements.cancelCurrentReplicationStateAnnouncementRetry();
|
|
11464
11368
|
}
|
|
11465
11369
|
catch (error) {
|
|
11466
11370
|
// The terminal preamble is not safely reversible. Preserve the fence until
|
|
@@ -11487,17 +11391,7 @@ let SharedLog = (() => {
|
|
|
11487
11391
|
}
|
|
11488
11392
|
}, 2_000);
|
|
11489
11393
|
try {
|
|
11490
|
-
|
|
11491
|
-
const resets = [this._v2Send.sendTerminalReset(abort.signal)];
|
|
11492
|
-
if (this.legacyReplicationInfoEnabled) {
|
|
11493
|
-
resets.push(this.rpc
|
|
11494
|
-
.send(reset, {
|
|
11495
|
-
priority: CONVERGENCE_MESSAGE_PRIORITY,
|
|
11496
|
-
signal: abort.signal,
|
|
11497
|
-
})
|
|
11498
|
-
.catch(() => { }));
|
|
11499
|
-
}
|
|
11500
|
-
await Promise.all(resets);
|
|
11394
|
+
await this._v2Send.sendTerminalReset(abort.signal);
|
|
11501
11395
|
}
|
|
11502
11396
|
finally {
|
|
11503
11397
|
clearTimeout(abortTimer);
|
|
@@ -11921,15 +11815,15 @@ let SharedLog = (() => {
|
|
|
11921
11815
|
if (!context.from) {
|
|
11922
11816
|
throw new Error("Missing from in update role message");
|
|
11923
11817
|
}
|
|
11924
|
-
if (
|
|
11925
|
-
|
|
11926
|
-
|
|
11927
|
-
|
|
11928
|
-
|
|
11929
|
-
|
|
11930
|
-
//
|
|
11931
|
-
//
|
|
11932
|
-
//
|
|
11818
|
+
if (msg instanceof RequestReplicationInfoMessage ||
|
|
11819
|
+
msg instanceof ResponseRoleMessage ||
|
|
11820
|
+
msg instanceof AllReplicatingSegmentsMessage ||
|
|
11821
|
+
msg instanceof AddedReplicationSegmentMessage ||
|
|
11822
|
+
msg instanceof StoppedReplicating) {
|
|
11823
|
+
// These variants remain registered decode tombstones, but logs fail
|
|
11824
|
+
// closed unconditionally before leases, synchronizer work, liveness,
|
|
11825
|
+
// watermarks or mutations (B12: the compatibility opens that once
|
|
11826
|
+
// admitted them reject at open()).
|
|
11933
11827
|
return;
|
|
11934
11828
|
}
|
|
11935
11829
|
// Snapshot receive ownership before any async handler gets a chance to
|
|
@@ -11959,29 +11853,20 @@ let SharedLog = (() => {
|
|
|
11959
11853
|
const receiveOwnershipRevision = this._instanceLifecycle?._receiveOwnershipRevision ?? 0;
|
|
11960
11854
|
const receiveOwnershipLifecycleController = this.captureReplicationOwnershipLifecycle();
|
|
11961
11855
|
const receiveReplicationInfoReceiveEpoch = this._peerSessions.receiveEpoch(receiveFromHash);
|
|
11962
|
-
if (msg instanceof
|
|
11963
|
-
msg = msg.toReplicationInfoMessage(); // migration
|
|
11964
|
-
}
|
|
11965
|
-
if (msg instanceof AllReplicatingSegmentsMessage ||
|
|
11966
|
-
msg instanceof AddedReplicationSegmentMessage ||
|
|
11967
|
-
msg instanceof FullReplicationInfoV2Message ||
|
|
11856
|
+
if (msg instanceof FullReplicationInfoV2Message ||
|
|
11968
11857
|
msg instanceof AddedReplicationInfoV2Message) {
|
|
11969
11858
|
// Bound decoded untrusted vectors before per-peer/global mutation
|
|
11970
11859
|
// queues, trusted-replicator authorization, or liveness side effects.
|
|
11971
11860
|
this.validateReplicationRangeAnnouncement(msg.segments);
|
|
11972
11861
|
}
|
|
11973
|
-
else if (msg instanceof
|
|
11974
|
-
msg instanceof StoppedReplicationInfoV2Message) {
|
|
11862
|
+
else if (msg instanceof StoppedReplicationInfoV2Message) {
|
|
11975
11863
|
// Bound the raw decoded vector before deduplication can hide the
|
|
11976
11864
|
// allocation cost, and before liveness or apply-queue side effects.
|
|
11977
11865
|
this.validateStoppedReplicationAnnouncement(msg.segmentIds);
|
|
11978
11866
|
}
|
|
11979
11867
|
if (!context.from.equals(this.node.identity.publicKey) &&
|
|
11980
11868
|
!(msg instanceof RequestReplicationInfoV2Message) &&
|
|
11981
|
-
!isReplicationInfoV2Message(msg)
|
|
11982
|
-
!(msg instanceof AllReplicatingSegmentsMessage) &&
|
|
11983
|
-
!(msg instanceof AddedReplicationSegmentMessage) &&
|
|
11984
|
-
!(msg instanceof StoppedReplicating)) {
|
|
11869
|
+
!isReplicationInfoV2Message(msg)) {
|
|
11985
11870
|
this._liveness.markReplicatorActivity(receiveFromHash);
|
|
11986
11871
|
}
|
|
11987
11872
|
const syncProfile = this._logProperties?.sync?.profile;
|
|
@@ -13403,16 +13288,6 @@ let SharedLog = (() => {
|
|
|
13403
13288
|
else if (msg instanceof ReplicationPingMessage) {
|
|
13404
13289
|
// No-op: used as an ACKed unicast liveness probe.
|
|
13405
13290
|
}
|
|
13406
|
-
else if (msg instanceof RequestReplicationInfoMessage) {
|
|
13407
|
-
await this.handleRequestReplicationInfo(msg, laneRequestContext, lane);
|
|
13408
|
-
}
|
|
13409
|
-
else if (msg instanceof AllReplicatingSegmentsMessage ||
|
|
13410
|
-
msg instanceof AddedReplicationSegmentMessage) {
|
|
13411
|
-
await this.handleReplicationInfoAnnouncement(msg, laneRequestContext, lane);
|
|
13412
|
-
}
|
|
13413
|
-
else if (msg instanceof StoppedReplicating) {
|
|
13414
|
-
await this.handleStoppedReplicating(msg, laneRequestContext, lane);
|
|
13415
|
-
}
|
|
13416
13291
|
else {
|
|
13417
13292
|
throw new Error("Unexpected message");
|
|
13418
13293
|
}
|
|
@@ -13598,68 +13473,6 @@ let SharedLog = (() => {
|
|
|
13598
13473
|
}
|
|
13599
13474
|
}
|
|
13600
13475
|
}
|
|
13601
|
-
async handleRequestReplicationInfo(_msg, context, lane) {
|
|
13602
|
-
const receiveFromHash = lane.fromHash;
|
|
13603
|
-
const receiveSession = lane.session;
|
|
13604
|
-
const receiveReplicationLifecycleController = lane.lifecycleController;
|
|
13605
|
-
if (context.from.equals(this.node.identity.publicKey)) {
|
|
13606
|
-
return;
|
|
13607
|
-
}
|
|
13608
|
-
const replicationLifecycleController = receiveReplicationLifecycleController;
|
|
13609
|
-
if (!replicationLifecycleController ||
|
|
13610
|
-
!this._peerSessions.isReceiveAdmissionOpen(receiveFromHash, receiveSession, replicationLifecycleController)) {
|
|
13611
|
-
return;
|
|
13612
|
-
}
|
|
13613
|
-
let replicationSegments;
|
|
13614
|
-
try {
|
|
13615
|
-
replicationSegments = await this.getMyReplicationSegments();
|
|
13616
|
-
}
|
|
13617
|
-
catch (error) {
|
|
13618
|
-
if (!this._peerSessions.isReceiveAdmissionOpen(receiveFromHash, receiveSession, replicationLifecycleController) &&
|
|
13619
|
-
isNotStartedError(error)) {
|
|
13620
|
-
return;
|
|
13621
|
-
}
|
|
13622
|
-
throw error;
|
|
13623
|
-
}
|
|
13624
|
-
if (!this._peerSessions.isReceiveAdmissionOpen(receiveFromHash, receiveSession, replicationLifecycleController)) {
|
|
13625
|
-
return;
|
|
13626
|
-
}
|
|
13627
|
-
const segments = replicationSegments.map((x) => x.toReplicationRange());
|
|
13628
|
-
this.validatePersistedReplicationRangeSnapshot(segments);
|
|
13629
|
-
this._v2Send.enqueueSnapshotForPeer(receiveFromHash);
|
|
13630
|
-
await this.rpc
|
|
13631
|
-
.send(new AllReplicatingSegmentsMessage({ segments }), {
|
|
13632
|
-
mode: new AcknowledgeDelivery({
|
|
13633
|
-
to: [context.from],
|
|
13634
|
-
redundancy: 1,
|
|
13635
|
-
}),
|
|
13636
|
-
signal: replicationLifecycleController.signal,
|
|
13637
|
-
})
|
|
13638
|
-
.catch((error) => this.handleReplicationLifecycleSendError(error, replicationLifecycleController));
|
|
13639
|
-
if (!this._peerSessions.isReceiveAdmissionOpen(receiveFromHash, receiveSession, replicationLifecycleController)) {
|
|
13640
|
-
return;
|
|
13641
|
-
}
|
|
13642
|
-
// for backwards compatibility (v8) remove this when we are sure that all nodes are v9+
|
|
13643
|
-
if (this.v8Behaviour) {
|
|
13644
|
-
const role = this.getRoleFromReplicationSegments(replicationSegments);
|
|
13645
|
-
if (role instanceof Replicator) {
|
|
13646
|
-
const fixedSettings = !this._isAdaptiveReplicating;
|
|
13647
|
-
if (fixedSettings) {
|
|
13648
|
-
await this.rpc
|
|
13649
|
-
.send(new ResponseRoleMessage({
|
|
13650
|
-
role,
|
|
13651
|
-
}), {
|
|
13652
|
-
mode: new SilentDelivery({
|
|
13653
|
-
to: [context.from],
|
|
13654
|
-
redundancy: 1,
|
|
13655
|
-
}),
|
|
13656
|
-
signal: replicationLifecycleController.signal,
|
|
13657
|
-
})
|
|
13658
|
-
.catch((error) => this.handleReplicationLifecycleSendError(error, replicationLifecycleController));
|
|
13659
|
-
}
|
|
13660
|
-
}
|
|
13661
|
-
}
|
|
13662
|
-
}
|
|
13663
13476
|
async handleReplicationInfoV2Announcement(msg, context, lane) {
|
|
13664
13477
|
const from = context.from;
|
|
13665
13478
|
const fromHash = lane.fromHash;
|
|
@@ -13795,187 +13608,12 @@ let SharedLog = (() => {
|
|
|
13795
13608
|
// A committed V2 announcement is applied progress: the peer answers,
|
|
13796
13609
|
// so recovery re-solicitation may restart from the base interval.
|
|
13797
13610
|
this.resetReplicationInfoV2RecoveryEscalation(fromHash);
|
|
13798
|
-
if (msg instanceof FullReplicationInfoV2Message &&
|
|
13799
|
-
this.legacyReplicationInfoEnabled) {
|
|
13800
|
-
this.cancelReplicationInfoRequests(fromHash);
|
|
13801
|
-
}
|
|
13802
13611
|
});
|
|
13803
13612
|
}
|
|
13804
13613
|
finally {
|
|
13805
13614
|
this._v2Receive.release(admission);
|
|
13806
13615
|
}
|
|
13807
13616
|
}
|
|
13808
|
-
async handleReplicationInfoAnnouncement(msg, context, lane) {
|
|
13809
|
-
const receiveFromHash = lane.fromHash;
|
|
13810
|
-
const receiveSession = lane.session;
|
|
13811
|
-
const receiveReplicationLifecycleController = lane.lifecycleController;
|
|
13812
|
-
const receiveReplicationInfoReceiveEpoch = lane.receiveEpoch;
|
|
13813
|
-
const peerReceiveLease = lane.lease;
|
|
13814
|
-
if (context.from.equals(this.node.identity.publicKey)) {
|
|
13815
|
-
return;
|
|
13816
|
-
}
|
|
13817
|
-
const replicationInfoMessage = msg;
|
|
13818
|
-
// Process replication updates even if the sender isn't yet considered "ready" by
|
|
13819
|
-
// `Program.waitFor()`. Dropping these messages can lead to missing replicator info
|
|
13820
|
-
// (and downstream `waitForReplicator()` timeouts) under timing-sensitive joins.
|
|
13821
|
-
const from = context.from;
|
|
13822
|
-
const fromHash = from.hashcode();
|
|
13823
|
-
if (this._v2Receive.isLegacyCutover(receiveSession)) {
|
|
13824
|
-
if (receiveSession) {
|
|
13825
|
-
this._v2Receive.noteLegacyAnnouncement({
|
|
13826
|
-
peerHash: fromHash,
|
|
13827
|
-
peerSession: receiveSession,
|
|
13828
|
-
receiveEpoch: receiveReplicationInfoReceiveEpoch,
|
|
13829
|
-
senderTransportSession: context.message.header.session,
|
|
13830
|
-
transportTimestamp: context.message.header.timestamp,
|
|
13831
|
-
message: msg,
|
|
13832
|
-
});
|
|
13833
|
-
}
|
|
13834
|
-
return;
|
|
13835
|
-
}
|
|
13836
|
-
// Pre-lane gate: lifecycle -> receive-epoch -> blocked, exactly the
|
|
13837
|
-
// legacy order. isMembershipActiveFor is the unit-pinned fold of
|
|
13838
|
-
// isReplicationLifecycleActive; isReceiveEpochCurrent is the
|
|
13839
|
-
// relocated `===`-with-`?? null`. The DELIBERATE absence of a
|
|
13840
|
-
// subscription-epoch term is preserved: the lease already validated
|
|
13841
|
-
// it, and the in-lane recheck owns post-await staleness.
|
|
13842
|
-
if (!this._instanceLifecycle.isMembershipActiveFor(receiveReplicationLifecycleController) ||
|
|
13843
|
-
!this._peerSessions.isReceiveEpochCurrent(receiveFromHash, receiveReplicationInfoReceiveEpoch) ||
|
|
13844
|
-
this._peerSessions.isReplicationInfoBlocked(fromHash)) {
|
|
13845
|
-
return;
|
|
13846
|
-
}
|
|
13847
|
-
const messageTimestamp = context.message.header.timestamp;
|
|
13848
|
-
peerReceiveLease.release();
|
|
13849
|
-
await this.withReplicationInfoApplyQueue(fromHash, async () => {
|
|
13850
|
-
try {
|
|
13851
|
-
// The peer may have unsubscribed after this message was queued.
|
|
13852
|
-
// In-lane gate: lifecycle -> subscription-epoch -> receive-epoch
|
|
13853
|
-
// -> blocked, term for term as before the session migration.
|
|
13854
|
-
if (!this._instanceLifecycle.isMembershipActiveFor(receiveReplicationLifecycleController) ||
|
|
13855
|
-
!this._peerSessions.isCurrent(fromHash, receiveSession) ||
|
|
13856
|
-
!this._peerSessions.isReceiveEpochCurrent(fromHash, receiveReplicationInfoReceiveEpoch) ||
|
|
13857
|
-
this._peerSessions.isReplicationInfoBlocked(fromHash)) {
|
|
13858
|
-
return;
|
|
13859
|
-
}
|
|
13860
|
-
if (receiveSession && this._v2Receive.isLegacyCutover(receiveSession)) {
|
|
13861
|
-
this._v2Receive.noteLegacyAnnouncement({
|
|
13862
|
-
peerHash: fromHash,
|
|
13863
|
-
peerSession: receiveSession,
|
|
13864
|
-
receiveEpoch: receiveReplicationInfoReceiveEpoch,
|
|
13865
|
-
senderTransportSession: context.message.header.session,
|
|
13866
|
-
transportTimestamp: context.message.header.timestamp,
|
|
13867
|
-
message: msg,
|
|
13868
|
-
});
|
|
13869
|
-
return;
|
|
13870
|
-
}
|
|
13871
|
-
// Process in-order to avoid races where repeated reset messages arrive
|
|
13872
|
-
// concurrently and trigger spurious "added" diffs / rebalancing.
|
|
13873
|
-
const prev = this.latestReplicationInfoMessage.get(fromHash);
|
|
13874
|
-
if (prev && prev > messageTimestamp) {
|
|
13875
|
-
return;
|
|
13876
|
-
}
|
|
13877
|
-
this.latestReplicationInfoMessage.set(fromHash, messageTimestamp);
|
|
13878
|
-
if (this.closed) {
|
|
13879
|
-
return;
|
|
13880
|
-
}
|
|
13881
|
-
const reset = msg instanceof AllReplicatingSegmentsMessage;
|
|
13882
|
-
const result = await this.addReplicationRange(replicationInfoMessage.segments.map((x) => x.toReplicationRangeIndexable(from)), from, {
|
|
13883
|
-
reset,
|
|
13884
|
-
checkDuplicates: true,
|
|
13885
|
-
timestamp: Number(messageTimestamp),
|
|
13886
|
-
allowLegacyOrderedReplacementPairs: msg instanceof AddedReplicationSegmentMessage,
|
|
13887
|
-
});
|
|
13888
|
-
if (result === undefined) {
|
|
13889
|
-
return;
|
|
13890
|
-
}
|
|
13891
|
-
this._liveness.markReplicatorActivity(fromHash);
|
|
13892
|
-
// If the peer reports any replication segments, stop re-requesting.
|
|
13893
|
-
// (Empty reports can be transient during startup.)
|
|
13894
|
-
if (replicationInfoMessage.segments.length > 0) {
|
|
13895
|
-
this.cancelReplicationInfoRequests(fromHash);
|
|
13896
|
-
}
|
|
13897
|
-
}
|
|
13898
|
-
catch (e) {
|
|
13899
|
-
if (isNotStartedError(e)) {
|
|
13900
|
-
return;
|
|
13901
|
-
}
|
|
13902
|
-
logger.error(`Failed to apply replication settings from '${fromHash}': ${e?.message ?? e}`);
|
|
13903
|
-
}
|
|
13904
|
-
});
|
|
13905
|
-
}
|
|
13906
|
-
async handleStoppedReplicating(msg, context, lane) {
|
|
13907
|
-
const receiveFromHash = lane.fromHash;
|
|
13908
|
-
const receiveSession = lane.session;
|
|
13909
|
-
const receiveReplicationLifecycleController = lane.lifecycleController;
|
|
13910
|
-
const receiveReplicationInfoReceiveEpoch = lane.receiveEpoch;
|
|
13911
|
-
const peerReceiveLease = lane.lease;
|
|
13912
|
-
const from = context.from;
|
|
13913
|
-
const segmentIds = msg.segmentIds;
|
|
13914
|
-
if (from.equals(this.node.identity.publicKey)) {
|
|
13915
|
-
return;
|
|
13916
|
-
}
|
|
13917
|
-
const fromHash = from.hashcode();
|
|
13918
|
-
if (this._v2Receive.isLegacyCutover(receiveSession)) {
|
|
13919
|
-
if (receiveSession) {
|
|
13920
|
-
this._v2Receive.noteLegacyAnnouncement({
|
|
13921
|
-
peerHash: fromHash,
|
|
13922
|
-
peerSession: receiveSession,
|
|
13923
|
-
receiveEpoch: receiveReplicationInfoReceiveEpoch,
|
|
13924
|
-
senderTransportSession: context.message.header.session,
|
|
13925
|
-
transportTimestamp: context.message.header.timestamp,
|
|
13926
|
-
message: msg,
|
|
13927
|
-
});
|
|
13928
|
-
}
|
|
13929
|
-
return;
|
|
13930
|
-
}
|
|
13931
|
-
// Same pre-lane gate shape as Added/All above (and the same
|
|
13932
|
-
// intentional absence of a subscription-epoch term).
|
|
13933
|
-
if (!this._instanceLifecycle.isMembershipActiveFor(receiveReplicationLifecycleController) ||
|
|
13934
|
-
!this._peerSessions.isReceiveEpochCurrent(receiveFromHash, receiveReplicationInfoReceiveEpoch) ||
|
|
13935
|
-
this._peerSessions.isReplicationInfoBlocked(fromHash)) {
|
|
13936
|
-
return;
|
|
13937
|
-
}
|
|
13938
|
-
const messageTimestamp = context.message.header.timestamp;
|
|
13939
|
-
peerReceiveLease.release();
|
|
13940
|
-
await this.withReplicationInfoApplyQueue(fromHash, async () => {
|
|
13941
|
-
if (!this._instanceLifecycle.isMembershipActiveFor(receiveReplicationLifecycleController) ||
|
|
13942
|
-
!this._peerSessions.isCurrent(fromHash, receiveSession) ||
|
|
13943
|
-
!this._peerSessions.isReceiveEpochCurrent(fromHash, receiveReplicationInfoReceiveEpoch) ||
|
|
13944
|
-
this._peerSessions.isReplicationInfoBlocked(fromHash)) {
|
|
13945
|
-
return;
|
|
13946
|
-
}
|
|
13947
|
-
if (receiveSession && this._v2Receive.isLegacyCutover(receiveSession)) {
|
|
13948
|
-
this._v2Receive.noteLegacyAnnouncement({
|
|
13949
|
-
peerHash: fromHash,
|
|
13950
|
-
peerSession: receiveSession,
|
|
13951
|
-
receiveEpoch: receiveReplicationInfoReceiveEpoch,
|
|
13952
|
-
senderTransportSession: context.message.header.session,
|
|
13953
|
-
transportTimestamp: context.message.header.timestamp,
|
|
13954
|
-
message: msg,
|
|
13955
|
-
});
|
|
13956
|
-
return;
|
|
13957
|
-
}
|
|
13958
|
-
const previousTimestamp = this.latestReplicationInfoMessage.get(fromHash);
|
|
13959
|
-
if (previousTimestamp && previousTimestamp > messageTimestamp) {
|
|
13960
|
-
return;
|
|
13961
|
-
}
|
|
13962
|
-
this.latestReplicationInfoMessage.set(fromHash, messageTimestamp);
|
|
13963
|
-
if (this.closed) {
|
|
13964
|
-
return;
|
|
13965
|
-
}
|
|
13966
|
-
const rangesToRemove = await this.resolveReplicationRangesFromIdsAndKey(segmentIds, from);
|
|
13967
|
-
await this.removeReplicationRanges(rangesToRemove, from);
|
|
13968
|
-
this._liveness.markReplicatorActivity(fromHash);
|
|
13969
|
-
const timestamp = BigInt(+new Date());
|
|
13970
|
-
for (const range of rangesToRemove) {
|
|
13971
|
-
this.replicationChangeDebounceFn.add({
|
|
13972
|
-
range,
|
|
13973
|
-
type: "removed",
|
|
13974
|
-
timestamp,
|
|
13975
|
-
});
|
|
13976
|
-
}
|
|
13977
|
-
});
|
|
13978
|
-
}
|
|
13979
13617
|
async calculateTotalParticipation(options) {
|
|
13980
13618
|
if (options?.sum) {
|
|
13981
13619
|
const ranges = await this.replicationIndex.iterate().all();
|
|
@@ -14217,13 +13855,13 @@ let SharedLog = (() => {
|
|
|
14217
13855
|
// in one large message instead
|
|
14218
13856
|
announce: (msg) => {
|
|
14219
13857
|
throwIfInactive();
|
|
14220
|
-
if (
|
|
13858
|
+
if (!("added" in msg)) {
|
|
14221
13859
|
throw new Error("Unexpected");
|
|
14222
13860
|
}
|
|
14223
13861
|
if (messageToSend) {
|
|
14224
13862
|
// merge segments to make it into one messages
|
|
14225
|
-
for (const segment of msg.segments) {
|
|
14226
|
-
messageToSend.segments.push(segment);
|
|
13863
|
+
for (const segment of msg.added.segments) {
|
|
13864
|
+
messageToSend.added.segments.push(segment);
|
|
14227
13865
|
}
|
|
14228
13866
|
}
|
|
14229
13867
|
else {
|
|
@@ -14332,36 +13970,21 @@ let SharedLog = (() => {
|
|
|
14332
13970
|
return;
|
|
14333
13971
|
}
|
|
14334
13972
|
requestAttempts++;
|
|
14335
|
-
|
|
14336
|
-
|
|
14337
|
-
|
|
14338
|
-
|
|
14339
|
-
|
|
14340
|
-
|
|
14341
|
-
|
|
14342
|
-
if (isNotStartedError(e)) {
|
|
14343
|
-
return;
|
|
14344
|
-
}
|
|
14345
|
-
logger.error(e?.toString?.() ?? String(e));
|
|
13973
|
+
const peerHash = key.hashcode();
|
|
13974
|
+
const peerSession = this._peerSessions.current(peerHash);
|
|
13975
|
+
if (peerSession?.phase === "open") {
|
|
13976
|
+
this._v2Receive.resumeParkedRequest({
|
|
13977
|
+
peerHash,
|
|
13978
|
+
peerSession,
|
|
13979
|
+
receiveEpoch: this._peerSessions.receiveEpoch(peerHash),
|
|
14346
13980
|
});
|
|
14347
13981
|
}
|
|
14348
|
-
else {
|
|
14349
|
-
|
|
14350
|
-
|
|
14351
|
-
|
|
14352
|
-
|
|
14353
|
-
|
|
14354
|
-
peerSession,
|
|
14355
|
-
receiveEpoch: this._peerSessions.receiveEpoch(peerHash),
|
|
14356
|
-
});
|
|
14357
|
-
}
|
|
14358
|
-
else if (peerSession === null || peerSession.phase === "departing") {
|
|
14359
|
-
// A peer can be known to routing before its SharedLog topic
|
|
14360
|
-
// subscription has been observed. Legacy requests used to bootstrap
|
|
14361
|
-
// that case directly; V2 needs an authoritative Subscribe snapshot
|
|
14362
|
-
// before it can create a fenced PeerSession and request a Full.
|
|
14363
|
-
requestSubscriberSnapshot();
|
|
14364
|
-
}
|
|
13982
|
+
else if (peerSession === null || peerSession.phase === "departing") {
|
|
13983
|
+
// A peer can be known to routing before its SharedLog topic
|
|
13984
|
+
// subscription has been observed. Legacy requests used to bootstrap
|
|
13985
|
+
// that case directly; V2 needs an authoritative Subscribe snapshot
|
|
13986
|
+
// before it can create a fenced PeerSession and request a Full.
|
|
13987
|
+
requestSubscriberSnapshot();
|
|
14365
13988
|
}
|
|
14366
13989
|
if (requestAttempts < maxRequestAttempts) {
|
|
14367
13990
|
requestTimer = setTimeout(requestReplicationInfo, requestIntervalMs);
|
|
@@ -16183,7 +15806,7 @@ let SharedLog = (() => {
|
|
|
16183
15806
|
*/
|
|
16184
15807
|
resetReplicationInfoV2RecoveryEscalation(peerHash) {
|
|
16185
15808
|
const state = this._replicationInfoRequestByPeer.get(peerHash);
|
|
16186
|
-
if (!state
|
|
15809
|
+
if (!state) {
|
|
16187
15810
|
return;
|
|
16188
15811
|
}
|
|
16189
15812
|
state.attempts = 0;
|
|
@@ -16272,65 +15895,20 @@ let SharedLog = (() => {
|
|
|
16272
15895
|
};
|
|
16273
15896
|
tick();
|
|
16274
15897
|
}
|
|
15898
|
+
/**
|
|
15899
|
+
* Collapsed B12 shell: the legacy request-polling body (bounded
|
|
15900
|
+
* RequestReplicationInfoMessage ticks) is deleted; V2 recovery is the
|
|
15901
|
+
* only scheduler. Retained as a named seam rather than inlined at the
|
|
15902
|
+
* callers because the liveness monitor wiring and several suites
|
|
15903
|
+
* stub/spy it by name.
|
|
15904
|
+
*/
|
|
16275
15905
|
scheduleReplicationInfoRequests(peer, replicationLifecycleController = this._instanceLifecycle
|
|
16276
15906
|
?.membershipLifecycleController) {
|
|
16277
15907
|
if (!replicationLifecycleController ||
|
|
16278
15908
|
!this.isReplicationLifecycleActive(replicationLifecycleController)) {
|
|
16279
15909
|
return;
|
|
16280
15910
|
}
|
|
16281
|
-
|
|
16282
|
-
this.scheduleReplicationInfoV2Recovery(peer, replicationLifecycleController);
|
|
16283
|
-
return;
|
|
16284
|
-
}
|
|
16285
|
-
const peerHash = peer.hashcode();
|
|
16286
|
-
const requestStates = this._replicationInfoRequestByPeer;
|
|
16287
|
-
if (requestStates.has(peerHash)) {
|
|
16288
|
-
return;
|
|
16289
|
-
}
|
|
16290
|
-
const state = {
|
|
16291
|
-
attempts: 0,
|
|
16292
|
-
};
|
|
16293
|
-
requestStates.set(peerHash, state);
|
|
16294
|
-
const cancel = () => {
|
|
16295
|
-
if (requestStates.get(peerHash) !== state) {
|
|
16296
|
-
return;
|
|
16297
|
-
}
|
|
16298
|
-
if (state.timer) {
|
|
16299
|
-
clearTimeout(state.timer);
|
|
16300
|
-
}
|
|
16301
|
-
requestStates.delete(peerHash);
|
|
16302
|
-
};
|
|
16303
|
-
const intervalMs = Math.max(50, this.waitForReplicatorRequestIntervalMs);
|
|
16304
|
-
const maxAttempts = this.waitForReplicatorRequestMaxAttempts ??
|
|
16305
|
-
Math.max(WAIT_FOR_REPLICATOR_REQUEST_MIN_ATTEMPTS, Math.ceil(this.waitForReplicatorTimeout / intervalMs));
|
|
16306
|
-
const tick = () => {
|
|
16307
|
-
if (!this.isReplicationLifecycleActive(replicationLifecycleController)) {
|
|
16308
|
-
cancel();
|
|
16309
|
-
return;
|
|
16310
|
-
}
|
|
16311
|
-
state.attempts++;
|
|
16312
|
-
this.rpc
|
|
16313
|
-
.send(new RequestReplicationInfoMessage(), {
|
|
16314
|
-
mode: new AcknowledgeDelivery({ redundancy: 1, to: [peer] }),
|
|
16315
|
-
signal: replicationLifecycleController.signal,
|
|
16316
|
-
})
|
|
16317
|
-
.catch((e) => {
|
|
16318
|
-
// Best-effort: missing peers / unopened RPC should not fail join flows.
|
|
16319
|
-
if (isNotStartedError(e) ||
|
|
16320
|
-
(replicationLifecycleController.signal.aborted &&
|
|
16321
|
-
e instanceof AbortError)) {
|
|
16322
|
-
return;
|
|
16323
|
-
}
|
|
16324
|
-
logger.error(e?.toString?.() ?? String(e));
|
|
16325
|
-
});
|
|
16326
|
-
if (state.attempts >= maxAttempts) {
|
|
16327
|
-
cancel();
|
|
16328
|
-
return;
|
|
16329
|
-
}
|
|
16330
|
-
state.timer = setTimeout(tick, intervalMs);
|
|
16331
|
-
state.timer.unref?.();
|
|
16332
|
-
};
|
|
16333
|
-
tick();
|
|
15911
|
+
this.scheduleReplicationInfoV2Recovery(peer, replicationLifecycleController);
|
|
16334
15912
|
}
|
|
16335
15913
|
async handleSubscriptionChange(publicKey, topics, subscribed, subscriptionEpoch, subscriptionTransportSession) {
|
|
16336
15914
|
if (!topics.includes(this.topic)) {
|
|
@@ -16393,10 +15971,6 @@ let SharedLog = (() => {
|
|
|
16393
15971
|
!ownsSubscriptionEpoch()) {
|
|
16394
15972
|
return;
|
|
16395
15973
|
}
|
|
16396
|
-
// The timestamp watermark belongs to the previous subscription epoch.
|
|
16397
|
-
// Sender clocks are not synchronized, so carrying a local unsubscribe
|
|
16398
|
-
// timestamp forward could reject every valid announcement after reconnect.
|
|
16399
|
-
this.latestReplicationInfoMessage.delete(peerHash);
|
|
16400
15974
|
this._pendingReplicatorLeaveByPeer.delete(peerHash);
|
|
16401
15975
|
const openingCapabilities = this._openingSyncCapabilitiesByPeer.get(peerHash);
|
|
16402
15976
|
if (openingCapabilities?.epoch === expectedSubscriptionEpoch) {
|
|
@@ -16439,11 +16013,6 @@ let SharedLog = (() => {
|
|
|
16439
16013
|
this._peerSessions.blockReplicationInfo(peerHash);
|
|
16440
16014
|
const disconnectedWarmupSession = this.joinWarmup._warmupSessionsByTarget.get(peerHash) ?? null;
|
|
16441
16015
|
this.joinWarmup.cancelJoinWarmupTarget(peerHash);
|
|
16442
|
-
const now = BigInt(+new Date());
|
|
16443
|
-
const previous = this.latestReplicationInfoMessage.get(peerHash);
|
|
16444
|
-
if (!previous || previous < now) {
|
|
16445
|
-
this.latestReplicationInfoMessage.set(peerHash, now);
|
|
16446
|
-
}
|
|
16447
16016
|
let removed = false;
|
|
16448
16017
|
try {
|
|
16449
16018
|
// Unsubscribe can race with the peer's final replication reset message.
|
|
@@ -16485,84 +16054,20 @@ let SharedLog = (() => {
|
|
|
16485
16054
|
this.promoteReplicationInfoV2ReceiveCapability(publicKey, expectedSubscriptionEpoch);
|
|
16486
16055
|
// Decode, sender and authenticated apply readiness are separate bits. An
|
|
16487
16056
|
// ACKed capability advert is the local half of receiver-led negotiation;
|
|
16488
|
-
// readiness is promoted
|
|
16489
|
-
// This
|
|
16490
|
-
//
|
|
16057
|
+
// readiness is promoted through the coordinator once the ACK arrives.
|
|
16058
|
+
// This keeps capability ACK latency off the subscription callback's
|
|
16059
|
+
// critical path.
|
|
16491
16060
|
const receiveEpoch = this._peerSessions.receiveEpoch(peerHash);
|
|
16492
|
-
|
|
16061
|
+
// B12: there is no legacy startup work to order ahead of RequestV2, so
|
|
16062
|
+
// the two-phase legacy barrier is gone — an ACKed advert promotes
|
|
16063
|
+
// readiness through the coordinator as soon as it lands.
|
|
16064
|
+
this._v2Receive.advertiseLocalCapability({
|
|
16493
16065
|
target: publicKey,
|
|
16494
16066
|
peerSession: expectedSubscriptionEpoch,
|
|
16495
16067
|
receiveEpoch,
|
|
16496
16068
|
signal: replicationLifecycleController.signal,
|
|
16497
16069
|
});
|
|
16498
|
-
|
|
16499
|
-
// Current logs have no legacy startup work to order ahead of RequestV2.
|
|
16500
|
-
// Releasing is synchronous and exact-session fenced; the ACK may still
|
|
16501
|
-
// arrive later and promote readiness through the coordinator.
|
|
16502
|
-
localCapabilityAdvertisement.releaseLegacyBarrier();
|
|
16503
|
-
this.scheduleReplicationInfoV2Recovery(publicKey, replicationLifecycleController);
|
|
16504
|
-
return;
|
|
16505
|
-
}
|
|
16506
|
-
try {
|
|
16507
|
-
let replicationSegments;
|
|
16508
|
-
try {
|
|
16509
|
-
replicationSegments = await this.getMyReplicationSegments();
|
|
16510
|
-
}
|
|
16511
|
-
catch (error) {
|
|
16512
|
-
if (!this.isReplicationLifecycleActive(replicationLifecycleController) &&
|
|
16513
|
-
isNotStartedError(error)) {
|
|
16514
|
-
return;
|
|
16515
|
-
}
|
|
16516
|
-
throw error;
|
|
16517
|
-
}
|
|
16518
|
-
if (!this.isReplicationLifecycleActive(replicationLifecycleController) ||
|
|
16519
|
-
!ownsSubscriptionEpoch()) {
|
|
16520
|
-
return;
|
|
16521
|
-
}
|
|
16522
|
-
if (replicationSegments.length > 0) {
|
|
16523
|
-
const segments = replicationSegments.map((x) => x.toReplicationRange());
|
|
16524
|
-
this.validatePersistedReplicationRangeSnapshot(segments);
|
|
16525
|
-
await this.rpc
|
|
16526
|
-
.send(new AllReplicatingSegmentsMessage({
|
|
16527
|
-
segments,
|
|
16528
|
-
}), {
|
|
16529
|
-
mode: new AcknowledgeDelivery({
|
|
16530
|
-
redundancy: 1,
|
|
16531
|
-
to: [publicKey],
|
|
16532
|
-
}),
|
|
16533
|
-
signal: replicationLifecycleController.signal,
|
|
16534
|
-
})
|
|
16535
|
-
.catch((error) => this.handleReplicationLifecycleSendError(error, replicationLifecycleController));
|
|
16536
|
-
if (!this.isReplicationLifecycleActive(replicationLifecycleController) ||
|
|
16537
|
-
!ownsSubscriptionEpoch()) {
|
|
16538
|
-
return;
|
|
16539
|
-
}
|
|
16540
|
-
if (this.v8Behaviour) {
|
|
16541
|
-
// for backwards compatibility
|
|
16542
|
-
await this.rpc
|
|
16543
|
-
.send(new ResponseRoleMessage({
|
|
16544
|
-
role: this.getRoleFromReplicationSegments(replicationSegments),
|
|
16545
|
-
}), {
|
|
16546
|
-
mode: new AcknowledgeDelivery({
|
|
16547
|
-
redundancy: 1,
|
|
16548
|
-
to: [publicKey],
|
|
16549
|
-
}),
|
|
16550
|
-
signal: replicationLifecycleController.signal,
|
|
16551
|
-
})
|
|
16552
|
-
.catch((error) => this.handleReplicationLifecycleSendError(error, replicationLifecycleController));
|
|
16553
|
-
}
|
|
16554
|
-
}
|
|
16555
|
-
// Keep legacy request-based discovery independent of the capability ACK.
|
|
16556
|
-
// This makes mixed-version joins resilient to timing-sensitive delivery/order
|
|
16557
|
-
// issues where we may miss the remote peer's initial announcement.
|
|
16558
|
-
if (this.isReplicationLifecycleActive(replicationLifecycleController) &&
|
|
16559
|
-
ownsSubscriptionEpoch()) {
|
|
16560
|
-
this.scheduleReplicationInfoRequests(publicKey, replicationLifecycleController);
|
|
16561
|
-
}
|
|
16562
|
-
}
|
|
16563
|
-
finally {
|
|
16564
|
-
localCapabilityAdvertisement.releaseLegacyBarrier();
|
|
16565
|
-
}
|
|
16070
|
+
this.scheduleReplicationInfoV2Recovery(publicKey, replicationLifecycleController);
|
|
16566
16071
|
}
|
|
16567
16072
|
getClampedReplicas(customValue) {
|
|
16568
16073
|
if (!customValue) {
|
|
@@ -17538,14 +17043,6 @@ let SharedLog = (() => {
|
|
|
17538
17043
|
const subscriptionEpoch = this._peerSessions.rotate(fromHash, "departing");
|
|
17539
17044
|
this._peerSessions.blockReplicationInfo(fromHash);
|
|
17540
17045
|
this._recentRepairDispatch.delete(fromHash);
|
|
17541
|
-
// Keep a per-peer timestamp watermark when we observe an unsubscribe. This
|
|
17542
|
-
// prevents late/out-of-order replication-info messages from re-introducing
|
|
17543
|
-
// stale segments for a peer that has already left the topic.
|
|
17544
|
-
const now = BigInt(+new Date());
|
|
17545
|
-
const prev = this.latestReplicationInfoMessage.get(fromHash);
|
|
17546
|
-
if (!prev || prev < now) {
|
|
17547
|
-
this.latestReplicationInfoMessage.set(fromHash, now);
|
|
17548
|
-
}
|
|
17549
17046
|
this.invalidateSharedLogTopicSubscribersCache();
|
|
17550
17047
|
return this.handleSubscriptionChange(evt.detail.from, evt.detail.topics, false, subscriptionEpoch);
|
|
17551
17048
|
}
|
|
@@ -17661,22 +17158,13 @@ let SharedLog = (() => {
|
|
|
17661
17158
|
if (!canReplicate) {
|
|
17662
17159
|
return false;
|
|
17663
17160
|
}
|
|
17664
|
-
|
|
17665
|
-
|
|
17666
|
-
|
|
17667
|
-
|
|
17668
|
-
|
|
17669
|
-
|
|
17670
|
-
|
|
17671
|
-
return false;
|
|
17672
|
-
}
|
|
17673
|
-
catch (error) {
|
|
17674
|
-
if (isTransientReplicationAnnouncementError(error) &&
|
|
17675
|
-
this._announcements._replicationAnnouncementRetryPending) {
|
|
17676
|
-
return false;
|
|
17677
|
-
}
|
|
17678
|
-
throw error;
|
|
17679
|
-
}
|
|
17161
|
+
await this.startAnnounceReplicating([dynamicRange], {
|
|
17162
|
+
checkDuplicates: false,
|
|
17163
|
+
reset: false,
|
|
17164
|
+
shouldApply: isCurrent,
|
|
17165
|
+
}, ownershipLifecycleController);
|
|
17166
|
+
if (!isCurrent())
|
|
17167
|
+
return false;
|
|
17680
17168
|
/* await this._updateRole(newRole, onRoleChange); */
|
|
17681
17169
|
if (isCurrent()) {
|
|
17682
17170
|
void rebalanceParticipationDebounced?.call();
|