@peerbit/shared-log 15.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 +7 -12
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +66 -580
- package/dist/src/index.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 +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 +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/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 +87 -794
- package/src/replication-announcement.ts +8 -739
- package/src/replication-info-v2-receive.ts +17 -263
- package/src/replication-info-v2-send.ts +0 -7
- 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
|
|
@@ -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
|
|
@@ -9255,7 +9189,6 @@ let SharedLog = (() => {
|
|
|
9255
9189
|
this._liveness.resetForOpen();
|
|
9256
9190
|
this._lastLocalAppendAt = 0;
|
|
9257
9191
|
this._announcements ??= this.createReplicationAnnouncementCoordinator();
|
|
9258
|
-
this._announcements.resetForOpen();
|
|
9259
9192
|
this._v2Receive ??= this.createReplicationInfoV2ReceiveCoordinator();
|
|
9260
9193
|
this._v2Receive.resetForOpen();
|
|
9261
9194
|
this._v2Send ??= this.createReplicationInfoV2SendCoordinator();
|
|
@@ -9297,13 +9230,6 @@ let SharedLog = (() => {
|
|
|
9297
9230
|
throw new Error("waitForReplicatorRequestMaxAttempts must be a positive number");
|
|
9298
9231
|
}
|
|
9299
9232
|
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
9233
|
this._closeController.signal.addEventListener("abort", () => {
|
|
9308
9234
|
for (const [_peer, state] of this._replicationInfoRequestByPeer) {
|
|
9309
9235
|
if (state.timer)
|
|
@@ -9658,7 +9584,6 @@ let SharedLog = (() => {
|
|
|
9658
9584
|
peerSupportsRawExchangeHeads: (peer) => this.peerSupportsRawExchangeHeads(peer),
|
|
9659
9585
|
sendRawExchangeHeads: (hashes, to, sendOptions) => this.trySendFusedRawExchangeHeads(hashes, to, sendOptions),
|
|
9660
9586
|
warn,
|
|
9661
|
-
compatibility: this._logProperties?.compatibility,
|
|
9662
9587
|
resolution: this.domain.resolution,
|
|
9663
9588
|
sync: options?.sync,
|
|
9664
9589
|
syncronizer: options?.syncronizer,
|
|
@@ -10399,7 +10324,7 @@ let SharedLog = (() => {
|
|
|
10399
10324
|
cleanupPeerDisconnectTracking(peerHash, ownershipLifecycleController = this.captureReplicationOwnershipLifecycle()) {
|
|
10400
10325
|
const peerSession = this._peerSessions.current(peerHash);
|
|
10401
10326
|
const preserveV2Session = peerSession?.phase === "open" && peerSession.isActive();
|
|
10402
|
-
if (
|
|
10327
|
+
if (!preserveV2Session) {
|
|
10403
10328
|
this.cancelReplicationInfoRequests(peerHash);
|
|
10404
10329
|
}
|
|
10405
10330
|
this._liveness._replicatorLivenessFailures.delete(peerHash);
|
|
@@ -10425,11 +10350,8 @@ let SharedLog = (() => {
|
|
|
10425
10350
|
}
|
|
10426
10351
|
advanceReplicationInfoRecoveryEpoch(peerHash) {
|
|
10427
10352
|
// 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.
|
|
10353
|
+
// when they eventually reach the apply lane.
|
|
10431
10354
|
const receiveEpoch = this._peerSessions.advanceReceiveEpoch(peerHash);
|
|
10432
|
-
this.latestReplicationInfoMessage.delete(peerHash);
|
|
10433
10355
|
const peerSession = this._peerSessions.current(peerHash);
|
|
10434
10356
|
if (peerSession?.phase === "open") {
|
|
10435
10357
|
this._v2Receive.advanceRecovery({
|
|
@@ -11138,13 +11060,6 @@ let SharedLog = (() => {
|
|
|
11138
11060
|
firstError ??= error;
|
|
11139
11061
|
}
|
|
11140
11062
|
};
|
|
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
11063
|
captureSync(() => {
|
|
11149
11064
|
if (this._wireSyncSession) {
|
|
11150
11065
|
this._wireSyncSession.unregisterTopic(this.topic);
|
|
@@ -11227,7 +11142,6 @@ let SharedLog = (() => {
|
|
|
11227
11142
|
captureSync(() => {
|
|
11228
11143
|
this._pendingIHave?.clear();
|
|
11229
11144
|
this._pendingIHaveCallbacks?.clear();
|
|
11230
|
-
this.latestReplicationInfoMessage?.clear();
|
|
11231
11145
|
this._peerSessions?.clearReceiveEpochsForClose();
|
|
11232
11146
|
this._peerSessions?.clearCleanupGatesForClose();
|
|
11233
11147
|
this._activeReceiveHandlersByPeer?.clear();
|
|
@@ -11317,7 +11231,6 @@ let SharedLog = (() => {
|
|
|
11317
11231
|
await pruneRemoveTerminalFence.drained;
|
|
11318
11232
|
await this.drainPendingIHaveCallbacks();
|
|
11319
11233
|
this.ensureNativeDurabilityRuntimeState();
|
|
11320
|
-
this._announcements.cancelCurrentReplicationStateAnnouncementRetry();
|
|
11321
11234
|
}
|
|
11322
11235
|
catch (error) {
|
|
11323
11236
|
// The terminal preamble has already disabled parent attachments and the
|
|
@@ -11360,17 +11273,7 @@ let SharedLog = (() => {
|
|
|
11360
11273
|
}
|
|
11361
11274
|
}, 2_000);
|
|
11362
11275
|
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);
|
|
11276
|
+
await this._v2Send.sendTerminalReset(abort.signal);
|
|
11374
11277
|
}
|
|
11375
11278
|
finally {
|
|
11376
11279
|
clearTimeout(abortTimer);
|
|
@@ -11462,7 +11365,6 @@ let SharedLog = (() => {
|
|
|
11462
11365
|
await replicationRangeTerminalFence.drained;
|
|
11463
11366
|
await pruneRemoveTerminalFence.drained;
|
|
11464
11367
|
await this.drainPendingIHaveCallbacks();
|
|
11465
|
-
this._announcements.cancelCurrentReplicationStateAnnouncementRetry();
|
|
11466
11368
|
}
|
|
11467
11369
|
catch (error) {
|
|
11468
11370
|
// The terminal preamble is not safely reversible. Preserve the fence until
|
|
@@ -11489,17 +11391,7 @@ let SharedLog = (() => {
|
|
|
11489
11391
|
}
|
|
11490
11392
|
}, 2_000);
|
|
11491
11393
|
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);
|
|
11394
|
+
await this._v2Send.sendTerminalReset(abort.signal);
|
|
11503
11395
|
}
|
|
11504
11396
|
finally {
|
|
11505
11397
|
clearTimeout(abortTimer);
|
|
@@ -11923,15 +11815,15 @@ let SharedLog = (() => {
|
|
|
11923
11815
|
if (!context.from) {
|
|
11924
11816
|
throw new Error("Missing from in update role message");
|
|
11925
11817
|
}
|
|
11926
|
-
if (
|
|
11927
|
-
|
|
11928
|
-
|
|
11929
|
-
|
|
11930
|
-
|
|
11931
|
-
|
|
11932
|
-
//
|
|
11933
|
-
//
|
|
11934
|
-
//
|
|
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()).
|
|
11935
11827
|
return;
|
|
11936
11828
|
}
|
|
11937
11829
|
// Snapshot receive ownership before any async handler gets a chance to
|
|
@@ -11961,29 +11853,20 @@ let SharedLog = (() => {
|
|
|
11961
11853
|
const receiveOwnershipRevision = this._instanceLifecycle?._receiveOwnershipRevision ?? 0;
|
|
11962
11854
|
const receiveOwnershipLifecycleController = this.captureReplicationOwnershipLifecycle();
|
|
11963
11855
|
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 ||
|
|
11856
|
+
if (msg instanceof FullReplicationInfoV2Message ||
|
|
11970
11857
|
msg instanceof AddedReplicationInfoV2Message) {
|
|
11971
11858
|
// Bound decoded untrusted vectors before per-peer/global mutation
|
|
11972
11859
|
// queues, trusted-replicator authorization, or liveness side effects.
|
|
11973
11860
|
this.validateReplicationRangeAnnouncement(msg.segments);
|
|
11974
11861
|
}
|
|
11975
|
-
else if (msg instanceof
|
|
11976
|
-
msg instanceof StoppedReplicationInfoV2Message) {
|
|
11862
|
+
else if (msg instanceof StoppedReplicationInfoV2Message) {
|
|
11977
11863
|
// Bound the raw decoded vector before deduplication can hide the
|
|
11978
11864
|
// allocation cost, and before liveness or apply-queue side effects.
|
|
11979
11865
|
this.validateStoppedReplicationAnnouncement(msg.segmentIds);
|
|
11980
11866
|
}
|
|
11981
11867
|
if (!context.from.equals(this.node.identity.publicKey) &&
|
|
11982
11868
|
!(msg instanceof RequestReplicationInfoV2Message) &&
|
|
11983
|
-
!isReplicationInfoV2Message(msg)
|
|
11984
|
-
!(msg instanceof AllReplicatingSegmentsMessage) &&
|
|
11985
|
-
!(msg instanceof AddedReplicationSegmentMessage) &&
|
|
11986
|
-
!(msg instanceof StoppedReplicating)) {
|
|
11869
|
+
!isReplicationInfoV2Message(msg)) {
|
|
11987
11870
|
this._liveness.markReplicatorActivity(receiveFromHash);
|
|
11988
11871
|
}
|
|
11989
11872
|
const syncProfile = this._logProperties?.sync?.profile;
|
|
@@ -13405,16 +13288,6 @@ let SharedLog = (() => {
|
|
|
13405
13288
|
else if (msg instanceof ReplicationPingMessage) {
|
|
13406
13289
|
// No-op: used as an ACKed unicast liveness probe.
|
|
13407
13290
|
}
|
|
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
13291
|
else {
|
|
13419
13292
|
throw new Error("Unexpected message");
|
|
13420
13293
|
}
|
|
@@ -13600,68 +13473,6 @@ let SharedLog = (() => {
|
|
|
13600
13473
|
}
|
|
13601
13474
|
}
|
|
13602
13475
|
}
|
|
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
13476
|
async handleReplicationInfoV2Announcement(msg, context, lane) {
|
|
13666
13477
|
const from = context.from;
|
|
13667
13478
|
const fromHash = lane.fromHash;
|
|
@@ -13797,187 +13608,12 @@ let SharedLog = (() => {
|
|
|
13797
13608
|
// A committed V2 announcement is applied progress: the peer answers,
|
|
13798
13609
|
// so recovery re-solicitation may restart from the base interval.
|
|
13799
13610
|
this.resetReplicationInfoV2RecoveryEscalation(fromHash);
|
|
13800
|
-
if (msg instanceof FullReplicationInfoV2Message &&
|
|
13801
|
-
this.legacyReplicationInfoEnabled) {
|
|
13802
|
-
this.cancelReplicationInfoRequests(fromHash);
|
|
13803
|
-
}
|
|
13804
13611
|
});
|
|
13805
13612
|
}
|
|
13806
13613
|
finally {
|
|
13807
13614
|
this._v2Receive.release(admission);
|
|
13808
13615
|
}
|
|
13809
13616
|
}
|
|
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
13617
|
async calculateTotalParticipation(options) {
|
|
13982
13618
|
if (options?.sum) {
|
|
13983
13619
|
const ranges = await this.replicationIndex.iterate().all();
|
|
@@ -14334,36 +13970,21 @@ let SharedLog = (() => {
|
|
|
14334
13970
|
return;
|
|
14335
13971
|
}
|
|
14336
13972
|
requestAttempts++;
|
|
14337
|
-
|
|
14338
|
-
|
|
14339
|
-
|
|
14340
|
-
|
|
14341
|
-
|
|
14342
|
-
|
|
14343
|
-
|
|
14344
|
-
if (isNotStartedError(e)) {
|
|
14345
|
-
return;
|
|
14346
|
-
}
|
|
14347
|
-
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),
|
|
14348
13980
|
});
|
|
14349
13981
|
}
|
|
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
|
-
}
|
|
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();
|
|
14367
13988
|
}
|
|
14368
13989
|
if (requestAttempts < maxRequestAttempts) {
|
|
14369
13990
|
requestTimer = setTimeout(requestReplicationInfo, requestIntervalMs);
|
|
@@ -16185,7 +15806,7 @@ let SharedLog = (() => {
|
|
|
16185
15806
|
*/
|
|
16186
15807
|
resetReplicationInfoV2RecoveryEscalation(peerHash) {
|
|
16187
15808
|
const state = this._replicationInfoRequestByPeer.get(peerHash);
|
|
16188
|
-
if (!state
|
|
15809
|
+
if (!state) {
|
|
16189
15810
|
return;
|
|
16190
15811
|
}
|
|
16191
15812
|
state.attempts = 0;
|
|
@@ -16274,65 +15895,20 @@ let SharedLog = (() => {
|
|
|
16274
15895
|
};
|
|
16275
15896
|
tick();
|
|
16276
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
|
+
*/
|
|
16277
15905
|
scheduleReplicationInfoRequests(peer, replicationLifecycleController = this._instanceLifecycle
|
|
16278
15906
|
?.membershipLifecycleController) {
|
|
16279
15907
|
if (!replicationLifecycleController ||
|
|
16280
15908
|
!this.isReplicationLifecycleActive(replicationLifecycleController)) {
|
|
16281
15909
|
return;
|
|
16282
15910
|
}
|
|
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();
|
|
15911
|
+
this.scheduleReplicationInfoV2Recovery(peer, replicationLifecycleController);
|
|
16336
15912
|
}
|
|
16337
15913
|
async handleSubscriptionChange(publicKey, topics, subscribed, subscriptionEpoch, subscriptionTransportSession) {
|
|
16338
15914
|
if (!topics.includes(this.topic)) {
|
|
@@ -16395,10 +15971,6 @@ let SharedLog = (() => {
|
|
|
16395
15971
|
!ownsSubscriptionEpoch()) {
|
|
16396
15972
|
return;
|
|
16397
15973
|
}
|
|
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
15974
|
this._pendingReplicatorLeaveByPeer.delete(peerHash);
|
|
16403
15975
|
const openingCapabilities = this._openingSyncCapabilitiesByPeer.get(peerHash);
|
|
16404
15976
|
if (openingCapabilities?.epoch === expectedSubscriptionEpoch) {
|
|
@@ -16441,11 +16013,6 @@ let SharedLog = (() => {
|
|
|
16441
16013
|
this._peerSessions.blockReplicationInfo(peerHash);
|
|
16442
16014
|
const disconnectedWarmupSession = this.joinWarmup._warmupSessionsByTarget.get(peerHash) ?? null;
|
|
16443
16015
|
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
16016
|
let removed = false;
|
|
16450
16017
|
try {
|
|
16451
16018
|
// Unsubscribe can race with the peer's final replication reset message.
|
|
@@ -16487,84 +16054,20 @@ let SharedLog = (() => {
|
|
|
16487
16054
|
this.promoteReplicationInfoV2ReceiveCapability(publicKey, expectedSubscriptionEpoch);
|
|
16488
16055
|
// Decode, sender and authenticated apply readiness are separate bits. An
|
|
16489
16056
|
// ACKed capability advert is the local half of receiver-led negotiation;
|
|
16490
|
-
// readiness is promoted
|
|
16491
|
-
// This
|
|
16492
|
-
//
|
|
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.
|
|
16493
16060
|
const receiveEpoch = this._peerSessions.receiveEpoch(peerHash);
|
|
16494
|
-
|
|
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({
|
|
16495
16065
|
target: publicKey,
|
|
16496
16066
|
peerSession: expectedSubscriptionEpoch,
|
|
16497
16067
|
receiveEpoch,
|
|
16498
16068
|
signal: replicationLifecycleController.signal,
|
|
16499
16069
|
});
|
|
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
|
-
}
|
|
16070
|
+
this.scheduleReplicationInfoV2Recovery(publicKey, replicationLifecycleController);
|
|
16568
16071
|
}
|
|
16569
16072
|
getClampedReplicas(customValue) {
|
|
16570
16073
|
if (!customValue) {
|
|
@@ -17540,14 +17043,6 @@ let SharedLog = (() => {
|
|
|
17540
17043
|
const subscriptionEpoch = this._peerSessions.rotate(fromHash, "departing");
|
|
17541
17044
|
this._peerSessions.blockReplicationInfo(fromHash);
|
|
17542
17045
|
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
17046
|
this.invalidateSharedLogTopicSubscribersCache();
|
|
17552
17047
|
return this.handleSubscriptionChange(evt.detail.from, evt.detail.topics, false, subscriptionEpoch);
|
|
17553
17048
|
}
|
|
@@ -17663,22 +17158,13 @@ let SharedLog = (() => {
|
|
|
17663
17158
|
if (!canReplicate) {
|
|
17664
17159
|
return false;
|
|
17665
17160
|
}
|
|
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
|
-
}
|
|
17161
|
+
await this.startAnnounceReplicating([dynamicRange], {
|
|
17162
|
+
checkDuplicates: false,
|
|
17163
|
+
reset: false,
|
|
17164
|
+
shouldApply: isCurrent,
|
|
17165
|
+
}, ownershipLifecycleController);
|
|
17166
|
+
if (!isCurrent())
|
|
17167
|
+
return false;
|
|
17682
17168
|
/* await this._updateRole(newRole, onRoleChange); */
|
|
17683
17169
|
if (isCurrent()) {
|
|
17684
17170
|
void rebalanceParticipationDebounced?.call();
|