From 3d38c5a1015c237c43cf7e944eb12b7da8de01b8 Mon Sep 17 00:00:00 2001 From: Jussi Saurio Date: Thu, 25 Sep 2025 10:47:06 +0300 Subject: [PATCH] mvcc: disallow promote to exclusive tx if another tx committed in between --- core/mvcc/database/mod.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/core/mvcc/database/mod.rs b/core/mvcc/database/mod.rs index 8305a9088..d992894d1 100644 --- a/core/mvcc/database/mod.rs +++ b/core/mvcc/database/mod.rs @@ -609,6 +609,9 @@ impl StateTransition for CommitStateMachine { .write() .replace(*tx_unlocked.header.read()); + mvcc_store + .last_committed_tx_ts + .store(*end_ts, Ordering::Release); if self.did_commit_schema_change { mvcc_store .last_committed_schema_change_ts @@ -847,6 +850,10 @@ pub struct MvStore { /// The timestamp of the last committed schema change. /// Schema changes always cause a [SchemaUpdated] error. last_committed_schema_change_ts: AtomicU64, + /// The timestamp of the last committed transaction. + /// If there are two concurrent BEGIN (non-CONCURRENT) transactions, and one tries to promote + /// to exclusive, it will abort if another transaction committed after its begin timestamp. + last_committed_tx_ts: AtomicU64, } impl MvStore { @@ -869,6 +876,7 @@ impl MvStore { blocking_checkpoint_lock: Arc::new(TursoRwLock::new()), checkpointed_txid_max: AtomicU64::new(0), last_committed_schema_change_ts: AtomicU64::new(0), + last_committed_tx_ts: AtomicU64::new(0), } } @@ -1386,6 +1394,14 @@ impl MvStore { /// Acquires the exclusive transaction lock to the given transaction ID. fn acquire_exclusive_tx(&self, tx_id: &TxID) -> Result<()> { + if let Some(tx) = self.txs.get(tx_id) { + let tx = tx.value(); + if tx.begin_ts < self.last_committed_tx_ts.load(Ordering::Acquire) { + // Another transaction committed after this transaction's begin timestamp, do not allow exclusive lock. + // This mimics regular (non-CONCURRENT) sqlite transaction behavior. + return Err(LimboError::Busy); + } + } let mut exclusive_tx = self.exclusive_tx.write(); if exclusive_tx.is_some() { // Another transaction already holds the exclusive lock