@peerbit/shared-log 16.0.24 → 16.0.26

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,12 +2024,20 @@ 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
- mode: new AcknowledgeDelivery({
2030
- redundancy: 1,
2031
- to: [properties.target],
2032
- }),
2032
+ // The transient rearm is only a bounded recovery hint. The ensuing
2033
+ // authenticated Full/Applied exchange is the readiness proof, so do not
2034
+ // let an ACK-delivery promise park the recovery scheduler itself.
2035
+ mode: properties.requestRemoteFullRearm
2036
+ ? new SilentDelivery({ redundancy: 1, to: [properties.target] })
2037
+ : new AcknowledgeDelivery({
2038
+ redundancy: 1,
2039
+ to: [properties.target],
2040
+ }),
2033
2041
  priority: CONVERGENCE_MESSAGE_PRIORITY,
2034
2042
  signal: properties.signal,
2035
2043
  });
@@ -2078,6 +2086,7 @@ let SharedLog = (() => {
2078
2086
  peerSession: properties.peerSession,
2079
2087
  receiveEpoch: properties.receiveEpoch,
2080
2088
  signal: properties.signal,
2089
+ requestRemoteFullRearm: properties.requestRemoteFullRearm,
2081
2090
  }),
2082
2091
  onRequestError: (error) => {
2083
2092
  if (isNotStartedError(error) ||
@@ -3447,7 +3456,7 @@ let SharedLog = (() => {
3447
3456
  })) {
3448
3457
  return undefined;
3449
3458
  }
3450
- return { capabilitySession, peerSession };
3459
+ return { capabilitySession, peerSession, receiveEpoch };
3451
3460
  }
