@peerbit/shared-log 16.0.29 → 16.0.30

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@peerbit/shared-log",
3
- "version": "16.0.29",
3
+ "version": "16.0.30",
4
4
  "description": "Shared log",
5
5
  "sideEffects": false,
6
6
  "type": "module",
@@ -62,37 +62,37 @@
62
62
  "pidusage": "^4.0.1",
63
63
  "pino": "^9.4.0",
64
64
  "uint8arrays": "^5.1.0",
65
- "@peerbit/blocks": "4.3.0",
66
- "@peerbit/blocks-interface": "2.2.0",
67
65
  "@peerbit/any-store": "2.2.17",
68
- "@peerbit/cache": "3.1.1",
66
+ "@peerbit/blocks": "4.3.1",
69
67
  "@peerbit/crypto": "3.1.6",
70
- "@peerbit/log": "6.2.32",
68
+ "@peerbit/blocks-interface": "2.2.1",
69
+ "@peerbit/cache": "3.1.1",
71
70
  "@peerbit/indexer-sqlite3": "3.0.20",
72
- "@peerbit/logger": "2.0.2",
73
- "@peerbit/indexer-interface": "3.0.13",
74
- "@peerbit/pubsub": "5.4.5",
75
- "@peerbit/rpc": "6.1.27",
76
71
  "@peerbit/diagnostics": "0.0.1",
72
+ "@peerbit/log": "6.2.33",
73
+ "@peerbit/program": "6.0.60",
74
+ "@peerbit/pubsub": "5.4.6",
75
+ "@peerbit/pubsub-interface": "5.2.2",
76
+ "@peerbit/logger": "2.0.2",
77
77
  "@peerbit/riblt": "1.2.0",
78
+ "@peerbit/rpc": "6.1.28",
78
79
  "@peerbit/stream-interface": "6.0.16",
79
- "@peerbit/pubsub-interface": "5.2.2",
80
- "@peerbit/time": "3.0.1",
81
- "@peerbit/program": "6.0.59"
80
+ "@peerbit/indexer-interface": "3.0.13",
81
+ "@peerbit/time": "3.0.1"
82
82
  },
83
83
  "optionalDependencies": {
84
- "@peerbit/native-backbone": "0.2.14",
85
- "@peerbit/shared-log-rust": "0.1.7"
84
+ "@peerbit/shared-log-rust": "0.1.7",
85
+ "@peerbit/native-backbone": "0.2.15"
86
86
  },
87
87
  "devDependencies": {
88
88
  "@types/libsodium-wrappers": "^0.7.14",
89
89
  "@types/pidusage": "^2.0.5",
90
90
  "uuid": "^11.1.1",
91
- "@peerbit/any-store-rust": "0.1.7",
92
- "@peerbit/indexer-simple": "1.2.17",
93
91
  "@peerbit/indexer-rust": "1.0.14",
94
- "peerbit": "5.4.1",
95
- "@peerbit/test-utils": "3.1.40"
92
+ "@peerbit/indexer-simple": "1.2.17",
93
+ "@peerbit/test-utils": "3.1.41",
94
+ "@peerbit/any-store-rust": "0.1.7",
95
+ "peerbit": "5.4.2"
96
96
  },
97
97
  "repository": {
98
98
  "type": "git",
package/src/index.ts CHANGED
@@ -564,7 +564,7 @@ const emitAdvisorySyncProfileDuration = (
564
564
  try {
565
565
  emitSyncProfileDuration(profile, startedAt, event);
566
566
  } catch {
567
- // Diagnostics must not change open or provider-resolution correctness.
567
+ // Advisory diagnostics must not change replication or lifecycle behavior.
568
568
  }
569
569
  };
570
570
 
@@ -4033,7 +4033,13 @@ export class SharedLog<
4033
4033
  this._repairMetrics["join-warmup"].simpleFallbackPasses += 1;
4034
4034
  },
4035
4035
  sendEntriesSimple: (target, entries, options) =>
4036
- this.sendRepairEntriesWithTransport(target, entries, "simple", options),
4036
+ this.sendRepairEntriesWithTransport(
4037
+ target,
4038
+ entries,
4039
+ "simple",
4040
+ options,
4041
+ "join-warmup",
4042
+ ),
4037
4043
  logError: (error) => logger.error(error),
4038
4044
  });
4039
4045
  }
@@ -10639,6 +10645,7 @@ export class SharedLog<
10639
10645
  isStillCurrent?: () => boolean;
10640
10646
  signal?: AbortSignal;
