@peerbit/shared-log 16.0.2 → 16.0.3

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
@@ -637,6 +637,13 @@ export type NativeBackboneCoordinateRollback<R extends "u32" | "u64"> = {
637
637
  hashes: Set<string>;
638
638
  entries: Map<string, ResidentCoordinateEntry<R>>;
639
639
  generations: Map<string, number>;
640
+ /**
641
+ * Set by `settleResidentCoordinateSnapshot` once the token's holds on the
642
+ * mutation-generation map have been released. Load-bearing: it makes the
643
+ * settle idempotent, so a second settle of this token cannot consume a
644
+ * different token's hold on a shared hash.
645
+ */
646
+ settled?: boolean;
640
647
  };
641
648
 
642
649
  export type RepairDispatchEntry<R extends "u32" | "u64"> =
@@ -3114,9 +3121,23 @@ export class SharedLog<
3114
3121
  };
3115
3122
  for (const coordinate of intent.coordinates) {
3116
3123
  rollback.hashes.add(coordinate.hash);
3117
- const generation =
3118
- (mutationGenerations.get(coordinate.hash) ?? 0) + 1;
3119
- mutationGenerations.set(coordinate.hash, generation);
3124
+ // Same hold-counted row shape the coordinator's own
3125
+ // snapshot writes: one hold per hash, released by the
3126
+ // settle after the replay consumes the token below.
3127
+ // RELIES ON `intent.coordinates` HOLDING UNIQUE HASHES —
3128
+ // it is built from the token's `hashes` Set (see
3129
+ // setNativeStrictDurableTransactionOperation). Holds are
3130
+ // taken per element here but released per unique hash by
3131
+ // the settle, so a duplicate would take two and release
3132
+ // one and retain that row forever. That is the safe
3133
+ // direction (a retained row, never a fail-open rollback),
3134
+ // but keep the source a Set.
3135
+ const row = mutationGenerations.get(coordinate.hash);
3136
+ const generation = (row?.generation ?? 0) + 1;
3137
+ mutationGenerations.set(coordinate.hash, {
3138
+ generation,
3139
+ holds: (row?.holds ?? 0) + 1,
3140
+ });
3120
3141
  rollback.generations.set(coordinate.hash, generation);
3121
3142
  if (coordinate.value) {
3122
3143
  const number = (value: string) =>
@@ -3142,6 +3163,9 @@ export class SharedLog<
3142
3163
  "",
3143
3164
  rollback,
3144
3165
  );
3166
+ // The replay fabricated these generations itself one turn
3167
+ // earlier and has now consumed them, so the rows are dead.
3168
+ this._coordinates.settleResidentCoordinateSnapshot(rollback);
3145
3169
  }
