@peerbit/shared-log 14.0.0 → 16.0.0

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