10641
10647
  },
10648
+ mode?: RepairDispatchMode,
10642
10649
  ) {
10643
10650
  const isStillCurrent = options?.isStillCurrent ?? (() => true);
10644
10651
  if (!isStillCurrent()) {
@@ -10646,53 +10653,87 @@ export class SharedLog<
10646
10653
  }
10647
10654
  const unknownEntries = new Map<string, RepairDispatchEntry<R>>();
10648
10655
  const knownHashes: string[] = [];
10649
- for (const [hash, entry] of entries) {
10650
- if (
10651
- (options?.bypassRecentKnownPeers ||
10652
- !this.isEntryRecentlyKnownByPeer(
10653
- hash,
10654
- target,
10655
- RECENT_KNOWN_REPAIR_SUPPRESSION_MS,
10656
- )) &&
10657
- (options?.bypassKnownPeers || !this.isEntryKnownByPeer(hash, target))
10658
- ) {
10659
- unknownEntries.set(hash, entry);
10660
- } else {
10661
- knownHashes.push(hash);
10656
+ const profile = this._logProperties?.sync?.profile;
10657
+ const startedAt = syncProfileStart(profile);
10658
+ const inputEntries = profile ? entries.size : 0;
10659
+ let selectedEntries = 0;
10660
+ let lastObservedCurrent = true;
10661
+ let outcome = "stale";
10662
+ try {
10663
+ for (const [hash, entry] of entries) {
10664
+ if (
10665
+ (options?.bypassRecentKnownPeers ||
10666
+ !this.isEntryRecentlyKnownByPeer(
10667
+ hash,
10668
+ target,
10669
+ RECENT_KNOWN_REPAIR_SUPPRESSION_MS,
10670
+ )) &&
10671
+ (options?.bypassKnownPeers || !this.isEntryKnownByPeer(hash, target))
10672
+ ) {
10673
+ unknownEntries.set(hash, entry);
10674
+ } else {
10675
+ knownHashes.push(hash);
10676
+ }
10662
10677
  }
10663
- }
10664
- if (!isStillCurrent()) {
10665
- return;
10666
- }
10667
- this.clearRepairFrontierHashes(target, knownHashes);
10668
- if (unknownEntries.size === 0) {
10669
- return;
10670
- }
10671
- if (transport === "simple") {
10672
- // Fallback repair should not depend on the target completing the
10673
- // RequestMaybeSync -> ResponseMaybeSync round trip.
10674
- await this.pushRepairEntries(
10675
- target,
10676
- unknownEntries,
10677
- isStillCurrent,
10678
- options?.signal,
10679
- );
10680
- return;
10681
- }
10682
-
10683
- const syncEntries = this._logProperties?.sync?.priority
10684
- ? (this._coordinates.materializeRepairDispatchEntries(
10678
+ // A custom synchronizer may mutate the Map once it receives it.
10679
+ if (profile) selectedEntries = unknownEntries.size;
10680
+ if (!isStillCurrent()) return;
10681
+ this.clearRepairFrontierHashes(target, knownHashes);
10682
+ if (unknownEntries.size === 0) {
10683
+ outcome = "known-suppressed";
10684
+ return;
10685
+ }
10686
+ if (transport === "simple") {
10687
+ // Observe only checks the lower path already makes, without adding
10688
+ // lifecycle decisions or wrapping the disabled-profiling path.
10689
+ const dispatchIsStillCurrent = profile
10690
+ ? () => (lastObservedCurrent = isStillCurrent())
10691
+ : isStillCurrent;
10692
+ // Fallback repair does not wait for the maybe-sync round trip.
10693
+ await this.pushRepairEntries(
10694
+ target,
10685
10695
  unknownEntries,
10686
- ) as unknown as Map<string, SyncEntryCoordinates<R>>)
10687
- : (unknownEntries as Map<string, SyncEntryCoordinates<R>>);
10688
- if (!isStillCurrent()) {
10689
- return;
10696
+ dispatchIsStillCurrent,
10697
+ options?.signal,
10698
+ );
10699
+ } else {
10700
+ const syncEntries = this._logProperties?.sync?.priority
10701
+ ? (this._coordinates.materializeRepairDispatchEntries(
10702
+ unknownEntries,
10703
+ ) as unknown as Map<string, SyncEntryCoordinates<R>>)
10704
+ : (unknownEntries as Map<string, SyncEntryCoordinates<R>>);
10705
+ if (!isStillCurrent()) return;
10706
+ await this.syncronizer.onMaybeMissingEntries({
10707
+ entries: syncEntries,
10708
+ targets: [target],
10709
+ signal: options?.signal,
10710
+ });
10711
+ }
10712
+ outcome = !lastObservedCurrent
10713
+ ? "stale"
10714
+ : options?.signal?.aborted
10715
+ ? "cancelled"
10716
+ : "dispatched";
10717
+ } catch (error) {
10718
+ outcome = "error";
10719
+ throw error;
10720
+ } finally {
10721
+ if (profile) {
10722
+ emitAdvisorySyncProfileDuration(profile, startedAt, {
10723
+ name: "sharedLog.repair.dispatch",
10724
+ component: "shared-log",
10725
+ entries: inputEntries,
10726
+ count: selectedEntries,
10727
+ targets: 1,
10728
+ details: {
10729
+ mode,
10730
+ transport,
10731
+ outcome,
10732
+ knownSuppressedEntries: knownHashes.length,
10733
+ },
10734
+ });
10735
+ }
10690
10736
  }
10691
- await this.syncronizer.onMaybeMissingEntries({
10692
- entries: syncEntries,
10693
- targets: [target],
10694
- signal: options?.signal,
10695
- });
10696
10737
  }
10697
10738
 
10698
10739
  private async sendMaybeMissingEntriesNow(
@@ -10776,6 +10817,7 @@ export class SharedLog<
10776
10817
  this.isRepairLifecycleActive(repairLifecycleController),
10777
10818
  signal: repairLifecycleController.signal,
10778
10819
  },
10820
+ options.mode,
10779
10821
  ),
10780
10822
  ).catch((error: any) => logger.error(error));