3146
3170
  for (const document of intent.documents) {
3147
3171
  this.restoreNativeBackboneDocument({
@@ -10979,12 +11003,21 @@ export class SharedLog<
10979
11003
  return rollbackLowerPublication(error);
10980
11004
  }
10981
11005
  if (!result) {
11006
+ // Abandon arm: the token was minted but nothing downstream can
11007
+ // roll it back from here.
11008
+ this._coordinates.settleResidentCoordinateSnapshot(
11009
+ lowerPublicationRollback?.coordinateEntries,
11010
+ );
10982
11011
  return this.completeNativeStrictDurableTransaction(
10983
11012
  nativeStrictTransaction,
10984
11013
  ).then(() => undefined);
10985
11014
  }
10986
11015
  return mapMaybePromise(result, async (prepared) => {
10987
11016
  if (!prepared) {
11017
+ // Abandon arm: same shape as the `!result` arm above.
11018
+ this._coordinates.settleResidentCoordinateSnapshot(
11019
+ lowerPublicationRollback?.coordinateEntries,
11020
+ );
10988
11021
  await this.completeNativeStrictDurableTransaction(
10989
11022
  nativeStrictTransaction,
10990
11023
  );
@@ -11018,6 +11051,11 @@ export class SharedLog<
11018
11051
  prepared.appendFacts.hash,
11019
11052
  lowerPublicationRollback?.coordinateEntries,
11020
11053
  );
11054
+ // Terminal: this is the last rollback consumer for the
11055
+ // token. A throw above leaves the row, which is safe.
11056
+ this._coordinates.settleResidentCoordinateSnapshot(
11057
+ lowerPublicationRollback?.coordinateEntries,
11058
+ );
11021
11059
  for (const document of lowerPublicationRollback?.documents ?? []) {
11022
11060
  this.restoreNativeBackboneDocument(document);
11023
11061
  }
@@ -11088,6 +11126,14 @@ export class SharedLog<
11088
11126
  } catch (error) {
11089
11127
  return rollback(error);
11090
11128
  }
11129
+ // Success seam. The last await inside the protected try is the
11130
+ // finalizer acknowledge; `finish()` is synchronous, so no async
11131
+ // boundary separates the catch above from this statement and
11132
+ // `rollback` can no longer fire. Nothing downstream rolls back
11133
+ // (the retire below only warns), so the token is terminal here.
11134
+ this._coordinates.settleResidentCoordinateSnapshot(
11135
+ lowerPublicationRollback?.coordinateEntries,
11136
+ );
11091
11137
  this.applyPreparedAppendFactsWithDeferredCoordinateDeletes(
11092
11138
  prepared.appendFacts,
11093
11139
  prepared.removed,
@@ -11544,6 +11590,12 @@ export class SharedLog<
11544
11590
  }
11545
11591
  return mapMaybePromise(result, async (prepared) => {
11546
11592
  if (!prepared || !backboneAppend) {
11593
+ // Abandon arm: the token was minted inside
11594
+ // `prepareBackboneAppend` and nothing downstream of
11595
+ // this return can roll it back.
11596
+ this._coordinates.settleResidentCoordinateSnapshot(
11597
+ nativeCoordinateRollback,
11598
+ );
11547
11599
  await this.completeNativeStrictDurableTransaction(
11548
11600
  nativeStrictTransaction,
11549
11601
  );
@@ -11635,6 +11687,10 @@ export class SharedLog<
11635
11687
  prepared.appendFacts.hash,
11636
11688
  rollbackCoordinateEntries,
11637
11689
  );
11690
+ // Terminal: last rollback consumer for the token.
11691
+ this._coordinates.settleResidentCoordinateSnapshot(
11692
+ rollbackCoordinateEntries,
11693
+ );
11638
11694
  } catch (rollbackError) {
11639
11695
  rollbackFailures.push(rollbackError);
11640
11696
  }
@@ -11711,6 +11767,12 @@ export class SharedLog<
11711
11767
  } catch (error) {
11712
11768
  return rollback(error);
11713
11769
  }
11770
+ // Success seam: the finalizer acknowledge above is the last
11771
+ // await inside the protected try, so `rollback` can no
11772
+ // longer fire and the retire below only warns.
11773
+ this._coordinates.settleResidentCoordinateSnapshot(
11774
+ rollbackCoordinateEntries,
11775
+ );
11714
11776
  this.applyPreparedAppendFactsWithDeferredCoordinateDeletes(
11715
11777
  prepared.appendFacts,
11716
11778
  prepared.removed,
@@ -12630,6 +12692,10 @@ export class SharedLog<
12630
12692
  throw error;
12631
12693
  }
12632
12694
  if (!appended || !backboneAppends) {
12695
+ // Abandon arm: no consumer downstream of this return.
12696
+ this._coordinates.settleResidentCoordinateSnapshot(
12697
+ batchCoordinateRollback,
12698
+ );
12633
12699
  await this.completeNativeStrictDurableTransaction(
12634
12700
  nativeStrictTransaction,
12635
12701
  );
@@ -12667,6 +12733,10 @@ export class SharedLog<
12667
12733
  appended.appendFacts[0]?.hash ?? "",
12668
12734
  batchCoordinateRollback,
12669
12735
  );
12736
+ // Terminal: last rollback consumer for the batch token.
12737
+ this._coordinates.settleResidentCoordinateSnapshot(
12738
+ batchCoordinateRollback,
12739
+ );
12670
12740
  } catch (rollbackError) {
12671
12741
  rollbackFailures.push(rollbackError);
12672
12742
  }
@@ -12798,6 +12868,11 @@ export class SharedLog<
12798
12868
  } catch (error) {
12799
12869
  return rollbackBatch(error);
12800
12870
  }
12871
+ // Success seam: `rollbackBatch` has exactly one call site (the catch
12872
+ // above), and everything from here on escapes without any rollback.
12873
+ this._coordinates.settleResidentCoordinateSnapshot(
12874
+ batchCoordinateRollback,
12875
+ );
12801
12876
 
12802
12877
  this.throwIfReplicationOwnershipLifecycleInactive(
12803
12878
  ownershipLifecycleController,
@@ -18907,292 +18982,305 @@ export class SharedLog<
18907
18982
  reusableCoordinatePersistItems,
18908
18983
  )
18909
18984
  : undefined;
18910
- const nativePreparedJoinCommit = canUsePreparedAppendFacts
18911
- ? this._coordinates.createNativeBackbonePreparedJoinCommit(
18912
- nativeReceiveCoordinateBatch,
18913
- (batch) => {
18914
- nativePreparedCoordinateBatch = batch;
18915
- },
18916
- nativeCommitVerifyHashes,
18917
- nativeCommitVerifyAllHashes,
18918
- syncProfile,
18919
- (committedHashes) => {
18920
- nativePreparedCommittedHashes = new Set(committedHashes);
18921
- },
18922
- )
18923
- : undefined;
18924
- const finishNativePreparedCoordinates = async (properties: {
18925
- nativePreparedCommitted: boolean;
18926
- }) => {
18927
- if (
18928
- !properties.nativePreparedCommitted ||
18929
- !nativePreparedCoordinateBatch
18930
- ) {
18931
- return;
18932
- }
18933
- try {
18934
- nativeBackboneOnlyPersistedHashes =
18935
- await this._coordinates.finishBackboneOnlyReceiveCoordinateBatch(
18936
- nativePreparedCoordinateBatch,
18985
+ try {
18986
+ const nativePreparedJoinCommit = canUsePreparedAppendFacts
18987
+ ? this._coordinates.createNativeBackbonePreparedJoinCommit(
18988
+ nativeReceiveCoordinateBatch,
18989
+ (batch) => {
18990
+ nativePreparedCoordinateBatch = batch;
18991
+ },
18992
+ nativeCommitVerifyHashes,
18993
+ nativeCommitVerifyAllHashes,
18937
18994
  syncProfile,
18995
+ (committedHashes) => {
18996
+ nativePreparedCommittedHashes = new Set(committedHashes);
18997
+ },
18998
+ )
18999
+ : undefined;
19000
+ const finishNativePreparedCoordinates = async (properties: {
19001
+ nativePreparedCommitted: boolean;
19002
+ }) => {
19003
+ if (
19004
+ !properties.nativePreparedCommitted ||
19005
+ !nativePreparedCoordinateBatch
19006
+ ) {
19007
+ return;
19008
+ }
19009
+ try {
19010
+ nativeBackboneOnlyPersistedHashes =
19011
+ await this._coordinates.finishBackboneOnlyReceiveCoordinateBatch(
19012
+ nativePreparedCoordinateBatch,
19013
+ syncProfile,
19014
+ );
19015
+ nativePreparedCoordinatesFinished = true;
19016
+ } catch (error) {
19017
+ this._coordinates.rollbackBackboneOnlyReceiveCoordinateBatch(
19018
+ nativePreparedCoordinateBatch,
18938
19019
  );
18939
- nativePreparedCoordinatesFinished = true;
18940
- } catch (error) {
18941
- this._coordinates.rollbackBackboneOnlyReceiveCoordinateBatch(
18942
- nativePreparedCoordinateBatch,
18943
- );
18944
- throw error;
19020
+ throw error;
19021
+ }
19022
+ };
19023
+ const preparedAppendCanValidateAppend =
19024
+ canAppendAlreadyValidated ||
19025
+ (nativeCommitCanValidateAppend && !!nativePreparedJoinCommit);
19026
+ if (!preparedAppendCanValidateAppend) {
19027
+ canUsePreparedAppendFacts = false;
18945
19028
  }
18946
- };
18947
- const preparedAppendCanValidateAppend =
18948
- canAppendAlreadyValidated ||
18949
- (nativeCommitCanValidateAppend && !!nativePreparedJoinCommit);
18950
- if (!preparedAppendCanValidateAppend) {
18951
- canUsePreparedAppendFacts = false;
18952
- }
18953
- const nativePreparedJoinCommitValidatesPlan =
18954
- !!nativePreparedJoinCommit &&
18955
- (nativeCommitVerifyHashes && nativeCommitVerifyHashes.length > 0
18956
- ? nativeCommitVerifyAllHashes
18957
- ? !!this._nativeBackbone?.graph
18958
- .commitVerifiedAllPreparedRawReceiveJoinBatch ||
18959
- !!this._nativeBackbone?.graph
18960
- .commitVerifiedPreparedRawReceiveJoinBatch
19029
+ const nativePreparedJoinCommitValidatesPlan =
19030
+ !!nativePreparedJoinCommit &&
19031
+ (nativeCommitVerifyHashes && nativeCommitVerifyHashes.length > 0
19032
+ ? nativeCommitVerifyAllHashes
19033
+ ? !!this._nativeBackbone?.graph
19034
+ .commitVerifiedAllPreparedRawReceiveJoinBatch ||
19035
+ !!this._nativeBackbone?.graph
19036
+ .commitVerifiedPreparedRawReceiveJoinBatch
19037
+ : !!this._nativeBackbone?.graph
19038
+ .commitVerifiedPreparedRawReceiveJoinBatch
18961
19039
  : !!this._nativeBackbone?.graph
18962
- .commitVerifiedPreparedRawReceiveJoinBatch
18963
- : !!this._nativeBackbone?.graph
18964
- .commitPreparedRawReceiveJoinBatch);
18965
- const trustedLowerLog = this.log as unknown as TrustedLowerLog<T>;
18966
- // With a program-level onChange consumer the hash-only
18967
- // sink is not used: the lower-log join dispatches the
18968
- // change event (lazy entry views over the prepared raw
18969
- // facts) so per-entry consumers observe every commit.
18970
- const joinOnAppendHashes = programOnChange
18971
- ? undefined
18972
- : onAppendHashes;
18973
- const joinedPreparedFacts =
18974
- canUsePreparedAppendFacts &&
18975
- (await trustedLowerLog.joinPreparedAppendFactsBatch(
18976
- preparedAppendFacts,
18977
- {
19040
+ .commitPreparedRawReceiveJoinBatch);
19041
+ const trustedLowerLog = this.log as unknown as TrustedLowerLog<T>;
19042
+ // With a program-level onChange consumer the hash-only
19043
+ // sink is not used: the lower-log join dispatches the
19044
+ // change event (lazy entry views over the prepared raw
19045
+ // facts) so per-entry consumers observe every commit.
19046
+ const joinOnAppendHashes = programOnChange
19047
+ ? undefined
19048
+ : onAppendHashes;
19049
+ const joinedPreparedFacts =
19050
+ canUsePreparedAppendFacts &&
19051
+ (await trustedLowerLog.joinPreparedAppendFactsBatch(
19052
+ preparedAppendFacts,
19053
+ {
19054
+ __peerbitEntriesAlreadyMissing: true,
19055
+ __peerbitCanAppendAlreadyValidated: true,
19056
+ __peerbitDeferIndexWrite: true,
19057
+ __peerbitOnAppendHashes: joinOnAppendHashes,
19058
+ __peerbitProfile: syncProfile,
19059
+ __peerbitNativePreparedJoinCommit: nativePreparedJoinCommit,
19060
+ __peerbitNativePreparedJoinCommitValidatesPlan:
19061
+ nativePreparedJoinCommitValidatesPlan,
19062
+ __peerbitOnPreparedJoinCommitted: nativePreparedJoinCommit
19063
+ ? finishNativePreparedCoordinates
19064
+ : undefined,
19065
+ },
19066
+ ));
19067
+ if (!joinedPreparedFacts) {
19068
+ await trustedLowerLog.join(materializeAllToMergeEntries(), {
19069
+ __peerbitBatchIndependent: true,
18978
19070
  __peerbitEntriesAlreadyMissing: true,
18979
- __peerbitCanAppendAlreadyValidated: true,
19071
+ __peerbitCanAppendAlreadyValidated:
19072
+ fallbackCanAppendAlreadyValidated,
18980
19073
  __peerbitDeferIndexWrite: true,
18981
19074
  __peerbitOnAppendHashes: joinOnAppendHashes,
18982
19075
  __peerbitProfile: syncProfile,
18983
- __peerbitNativePreparedJoinCommit: nativePreparedJoinCommit,
18984
- __peerbitNativePreparedJoinCommitValidatesPlan:
18985
- nativePreparedJoinCommitValidatesPlan,
18986
- __peerbitOnPreparedJoinCommitted: nativePreparedJoinCommit
18987
- ? finishNativePreparedCoordinates
18988
- : undefined,
18989
- },
18990
- ));
18991
- if (!joinedPreparedFacts) {
18992
- await trustedLowerLog.join(materializeAllToMergeEntries(), {
18993
- __peerbitBatchIndependent: true,
18994
- __peerbitEntriesAlreadyMissing: true,
18995
- __peerbitCanAppendAlreadyValidated:
18996
- fallbackCanAppendAlreadyValidated,
18997
- __peerbitDeferIndexWrite: true,
18998
- __peerbitOnAppendHashes: joinOnAppendHashes,
18999
- __peerbitProfile: syncProfile,
19000
- });
19001
- }
19002
- // A recursive lower-log join can resolve successfully while declining
19003
- // an individual top-level entry (for example, when one of its parents
19004
- // is temporarily unavailable). The public Log.join() API intentionally
19005
- // does not expose that per-entry result, so make local index presence the
19006
- // authority before publishing any SharedLog-side effects. A successful
19007
- // prepared-facts batch is atomic and already proves every input hash.
19008
- const admittedHashes = joinedPreparedFacts
19009
- ? new Set(allToMergeHashes)
19010
- : await this.log.hasMany(allToMergeHashes);
19011
- admittedMergeHashes = admittedHashes;
19012
- const admittedShallowEntries =
19013
- admittedHashes.size === allToMergeShallowEntries.length
19014
- ? allToMergeShallowEntries
19015
- : allToMergeShallowEntries.filter((entry) =>
19076
+ });
19077
+ }
19078
+ // A recursive lower-log join can resolve successfully while declining
19079
+ // an individual top-level entry (for example, when one of its parents
19080
+ // is temporarily unavailable). The public Log.join() API intentionally
19081
+ // does not expose that per-entry result, so make local index presence the
19082
+ // authority before publishing any SharedLog-side effects. A successful
19083
+ // prepared-facts batch is atomic and already proves every input hash.
19084
+ const admittedHashes = joinedPreparedFacts
19085
+ ? new Set(allToMergeHashes)
19086
+ : await this.log.hasMany(allToMergeHashes);
19087
+ admittedMergeHashes = admittedHashes;
19088
+ const admittedShallowEntries =
19089
+ admittedHashes.size === allToMergeShallowEntries.length
19090
+ ? allToMergeShallowEntries
19091
+ : allToMergeShallowEntries.filter((entry) =>
19092
+ admittedHashes.has(entry.hash),
19093
+ );
19094
+ if (!joinedPreparedFacts) {
19095
+ reusableCoordinatePersistItems =
19096
+ reusableCoordinatePersistItems.filter((item) =>
19097
+ admittedHashes.has(item.entry.hash),
19098
+ );
19099
+ coordinatePersistFallbackEntries =
19100
+ coordinatePersistFallbackEntries.filter((entry) =>
19016
19101
  admittedHashes.has(entry.hash),
19017
19102
  );
19018
- if (!joinedPreparedFacts) {
19019
- reusableCoordinatePersistItems =
19020
- reusableCoordinatePersistItems.filter((item) =>
19021
- admittedHashes.has(item.entry.hash),
19022
- );
19023
- coordinatePersistFallbackEntries =
19024
- coordinatePersistFallbackEntries.filter((entry) =>
19025
- admittedHashes.has(entry.hash),
19026
- );
19027
- }
19028
- const reusableCoordinatePersistItemCount =
19029
- reusableCoordinatePersistItems.length;
19030
- if (syncProfile) {
19031
- emitSyncProfileDuration(syncProfile, lowerLogJoinStartedAt, {
19032
- name: "sharedLog.receive.lowerLogJoin",
19033
- component: "shared-log",
19034
- entries: allToMerge.length,
19035
- messages: 1,
19036
- details: {
19037
- hashOnlyEntryAdded,
19038
- batchHashOnlyEntryAdded,
19039
- programOnChange,
19040
- joinedPreparedFacts,
19041
- admittedEntries: admittedHashes.size,
19042
- nativePreparedCoordinatesFinished,
19043
- },
19044
- });
19045
- }
19046
- const coordinatePersistStartedAt = syncProfileStart(syncProfile);
19047
- if (nativePreparedCoordinatesFinished) {
19048
- // The lower-log prepared receive transaction already finished
19049
- // the native coordinate mirror/journal after entry-index commit.
19050
- } else if (nativePreparedCoordinateBatch) {
19051
- try {
19052
- nativeBackboneOnlyPersistedHashes =
19053
- await this._coordinates.finishBackboneOnlyReceiveCoordinateBatch(
19103
+ }
19104
+ const reusableCoordinatePersistItemCount =
19105
+ reusableCoordinatePersistItems.length;
19106
+ if (syncProfile) {
19107
+ emitSyncProfileDuration(syncProfile, lowerLogJoinStartedAt, {
19108
+ name: "sharedLog.receive.lowerLogJoin",
19109
+ component: "shared-log",
19110
+ entries: allToMerge.length,
19111
+ messages: 1,
19112
+ details: {
19113
+ hashOnlyEntryAdded,
19114
+ batchHashOnlyEntryAdded,
19115
+ programOnChange,
19116
+ joinedPreparedFacts,
19117
+ admittedEntries: admittedHashes.size,
19118
+ nativePreparedCoordinatesFinished,
19119
+ },
19120
+ });
19121
+ }
19122
+ const coordinatePersistStartedAt = syncProfileStart(syncProfile);
19123
+ if (nativePreparedCoordinatesFinished) {
19124
+ // The lower-log prepared receive transaction already finished
19125
+ // the native coordinate mirror/journal after entry-index commit.
19126
+ } else if (nativePreparedCoordinateBatch) {
19127
+ try {
19128
+ nativeBackboneOnlyPersistedHashes =
19129
+ await this._coordinates.finishBackboneOnlyReceiveCoordinateBatch(
19130
+ nativePreparedCoordinateBatch,
19131
+ syncProfile,
19132
+ );
19133
+ } catch (error) {
19134
+ this._coordinates.rollbackBackboneOnlyReceiveCoordinateBatch(
19054
19135
  nativePreparedCoordinateBatch,
19055
- syncProfile,
19056
19136
  );
19057
- } catch (error) {
19058
- this._coordinates.rollbackBackboneOnlyReceiveCoordinateBatch(
19059
- nativePreparedCoordinateBatch,
19060
- );
19061
- throw error;
19137
+ throw error;
19138
+ }
19139
+ } else {
19140
+ nativeBackboneOnlyPersistedHashes =
19141
+ await this._coordinates.persistBackboneOnlyReceiveCoordinateBatch(
19142
+ reusableCoordinatePersistItems,
19143
+ );
19062
19144
  }
19063
- } else {
19064
- nativeBackboneOnlyPersistedHashes =
19065
- await this._coordinates.persistBackboneOnlyReceiveCoordinateBatch(
19066
- reusableCoordinatePersistItems,
19067
- );
19068
- }
19069
- if (
19070
- nativeBackboneOnlyPersistedHashes &&
19071
- nativeBackboneOnlyPersistedHashes.size > 0
19072
- ) {
19073
- for (
19074
- let i = reusableCoordinatePersistItems.length - 1;
19075
- i >= 0;
19076
- i--
19145
+ if (
19146
+ nativeBackboneOnlyPersistedHashes &&
19147
+ nativeBackboneOnlyPersistedHashes.size > 0
19077
19148
  ) {
19078
- if (
19079
- nativeBackboneOnlyPersistedHashes.has(
19080
- reusableCoordinatePersistItems[i]!.entry.hash,
19081
- )
19149
+ for (
19150
+ let i = reusableCoordinatePersistItems.length - 1;
19151
+ i >= 0;
19152
+ i--
19082
19153
  ) {
19083
- reusableCoordinatePersistItems.splice(i, 1);
19154
+ if (
19155
+ nativeBackboneOnlyPersistedHashes.has(
19156
+ reusableCoordinatePersistItems[i]!.entry.hash,
19157
+ )
19158
+ ) {
19159
+ reusableCoordinatePersistItems.splice(i, 1);
19160
+ }
19084
19161
  }
19085
19162
  }
19086
- }
19087
- if (reusableCoordinatePersistItems.length > 0) {
19088
- await this._coordinates.persistCoordinatesBatch(
19089
- reusableCoordinatePersistItems,
19090
- );
19091
- }
19092
- if (coordinatePersistFallbackEntries.length > 0) {
19093
- await this.planEntryLeaderBatch(
19094
- coordinatePersistFallbackEntries.map((entry) => ({
19095
- entry,
19096
- replicas:
19097
- receiveReplicaCounts.get(entry.hash) ??
19098
- decodeReplicas(entry).getValue(this),
19099
- options: { roleAge: 0, persist: {} },
19100
- })),
19101
- );
19102
- }
19103
- if (syncProfile) {
19104
- emitSyncProfileDuration(syncProfile, coordinatePersistStartedAt, {
19105
- name: "sharedLog.receive.coordinatePersist",
19106
- component: "shared-log",
19107
- entries: entriesToPersist.length,
19108
- messages: 1,
19109
- details: {
19110
- reusedLeaderPlans: reusableCoordinatePersistItemCount,
19111
- nativeBackboneOnly:
19112
- nativeBackboneOnlyPersistedHashes?.size ?? 0,
19113
- },
19114
- });
19115
- }
19116
- for (const hash of admittedHashes) {
19117
- confirmedHashes.add(hash);
19118
- }
19119
- const checkedPruneStartedAt = syncProfileStart(syncProfile);
19120
- const ownershipChangedDuringReceive =
19121
- !this.isReceiveOwnershipSnapshotStable(receiveOwnershipRevision);
19122
- if (ownershipChangedDuringReceive) {
19123
- const freshAuditRevision =
19124
- this._instanceLifecycle?._receiveOwnershipRevision ?? 0;
19125
- const armFreshAuditRetry = () => {
19126
- for (const entry of admittedShallowEntries) {
19127
- this.scheduleCheckedPruneRetry(
19128
- { entry, leaders: new Map() },
19163
+ if (reusableCoordinatePersistItems.length > 0) {
19164
+ await this._coordinates.persistCoordinatesBatch(
19165
+ reusableCoordinatePersistItems,
19166
+ );
19167
+ }
19168
+ if (coordinatePersistFallbackEntries.length > 0) {
19169
+ await this.planEntryLeaderBatch(
19170
+ coordinatePersistFallbackEntries.map((entry) => ({
19171
+ entry,
19172
+ replicas:
19173
+ receiveReplicaCounts.get(entry.hash) ??
19174
+ decodeReplicas(entry).getValue(this),
19175
+ options: { roleAge: 0, persist: {} },
19176
+ })),
19177
+ );
19178
+ }
19179
+ if (syncProfile) {
19180
+ emitSyncProfileDuration(syncProfile, coordinatePersistStartedAt, {
19181
+ name: "sharedLog.receive.coordinatePersist",
19182
+ component: "shared-log",
19183
+ entries: entriesToPersist.length,
19184
+ messages: 1,
19185
+ details: {
19186
+ reusedLeaderPlans: reusableCoordinatePersistItemCount,
19187
+ nativeBackboneOnly:
19188
+ nativeBackboneOnlyPersistedHashes?.size ?? 0,
19189
+ },
19190
+ });
19191
+ }
19192
+ for (const hash of admittedHashes) {
19193
+ confirmedHashes.add(hash);
19194
+ }
19195
+ const checkedPruneStartedAt = syncProfileStart(syncProfile);
19196
+ const ownershipChangedDuringReceive =
19197
+ !this.isReceiveOwnershipSnapshotStable(receiveOwnershipRevision);
19198
+ if (ownershipChangedDuringReceive) {
19199
+ const freshAuditRevision =
19200
+ this._instanceLifecycle?._receiveOwnershipRevision ?? 0;
19201
+ const armFreshAuditRetry = () => {
19202
+ for (const entry of admittedShallowEntries) {
19203
+ this.scheduleCheckedPruneRetry(
19204
+ { entry, leaders: new Map() },
19205
+ receiveOwnershipLifecycleController,
19206
+ );
19207
+ }
19208
+ };
19209
+ try {
19210
+ await this.pruneJoinedEntriesNoLongerLed(
19211
+ admittedShallowEntries,
19212
+ {
19213
+ decodedReplicaCounts: receiveReplicaCounts,
19214
+ freshReceiveOwnerAudit: true,
19215
+ preserveExistingPruneOnLocalResult: true,
19216
+ profile: syncProfile,
19217
+ },
19129
19218
  receiveOwnershipLifecycleController,
19130
19219
  );
19220
+ this.throwIfReplicationOwnershipLifecycleInactive(
19221
+ receiveOwnershipLifecycleController,
19222
+ );
19223
+ if (
19224
+ !this.isReceiveOwnershipSnapshotStable(freshAuditRevision)
19225
+ ) {
19226
+ armFreshAuditRetry();
19227
+ }
19228
+ } catch {
19229
+ // The lower-log and coordinate commits are already durable. A
19230
+ // sender retry will filter these hashes as present, so retain a
19231
+ // bounded local obligation instead of failing the admitted receive.
19232
+ this.throwIfReplicationOwnershipLifecycleInactive(
19233
+ receiveOwnershipLifecycleController,
19234
+ );
19235
+ armFreshAuditRetry();
19131
19236
  }
19132
- };
19133
- try {
19237
+ } else {
19134
19238
  await this.pruneJoinedEntriesNoLongerLed(
19135
19239
  admittedShallowEntries,
19136
19240
  {
19137
19241
  decodedReplicaCounts: receiveReplicaCounts,
19138
- freshReceiveOwnerAudit: true,
19139
19242
  preserveExistingPruneOnLocalResult: true,
19243
+ reusableLeaderPlans: reusableCoordinatePlans,
19140
19244
  profile: syncProfile,
19141
19245
  },
19142
19246
  receiveOwnershipLifecycleController,
19143
19247
  );
19144
- this.throwIfReplicationOwnershipLifecycleInactive(
19145
- receiveOwnershipLifecycleController,
19146
- );
19147
- if (
19148
- !this.isReceiveOwnershipSnapshotStable(freshAuditRevision)
19149
- ) {
19150
- armFreshAuditRetry();
19151
- }
19152
- } catch {
19153
- // The lower-log and coordinate commits are already durable. A
19154
- // sender retry will filter these hashes as present, so retain a
19155
- // bounded local obligation instead of failing the admitted receive.
19156
- this.throwIfReplicationOwnershipLifecycleInactive(
19157
- receiveOwnershipLifecycleController,
19158
- );
19159
- armFreshAuditRetry();
19160
19248
  }
19161
- } else {
19162
- await this.pruneJoinedEntriesNoLongerLed(
19163
- admittedShallowEntries,
19164
- {
19165
- decodedReplicaCounts: receiveReplicaCounts,
19166
- preserveExistingPruneOnLocalResult: true,
19167
- reusableLeaderPlans: reusableCoordinatePlans,
19168
- profile: syncProfile,
19169
- },
19170
- receiveOwnershipLifecycleController,
19171
- );
19172
- }
19173
- if (syncProfile) {
19174
- emitSyncProfileDuration(syncProfile, checkedPruneStartedAt, {
19175
- name: "sharedLog.receive.checkedPrune",
19176
- component: "shared-log",
19177
- entries: allToMerge.length,
19178
- messages: 1,
19179
- });
19180
- }
19249
+ if (syncProfile) {
19250
+ emitSyncProfileDuration(syncProfile, checkedPruneStartedAt, {
19251
+ name: "sharedLog.receive.checkedPrune",
19252
+ component: "shared-log",
19253
+ entries: allToMerge.length,
19254
+ messages: 1,
19255
+ });
19256
+ }
19181
19257
 
19182
- for (const plan of joinPlans) {
19183
- plan.toDelete
19184
- ?.filter((entry) => admittedMergeHashes.has(entry.hash))
19185
- .map((entry) =>
19186
- this.pruneDebouncedFnAddIfNotKeeping({
19187
- key: entry.hash,
19188
- value: {
19189
- entry,
19190
- leaders: plan.leaders as Map<string, any>,
19191
- },
19192
- }),
19193
- );
19258
+ for (const plan of joinPlans) {
19259
+ plan.toDelete
19260
+ ?.filter((entry) => admittedMergeHashes.has(entry.hash))
19261
+ .map((entry) =>
19262
+ this.pruneDebouncedFnAddIfNotKeeping({
19263
+ key: entry.hash,
19264
+ value: {
19265
+ entry,
19266
+ leaders: plan.leaders as Map<string, any>,
19267
+ },
19268
+ }),
19269
+ );
19270
+ }
19271
+ this.rebalanceParticipationDebounced?.call();
19272
+ } finally {
19273
+ // Settle seam for the receive token. Every consumer that can
19274
+ // roll it back runs inline before control leaves this block: the
19275
+ // prepared-join callback resolves during the join await, and the
19276
+ // late finish/rollback arm runs above. This `finally` is what
19277
+ // closes the abandon arms (no prepared-join commit, a declined
19278
+ // native commit, a downgrade to the plain join) without having
19279
+ // to enumerate them.
19280
+ this._coordinates.settleResidentCoordinateSnapshot(
19281
+ nativeReceiveCoordinateBatch?.rollbackCoordinateEntries,
19282
+ );
19194
19283
  }
19195
- this.rebalanceParticipationDebounced?.call();
19196
19284
  }
19197
19285
 
19198
19286
  for (const plan of joinPlans) {