3452
3461
  async waitPersistedReceiptRetry(signal, ms) {
3453
3462
  try {
@@ -15966,6 +15975,13 @@ let SharedLog = (() => {
15966
15975
  if (!context.from.equals(this.node.identity.publicKey)) {
15967
15976
  const capabilityTransportSession = context.message?.header?.session;
15968
15977
  const capabilityTimestamp = context.message?.header?.timestamp;
15978
+ const previousCapabilitySession = this._peerSyncCapabilitySessions.get(receiveFromHash);
15979
+ const previousCapabilityTimestamp = this._peerSyncCapabilityTimestamps.get(receiveFromHash);
15980
+ const requestsRemoteFullRearm = (msg.capabilities & SYNC_CAPABILITY_REPLICATION_INFO_V2_REARM) !==
15981
+ 0;
15982
+ // The rearm bit is a one-shot authenticated command, not a sticky
15983
+ // negotiated capability. Store and promote only the steady bits.
15984
+ const steadyCapabilities = msg.capabilities & ~SYNC_CAPABILITY_REPLICATION_INFO_V2_REARM;
15969
15985
  // No await separates this from the capture above, so the captured
15970
15986
  // window state is exact: the legacy re-read of the opening map here
15971
15987
  // could never observe a different value.
@@ -15976,7 +15992,7 @@ let SharedLog = (() => {
15976
15992
  // cleanup cannot erase it before the opening transition commits.
15977
15993
  this.observePeerSyncCapabilities({
15978
15994
  peerHash: receiveFromHash,
15979
- capabilities: msg.capabilities,
15995
+ capabilities: steadyCapabilities,
15980
15996
  transportSession: capabilityTransportSession,
15981
15997
  timestamp: capabilityTimestamp,
15982
15998
  openingSession: receiveSession,
@@ -15985,12 +16001,34 @@ let SharedLog = (() => {
15985
16001
  else {
15986
16002
  const observed = this.observePeerSyncCapabilities({
15987
16003
  peerHash: receiveFromHash,
15988
- capabilities: msg.capabilities,
16004
+ capabilities: steadyCapabilities,
15989
16005
  transportSession: capabilityTransportSession,
15990
16006
  timestamp: capabilityTimestamp,
15991
16007
  });
15992
16008
  if (observed && receiveSession?.phase === "open") {
15993
16009
  this.promoteReplicationInfoV2ReceiveCapability(context.from, receiveSession);
16010
+ const freshExactRearm = requestsRemoteFullRearm &&
16011
+ capabilityTransportSession !== undefined &&
16012
+ capabilityTimestamp !== undefined &&
16013
+ previousCapabilitySession === capabilityTransportSession &&
16014
+ previousCapabilityTimestamp !== undefined &&
16015
+ capabilityTimestamp > previousCapabilityTimestamp;
16016
+ if (freshExactRearm &&
16017
+ this._v2Receive.isCurrentActive({
16018
+ peerHash: receiveFromHash,
16019
+ peerSession: receiveSession,
16020
+ receiveEpoch: this._peerSessions.receiveEpoch(receiveFromHash),
16021
+ senderTransportSession: capabilityTransportSession,
16022
+ })) {
16023
+ // Rotate the receiver grant/challenge before requesting Full. A
16024
+ // sender rebuilt from no state starts at sequence one, which an
16025
+ // active receiver's old sequence fence must otherwise reject.
16026
+ this._v2Receive.advanceRecovery({
16027
+ peerHash: receiveFromHash,
16028
+ peerSession: receiveSession,
16029
+ receiveEpoch: this._peerSessions.receiveEpoch(receiveFromHash),
16030
+ });
16031
+ }
15994
16032
  }
15995
16033
  else if (observed && receiveSession === null) {
15996
16034
  // A capability can arrive before the sender's topic Subscribe after
@@ -16855,8 +16893,8 @@ let SharedLog = (() => {
16855
16893
  }
16856
16894
  throwIfInactive();
16857
16895
  }
16858
- nudgePersistedReceiptPeerReadiness(publicKey) {
16859
- if (this.closed)
16896
+ nudgePersistedReceiptPeerReadiness(publicKey, signal, rearmCurrentUnconfirmed) {
16897
+ if (this.closed || signal.aborted)
16860
16898
  return;
16861
16899
  const peerHash = publicKey.hashcode();
16862
16900
  const peerSession = this._peerSessions.current(peerHash);
@@ -16886,6 +16924,29 @@ let SharedLog = (() => {
16886
16924
  peerSession,
16887
16925
  receiveEpoch,
16888
16926
  });
16927
+ const receiptTarget = this.persistedReceiptPeerSession(peerHash);
16928
+ if (receiptTarget &&
16929
+ (!this._v2Send.hasCurrentStateForPeer({
16930
+ peerHash,
16931
+ peerSession: receiptTarget.peerSession,
16932
+ receiverTransportSession: receiptTarget.capabilitySession,
16933
+ }) ||
16934
+ (rearmCurrentUnconfirmed?.peerSession === receiptTarget.peerSession &&
16935
+ rearmCurrentUnconfirmed.receiveEpoch === receiptTarget.receiveEpoch &&
16936
+ rearmCurrentUnconfirmed.capabilitySession ===
16937
+ receiptTarget.capabilitySession &&
16938
+ !this._v2Send.isLatestConfirmedForPeer({
16939
+ peerHash,
16940
+ peerSession: receiptTarget.peerSession,
16941
+ receiverTransportSession: receiptTarget.capabilitySession,
16942
+ })))) {
16943
+ this._v2Receive.reAdvertiseLocalCapabilityForRemoteFull({
16944
+ peerHash,
16945
+ peerSession: receiptTarget.peerSession,
16946
+ receiveEpoch: receiptTarget.receiveEpoch,
16947
+ signal,
16948
+ });
16949
+ }
16889
16950
  this.scheduleReplicationInfoV2Recovery(publicKey);
16890
16951
  }
16891
16952
  /**
@@ -16973,7 +17034,9 @@ let SharedLog = (() => {
16973
17034
  * Wait for a public key's current (or replacement) connection generation to
16974
17035
  * become persisted-receipt ready. Transition listeners are installed before
16975
17036
  * the first asynchronous inspection, and a bounded recovery tick repairs
16976
- * missed subscriber/capability wakes without retaining stale PeerSessions.
17037
+ * missed subscriber/capability wakes without retaining stale PeerSessions. A
17038
+ * current sender gets one normal confirmation interval before an exact-
17039
+ * generation watchdog requests a bounded Full rearm.
16977
17040
  * This waiter is advisory only; the following persisted delivery remains the
16978
17041
  * operation that proves the requested remote durability quorum.
16979
17042
  */
@@ -17017,6 +17080,7 @@ let SharedLog = (() => {
17017
17080
  let rerun = false;
17018
17081
  let recoveryTimer;
17019
17082
  let confirmationController;
17083
+ let confirmationRecoveryTarget;
17020
17084
  let lastSnapshot;
17021
17085
  const createTimeoutError = () => {
17022
17086
  const suffix = lastSnapshot
@@ -17037,6 +17101,7 @@ let SharedLog = (() => {
17037
17101
  }
17038
17102
  confirmationController?.abort(new AbortError("Persisted-receipt readiness generation changed"));
17039
17103
  confirmationController = undefined;
17104
+ confirmationRecoveryTarget = undefined;
17040
17105
  operationController.abort(new AbortError("Persisted-receipt readiness wait settled"));
17041
17106
  };
17042
17107
  const resolve = (snapshot) => {
@@ -17079,19 +17144,52 @@ let SharedLog = (() => {
17079
17144
  throw new AbortError("Persisted-receipt readiness wait settled");
17080
17145
  }
17081
17146
  };
17147
+ const recoveryDelayMs = Math.max(50, Math.min(1_000, this.waitForReplicatorRequestIntervalMs));
17082
17148
  const armRecoveryTick = () => {
17083
17149
  if (settled || recoveryTimer)
17084
17150
  return;
17085
- const delayMs = Math.max(50, Math.min(1_000, this.waitForReplicatorRequestIntervalMs));
17086
17151
  recoveryTimer = setTimeout(() => {
17087
17152
  recoveryTimer = undefined;
17088
17153
  if (!continueWait())
17089
17154
  return;
17090
- this.nudgePersistedReceiptPeerReadiness(key);
17155
+ const recoveryTarget = confirmationRecoveryTarget;
17156
+ this.nudgePersistedReceiptPeerReadiness(key, operationSignal, recoveryTarget && Date.now() >= recoveryTarget.notBefore
17157
+ ? recoveryTarget
17158
+ : undefined);
17159
+ if (checkInFlight) {
17160
+ // Confirmation can legitimately span several recovery intervals.
17161
+ // Keep repairing the exact current generation without aborting its
17162
+ // one application-confirmation waiter on every tick.
17163
+ rerun = true;
17164
+ armRecoveryTick();
17165
+ return;
17166
+ }
17091
17167
  scheduleCheck();
17092
- }, delayMs);
17168
+ }, recoveryDelayMs);
17093
17169
  recoveryTimer.unref?.();
17094
17170
  };
17171
+ const prepareConfirmationRecovery = () => {
17172
+ const target = this.persistedReceiptPeerSession(peerHash);
17173
+ if (!target) {
17174
+ confirmationRecoveryTarget = undefined;
17175
+ return undefined;
17176
+ }
17177
+ const retained = confirmationRecoveryTarget;
17178
+ if (retained?.peerSession !== target.peerSession ||
17179
+ retained.receiveEpoch !== target.receiveEpoch ||
17180
+ retained.capabilitySession !== target.capabilitySession) {
17181
+ confirmationRecoveryTarget = {
17182
+ peerSession: target.peerSession,
17183
+ receiveEpoch: target.receiveEpoch,
17184
+ capabilitySession: target.capabilitySession,
17185
+ notBefore: Date.now() + recoveryDelayMs,
17186
+ };
17187
+ }
17188
+ const recoveryTarget = confirmationRecoveryTarget;
17189
+ this.nudgePersistedReceiptPeerReadiness(key, operationSignal, Date.now() >= recoveryTarget.notBefore ? recoveryTarget : undefined);
17190
+ armRecoveryTick();
17191
+ return target;
17192
+ };
17095
17193
  const runCheck = async () => {
17096
17194
  checkScheduled = false;
17097
17195
  if (!continueWait())
@@ -17106,9 +17204,10 @@ let SharedLog = (() => {
17106
17204
  lastSnapshot = snapshot;
17107
17205
  if (!continueWait())
17108
17206
  return;
17109
- if (rerun)
17110
- return;
17111
17207
  if (snapshot.status === "ready") {
17208
+ confirmationRecoveryTarget = undefined;
17209
+ if (rerun)
17210
+ return;
17112
17211
  // A wake observed while the asynchronous inspection was running may
17113
17212
  // already have invalidated this snapshot. Drain that coalesced wake
17114
17213
  // before publishing readiness.
@@ -17117,10 +17216,17 @@ let SharedLog = (() => {
17117
17216
  }
17118
17217
  if (snapshot.status === "pending" &&
17119
17218
  snapshot.reason === "replication-confirmation-pending") {
17120
- const target = this.persistedReceiptPeerSession(peerHash);
17219
+ // Retain one watchdog deadline for the exact generation across
17220
+ // coalesced inspection wakes. A sub-interval event stream must drive,
17221
+ // rather than indefinitely postpone, the mature recovery nudge.
17222
+ const target = prepareConfirmationRecovery();
17223
+ if (rerun)
17224
+ return;
17121
17225
  if (target) {
17122
17226
  const currentConfirmationController = new AbortController();
17123
17227
  confirmationController = currentConfirmationController;
17228
+ const currentConfirmationRecoveryTarget = confirmationRecoveryTarget;
17229
+ let confirmationSucceeded = false;
17124
17230
  try {
17125
17231
  await this._v2Send.confirmLatestForPeer({
17126
17232
  peerHash,
@@ -17133,6 +17239,7 @@ let SharedLog = (() => {
17133
17239
  currentConfirmationController.signal,
17134
17240
  ]),
17135
17241
  });
17242
+ confirmationSucceeded = true;
17136
17243
  }
17137
17244
  catch (error) {
17138
17245
  if (!continueWait())
@@ -17146,6 +17253,10 @@ let SharedLog = (() => {
17146
17253
  if (confirmationController === currentConfirmationController) {
17147
17254
  confirmationController = undefined;
17148
17255
  }
17256
+ if (confirmationSucceeded &&
17257
+ confirmationRecoveryTarget === currentConfirmationRecoveryTarget) {
17258
+ confirmationRecoveryTarget = undefined;
17259
+ }
17149
17260
  }
17150
17261
  if (!continueWait())
17151
17262
  return;
@@ -17153,17 +17264,32 @@ let SharedLog = (() => {
17153
17264
  lastSnapshot = snapshot;
17154
17265
  if (!continueWait())
17155
17266
  return;
17156
- if (rerun)
17157
- return;
17158
17267
  if (snapshot.status === "ready") {
17268
+ confirmationRecoveryTarget = undefined;
17269
+ if (rerun)
17270
+ return;
17159
17271
  resolve(snapshot);
17160
17272
  return;
17161
17273
  }
17274
+ if (snapshot.status === "pending" &&
17275
+ snapshot.reason === "replication-confirmation-pending") {
17276
+ prepareConfirmationRecovery();
17277
+ }
17278
+ else {
17279
+ confirmationRecoveryTarget = undefined;
17280
+ }
17281
+ if (rerun)
17282
+ return;
17162
17283
  }
17163
17284
  }
17285
+ else {
17286
+ confirmationRecoveryTarget = undefined;
17287
+ }
17288
+ if (rerun)
17289
+ return;
17164
17290
  if (!continueWait())
17165
17291
  return;
17166
- this.nudgePersistedReceiptPeerReadiness(key);
17292
+ this.nudgePersistedReceiptPeerReadiness(key, operationSignal);
17167
17293
  }
17168
17294
  catch (error) {
17169
17295
  if (!settled)