10781
10823
  }
@@ -11166,6 +11208,7 @@ export class SharedLog<
11166
11208
  this.isRepairLifecycleActive(repairLifecycleController),
11167
11209
  signal: repairLifecycleController.signal,
11168
11210
  },
11211
+ options.mode,
11169
11212
  ),
11170
11213
  ).catch((error: any) => logger.error(error));
11171
11214
  };
@@ -11309,6 +11352,18 @@ export class SharedLog<
11309
11352
  repairLifecycleController: AbortController = this._instanceLifecycle
11310
11353
  ?.ownershipLifecycleController as AbortController,
11311
11354
  ) {
11355
+ const profile = this._logProperties?.sync?.profile;
11356
+ const startedAt = syncProfileStart(profile);
11357
+ const profileCounts = profile
11358
+ ? {
11359
+ passes: 0,
11360
+ inputEntries: 0,
11361
+ nativePasses: 0,
11362
+ repairCandidates: 0,
11363
+ repairBatches: 0,
11364
+ outcome: "stale",
11365
+ }
11366
+ : undefined;
11312
11367
  try {
11313
11368
  while (this.isRepairLifecycleActive(repairLifecycleController)) {
11314
11369
  if (!this.isRepairLifecycleActive(repairLifecycleController)) {
@@ -11347,8 +11402,10 @@ export class SharedLog<
11347
11402
  pruneStaleJoinWarmupPeers();
11348
11403
 
11349
11404
  if (pendingModes.size === 0) {
11405
+ if (profileCounts) profileCounts.outcome = "completed";
11350
11406
  return;
11351
11407
  }
11408
+ if (profileCounts) profileCounts.passes += 1;
11352
11409
 
11353
11410
  const optimisticGidPeersByMode = new Map<
11354
11411
  RepairDispatchMode,
@@ -11447,6 +11504,10 @@ export class SharedLog<
11447
11504
  }
11448
11505
  return;
11449
11506
  }
11507
+ if (profileCounts) {
11508
+ profileCounts.repairCandidates += entries.size;
11509
+ profileCounts.repairBatches += 1;
11510
+ }
11450
11511
  this.dispatchMaybeMissingEntries(
11451
11512
  target,
11452
11513
  entries,
@@ -11513,6 +11574,10 @@ export class SharedLog<
11513
11574
  residentEntriesByHash &&
11514
11575
  !this.hasCustomFindLeaders()
11515
11576
  ) {
11577
+ if (profileCounts) {
11578
+ profileCounts.nativePasses += 1;
11579
+ profileCounts.inputEntries += residentEntriesByHash.size;
11580
+ }
11516
11581
  const repairDispatchPlan = pruneStaleJoinWarmupPeers()
11517
11582
  ? await this.planResidentRepairDispatchBatch(
11518
11583
  {
@@ -11551,6 +11616,7 @@ export class SharedLog<
11551
11616
  const entries = await iterator.next(
11552
11617
  REPAIR_SWEEP_ENTRY_BATCH_SIZE,
11553
11618
  );
11619
+ if (profileCounts) profileCounts.inputEntries += entries.length;
11554
11620
  if (!this.isRepairLifecycleActive(repairLifecycleController)) {
11555
11621
  return;
11556
11622
  }
@@ -11675,6 +11741,7 @@ export class SharedLog<
11675
11741
  }
11676
11742
  }
11677
11743
  } catch (error: any) {
11744
+ if (profileCounts) profileCounts.outcome = "error";
11678
11745
  if (
11679
11746
  this.isRepairLifecycleActive(repairLifecycleController) &&
11680
11747
  !isNotStartedError(error)
@@ -11696,6 +11763,21 @@ export class SharedLog<
11696
11763
  void this.runRepairSweep(repairLifecycleController);
11697
11764
  }
11698
11765
  }
11766
+ if (profileCounts) {
11767
+ emitAdvisorySyncProfileDuration(profile, startedAt, {
11768
+ name: "sharedLog.placement.pass",
11769
+ component: "shared-log",
11770
+ entries: profileCounts.inputEntries,
11771
+ count: profileCounts.repairCandidates,
11772
+ details: {
11773
+ phase: "repair-sweep",
11774
+ outcome: profileCounts.outcome,
11775
+ passes: profileCounts.passes,
11776
+ nativePasses: profileCounts.nativePasses,
11777
+ repairBatches: profileCounts.repairBatches,
11778
+ },
11779
+ });
11780
+ }
11699
11781
  }
11700
11782
  }
