@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/dist/src/index.js CHANGED
@@ -1518,8 +1518,23 @@ let SharedLog = (() => {
1518
1518
  };
1519
1519
  for (const coordinate of intent.coordinates) {
1520
1520
  rollback.hashes.add(coordinate.hash);
1521
- const generation = (mutationGenerations.get(coordinate.hash) ?? 0) + 1;
1522
- mutationGenerations.set(coordinate.hash, generation);
1521
+ // Same hold-counted row shape the coordinator's own
1522
+ // snapshot writes: one hold per hash, released by the
1523
+ // settle after the replay consumes the token below.
1524
+ // RELIES ON `intent.coordinates` HOLDING UNIQUE HASHES —
1525
+ // it is built from the token's `hashes` Set (see
1526
+ // setNativeStrictDurableTransactionOperation). Holds are
1527
+ // taken per element here but released per unique hash by
1528
+ // the settle, so a duplicate would take two and release
1529
+ // one and retain that row forever. That is the safe
1530
+ // direction (a retained row, never a fail-open rollback),
1531
+ // but keep the source a Set.
1532
+ const row = mutationGenerations.get(coordinate.hash);
1533
+ const generation = (row?.generation ?? 0) + 1;
1534
+ mutationGenerations.set(coordinate.hash, {
1535
+ generation,
1536
+ holds: (row?.holds ?? 0) + 1,
1537
+ });
1523
1538
  rollback.generations.set(coordinate.hash, generation);
1524
1539
  if (coordinate.value) {
1525
1540
  const number = (value) => (this.domain.resolution === "u32"
@@ -1537,6 +1552,9 @@ let SharedLog = (() => {
1537
1552
  }
1538
1553
  }
1539
1554
  await this._coordinates.rollbackNativeBackboneCoordinateAppendDurably("", rollback);
1555
+ // The replay fabricated these generations itself one turn
1556
+ // earlier and has now consumed them, so the rows are dead.
1557
+ this._coordinates.settleResidentCoordinateSnapshot(rollback);
1540
1558
  }
1541
1559
  for (const document of intent.documents) {
1542
1560
  this.restoreNativeBackboneDocument({
@@ -7081,10 +7099,15 @@ let SharedLog = (() => {
7081
7099
  return rollbackLowerPublication(error);
7082
7100
  }
7083
7101
  if (!result) {
7102
+ // Abandon arm: the token was minted but nothing downstream can
7103
+ // roll it back from here.
7104
+ this._coordinates.settleResidentCoordinateSnapshot(lowerPublicationRollback?.coordinateEntries);
7084
7105
  return this.completeNativeStrictDurableTransaction(nativeStrictTransaction).then(() => undefined);
7085
7106
  }
7086
7107
  return mapMaybePromise(result, async (prepared) => {
7087
7108
  if (!prepared) {
7109
+ // Abandon arm: same shape as the `!result` arm above.
7110
+ this._coordinates.settleResidentCoordinateSnapshot(lowerPublicationRollback?.coordinateEntries);
7088
7111
  await this.completeNativeStrictDurableTransaction(nativeStrictTransaction);
7089
7112
  return undefined;
7090
7113
  }
@@ -7105,6 +7128,9 @@ let SharedLog = (() => {
7105
7128
  }
7106
7129
  try {
7107
7130
  await this._coordinates.rollbackNativeBackboneCoordinateAppendDurably(prepared.appendFacts.hash, lowerPublicationRollback?.coordinateEntries);
7131
+ // Terminal: this is the last rollback consumer for the
7132
+ // token. A throw above leaves the row, which is safe.
7133
+ this._coordinates.settleResidentCoordinateSnapshot(lowerPublicationRollback?.coordinateEntries);
7108
7134
  for (const document of lowerPublicationRollback?.documents ?? []) {
7109
7135
  this.restoreNativeBackboneDocument(document);
7110
7136
  }
@@ -7164,6 +7190,12 @@ let SharedLog = (() => {
7164
7190
  catch (error) {
7165
7191
  return rollback(error);
7166
7192
  }
7193
+ // Success seam. The last await inside the protected try is the
7194
+ // finalizer acknowledge; `finish()` is synchronous, so no async
7195
+ // boundary separates the catch above from this statement and
7196
+ // `rollback` can no longer fire. Nothing downstream rolls back
7197
+ // (the retire below only warns), so the token is terminal here.
7198
+ this._coordinates.settleResidentCoordinateSnapshot(lowerPublicationRollback?.coordinateEntries);
7167
7199
  this.applyPreparedAppendFactsWithDeferredCoordinateDeletes(prepared.appendFacts, prepared.removed, prepared.materializeEntry, { removedHashes: prepared.removedHashes });
7168
7200
  try {
7169
7201
  await this.completeNativeStrictDurableTransaction(nativeStrictTransaction);
@@ -7466,6 +7498,10 @@ let SharedLog = (() => {
7466
7498
  }
7467
7499
  return mapMaybePromise(result, async (prepared) => {
7468
7500
  if (!prepared || !backboneAppend) {
7501
+ // Abandon arm: the token was minted inside
7502
+ // `prepareBackboneAppend` and nothing downstream of
7503
+ // this return can roll it back.
7504
+ this._coordinates.settleResidentCoordinateSnapshot(nativeCoordinateRollback);
7469
7505
  await this.completeNativeStrictDurableTransaction(nativeStrictTransaction);
7470
7506
  return undefined;
7471
7507
  }
@@ -7533,6 +7569,8 @@ let SharedLog = (() => {
7533
7569
  }
7534
7570
  try {
7535
7571
  await this._coordinates.rollbackNativeBackboneCoordinateAppendDurably(prepared.appendFacts.hash, rollbackCoordinateEntries);
7572
+ // Terminal: last rollback consumer for the token.
7573
+ this._coordinates.settleResidentCoordinateSnapshot(rollbackCoordinateEntries);
7536
7574
  }
7537
7575
  catch (rollbackError) {
7538
7576
  rollbackFailures.push(rollbackError);
@@ -7594,6 +7632,10 @@ let SharedLog = (() => {
7594
7632
  catch (error) {
7595
7633
  return rollback(error);
7596
7634
  }
7635
+ // Success seam: the finalizer acknowledge above is the last
7636
+ // await inside the protected try, so `rollback` can no
7637
+ // longer fire and the retire below only warns.
7638
+ this._coordinates.settleResidentCoordinateSnapshot(rollbackCoordinateEntries);
7597
7639
  this.applyPreparedAppendFactsWithDeferredCoordinateDeletes(prepared.appendFacts, prepared.removed, prepared.materializeEntry, {
7598
7640
  forgetNativeCoordinates: false,
7599
7641
  removedHashes: prepared.removedHashes,
@@ -8148,6 +8190,8 @@ let SharedLog = (() => {
8148
8190
  throw error;
8149
8191
  }
8150
8192
  if (!appended || !backboneAppends) {
8193
+ // Abandon arm: no consumer downstream of this return.
8194
+ this._coordinates.settleResidentCoordinateSnapshot(batchCoordinateRollback);
8151
8195
  await this.completeNativeStrictDurableTransaction(nativeStrictTransaction);
8152
8196
  this.throwIfReplicationOwnershipLifecycleInactive(ownershipLifecycleController);
8153
8197
  return undefined;
@@ -8170,6 +8214,8 @@ let SharedLog = (() => {
8170
8214
  }
8171
8215
  try {
8172
8216
  await this._coordinates.rollbackNativeBackboneCoordinateAppendDurably(appended.appendFacts[0]?.hash ?? "", batchCoordinateRollback);
8217
+ // Terminal: last rollback consumer for the batch token.
8218
+ this._coordinates.settleResidentCoordinateSnapshot(batchCoordinateRollback);
8173
8219
  }
8174
8220
  catch (rollbackError) {
8175
8221
  rollbackFailures.push(rollbackError);
@@ -8260,6 +8306,9 @@ let SharedLog = (() => {
8260
8306
  catch (error) {
8261
8307
  return rollbackBatch(error);
8262
8308
  }
8309
+ // Success seam: `rollbackBatch` has exactly one call site (the catch
8310
+ // above), and everything from here on escapes without any rollback.
8311
+ this._coordinates.settleResidentCoordinateSnapshot(batchCoordinateRollback);
8263
8312
  this.throwIfReplicationOwnershipLifecycleInactive(ownershipLifecycleController);
8264
8313
  const appendCommits = [];
8265
8314
  for (let i = 0; i < coordinateRows.length; i++) {
@@ -12925,221 +12974,233 @@ let SharedLog = (() => {
12925
12974
  const nativeReceiveCoordinateBatch = canUsePreparedAppendFacts
12926
12975
  ? this._coordinates.createBackboneOnlyReceiveCoordinateBatch(reusableCoordinatePersistItems)
12927
12976
  : undefined;
12928
- const nativePreparedJoinCommit = canUsePreparedAppendFacts
12929
- ? this._coordinates.createNativeBackbonePreparedJoinCommit(nativeReceiveCoordinateBatch, (batch) => {
12930
- nativePreparedCoordinateBatch = batch;
12931
- }, nativeCommitVerifyHashes, nativeCommitVerifyAllHashes, syncProfile, (committedHashes) => {
12932
- nativePreparedCommittedHashes = new Set(committedHashes);
12933
- })
12934
- : undefined;
12935
- const finishNativePreparedCoordinates = async (properties) => {
12936
- if (!properties.nativePreparedCommitted ||
12937
- !nativePreparedCoordinateBatch) {
12938
- return;
12939
- }
12940
- try {
12941
- nativeBackboneOnlyPersistedHashes =
12942
- await this._coordinates.finishBackboneOnlyReceiveCoordinateBatch(nativePreparedCoordinateBatch, syncProfile);
12943
- nativePreparedCoordinatesFinished = true;
12944
- }
12945
- catch (error) {
12946
- this._coordinates.rollbackBackboneOnlyReceiveCoordinateBatch(nativePreparedCoordinateBatch);
12947
- throw error;
12977
+ try {
12978
+ const nativePreparedJoinCommit = canUsePreparedAppendFacts
12979
+ ? this._coordinates.createNativeBackbonePreparedJoinCommit(nativeReceiveCoordinateBatch, (batch) => {
12980
+ nativePreparedCoordinateBatch = batch;
12981
+ }, nativeCommitVerifyHashes, nativeCommitVerifyAllHashes, syncProfile, (committedHashes) => {
12982
+ nativePreparedCommittedHashes = new Set(committedHashes);
12983
+ })
12984
+ : undefined;
12985
+ const finishNativePreparedCoordinates = async (properties) => {
12986
+ if (!properties.nativePreparedCommitted ||
12987
+ !nativePreparedCoordinateBatch) {
12988
+ return;
12989
+ }
12990
+ try {
12991
+ nativeBackboneOnlyPersistedHashes =
12992
+ await this._coordinates.finishBackboneOnlyReceiveCoordinateBatch(nativePreparedCoordinateBatch, syncProfile);
12993
+ nativePreparedCoordinatesFinished = true;
12994
+ }
12995
+ catch (error) {
12996
+ this._coordinates.rollbackBackboneOnlyReceiveCoordinateBatch(nativePreparedCoordinateBatch);
12997
+ throw error;
12998
+ }
12999
+ };
13000
+ const preparedAppendCanValidateAppend = canAppendAlreadyValidated ||
13001
+ (nativeCommitCanValidateAppend && !!nativePreparedJoinCommit);
13002
+ if (!preparedAppendCanValidateAppend) {
13003
+ canUsePreparedAppendFacts = false;
12948
13004
  }
12949
- };
12950
- const preparedAppendCanValidateAppend = canAppendAlreadyValidated ||
12951
- (nativeCommitCanValidateAppend && !!nativePreparedJoinCommit);
12952
- if (!preparedAppendCanValidateAppend) {
12953
- canUsePreparedAppendFacts = false;
12954
- }
12955
- const nativePreparedJoinCommitValidatesPlan = !!nativePreparedJoinCommit &&
12956
- (nativeCommitVerifyHashes && nativeCommitVerifyHashes.length > 0
12957
- ? nativeCommitVerifyAllHashes
12958
- ? !!this._nativeBackbone?.graph
12959
- .commitVerifiedAllPreparedRawReceiveJoinBatch ||
12960
- !!this._nativeBackbone?.graph
13005
+ const nativePreparedJoinCommitValidatesPlan = !!nativePreparedJoinCommit &&
13006
+ (nativeCommitVerifyHashes && nativeCommitVerifyHashes.length > 0
13007
+ ? nativeCommitVerifyAllHashes
13008
+ ? !!this._nativeBackbone?.graph
13009
+ .commitVerifiedAllPreparedRawReceiveJoinBatch ||
13010
+ !!this._nativeBackbone?.graph
13011
+ .commitVerifiedPreparedRawReceiveJoinBatch
13012
+ : !!this._nativeBackbone?.graph
12961
13013
  .commitVerifiedPreparedRawReceiveJoinBatch
12962
13014
  : !!this._nativeBackbone?.graph
12963
- .commitVerifiedPreparedRawReceiveJoinBatch
12964
- : !!this._nativeBackbone?.graph
12965
- .commitPreparedRawReceiveJoinBatch);
12966
- const trustedLowerLog = this.log;
12967
- // With a program-level onChange consumer the hash-only
12968
- // sink is not used: the lower-log join dispatches the
12969
- // change event (lazy entry views over the prepared raw
12970
- // facts) so per-entry consumers observe every commit.
12971
- const joinOnAppendHashes = programOnChange
12972
- ? undefined
12973
- : onAppendHashes;
12974
- const joinedPreparedFacts = canUsePreparedAppendFacts &&
12975
- (await trustedLowerLog.joinPreparedAppendFactsBatch(preparedAppendFacts, {
12976
- __peerbitEntriesAlreadyMissing: true,
12977
- __peerbitCanAppendAlreadyValidated: true,
12978
- __peerbitDeferIndexWrite: true,
12979
- __peerbitOnAppendHashes: joinOnAppendHashes,
12980
- __peerbitProfile: syncProfile,
12981
- __peerbitNativePreparedJoinCommit: nativePreparedJoinCommit,
12982
- __peerbitNativePreparedJoinCommitValidatesPlan: nativePreparedJoinCommitValidatesPlan,
12983
- __peerbitOnPreparedJoinCommitted: nativePreparedJoinCommit
12984
- ? finishNativePreparedCoordinates
12985
- : undefined,
12986
- }));
12987
- if (!joinedPreparedFacts) {
12988
- await trustedLowerLog.join(materializeAllToMergeEntries(), {
12989
- __peerbitBatchIndependent: true,
12990
- __peerbitEntriesAlreadyMissing: true,
12991
- __peerbitCanAppendAlreadyValidated: fallbackCanAppendAlreadyValidated,
12992
- __peerbitDeferIndexWrite: true,
12993
- __peerbitOnAppendHashes: joinOnAppendHashes,
12994
- __peerbitProfile: syncProfile,
12995
- });
12996
- }
12997
- // A recursive lower-log join can resolve successfully while declining
12998
- // an individual top-level entry (for example, when one of its parents
12999
- // is temporarily unavailable). The public Log.join() API intentionally
13000
- // does not expose that per-entry result, so make local index presence the
13001
- // authority before publishing any SharedLog-side effects. A successful
13002
- // prepared-facts batch is atomic and already proves every input hash.
13003
- const admittedHashes = joinedPreparedFacts
13004
- ? new Set(allToMergeHashes)
13005
- : await this.log.hasMany(allToMergeHashes);
13006
- admittedMergeHashes = admittedHashes;
13007
- const admittedShallowEntries = admittedHashes.size === allToMergeShallowEntries.length
13008
- ? allToMergeShallowEntries
13009
- : allToMergeShallowEntries.filter((entry) => admittedHashes.has(entry.hash));
13010
- if (!joinedPreparedFacts) {
13011
- reusableCoordinatePersistItems =
13012
- reusableCoordinatePersistItems.filter((item) => admittedHashes.has(item.entry.hash));
13013
- coordinatePersistFallbackEntries =
13014
- coordinatePersistFallbackEntries.filter((entry) => admittedHashes.has(entry.hash));
13015
- }
13016
- const reusableCoordinatePersistItemCount = reusableCoordinatePersistItems.length;
13017
- if (syncProfile) {
13018
- emitSyncProfileDuration(syncProfile, lowerLogJoinStartedAt, {
13019
- name: "sharedLog.receive.lowerLogJoin",
13020
- component: "shared-log",
13021
- entries: allToMerge.length,
13022
- messages: 1,
13023
- details: {
13024
- hashOnlyEntryAdded,
13025
- batchHashOnlyEntryAdded,
13026
- programOnChange,
13027
- joinedPreparedFacts,
13028
- admittedEntries: admittedHashes.size,
13029
- nativePreparedCoordinatesFinished,
13030
- },
13031
- });
13032
- }
13033
- const coordinatePersistStartedAt = syncProfileStart(syncProfile);
13034
- if (nativePreparedCoordinatesFinished) {
13035
- // The lower-log prepared receive transaction already finished
13036
- // the native coordinate mirror/journal after entry-index commit.
13037
- }
13038
- else if (nativePreparedCoordinateBatch) {
13039
- try {
13040
- nativeBackboneOnlyPersistedHashes =
13041
- await this._coordinates.finishBackboneOnlyReceiveCoordinateBatch(nativePreparedCoordinateBatch, syncProfile);
13015
+ .commitPreparedRawReceiveJoinBatch);
13016
+ const trustedLowerLog = this.log;
13017
+ // With a program-level onChange consumer the hash-only
13018
+ // sink is not used: the lower-log join dispatches the
13019
+ // change event (lazy entry views over the prepared raw
13020
+ // facts) so per-entry consumers observe every commit.
13021
+ const joinOnAppendHashes = programOnChange
13022
+ ? undefined
13023
+ : onAppendHashes;
13024
+ const joinedPreparedFacts = canUsePreparedAppendFacts &&
13025
+ (await trustedLowerLog.joinPreparedAppendFactsBatch(preparedAppendFacts, {
13026
+ __peerbitEntriesAlreadyMissing: true,
13027
+ __peerbitCanAppendAlreadyValidated: true,
13028
+ __peerbitDeferIndexWrite: true,
13029
+ __peerbitOnAppendHashes: joinOnAppendHashes,
13030
+ __peerbitProfile: syncProfile,
13031
+ __peerbitNativePreparedJoinCommit: nativePreparedJoinCommit,
13032
+ __peerbitNativePreparedJoinCommitValidatesPlan: nativePreparedJoinCommitValidatesPlan,
13033
+ __peerbitOnPreparedJoinCommitted: nativePreparedJoinCommit
13034
+ ? finishNativePreparedCoordinates
13035
+ : undefined,
13036
+ }));
13037
+ if (!joinedPreparedFacts) {
13038
+ await trustedLowerLog.join(materializeAllToMergeEntries(), {
13039
+ __peerbitBatchIndependent: true,
13040
+ __peerbitEntriesAlreadyMissing: true,
13041
+ __peerbitCanAppendAlreadyValidated: fallbackCanAppendAlreadyValidated,
13042
+ __peerbitDeferIndexWrite: true,
13043
+ __peerbitOnAppendHashes: joinOnAppendHashes,
13044
+ __peerbitProfile: syncProfile,
13045
+ });
13042
13046
  }
13043
- catch (error) {
13044
- this._coordinates.rollbackBackboneOnlyReceiveCoordinateBatch(nativePreparedCoordinateBatch);
13045
- throw error;
13047
+ // A recursive lower-log join can resolve successfully while declining
13048
+ // an individual top-level entry (for example, when one of its parents
13049
+ // is temporarily unavailable). The public Log.join() API intentionally
13050
+ // does not expose that per-entry result, so make local index presence the
13051
+ // authority before publishing any SharedLog-side effects. A successful
13052
+ // prepared-facts batch is atomic and already proves every input hash.
13053
+ const admittedHashes = joinedPreparedFacts
13054
+ ? new Set(allToMergeHashes)
13055
+ : await this.log.hasMany(allToMergeHashes);
13056
+ admittedMergeHashes = admittedHashes;
13057
+ const admittedShallowEntries = admittedHashes.size === allToMergeShallowEntries.length
13058
+ ? allToMergeShallowEntries
13059
+ : allToMergeShallowEntries.filter((entry) => admittedHashes.has(entry.hash));
13060
+ if (!joinedPreparedFacts) {
13061
+ reusableCoordinatePersistItems =
13062
+ reusableCoordinatePersistItems.filter((item) => admittedHashes.has(item.entry.hash));
13063
+ coordinatePersistFallbackEntries =
13064
+ coordinatePersistFallbackEntries.filter((entry) => admittedHashes.has(entry.hash));
13046
13065
  }
13047
- }
13048
- else {
13049
- nativeBackboneOnlyPersistedHashes =
13050
- await this._coordinates.persistBackboneOnlyReceiveCoordinateBatch(reusableCoordinatePersistItems);
13051
- }
13052
- if (nativeBackboneOnlyPersistedHashes &&
13053
- nativeBackboneOnlyPersistedHashes.size > 0) {
13054
- for (let i = reusableCoordinatePersistItems.length - 1; i >= 0; i--) {
13055
- if (nativeBackboneOnlyPersistedHashes.has(reusableCoordinatePersistItems[i].entry.hash)) {
13056
- reusableCoordinatePersistItems.splice(i, 1);
13066
+ const reusableCoordinatePersistItemCount = reusableCoordinatePersistItems.length;
13067
+ if (syncProfile) {
13068
+ emitSyncProfileDuration(syncProfile, lowerLogJoinStartedAt, {
13069
+ name: "sharedLog.receive.lowerLogJoin",
13070
+ component: "shared-log",
13071
+ entries: allToMerge.length,
13072
+ messages: 1,
13073
+ details: {
13074
+ hashOnlyEntryAdded,
13075
+ batchHashOnlyEntryAdded,
13076
+ programOnChange,
13077
+ joinedPreparedFacts,
13078
+ admittedEntries: admittedHashes.size,
13079
+ nativePreparedCoordinatesFinished,
13080
+ },
13081
+ });
13082
+ }
13083
+ const coordinatePersistStartedAt = syncProfileStart(syncProfile);
13084
+ if (nativePreparedCoordinatesFinished) {
13085
+ // The lower-log prepared receive transaction already finished
13086
+ // the native coordinate mirror/journal after entry-index commit.
13087
+ }
13088
+ else if (nativePreparedCoordinateBatch) {
13089
+ try {
13090
+ nativeBackboneOnlyPersistedHashes =
13091
+ await this._coordinates.finishBackboneOnlyReceiveCoordinateBatch(nativePreparedCoordinateBatch, syncProfile);
13092
+ }
13093
+ catch (error) {
13094
+ this._coordinates.rollbackBackboneOnlyReceiveCoordinateBatch(nativePreparedCoordinateBatch);
13095
+ throw error;
13057
13096
  }
13058
13097
  }
13059
- }
13060
- if (reusableCoordinatePersistItems.length > 0) {
13061
- await this._coordinates.persistCoordinatesBatch(reusableCoordinatePersistItems);
13062
- }
13063
- if (coordinatePersistFallbackEntries.length > 0) {
13064
- await this.planEntryLeaderBatch(coordinatePersistFallbackEntries.map((entry) => ({
13065
- entry,
13066
- replicas: receiveReplicaCounts.get(entry.hash) ??
13067
- decodeReplicas(entry).getValue(this),
13068
- options: { roleAge: 0, persist: {} },
13069
- })));
13070
- }
13071
- if (syncProfile) {
13072
- emitSyncProfileDuration(syncProfile, coordinatePersistStartedAt, {
13073
- name: "sharedLog.receive.coordinatePersist",
13074
- component: "shared-log",
13075
- entries: entriesToPersist.length,
13076
- messages: 1,
13077
- details: {
13078
- reusedLeaderPlans: reusableCoordinatePersistItemCount,
13079
- nativeBackboneOnly: nativeBackboneOnlyPersistedHashes?.size ?? 0,
13080
- },
13081
- });
13082
- }
13083
- for (const hash of admittedHashes) {
13084
- confirmedHashes.add(hash);
13085
- }
13086
- const checkedPruneStartedAt = syncProfileStart(syncProfile);
13087
- const ownershipChangedDuringReceive = !this.isReceiveOwnershipSnapshotStable(receiveOwnershipRevision);
13088
- if (ownershipChangedDuringReceive) {
13089
- const freshAuditRevision = this._instanceLifecycle?._receiveOwnershipRevision ?? 0;
13090
- const armFreshAuditRetry = () => {
13091
- for (const entry of admittedShallowEntries) {
13092
- this.scheduleCheckedPruneRetry({ entry, leaders: new Map() }, receiveOwnershipLifecycleController);
13098
+ else {
13099
+ nativeBackboneOnlyPersistedHashes =
13100
+ await this._coordinates.persistBackboneOnlyReceiveCoordinateBatch(reusableCoordinatePersistItems);
13101
+ }
13102
+ if (nativeBackboneOnlyPersistedHashes &&
13103
+ nativeBackboneOnlyPersistedHashes.size > 0) {
13104
+ for (let i = reusableCoordinatePersistItems.length - 1; i >= 0; i--) {
13105
+ if (nativeBackboneOnlyPersistedHashes.has(reusableCoordinatePersistItems[i].entry.hash)) {
13106
+ reusableCoordinatePersistItems.splice(i, 1);
13107
+ }
13093
13108
  }
13094
- };
13095
- try {
13109
+ }
13110
+ if (reusableCoordinatePersistItems.length > 0) {
13111
+ await this._coordinates.persistCoordinatesBatch(reusableCoordinatePersistItems);
13112
+ }
13113
+ if (coordinatePersistFallbackEntries.length > 0) {
13114
+ await this.planEntryLeaderBatch(coordinatePersistFallbackEntries.map((entry) => ({
13115
+ entry,
13116
+ replicas: receiveReplicaCounts.get(entry.hash) ??
13117
+ decodeReplicas(entry).getValue(this),
13118
+ options: { roleAge: 0, persist: {} },
13119
+ })));
13120
+ }
13121
+ if (syncProfile) {
13122
+ emitSyncProfileDuration(syncProfile, coordinatePersistStartedAt, {
13123
+ name: "sharedLog.receive.coordinatePersist",
13124
+ component: "shared-log",
13125
+ entries: entriesToPersist.length,
13126
+ messages: 1,
13127
+ details: {
13128
+ reusedLeaderPlans: reusableCoordinatePersistItemCount,
13129
+ nativeBackboneOnly: nativeBackboneOnlyPersistedHashes?.size ?? 0,
13130
+ },
13131
+ });
13132
+ }
13133
+ for (const hash of admittedHashes) {
13134
+ confirmedHashes.add(hash);
13135
+ }
13136
+ const checkedPruneStartedAt = syncProfileStart(syncProfile);
13137
+ const ownershipChangedDuringReceive = !this.isReceiveOwnershipSnapshotStable(receiveOwnershipRevision);
13138
+ if (ownershipChangedDuringReceive) {
13139
+ const freshAuditRevision = this._instanceLifecycle?._receiveOwnershipRevision ?? 0;
13140
+ const armFreshAuditRetry = () => {
13141
+ for (const entry of admittedShallowEntries) {
13142
+ this.scheduleCheckedPruneRetry({ entry, leaders: new Map() }, receiveOwnershipLifecycleController);
13143
+ }
13144
+ };
13145
+ try {
13146
+ await this.pruneJoinedEntriesNoLongerLed(admittedShallowEntries, {
13147
+ decodedReplicaCounts: receiveReplicaCounts,
13148
+ freshReceiveOwnerAudit: true,
13149
+ preserveExistingPruneOnLocalResult: true,
13150
+ profile: syncProfile,
13151
+ }, receiveOwnershipLifecycleController);
13152
+ this.throwIfReplicationOwnershipLifecycleInactive(receiveOwnershipLifecycleController);
13153
+ if (!this.isReceiveOwnershipSnapshotStable(freshAuditRevision)) {
13154
+ armFreshAuditRetry();
13155
+ }
13156
+ }
13157
+ catch {
13158
+ // The lower-log and coordinate commits are already durable. A
13159
+ // sender retry will filter these hashes as present, so retain a
13160
+ // bounded local obligation instead of failing the admitted receive.
13161
+ this.throwIfReplicationOwnershipLifecycleInactive(receiveOwnershipLifecycleController);
13162
+ armFreshAuditRetry();
13163
+ }
13164
+ }
13165
+ else {
13096
13166
  await this.pruneJoinedEntriesNoLongerLed(admittedShallowEntries, {
13097
13167
  decodedReplicaCounts: receiveReplicaCounts,
13098
- freshReceiveOwnerAudit: true,
13099
13168
  preserveExistingPruneOnLocalResult: true,
13169
+ reusableLeaderPlans: reusableCoordinatePlans,
13100
13170
  profile: syncProfile,
13101
13171
  }, receiveOwnershipLifecycleController);
13102
- this.throwIfReplicationOwnershipLifecycleInactive(receiveOwnershipLifecycleController);
13103
- if (!this.isReceiveOwnershipSnapshotStable(freshAuditRevision)) {
13104
- armFreshAuditRetry();
13105
- }
13106
13172
  }
13107
- catch {
13108
- // The lower-log and coordinate commits are already durable. A
13109
- // sender retry will filter these hashes as present, so retain a
13110
- // bounded local obligation instead of failing the admitted receive.
13111
- this.throwIfReplicationOwnershipLifecycleInactive(receiveOwnershipLifecycleController);
13112
- armFreshAuditRetry();
13173
+ if (syncProfile) {
13174
+ emitSyncProfileDuration(syncProfile, checkedPruneStartedAt, {
13175
+ name: "sharedLog.receive.checkedPrune",
13176
+ component: "shared-log",
13177
+ entries: allToMerge.length,
13178
+ messages: 1,
13179
+ });
13113
13180
  }
13181
+ for (const plan of joinPlans) {
13182
+ plan.toDelete
13183
+ ?.filter((entry) => admittedMergeHashes.has(entry.hash))
13184
+ .map((entry) => this.pruneDebouncedFnAddIfNotKeeping({
13185
+ key: entry.hash,
13186
+ value: {
13187
+ entry,
13188
+ leaders: plan.leaders,
13189
+ },
13190
+ }));
13191
+ }
13192
+ this.rebalanceParticipationDebounced?.call();
13114
13193
  }
13115
- else {
13116
- await this.pruneJoinedEntriesNoLongerLed(admittedShallowEntries, {
13117
- decodedReplicaCounts: receiveReplicaCounts,
13118
- preserveExistingPruneOnLocalResult: true,
13119
- reusableLeaderPlans: reusableCoordinatePlans,
13120
- profile: syncProfile,
13121
- }, receiveOwnershipLifecycleController);
13122
- }
13123
- if (syncProfile) {
13124
- emitSyncProfileDuration(syncProfile, checkedPruneStartedAt, {
13125
- name: "sharedLog.receive.checkedPrune",
13126
- component: "shared-log",
13127
- entries: allToMerge.length,
13128
- messages: 1,
13129
- });
13130
- }
13131
- for (const plan of joinPlans) {
13132
- plan.toDelete
13133
- ?.filter((entry) => admittedMergeHashes.has(entry.hash))
13134
- .map((entry) => this.pruneDebouncedFnAddIfNotKeeping({
13135
- key: entry.hash,
13136
- value: {
13137
- entry,
13138
- leaders: plan.leaders,
13139
- },
13140
- }));
13194
+ finally {
13195
+ // Settle seam for the receive token. Every consumer that can
13196
+ // roll it back runs inline before control leaves this block: the
13197
+ // prepared-join callback resolves during the join await, and the
13198
+ // late finish/rollback arm runs above. This `finally` is what
13199
+ // closes the abandon arms (no prepared-join commit, a declined
13200
+ // native commit, a downgrade to the plain join) without having
13201
+ // to enumerate them.
13202
+ this._coordinates.settleResidentCoordinateSnapshot(nativeReceiveCoordinateBatch?.rollbackCoordinateEntries);
13141
13203
  }
13142
- this.rebalanceParticipationDebounced?.call();
13143
13204
  }
13144
13205
  for (const plan of joinPlans) {
13145
13206
  if (!plan.maybeDelete) {