@peerbit/shared-log 16.0.1 → 16.0.2

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/src/index.ts CHANGED
@@ -290,6 +290,7 @@ import {
290
290
  } from "./sync/profile.js";
291
291
  import {
292
292
  ConfirmEntriesMessage,
293
+ RECENT_KNOWN_EXCHANGE_HEAD_SUPPRESSION_MS,
293
294
  SYNC_MESSAGE_PRIORITY,
294
295
  SimpleSyncronizer,
295
296
  } from "./sync/simple.js";
@@ -1439,6 +1440,16 @@ const JOIN_AUTHORITATIVE_RETRY_SCHEDULE_MS = [
1439
1440
  ];
1440
1441
  const APPEND_BACKFILL_RETRY_SCHEDULE_MS = [0, 1_000, 3_000, 7_000];
1441
1442
  const RECENT_KNOWN_REPAIR_SUPPRESSION_MS = 30_000;
1443
+ // `_entryKnownPeerObservedAt` is read ONLY through isEntryRecentlyKnownByPeer,
1444
+ // which treats an over-age row and an absent row identically (both false). So
1445
+ // rows older than the longest horizon any caller asks about are dead weight,
1446
+ // and dropping them is behaviour-identical rather than merely safe. Derived
1447
+ // from the horizons themselves -- never hardcode it -- so a future caller with
1448
+ // a longer window cannot silently outlive the retention that serves it.
1449
+ const ENTRY_KNOWN_PEER_OBSERVED_AT_RETENTION_MS = Math.max(
1450
+ RECENT_KNOWN_REPAIR_SUPPRESSION_MS,
1451
+ RECENT_KNOWN_EXCHANGE_HEAD_SUPPRESSION_MS,
1452
+ );
1442
1453
  const JOIN_AUTHORITATIVE_REPAIR_DELAY_MS = 2_000;
1443
1454
  const JOIN_AUTHORITATIVE_REPAIR_SWEEP_DELAYS_MS = [
1444
1455
  JOIN_AUTHORITATIVE_REPAIR_DELAY_MS,
@@ -3294,6 +3305,7 @@ export class SharedLog<
3294
3305
  private _repairSweepOptimisticGidsByPeer!: Map<string, Set<string>>;
3295
3306
  private _entryKnownPeers!: Map<string, Set<string>>;
3296
3307
  private _entryKnownPeerObservedAt!: Map<string, Map<string, number>>;
3308
+ private _entryKnownPeerObservedAtSweptAt = 0;
3297
3309
  private _joinAuthoritativeRepairTimersByDelay!: Map<
3298
3310
  number,
3299
3311
  ReturnType<typeof setTimeout>
@@ -3644,6 +3656,7 @@ export class SharedLog<
3644
3656
  this._repairSweepOptimisticGidsByPeer = new Map();
3645
3657
  this._entryKnownPeers = new Map();
3646
3658
  this._entryKnownPeerObservedAt = new Map();
3659
+ this._entryKnownPeerObservedAtSweptAt = 0;
3647
3660
  this._joinAuthoritativeRepairTimersByDelay = new Map();
3648
3661
  this._joinAuthoritativeRepairPeersByDelay = new Map();
3649
3662
  this._appendBackfillPendingByTarget = new Map();
@@ -7649,6 +7662,16 @@ export class SharedLog<
7649
7662
  this._nativeSharedLogState?.markEntriesKnownByPeer(hashArray, peer);
7650
7663
  this._nativeBackbone?.markEntriesKnownByPeer(hashArray, peer);
7651
7664
  const now = Date.now();
7665
+ // Growth is driven by writes, so the sweep rides the write path rather
7666
+ // than a timer or the rebalance pass: cost stays proportional to the
7667
+ // traffic that creates rows. Rate-limited to one pass per retention
7668
+ // window, over a map that after the first pass holds one window of marks.
7669
+ if (
7670
+ now - this._entryKnownPeerObservedAtSweptAt >=
7671
+ ENTRY_KNOWN_PEER_OBSERVED_AT_RETENTION_MS
7672
+ ) {
7673
+ this.sweepEntryKnownPeerObservedAt(now);
7674
+ }
7652
7675
  for (const hash of hashArray) {
7653
7676
  let peers = this._entryKnownPeers.get(hash);
7654
7677
  if (!peers) {
@@ -7718,6 +7741,28 @@ export class SharedLog<
7718
7741
  return observedAt != null && Date.now() - observedAt <= maxAgeMs;
7719
7742
  }
7720
7743
 
7744
+ /** Drop recency marks no reader can still act on.
7745
+ *
7746
+ * Touches ONLY `_entryKnownPeerObservedAt`. `_entryKnownPeers` carries
7747
+ * membership, not recency, and its rows stay until the peer dimension
7748
+ * clears them; the native mirrors have no recency dimension at all
7749
+ * (mark/remove/removePeer only), so this must not call into them or the
7750
+ * two sides would disagree.
7751
+ */
7752
+ private sweepEntryKnownPeerObservedAt(now: number) {
7753
+ for (const [hash, observedAt] of this._entryKnownPeerObservedAt) {
7754
+ for (const [peer, timestamp] of observedAt) {
7755
+ if (now - timestamp > ENTRY_KNOWN_PEER_OBSERVED_AT_RETENTION_MS) {
7756
+ observedAt.delete(peer);
7757
+ }
7758
+ }
7759
+ if (observedAt.size === 0) {
7760
+ this._entryKnownPeerObservedAt.delete(hash);
7761
+ }
7762
+ }
7763
+ this._entryKnownPeerObservedAtSweptAt = now;
7764
+ }
7765
+
7721
7766
  private markRepairSweepOptimisticPeer(
7722
7767
  gid: string,
7723
7768
  peer: string,
@@ -14213,6 +14258,7 @@ export class SharedLog<
14213
14258
  this._repairSweepOptimisticGidsByPeer = new Map();
14214
14259
  this._entryKnownPeers = new Map();
14215
14260
  this._entryKnownPeerObservedAt = new Map();
14261
+ this._entryKnownPeerObservedAtSweptAt = 0;
14216
14262
  this._joinAuthoritativeRepairTimersByDelay = new Map();
14217
14263
  this._joinAuthoritativeRepairPeersByDelay = new Map();
14218
14264
  this._assumeSyncedRepairSuppressedUntil = 0;
@@ -325,7 +325,7 @@ export const SYNC_MESSAGE_PRIORITY = CONVERGENCE_MESSAGE_PRIORITY;
325
325
  // large historical backfills.
326
326
  const SIMPLE_SYNC_RETRY_AFTER_MS = 10_000;
327
327
  const EXCHANGE_HEAD_RESPONSE_DEDUPE_TTL_MS = SIMPLE_SYNC_RETRY_AFTER_MS - 1_000;
328
- const RECENT_KNOWN_EXCHANGE_HEAD_SUPPRESSION_MS = 30_000;
328
+ export const RECENT_KNOWN_EXCHANGE_HEAD_SUPPRESSION_MS = 30_000;
329
329
  const PENDING_MAYBE_SYNC_RESPONSE_TTL_MS = 30_000;
330
330
  // An incoming maybe-sync claim keeps one retry candidate in both
331
331
  // syncInFlightQueue and syncInFlightQueueInverted. Bound associations rather