11701
11783
 
@@ -21274,10 +21356,11 @@ export class SharedLog<
21274
21356
  msg.heads.map((head) => head.hash),
21275
21357
  );
21276
21358
  if (syncProfile) {
21277
- emitSyncProfileDuration(syncProfile, rawExistingStartedAt, {
21359
+ emitAdvisorySyncProfileDuration(syncProfile, rawExistingStartedAt, {
21278
21360
  name: "sharedLog.rawReceive.existingHeads",
21279
21361
  component: "shared-log",
21280
21362
  entries: msg.heads.length,
21363
+ count: rawExistingHashes.size,
21281
21364
  messages: 1,
21282
21365
  });
21283
21366
  }
@@ -21711,10 +21794,11 @@ export class SharedLog<
21711
21794
  ? undefined
21712
21795
  : await this.log.hasMany(headHashes);
21713
21796
  if (syncProfile) {
21714
- emitSyncProfileDuration(syncProfile, existingStartedAt, {
21797
+ emitAdvisorySyncProfileDuration(syncProfile, existingStartedAt, {
21715
21798
  name: "sharedLog.receive.existingHeads",
21716
21799
  component: "shared-log",
21717
21800
  entries: heads.length,
21801
+ count: existingHashes?.size,
21718
21802
  messages: 1,
21719
21803
  details: { rawMaterializedKnownMissing },
21720
21804
  });
@@ -29478,6 +29562,17 @@ export class SharedLog<
29478
29562
  isOwnershipLifecycleCurrent() &&
29479
29563
  [...warmupPeers].every(isCurrentJoinWarmupTarget);
29480
29564
 
29565
+ const profile = this._logProperties?.sync?.profile;
29566
+ const profileStartedAt = syncProfileStart(profile);
29567
+ const profileCounts = profile
29568
+ ? {
29569
+ examinedEntries: 0,
29570
+ repairCandidates: 0,
29571
+ repairBatches: 0,
29572
+ pruneScan: false,
29573
+ outcome: "stale",
29574
+ }
29575
+ : undefined;
29481
29576
  try {
29482
29577
  const uncheckedDeliver: Map<
29483
29578
  string,
@@ -29501,6 +29596,10 @@ export class SharedLog<
29501
29596
  : isWarmupTarget
29502
29597
  ? "join-warmup"
29503
29598
  : "join-authoritative";
29599
+ if (profileCounts) {
29600
+ profileCounts.repairCandidates += entries.size;
29601
+ profileCounts.repairBatches += 1;
29602
+ }
29504
29603
  this.dispatchMaybeMissingEntries(
29505
29604
  target,
29506
29605
  entries,
@@ -29555,6 +29654,7 @@ export class SharedLog<
29555
29654
  forceFresh: forceFreshDelivery || useJoinWarmupFastPath,
29556
29655
  },
29557
29656
  )) {
29657
+ if (profileCounts) profileCounts.examinedEntries += 1;
29558
29658
  if (
29559
29659
  !isOwnershipLifecycleCurrent() ||
29560
29660
  (useJoinWarmupFastPath && !areJoinWarmupGenerationsCurrent())
@@ -29838,6 +29938,7 @@ export class SharedLog<
29838
29938
  ));
29839
29939
 
29840
29940
  if (shouldRunLocalPruneScan) {
29941
+ if (profileCounts) profileCounts.pruneScan = true;
29841
29942
  throwIfOwnershipLifecycleInactive();
29842
29943
  // Adaptive range changes and fixed zero-width updates can make already-indexed
29843
29944
  // local heads prunable even when the incremental rebalance scan misses them
@@ -29857,6 +29958,7 @@ export class SharedLog<
29857
29958
  }
29858
29959
  }
29859
29960
 
29961
+ if (profileCounts) profileCounts.outcome = "completed";
29860
29962
  return changed;
29861
29963
  } catch (error: any) {
29862
29964
  if (!isOwnershipLifecycleCurrent()) {
@@ -29866,8 +29968,27 @@ export class SharedLog<
29866
29968
  return false; // we are not started yet, so no changes
29867
29969
  }
29868
29970
 
29971
+ if (profileCounts) profileCounts.outcome = "error";
29869
29972
  logger.error(error.toString());
29870
29973
  throw error;
29974
+ } finally {
29975
+ if (profileCounts) {
29976
+ emitAdvisorySyncProfileDuration(profile, profileStartedAt, {
29977
+ name: "sharedLog.placement.pass",
29978
+ component: "shared-log",
29979
+ entries: profileCounts.examinedEntries,
29980
+ count: profileCounts.repairCandidates,
29981
+ details: {
29982
+ phase: "range-change",
29983
+ outcome: profileCounts.outcome,
29984
+ changes: changes.length,
29985
+ repairBatches: profileCounts.repairBatches,
29986
+ pruneScan: profileCounts.pruneScan,
29987
+ forceFreshDelivery,
29988
+ joinWarmupFastPath: useJoinWarmupFastPath,
29989
+ },
29990
+ });
29991
+ }
29871
29992
  }
29872
29993
  }
29873
29994
 
@@ -29926,6 +30047,15 @@ export class SharedLog<
29926
30047
  ownershipLifecycleController = this.captureReplicationOwnershipLifecycle(),
29927
30048
  rebalanceParticipationDebounced = this.rebalanceParticipationDebounced,
29928
30049
  ) {
30050
+ const profile = this._isAdaptiveReplicating
30051
+ ? this._logProperties?.sync?.profile
30052
+ : undefined;
30053
+ const profileStartedAt = syncProfileStart(profile);
30054
+ const profileDetails:
30055
+ | Record<string, string | number | boolean | undefined>
30056
+ | undefined = profile
30057
+ ? { outcome: "stale", idleRemainingMs: 0 }
30058
+ : undefined;
29929
30059
  // Stage 3: the lifecycle owns all three identity terms. `lifecycle` may
29930
30060
  // go stale later; its deps late-bind to the host, so the debouncer term
29931
30061
  // still reads the current host field, and the role term can disagree
@@ -29965,6 +30095,14 @@ export class SharedLog<
29965
30095
 
29966
30096
  if (this._isAdaptiveReplicating) {
29967
30097
  if (this.shouldDelayAdaptiveRebalance()) {
30098
+ if (profileDetails) {
30099
+ profileDetails.outcome = "idle-deferred";
30100
+ profileDetails.idleRemainingMs = Math.max(
30101
+ 0,
30102
+ this.adaptiveRebalanceIdleMs -
30103
+ (Date.now() - this._lastLocalAppendAt),
30104
+ );
30105
+ }
29968
30106
  if (isCurrent()) {
29969
30107
  void rebalanceParticipationDebounced?.call();
29970
30108
  }
@@ -29974,11 +30112,17 @@ export class SharedLog<
29974
30112
  const peers = this.replicationIndex;
29975
30113
  const usedMemory = await this.getMemoryUsage();
29976
30114
  if (!isCurrent()) return false;
30115
+ if (profileDetails) {
30116
+ profileDetails.storageUsedBytes = usedMemory;
30117
+ profileDetails.storageObjectiveBytes =
30118
+ this.replicationController.maxMemoryLimit;
30119
+ }
29977
30120
  this.scheduleReplicationStatusRefreshForStorage(usedMemory);
29978
30121
  let dynamicRange = await this.getDynamicRange();
29979
30122
  if (!isCurrent()) return false;
29980
30123
 
29981
30124
  if (!dynamicRange) {
30125
+ if (profileDetails) profileDetails.outcome = "not-permitted";
29982
30126
  return; // not allowed to replicate
29983
30127
  }
29984
30128
 
@@ -30005,13 +30149,24 @@ export class SharedLog<
30005
30149
  const totalParticipation = await this.calculateTotalParticipation();
30006
30150
  if (!isCurrent()) return false;
30007
30151
 
30152
+ const cpuUsage = this.cpuUsage?.value();
30153
+ const stepStartedAt = syncProfileStart(profile);
30008
30154
  const newFactor = this.replicationController.step({
30009
30155
  memoryUsage: usedMemory,
30010
30156
  currentFactor: dynamicRange.widthNormalized,
30011
30157
  totalFactor: totalParticipation, // TODO use this._totalParticipation when flakiness is fixed
30012
30158
  peerCount: peersSize,
30013
- cpuUsage: this.cpuUsage?.value(),
30159
+ cpuUsage,
30014
30160
  });
30161
+ if (profileDetails) {
30162
+ profileDetails.preStepMs = stepStartedAt - profileStartedAt;
30163
+ profileDetails.stepMs = syncProfileStart(profile) - stepStartedAt;
30164
+ profileDetails.currentFactor = dynamicRange.widthNormalized;
30165
+ profileDetails.proposedFactor = newFactor;
30166
+ profileDetails.totalFactor = totalParticipation;
30167
+ profileDetails.controllerPeerCount = peersSize;
30168
+ profileDetails.cpuUsage = cpuUsage;
30169
+ }
30015
30170
 
30016
30171
  const absoluteDifference = Math.abs(
30017
30172
  dynamicRange.widthNormalized - newFactor,
@@ -30048,9 +30203,11 @@ export class SharedLog<
30048
30203
  (await this._isTrustedReplicator(this.node.identity.publicKey));
30049
30204
  if (!isCurrent()) return false;
30050
30205
  if (!canReplicate) {
30206
+ if (profileDetails) profileDetails.outcome = "not-permitted";
30051
30207
  return false;
30052
30208
  }
30053
30209
 
30210
+ const applyStartedAt = syncProfileStart(profile);
30054
30211
  await this.startAnnounceReplicating(
30055
30212
  [dynamicRange],
30056
30213
  {
@@ -30061,6 +30218,10 @@ export class SharedLog<
30061
30218
  ownershipLifecycleController,
30062
30219
  );
30063
30220
  if (!isCurrent()) return false;
30221
+ if (profileDetails) {
30222
+ profileDetails.outcome = "apply-settled";
30223
+ profileDetails.applyMs = syncProfileStart(profile) - applyStartedAt;
30224
+ }
30064
30225
 
30065
30226
  /* await this._updateRole(newRole, onRoleChange); */
30066
30227
  if (isCurrent()) {
@@ -30069,6 +30230,7 @@ export class SharedLog<
30069
30230
 
30070
30231
  return true;
30071
30232
  } else {
30233
+ if (profileDetails) profileDetails.outcome = "unchanged";
30072
30234
  if (isCurrent()) {
30073
30235
  void rebalanceParticipationDebounced?.call();
30074
30236
  }
@@ -30078,14 +30240,24 @@ export class SharedLog<
30078
30240
  return false;
30079
30241
  };
30080
30242
 
30081
- const resp = await fn().catch((error: any) => {
30082
- if (isNotStartedError(error) || isClosedStoreRace(error)) {
30083
- return false;
30243
+ try {
30244
+ return await fn().catch((error: any) => {
30245
+ if (isNotStartedError(error) || isClosedStoreRace(error)) {
30246
+ if (profileDetails) profileDetails.outcome = "stale";
30247
+ return false;
30248
+ }
30249
+ if (profileDetails) profileDetails.outcome = "error";
30250
+ throw error;
30251
+ });
30252
+ } finally {
30253
+ if (profileDetails) {
30254
+ emitAdvisorySyncProfileDuration(profile, profileStartedAt, {
30255
+ name: "sharedLog.adaptive.rebalance",
30256
+ component: "shared-log",
30257
+ details: profileDetails,
30258
+ });
30084
30259
  }
30085
- throw error;
30086
- });
30087
-
30088
- return resp;
30260
+ }
30089
30261
  }
30090
30262
 
30091
30263
  private getDynamicRangeOffset(): NumberFromType<R> {