@peerbit/shared-log 16.0.24 → 16.0.25

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/src/index.js CHANGED
@@ -55,7 +55,7 @@ import { CoordinatePersistenceCoordinator, combineCoordinateDeleteHashes, isProm
55
55
  import { CPUUsageIntervalLag } from "./cpu.js";
56
56
  import { debouncedAccumulatorMap, } from "./debounce.js";
57
57
  import { CompatibilityModeRetiredError, NativeDurableCommitError, NoPeersError, PersistedDeliveryError, isNotStartedError, } from "./errors.js";
58
- import { EXCHANGE_HEADS_REPAIR_HINT, EntryWithRefs, ExchangeHeadsMessage, MAX_RAW_EXCHANGE_MESSAGE_SIZE, RawEntryWithRefs, RawExchangeHeadsMessage, RequestIPrune, RequestIPruneV2, ResponseIPrune, ResponseIPruneV2, SYNC_CAPABILITY_PERSISTED_ENTRY_RECEIPTS, SYNC_CAPABILITY_RAW_EXCHANGE_HEADS, SYNC_CAPABILITY_REPLICATION_INFO_V2_APPLY, SYNC_CAPABILITY_REPLICATION_INFO_V2_CONFIRM, SYNC_CAPABILITY_REPLICATION_INFO_V2_DECODE, SYNC_CAPABILITY_REPLICATION_INFO_V2_SEND, StashBackedRawExchangeHeadsMessage, SyncCapabilitiesMessage, collectRawExchangeHeadSendPlan, createExchangeHeadsMessages, createRawExchangeHeadsMessages, getExchangeHeadHash, getPreparedRawExchangeGid, getPreparedRawExchangeHashNumber, getPreparedRawExchangeHeadAppendFacts, getPreparedRawExchangeHeadGid, getPreparedRawExchangeHeadRequestedReplicas, getPreparedRawExchangeHeadShallowEntry, getPreparedRawExchangeHeadSignatureVerified, getPreparedRawExchangeNext, getPreparedRawExchangeRequestedReplicas, getPreparedRawExchangeTimestamp, getRawExchangeHeadByteLength, getRawExchangeHeadStashIndexes, initExchangeHeadEntry, isPreparedRawEntryWithRefs, isStashBackedRawExchangeHeadsMessage, materializeVerifiedRawExchangeHeadsMessage, } from "./exchange-heads.js";
58
+ import { EXCHANGE_HEADS_REPAIR_HINT, EntryWithRefs, ExchangeHeadsMessage, MAX_RAW_EXCHANGE_MESSAGE_SIZE, RawEntryWithRefs, RawExchangeHeadsMessage, RequestIPrune, RequestIPruneV2, ResponseIPrune, ResponseIPruneV2, SYNC_CAPABILITY_PERSISTED_ENTRY_RECEIPTS, SYNC_CAPABILITY_RAW_EXCHANGE_HEADS, SYNC_CAPABILITY_REPLICATION_INFO_V2_APPLY, SYNC_CAPABILITY_REPLICATION_INFO_V2_CONFIRM, SYNC_CAPABILITY_REPLICATION_INFO_V2_DECODE, SYNC_CAPABILITY_REPLICATION_INFO_V2_REARM, SYNC_CAPABILITY_REPLICATION_INFO_V2_SEND, StashBackedRawExchangeHeadsMessage, SyncCapabilitiesMessage, collectRawExchangeHeadSendPlan, createExchangeHeadsMessages, createRawExchangeHeadsMessages, getExchangeHeadHash, getPreparedRawExchangeGid, getPreparedRawExchangeHashNumber, getPreparedRawExchangeHeadAppendFacts, getPreparedRawExchangeHeadGid, getPreparedRawExchangeHeadRequestedReplicas, getPreparedRawExchangeHeadShallowEntry, getPreparedRawExchangeHeadSignatureVerified, getPreparedRawExchangeNext, getPreparedRawExchangeRequestedReplicas, getPreparedRawExchangeTimestamp, getRawExchangeHeadByteLength, getRawExchangeHeadStashIndexes, initExchangeHeadEntry, isPreparedRawEntryWithRefs, isStashBackedRawExchangeHeadsMessage, materializeVerifiedRawExchangeHeadsMessage, } from "./exchange-heads.js";
59
59
  import { FanoutEnvelope } from "./fanout-envelope.js";
60
60
  import { InstanceLifecycle } from "./instance-lifecycle.js";
61
61
  import { MAX_U32, MAX_U64, createNumbers, } from "./integers.js";
@@ -2024,7 +2024,10 @@ let SharedLog = (() => {
2024
2024
  const peerHash = properties.target.hashcode();
2025
2025
  const receiverTransportSession = this.ownTransportSession();
2026
2026
  await this.rpc.send(new SyncCapabilitiesMessage({
2027
- capabilities: this.replicationInfoV2ReceiveCapabilities(),
2027
+ capabilities: this.replicationInfoV2ReceiveCapabilities() |
2028
+ (properties.requestRemoteFullRearm
2029
+ ? SYNC_CAPABILITY_REPLICATION_INFO_V2_REARM
2030
+ : 0),
2028
2031
  }), {
2029
2032
  mode: new AcknowledgeDelivery({
2030
2033
  redundancy: 1,
@@ -2078,6 +2081,7 @@ let SharedLog = (() => {
2078
2081
  peerSession: properties.peerSession,
2079
2082
  receiveEpoch: properties.receiveEpoch,
2080
2083
  signal: properties.signal,
2084
+ requestRemoteFullRearm: properties.requestRemoteFullRearm,
2081
2085
  }),
2082
2086
  onRequestError: (error) => {
2083
2087
  if (isNotStartedError(error) ||
@@ -3447,7 +3451,7 @@ let SharedLog = (() => {
3447
3451
  })) {
3448
3452
  return undefined;
3449
3453
  }
3450
- return { capabilitySession, peerSession };
3454
+ return { capabilitySession, peerSession, receiveEpoch };
3451
3455
  }
3452
3456
  async waitPersistedReceiptRetry(signal, ms) {
3453
3457
  try {
@@ -15966,6 +15970,13 @@ let SharedLog = (() => {
15966
15970
  if (!context.from.equals(this.node.identity.publicKey)) {
15967
15971
  const capabilityTransportSession = context.message?.header?.session;
15968
15972
  const capabilityTimestamp = context.message?.header?.timestamp;
15973
+ const previousCapabilitySession = this._peerSyncCapabilitySessions.get(receiveFromHash);
15974
+ const previousCapabilityTimestamp = this._peerSyncCapabilityTimestamps.get(receiveFromHash);
15975
+ const requestsRemoteFullRearm = (msg.capabilities & SYNC_CAPABILITY_REPLICATION_INFO_V2_REARM) !==
15976
+ 0;
15977
+ // The rearm bit is a one-shot authenticated command, not a sticky
15978
+ // negotiated capability. Store and promote only the steady bits.
15979
+ const steadyCapabilities = msg.capabilities & ~SYNC_CAPABILITY_REPLICATION_INFO_V2_REARM;
15969
15980
  // No await separates this from the capture above, so the captured
15970
15981
  // window state is exact: the legacy re-read of the opening map here
15971
15982
  // could never observe a different value.
@@ -15976,7 +15987,7 @@ let SharedLog = (() => {
15976
15987
  // cleanup cannot erase it before the opening transition commits.
15977
15988
  this.observePeerSyncCapabilities({
15978
15989
  peerHash: receiveFromHash,
15979
- capabilities: msg.capabilities,
15990
+ capabilities: steadyCapabilities,
15980
15991
  transportSession: capabilityTransportSession,
15981
15992
  timestamp: capabilityTimestamp,
15982
15993
  openingSession: receiveSession,
@@ -15985,12 +15996,34 @@ let SharedLog = (() => {
15985
15996
  else {
15986
15997
  const observed = this.observePeerSyncCapabilities({
15987
15998
  peerHash: receiveFromHash,
15988
- capabilities: msg.capabilities,
15999
+ capabilities: steadyCapabilities,
15989
16000
  transportSession: capabilityTransportSession,
15990
16001
  timestamp: capabilityTimestamp,
15991
16002
  });
15992
16003
  if (observed && receiveSession?.phase === "open") {
15993
16004
  this.promoteReplicationInfoV2ReceiveCapability(context.from, receiveSession);
16005
+ const freshExactRearm = requestsRemoteFullRearm &&
16006
+ capabilityTransportSession !== undefined &&
16007
+ capabilityTimestamp !== undefined &&
16008
+ previousCapabilitySession === capabilityTransportSession &&
16009
+ previousCapabilityTimestamp !== undefined &&
16010
+ capabilityTimestamp > previousCapabilityTimestamp;
16011
+ if (freshExactRearm &&
16012
+ this._v2Receive.isCurrentActive({
16013
+ peerHash: receiveFromHash,
16014
+ peerSession: receiveSession,
16015
+ receiveEpoch: this._peerSessions.receiveEpoch(receiveFromHash),
16016
+ senderTransportSession: capabilityTransportSession,
16017
+ })) {
16018
+ // Rotate the receiver grant/challenge before requesting Full. A
16019
+ // sender rebuilt from no state starts at sequence one, which an
16020
+ // active receiver's old sequence fence must otherwise reject.
16021
+ this._v2Receive.advanceRecovery({
16022
+ peerHash: receiveFromHash,
16023
+ peerSession: receiveSession,
16024
+ receiveEpoch: this._peerSessions.receiveEpoch(receiveFromHash),
16025
+ });
16026
+ }
15994
16027
  }
15995
16028
  else if (observed && receiveSession === null) {
15996
16029
  // A capability can arrive before the sender's topic Subscribe after
@@ -16855,8 +16888,8 @@ let SharedLog = (() => {
16855
16888
  }
16856
16889
  throwIfInactive();
16857
16890
  }
16858
- nudgePersistedReceiptPeerReadiness(publicKey) {
16859
- if (this.closed)
16891
+ nudgePersistedReceiptPeerReadiness(publicKey, signal) {
16892
+ if (this.closed || signal.aborted)
16860
16893
  return;
16861
16894
  const peerHash = publicKey.hashcode();
16862
16895
  const peerSession = this._peerSessions.current(peerHash);
@@ -16886,6 +16919,20 @@ let SharedLog = (() => {
16886
16919
  peerSession,
16887
16920
  receiveEpoch,
16888
16921
  });
16922
+ const receiptTarget = this.persistedReceiptPeerSession(peerHash);
16923
+ if (receiptTarget &&
16924
+ !this._v2Send.hasCurrentStateForPeer({
16925
+ peerHash,
16926
+ peerSession: receiptTarget.peerSession,
16927
+ receiverTransportSession: receiptTarget.capabilitySession,
16928
+ })) {
16929
+ this._v2Receive.reAdvertiseLocalCapabilityForRemoteFull({
16930
+ peerHash,
16931
+ peerSession: receiptTarget.peerSession,
16932
+ receiveEpoch: receiptTarget.receiveEpoch,
16933
+ signal,
16934
+ });
16935
+ }
16889
16936
  this.scheduleReplicationInfoV2Recovery(publicKey);
16890
16937
  }
16891
16938
  /**
@@ -17079,17 +17126,25 @@ let SharedLog = (() => {
17079
17126
  throw new AbortError("Persisted-receipt readiness wait settled");
17080
17127
  }
17081
17128
  };
17129
+ const recoveryDelayMs = Math.max(50, Math.min(1_000, this.waitForReplicatorRequestIntervalMs));
17082
17130
  const armRecoveryTick = () => {
17083
17131
  if (settled || recoveryTimer)
17084
17132
  return;
17085
- const delayMs = Math.max(50, Math.min(1_000, this.waitForReplicatorRequestIntervalMs));
17086
17133
  recoveryTimer = setTimeout(() => {
17087
17134
  recoveryTimer = undefined;
17088
17135
  if (!continueWait())
17089
17136
  return;
17090
- this.nudgePersistedReceiptPeerReadiness(key);
17137
+ this.nudgePersistedReceiptPeerReadiness(key, operationSignal);
17138
+ if (checkInFlight) {
17139
+ // Confirmation can legitimately span several recovery intervals.
17140
+ // Keep repairing the exact current generation without aborting its
17141
+ // one application-confirmation waiter on every tick.
17142
+ rerun = true;
17143
+ armRecoveryTick();
17144
+ return;
17145
+ }
17091
17146
  scheduleCheck();
17092
- }, delayMs);
17147
+ }, recoveryDelayMs);
17093
17148
  recoveryTimer.unref?.();
17094
17149
  };
17095
17150
  const runCheck = async () => {
@@ -17119,6 +17174,11 @@ let SharedLog = (() => {
17119
17174
  snapshot.reason === "replication-confirmation-pending") {
17120
17175
  const target = this.persistedReceiptPeerSession(peerHash);
17121
17176
  if (target) {
17177
+ // A confirmation waiter cannot create an outbound V2 stream when
17178
+ // that exact session state is missing. Nudge first, then keep the
17179
+ // recovery tick alive while the single confirmation wait remains.
17180
+ this.nudgePersistedReceiptPeerReadiness(key, operationSignal);
17181
+ armRecoveryTick();
17122
17182
  const currentConfirmationController = new AbortController();
17123
17183
  confirmationController = currentConfirmationController;
17124
17184
  try {
@@ -17163,7 +17223,7 @@ let SharedLog = (() => {
17163
17223
  }
17164
17224
  if (!continueWait())
17165
17225
  return;
17166
- this.nudgePersistedReceiptPeerReadiness(key);
17226
+ this.nudgePersistedReceiptPeerReadiness(key, operationSignal);
17167
17227
  }
17168
17228
  catch (error) {
17169
17229
  if (!settled)