@peerbit/shared-log 15.0.0 → 16.0.1

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