Skip to content

Commit

Permalink
Make transaction name conflict check more robust (facebook#12895)
Browse files Browse the repository at this point in the history
Summary:
The `PessimisticTransaction::SetName()` code checks for an existing txn of the given name before registering the new txn. However, this is not atomic, which could result in a race condition if two txns try to register with the same name. Both might succeed and lead to unpredictable behavior. This PR makes the test and set atomic.

Pull Request resolved: facebook#12895

Reviewed By: pdillinger

Differential Revision: D60460482

Pulled By: anand1976

fbshipit-source-id: e8afeb2356e1b8f4e8df785cb73532739f82579d
  • Loading branch information
anand1976 authored and facebook-github-bot committed Jul 30, 2024
1 parent 9058fd0 commit 55877d8
Show file tree
Hide file tree
Showing 4 changed files with 17 additions and 7 deletions.
1 change: 1 addition & 0 deletions unreleased_history/bug_fixes/prevent_duplicate_txn_name.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix a race condition in pessimistic transactions that could allow multiple transactions with the same name to be registered simultaneously, resulting in a crash or other unpredictable behavior.
7 changes: 4 additions & 3 deletions utilities/transactions/pessimistic_transaction.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1247,14 +1247,15 @@ Status PessimisticTransaction::SetName(const TransactionName& name) {
if (txn_state_ == STARTED) {
if (name_.length()) {
s = Status::InvalidArgument("Transaction has already been named.");
} else if (txn_db_impl_->GetTransactionByName(name) != nullptr) {
s = Status::InvalidArgument("Transaction name must be unique.");
} else if (name.length() < 1 || name.length() > 512) {
s = Status::InvalidArgument(
"Transaction name length must be between 1 and 512 chars.");
} else {
name_ = name;
txn_db_impl_->RegisterTransaction(this);
s = txn_db_impl_->RegisterTransaction(this);
if (!s.ok()) {
name_.clear();
}
}
} else {
s = Status::InvalidArgument("Transaction is beyond state for naming.");
Expand Down
13 changes: 10 additions & 3 deletions utilities/transactions/pessimistic_transaction_db.cc
Original file line number Diff line number Diff line change
Expand Up @@ -723,6 +723,11 @@ void PessimisticTransactionDB::ReinitializeTransaction(
Transaction* PessimisticTransactionDB::GetTransactionByName(
const TransactionName& name) {
std::lock_guard<std::mutex> lock(name_map_mutex_);
return GetTransactionByNameLocked(name);
}

Transaction* PessimisticTransactionDB::GetTransactionByNameLocked(
const TransactionName& name) {
auto it = transactions_.find(name);
if (it == transactions_.end()) {
return nullptr;
Expand Down Expand Up @@ -755,13 +760,15 @@ void PessimisticTransactionDB::SetDeadlockInfoBufferSize(uint32_t target_size) {
lock_manager_->Resize(target_size);
}

void PessimisticTransactionDB::RegisterTransaction(Transaction* txn) {
Status PessimisticTransactionDB::RegisterTransaction(Transaction* txn) {
assert(txn);
assert(txn->GetName().length() > 0);
assert(GetTransactionByName(txn->GetName()) == nullptr);
assert(txn->GetState() == Transaction::STARTED);
std::lock_guard<std::mutex> lock(name_map_mutex_);
transactions_[txn->GetName()] = txn;
if (!transactions_.insert({txn->GetName(), txn}).second) {
return Status::InvalidArgument("Duplicate txn name " + txn->GetName());
}
return Status::OK();
}

void PessimisticTransactionDB::UnregisterTransaction(Transaction* txn) {
Expand Down
3 changes: 2 additions & 1 deletion utilities/transactions/pessimistic_transaction_db.h
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ class PessimisticTransactionDB : public TransactionDB {

Transaction* GetTransactionByName(const TransactionName& name) override;

void RegisterTransaction(Transaction* txn);
Status RegisterTransaction(Transaction* txn);
void UnregisterTransaction(Transaction* txn);

// not thread safe. current use case is during recovery (single thread)
Expand Down Expand Up @@ -239,6 +239,7 @@ class PessimisticTransactionDB : public TransactionDB {
friend class WriteUnpreparedTransactionTest_MarkLogWithPrepSection_Test;

Transaction* BeginInternalTransaction(const WriteOptions& options);
Transaction* GetTransactionByNameLocked(const TransactionName& name);

std::shared_ptr<LockManager> lock_manager_;

Expand Down

0 comments on commit 55877d8

Please sign in to comment.