Make users explicitly be aware of prepare before commit (#6775)

Summary:
In current commit protocol of pessimistic transaction, if the transaction is not prepared before commit, the commit protocol implicitly assumes that the user wants to commit without prepare.

This PR adds TransactionOptions::skip_prepare, the default value is `true` because if set to `false`, all existing users who commit without prepare need to update their code to set skip_prepare to true. Although this does not force the user to explicitly express their intention of skip_prepare, it at least lets the user be aware of the assumption of being able to commit without prepare.
Pull Request resolved: https://github.com/facebook/rocksdb/pull/6775

Test Plan: added a new unit test TransactionTest::CommitWithoutPrepare

Reviewed By: lth

Differential Revision: D21313270

Pulled By: cheng-chang

fbshipit-source-id: 3d95b7c9b2d6cdddc09bdd66c561bc4fae8c3251
This commit is contained in:
Cheng Chang 2020-04-30 16:21:05 -07:00 committed by Facebook GitHub Bot
parent 079e50d2ba
commit ef0c3eda27
6 changed files with 51 additions and 5 deletions

View File

@ -79,6 +79,7 @@ class Status {
KMergeOperandsInsufficientCapacity = 10,
kManualCompactionPaused = 11,
kOverwritten = 12,
kTxnNotPrepared = 13,
kMaxSubCode
};
@ -224,6 +225,13 @@ class Status {
return Status(kIOError, kPathNotFound, msg, msg2);
}
static Status TxnNotPrepared() {
return Status(kInvalidArgument, kTxnNotPrepared);
}
static Status TxnNotPrepared(const Slice& msg, const Slice& msg2 = Slice()) {
return Status(kInvalidArgument, kTxnNotPrepared, msg, msg2);
}
// Returns true iff the status indicates success.
bool ok() const { return code() == kOk; }
@ -315,6 +323,11 @@ class Status {
return (code() == kIncomplete) && (subcode() == kManualCompactionPaused);
}
// Returns true iff the status indicates a TxnNotPrepared error.
bool IsTxnNotPrepared() const {
return (code() == kInvalidArgument) && (subcode() == kTxnNotPrepared);
}
// Return a string representation of this status suitable for printing.
// Returns the string "OK" for success.
std::string ToString() const;

View File

@ -139,7 +139,9 @@ class Transaction {
//
// If this transaction was created by a TransactionDB(), Status::Expired()
// may be returned if this transaction has lived for longer than
// TransactionOptions.expiration.
// TransactionOptions.expiration. Status::TxnNotPrepared() may be returned if
// TransactionOptions.skip_prepare is false and Prepare is not called on this
// transaction before Commit.
virtual Status Commit() = 0;
// Discard all batched writes in this transaction.

View File

@ -172,6 +172,10 @@ struct TransactionOptions {
// Default: false
bool skip_concurrency_control = false;
// In pessimistic transaction, if this is true, then you can skip Prepare
// before Commit, otherwise, you must Prepare before Commit.
bool skip_prepare = true;
// See TransactionDBOptions::default_write_batch_flush_threshold for
// description. If a negative value is specified, then the default value from
// TransactionDBOptions is used.

View File

@ -87,6 +87,7 @@ void PessimisticTransaction::Initialize(const TransactionOptions& txn_options) {
}
use_only_the_last_commit_time_batch_for_recovery_ =
txn_options.use_only_the_last_commit_time_batch_for_recovery;
skip_prepare_ = txn_options.skip_prepare;
}
PessimisticTransaction::~PessimisticTransaction() {
@ -283,10 +284,11 @@ Status PessimisticTransaction::Commit() {
commit_prepared = true;
} else if (txn_state_ == STARTED) {
// expiration and lock stealing is not a concern
commit_without_prepare = true;
// TODO(myabandeh): what if the user mistakenly forgets prepare? We should
// add an option so that the user explictly express the intention of
// skipping the prepare phase.
if (skip_prepare_) {
commit_without_prepare = true;
} else {
return Status::TxnNotPrepared();
}
}
if (commit_without_prepare) {

View File

@ -120,6 +120,9 @@ class PessimisticTransaction : public TransactionBaseImpl {
// Refer to
// TransactionOptions::use_only_the_last_commit_time_batch_for_recovery
bool use_only_the_last_commit_time_batch_for_recovery_ = false;
// Refer to
// TransactionOptions::skip_prepare
bool skip_prepare_ = false;
virtual Status PrepareInternal() = 0;

View File

@ -6205,6 +6205,28 @@ TEST_P(TransactionTest, DoubleCrashInRecovery) {
}
}
TEST_P(TransactionTest, CommitWithoutPrepare) {
{
// skip_prepare = false.
WriteOptions write_options;
TransactionOptions txn_options;
txn_options.skip_prepare = false;
Transaction* txn = db->BeginTransaction(write_options, txn_options);
ASSERT_TRUE(txn->Commit().IsTxnNotPrepared());
delete txn;
}
{
// skip_prepare = true.
WriteOptions write_options;
TransactionOptions txn_options;
txn_options.skip_prepare = true;
Transaction* txn = db->BeginTransaction(write_options, txn_options);
ASSERT_OK(txn->Commit());
delete txn;
}
}
} // namespace ROCKSDB_NAMESPACE
int main(int argc, char** argv) {