|
|
@ -55,6 +55,13 @@ class TransactionTest : public testing::Test { |
|
|
|
DestroyDB(dbname, options); |
|
|
|
DestroyDB(dbname, options); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Status ReOpenNoDelete() { |
|
|
|
|
|
|
|
delete db; |
|
|
|
|
|
|
|
db = nullptr; |
|
|
|
|
|
|
|
Status s = TransactionDB::Open(options, txn_db_options, dbname, &db); |
|
|
|
|
|
|
|
return s; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
Status ReOpen() { |
|
|
|
Status ReOpen() { |
|
|
|
delete db; |
|
|
|
delete db; |
|
|
|
DestroyDB(dbname, options); |
|
|
|
DestroyDB(dbname, options); |
|
|
@ -113,6 +120,635 @@ TEST_F(TransactionTest, SuccessTest) { |
|
|
|
delete txn; |
|
|
|
delete txn; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
TEST_F(TransactionTest, CommitTimeBatchFailTest) { |
|
|
|
|
|
|
|
WriteOptions write_options; |
|
|
|
|
|
|
|
TransactionOptions txn_options; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
string value; |
|
|
|
|
|
|
|
Status s; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Transaction* txn1 = db->BeginTransaction(write_options, txn_options); |
|
|
|
|
|
|
|
ASSERT_TRUE(txn1); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
txn1->GetCommitTimeWriteBatch()->Put("cat", "dog"); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
s = txn1->Put("foo", "bar"); |
|
|
|
|
|
|
|
ASSERT_OK(s); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// fails due to non-empty commit-time batch
|
|
|
|
|
|
|
|
s = txn1->Commit(); |
|
|
|
|
|
|
|
ASSERT_EQ(s, Status::InvalidArgument()); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
delete txn1; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
TEST_F(TransactionTest, SimpleTwoPhaseTransactionTest) { |
|
|
|
|
|
|
|
WriteOptions write_options; |
|
|
|
|
|
|
|
ReadOptions read_options; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
TransactionOptions txn_options; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
string value; |
|
|
|
|
|
|
|
Status s; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
DBImpl* db_impl = reinterpret_cast<DBImpl*>(db->GetRootDB()); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Transaction* txn = db->BeginTransaction(write_options, txn_options); |
|
|
|
|
|
|
|
s = txn->SetName("xid"); |
|
|
|
|
|
|
|
ASSERT_OK(s); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ASSERT_EQ(db->GetTransactionByName("xid"), txn); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// transaction put
|
|
|
|
|
|
|
|
s = txn->Put(Slice("foo"), Slice("bar")); |
|
|
|
|
|
|
|
ASSERT_OK(s); |
|
|
|
|
|
|
|
ASSERT_EQ(1, txn->GetNumPuts()); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// regular db put
|
|
|
|
|
|
|
|
s = db->Put(write_options, Slice("foo2"), Slice("bar2")); |
|
|
|
|
|
|
|
ASSERT_OK(s); |
|
|
|
|
|
|
|
ASSERT_EQ(1, txn->GetNumPuts()); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// regular db read
|
|
|
|
|
|
|
|
db->Get(read_options, "foo2", &value); |
|
|
|
|
|
|
|
ASSERT_EQ(value, "bar2"); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// commit time put
|
|
|
|
|
|
|
|
txn->GetCommitTimeWriteBatch()->Put(Slice("gtid"), Slice("dogs")); |
|
|
|
|
|
|
|
txn->GetCommitTimeWriteBatch()->Put(Slice("gtid2"), Slice("cats")); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// nothing has been prepped yet
|
|
|
|
|
|
|
|
ASSERT_EQ(db_impl->TEST_FindMinLogContainingOutstandingPrep(), 0); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
s = txn->Prepare(); |
|
|
|
|
|
|
|
ASSERT_OK(s); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// data not im mem yet
|
|
|
|
|
|
|
|
s = db->Get(read_options, Slice("foo"), &value); |
|
|
|
|
|
|
|
ASSERT_TRUE(s.IsNotFound()); |
|
|
|
|
|
|
|
s = db->Get(read_options, Slice("gtid"), &value); |
|
|
|
|
|
|
|
ASSERT_TRUE(s.IsNotFound()); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// find trans in list of prepared transactions
|
|
|
|
|
|
|
|
std::vector<Transaction*> prepared_trans; |
|
|
|
|
|
|
|
db->GetAllPreparedTransactions(&prepared_trans); |
|
|
|
|
|
|
|
ASSERT_EQ(prepared_trans.size(), 1); |
|
|
|
|
|
|
|
ASSERT_EQ(prepared_trans.front()->GetName(), "xid"); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
auto log_containing_prep = |
|
|
|
|
|
|
|
db_impl->TEST_FindMinLogContainingOutstandingPrep(); |
|
|
|
|
|
|
|
ASSERT_GT(log_containing_prep, 0); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// make commit
|
|
|
|
|
|
|
|
s = txn->Commit(); |
|
|
|
|
|
|
|
ASSERT_OK(s); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// value is now available
|
|
|
|
|
|
|
|
s = db->Get(read_options, "foo", &value); |
|
|
|
|
|
|
|
ASSERT_OK(s); |
|
|
|
|
|
|
|
ASSERT_EQ(value, "bar"); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
s = db->Get(read_options, "gtid", &value); |
|
|
|
|
|
|
|
ASSERT_OK(s); |
|
|
|
|
|
|
|
ASSERT_EQ(value, "dogs"); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
s = db->Get(read_options, "gtid2", &value); |
|
|
|
|
|
|
|
ASSERT_OK(s); |
|
|
|
|
|
|
|
ASSERT_EQ(value, "cats"); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// we already committed
|
|
|
|
|
|
|
|
s = txn->Commit(); |
|
|
|
|
|
|
|
ASSERT_EQ(s, Status::InvalidArgument()); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// no longer is prpared results
|
|
|
|
|
|
|
|
db->GetAllPreparedTransactions(&prepared_trans); |
|
|
|
|
|
|
|
ASSERT_EQ(prepared_trans.size(), 0); |
|
|
|
|
|
|
|
ASSERT_EQ(db->GetTransactionByName("xid"), nullptr); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// heap should not care about prepared section anymore
|
|
|
|
|
|
|
|
ASSERT_EQ(db_impl->TEST_FindMinLogContainingOutstandingPrep(), 0); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// but now our memtable should be referencing the prep section
|
|
|
|
|
|
|
|
ASSERT_EQ(log_containing_prep, |
|
|
|
|
|
|
|
db_impl->TEST_FindMinPrepLogReferencedByMemTable()); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
db_impl->TEST_FlushMemTable(true); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// after memtable flush we can now relese the log
|
|
|
|
|
|
|
|
ASSERT_EQ(0, db_impl->TEST_FindMinPrepLogReferencedByMemTable()); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
delete txn; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
TEST_F(TransactionTest, TwoPhaseNameTest) { |
|
|
|
|
|
|
|
Status s; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
WriteOptions write_options; |
|
|
|
|
|
|
|
TransactionOptions txn_options; |
|
|
|
|
|
|
|
Transaction* txn1 = db->BeginTransaction(write_options, txn_options); |
|
|
|
|
|
|
|
Transaction* txn2 = db->BeginTransaction(write_options, txn_options); |
|
|
|
|
|
|
|
Transaction* txn3 = db->BeginTransaction(write_options, txn_options); |
|
|
|
|
|
|
|
ASSERT_TRUE(txn3); |
|
|
|
|
|
|
|
delete txn3; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// cant prepare txn without name
|
|
|
|
|
|
|
|
s = txn1->Prepare(); |
|
|
|
|
|
|
|
ASSERT_EQ(s, Status::InvalidArgument()); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// name too short
|
|
|
|
|
|
|
|
s = txn1->SetName(""); |
|
|
|
|
|
|
|
ASSERT_EQ(s, Status::InvalidArgument()); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// name too long
|
|
|
|
|
|
|
|
s = txn1->SetName(std::string(513, 'x')); |
|
|
|
|
|
|
|
ASSERT_EQ(s, Status::InvalidArgument()); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// valid set name
|
|
|
|
|
|
|
|
s = txn1->SetName("name1"); |
|
|
|
|
|
|
|
ASSERT_OK(s); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// cant have duplicate name
|
|
|
|
|
|
|
|
s = txn2->SetName("name1"); |
|
|
|
|
|
|
|
ASSERT_EQ(s, Status::InvalidArgument()); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// shouldn't be able to prepare
|
|
|
|
|
|
|
|
s = txn2->Prepare(); |
|
|
|
|
|
|
|
ASSERT_EQ(s, Status::InvalidArgument()); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// valid name set
|
|
|
|
|
|
|
|
s = txn2->SetName("name2"); |
|
|
|
|
|
|
|
ASSERT_OK(s); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// cant reset name
|
|
|
|
|
|
|
|
s = txn2->SetName("name3"); |
|
|
|
|
|
|
|
ASSERT_EQ(s, Status::InvalidArgument()); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ASSERT_EQ(txn1->GetName(), "name1"); |
|
|
|
|
|
|
|
ASSERT_EQ(txn2->GetName(), "name2"); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
s = txn1->Prepare(); |
|
|
|
|
|
|
|
ASSERT_OK(s); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// can't rename after prepare
|
|
|
|
|
|
|
|
s = txn1->SetName("name4"); |
|
|
|
|
|
|
|
ASSERT_EQ(s, Status::InvalidArgument()); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
delete txn1; |
|
|
|
|
|
|
|
delete txn2; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
TEST_F(TransactionTest, TwoPhaseEmptyWriteTest) { |
|
|
|
|
|
|
|
Status s; |
|
|
|
|
|
|
|
std::string value; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
WriteOptions write_options; |
|
|
|
|
|
|
|
ReadOptions read_options; |
|
|
|
|
|
|
|
TransactionOptions txn_options; |
|
|
|
|
|
|
|
Transaction* txn1 = db->BeginTransaction(write_options, txn_options); |
|
|
|
|
|
|
|
ASSERT_TRUE(txn1); |
|
|
|
|
|
|
|
Transaction* txn2 = db->BeginTransaction(write_options, txn_options); |
|
|
|
|
|
|
|
ASSERT_TRUE(txn2); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
s = txn1->SetName("joe"); |
|
|
|
|
|
|
|
ASSERT_OK(s); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
s = txn2->SetName("bob"); |
|
|
|
|
|
|
|
ASSERT_OK(s); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
s = txn1->Prepare(); |
|
|
|
|
|
|
|
ASSERT_OK(s); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
s = txn1->Commit(); |
|
|
|
|
|
|
|
ASSERT_OK(s); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
delete txn1; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
txn2->GetCommitTimeWriteBatch()->Put(Slice("foo"), Slice("bar")); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
s = txn2->Prepare(); |
|
|
|
|
|
|
|
ASSERT_OK(s); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
s = txn2->Commit(); |
|
|
|
|
|
|
|
ASSERT_OK(s); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
s = db->Get(read_options, "foo", &value); |
|
|
|
|
|
|
|
ASSERT_OK(s); |
|
|
|
|
|
|
|
ASSERT_EQ(value, "bar"); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
delete txn2; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
TEST_F(TransactionTest, TwoPhaseExpirationTest) { |
|
|
|
|
|
|
|
Status s; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
WriteOptions write_options; |
|
|
|
|
|
|
|
TransactionOptions txn_options; |
|
|
|
|
|
|
|
txn_options.expiration = 500; // 500ms
|
|
|
|
|
|
|
|
Transaction* txn1 = db->BeginTransaction(write_options, txn_options); |
|
|
|
|
|
|
|
Transaction* txn2 = db->BeginTransaction(write_options, txn_options); |
|
|
|
|
|
|
|
ASSERT_TRUE(txn1); |
|
|
|
|
|
|
|
ASSERT_TRUE(txn1); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
s = txn1->SetName("joe"); |
|
|
|
|
|
|
|
ASSERT_OK(s); |
|
|
|
|
|
|
|
s = txn2->SetName("bob"); |
|
|
|
|
|
|
|
ASSERT_OK(s); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
s = txn1->Prepare(); |
|
|
|
|
|
|
|
ASSERT_OK(s); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* sleep override */ |
|
|
|
|
|
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(1000)); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
s = txn1->Commit(); |
|
|
|
|
|
|
|
ASSERT_OK(s); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
s = txn2->Prepare(); |
|
|
|
|
|
|
|
ASSERT_EQ(s, Status::Expired()); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
delete txn1; |
|
|
|
|
|
|
|
delete txn2; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
TEST_F(TransactionTest, TwoPhaseRollbackTest) { |
|
|
|
|
|
|
|
WriteOptions write_options; |
|
|
|
|
|
|
|
ReadOptions read_options; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
TransactionOptions txn_options; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
string value; |
|
|
|
|
|
|
|
Status s; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
DBImpl* db_impl = reinterpret_cast<DBImpl*>(db->GetRootDB()); |
|
|
|
|
|
|
|
Transaction* txn = db->BeginTransaction(write_options, txn_options); |
|
|
|
|
|
|
|
s = txn->SetName("xid"); |
|
|
|
|
|
|
|
ASSERT_OK(s); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// transaction put
|
|
|
|
|
|
|
|
s = txn->Put(Slice("tfoo"), Slice("tbar")); |
|
|
|
|
|
|
|
ASSERT_OK(s); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// value is readable form txn
|
|
|
|
|
|
|
|
s = txn->Get(read_options, Slice("tfoo"), &value); |
|
|
|
|
|
|
|
ASSERT_OK(s); |
|
|
|
|
|
|
|
ASSERT_EQ(value, "tbar"); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// issue rollback
|
|
|
|
|
|
|
|
s = txn->Rollback(); |
|
|
|
|
|
|
|
ASSERT_OK(s); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// value is nolonger readable
|
|
|
|
|
|
|
|
s = txn->Get(read_options, Slice("tfoo"), &value); |
|
|
|
|
|
|
|
ASSERT_TRUE(s.IsNotFound()); |
|
|
|
|
|
|
|
ASSERT_EQ(txn->GetNumPuts(), 0); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// put new txn values
|
|
|
|
|
|
|
|
s = txn->Put(Slice("tfoo2"), Slice("tbar2")); |
|
|
|
|
|
|
|
ASSERT_OK(s); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// new value is readable from txn
|
|
|
|
|
|
|
|
s = txn->Get(read_options, Slice("tfoo2"), &value); |
|
|
|
|
|
|
|
ASSERT_OK(s); |
|
|
|
|
|
|
|
ASSERT_EQ(value, "tbar2"); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
s = txn->Prepare(); |
|
|
|
|
|
|
|
ASSERT_OK(s); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// flush to next wal
|
|
|
|
|
|
|
|
s = db->Put(write_options, Slice("foo"), Slice("bar")); |
|
|
|
|
|
|
|
ASSERT_OK(s); |
|
|
|
|
|
|
|
db_impl->TEST_FlushMemTable(true); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// issue rollback (marker written to WAL)
|
|
|
|
|
|
|
|
s = txn->Rollback(); |
|
|
|
|
|
|
|
ASSERT_OK(s); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// value is nolonger readable
|
|
|
|
|
|
|
|
s = txn->Get(read_options, Slice("tfoo2"), &value); |
|
|
|
|
|
|
|
ASSERT_TRUE(s.IsNotFound()); |
|
|
|
|
|
|
|
ASSERT_EQ(txn->GetNumPuts(), 0); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// make commit
|
|
|
|
|
|
|
|
s = txn->Commit(); |
|
|
|
|
|
|
|
ASSERT_EQ(s, Status::InvalidArgument()); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// try rollback again
|
|
|
|
|
|
|
|
s = txn->Rollback(); |
|
|
|
|
|
|
|
ASSERT_EQ(s, Status::InvalidArgument()); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
TEST_F(TransactionTest, PersistentTwoPhaseTransactionTest) { |
|
|
|
|
|
|
|
WriteOptions write_options; |
|
|
|
|
|
|
|
write_options.sync = true; |
|
|
|
|
|
|
|
write_options.disableWAL = false; |
|
|
|
|
|
|
|
ReadOptions read_options; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
TransactionOptions txn_options; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
string value; |
|
|
|
|
|
|
|
Status s; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
DBImpl* db_impl = reinterpret_cast<DBImpl*>(db->GetRootDB()); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Transaction* txn = db->BeginTransaction(write_options, txn_options); |
|
|
|
|
|
|
|
s = txn->SetName("xid"); |
|
|
|
|
|
|
|
ASSERT_OK(s); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ASSERT_EQ(db->GetTransactionByName("xid"), txn); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// transaction put
|
|
|
|
|
|
|
|
s = txn->Put(Slice("foo"), Slice("bar")); |
|
|
|
|
|
|
|
ASSERT_OK(s); |
|
|
|
|
|
|
|
ASSERT_EQ(1, txn->GetNumPuts()); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// txn read
|
|
|
|
|
|
|
|
s = txn->Get(read_options, "foo", &value); |
|
|
|
|
|
|
|
ASSERT_OK(s); |
|
|
|
|
|
|
|
ASSERT_EQ(value, "bar"); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// regular db put
|
|
|
|
|
|
|
|
s = db->Put(write_options, Slice("foo2"), Slice("bar2")); |
|
|
|
|
|
|
|
ASSERT_OK(s); |
|
|
|
|
|
|
|
ASSERT_EQ(1, txn->GetNumPuts()); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
db_impl->TEST_FlushMemTable(true); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// regular db read
|
|
|
|
|
|
|
|
db->Get(read_options, "foo2", &value); |
|
|
|
|
|
|
|
ASSERT_EQ(value, "bar2"); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// nothing has been prepped yet
|
|
|
|
|
|
|
|
ASSERT_EQ(db_impl->TEST_FindMinLogContainingOutstandingPrep(), 0); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// prepare
|
|
|
|
|
|
|
|
s = txn->Prepare(); |
|
|
|
|
|
|
|
ASSERT_OK(s); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// still not available to db
|
|
|
|
|
|
|
|
s = db->Get(read_options, Slice("foo"), &value); |
|
|
|
|
|
|
|
ASSERT_TRUE(s.IsNotFound()); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// kill and reopen
|
|
|
|
|
|
|
|
s = ReOpenNoDelete(); |
|
|
|
|
|
|
|
ASSERT_OK(s); |
|
|
|
|
|
|
|
db_impl = reinterpret_cast<DBImpl*>(db->GetRootDB()); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// find trans in list of prepared transactions
|
|
|
|
|
|
|
|
std::vector<Transaction*> prepared_trans; |
|
|
|
|
|
|
|
db->GetAllPreparedTransactions(&prepared_trans); |
|
|
|
|
|
|
|
ASSERT_EQ(prepared_trans.size(), 1); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
txn = prepared_trans.front(); |
|
|
|
|
|
|
|
ASSERT_TRUE(txn); |
|
|
|
|
|
|
|
ASSERT_EQ(txn->GetName(), "xid"); |
|
|
|
|
|
|
|
ASSERT_EQ(db->GetTransactionByName("xid"), txn); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// log has been marked
|
|
|
|
|
|
|
|
auto log_containing_prep = |
|
|
|
|
|
|
|
db_impl->TEST_FindMinLogContainingOutstandingPrep(); |
|
|
|
|
|
|
|
ASSERT_GT(log_containing_prep, 0); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// value is readable from txn
|
|
|
|
|
|
|
|
s = txn->Get(read_options, "foo", &value); |
|
|
|
|
|
|
|
ASSERT_OK(s); |
|
|
|
|
|
|
|
ASSERT_EQ(value, "bar"); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// make commit
|
|
|
|
|
|
|
|
s = txn->Commit(); |
|
|
|
|
|
|
|
ASSERT_OK(s); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// value is now available
|
|
|
|
|
|
|
|
db->Get(read_options, "foo", &value); |
|
|
|
|
|
|
|
ASSERT_EQ(value, "bar"); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// we already committed
|
|
|
|
|
|
|
|
s = txn->Commit(); |
|
|
|
|
|
|
|
ASSERT_EQ(s, Status::InvalidArgument()); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// no longer is prpared results
|
|
|
|
|
|
|
|
prepared_trans.clear(); |
|
|
|
|
|
|
|
db->GetAllPreparedTransactions(&prepared_trans); |
|
|
|
|
|
|
|
ASSERT_EQ(prepared_trans.size(), 0); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// transaction should no longer be visible
|
|
|
|
|
|
|
|
ASSERT_EQ(db->GetTransactionByName("xid"), nullptr); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// heap should not care about prepared section anymore
|
|
|
|
|
|
|
|
ASSERT_EQ(db_impl->TEST_FindMinLogContainingOutstandingPrep(), 0); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// but now our memtable should be referencing the prep section
|
|
|
|
|
|
|
|
ASSERT_EQ(log_containing_prep, |
|
|
|
|
|
|
|
db_impl->TEST_FindMinPrepLogReferencedByMemTable()); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
db_impl->TEST_FlushMemTable(true); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// after memtable flush we can now relese the log
|
|
|
|
|
|
|
|
ASSERT_EQ(0, db_impl->TEST_FindMinPrepLogReferencedByMemTable()); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
delete txn; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// deleting transaction should unregister transaction
|
|
|
|
|
|
|
|
ASSERT_EQ(db->GetTransactionByName("xid"), nullptr); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
TEST_F(TransactionTest, TwoPhaseMultiThreadTest) { |
|
|
|
|
|
|
|
// mix transaction writes and regular writes
|
|
|
|
|
|
|
|
const int NUM_TXN_THREADS = 50; |
|
|
|
|
|
|
|
std::atomic<uint32_t> txn_thread_num(0); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
std::function<void()> txn_write_thread = [&]() { |
|
|
|
|
|
|
|
uint32_t id = txn_thread_num.fetch_add(1); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
WriteOptions write_options; |
|
|
|
|
|
|
|
write_options.sync = true; |
|
|
|
|
|
|
|
write_options.disableWAL = false; |
|
|
|
|
|
|
|
TransactionOptions txn_options; |
|
|
|
|
|
|
|
txn_options.lock_timeout = 1000000; |
|
|
|
|
|
|
|
if (id % 2 == 0) { |
|
|
|
|
|
|
|
txn_options.expiration = 1000000; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
TransactionName name("xid_" + std::string(1, 'A' + id)); |
|
|
|
|
|
|
|
Transaction* txn = db->BeginTransaction(write_options, txn_options); |
|
|
|
|
|
|
|
ASSERT_OK(txn->SetName(name)); |
|
|
|
|
|
|
|
for (int i = 0; i < 10; i++) { |
|
|
|
|
|
|
|
std::string key(name + "_" + std::string(1, 'A' + i)); |
|
|
|
|
|
|
|
ASSERT_OK(txn->Put(key, "val")); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
ASSERT_OK(txn->Prepare()); |
|
|
|
|
|
|
|
ASSERT_OK(txn->Commit()); |
|
|
|
|
|
|
|
delete txn; |
|
|
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// assure that all thread are in the same write group
|
|
|
|
|
|
|
|
std::atomic<uint32_t> t_wait_on_prepare(0); |
|
|
|
|
|
|
|
std::atomic<uint32_t> t_wait_on_commit(0); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
rocksdb::SyncPoint::GetInstance()->SetCallBack( |
|
|
|
|
|
|
|
"WriteThread::JoinBatchGroup:Wait", [&](void* arg) { |
|
|
|
|
|
|
|
auto* writer = reinterpret_cast<WriteThread::Writer*>(arg); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (writer->ShouldWriteToWAL()) { |
|
|
|
|
|
|
|
t_wait_on_prepare.fetch_add(1); |
|
|
|
|
|
|
|
// wait for friends
|
|
|
|
|
|
|
|
while (t_wait_on_prepare.load() < NUM_TXN_THREADS) { |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} else if (writer->ShouldWriteToMemtable()) { |
|
|
|
|
|
|
|
t_wait_on_commit.fetch_add(1); |
|
|
|
|
|
|
|
// wait for friends
|
|
|
|
|
|
|
|
while (t_wait_on_commit.load() < NUM_TXN_THREADS) { |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
ASSERT_TRUE(false); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
rocksdb::SyncPoint::GetInstance()->EnableProcessing(); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// do all the writes
|
|
|
|
|
|
|
|
std::vector<std::thread> threads; |
|
|
|
|
|
|
|
for (uint32_t i = 0; i < NUM_TXN_THREADS; i++) { |
|
|
|
|
|
|
|
threads.emplace_back(txn_write_thread); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
for (auto& t : threads) { |
|
|
|
|
|
|
|
t.join(); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
rocksdb::SyncPoint::GetInstance()->DisableProcessing(); |
|
|
|
|
|
|
|
rocksdb::SyncPoint::GetInstance()->ClearAllCallBacks(); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ReadOptions read_options; |
|
|
|
|
|
|
|
std::string value; |
|
|
|
|
|
|
|
Status s; |
|
|
|
|
|
|
|
for (int t = 0; t < NUM_TXN_THREADS; t++) { |
|
|
|
|
|
|
|
TransactionName name("xid_" + std::string(1, 'A' + t)); |
|
|
|
|
|
|
|
for (int i = 0; i < 10; i++) { |
|
|
|
|
|
|
|
std::string key(name + "_" + std::string(1, 'A' + i)); |
|
|
|
|
|
|
|
s = db->Get(read_options, key, &value); |
|
|
|
|
|
|
|
ASSERT_OK(s); |
|
|
|
|
|
|
|
ASSERT_EQ(value, "val"); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
TEST_F(TransactionTest, TwoPhaseLogRollingTest) { |
|
|
|
|
|
|
|
DBImpl* db_impl = reinterpret_cast<DBImpl*>(db->GetRootDB()); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Status s; |
|
|
|
|
|
|
|
string v; |
|
|
|
|
|
|
|
ColumnFamilyHandle *cfa, *cfb; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Create 2 new column families
|
|
|
|
|
|
|
|
ColumnFamilyOptions cf_options; |
|
|
|
|
|
|
|
s = db->CreateColumnFamily(cf_options, "CFA", &cfa); |
|
|
|
|
|
|
|
ASSERT_OK(s); |
|
|
|
|
|
|
|
s = db->CreateColumnFamily(cf_options, "CFB", &cfb); |
|
|
|
|
|
|
|
ASSERT_OK(s); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
WriteOptions wopts; |
|
|
|
|
|
|
|
wopts.disableWAL = false; |
|
|
|
|
|
|
|
wopts.sync = true; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
TransactionOptions topts1; |
|
|
|
|
|
|
|
Transaction* txn1 = db->BeginTransaction(wopts, topts1); |
|
|
|
|
|
|
|
s = txn1->SetName("xid1"); |
|
|
|
|
|
|
|
ASSERT_OK(s); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
TransactionOptions topts2; |
|
|
|
|
|
|
|
Transaction* txn2 = db->BeginTransaction(wopts, topts2); |
|
|
|
|
|
|
|
s = txn2->SetName("xid2"); |
|
|
|
|
|
|
|
ASSERT_OK(s); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// transaction put in two column families
|
|
|
|
|
|
|
|
s = txn1->Put(cfa, "ka1", "va1"); |
|
|
|
|
|
|
|
ASSERT_OK(s); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// transaction put in two column families
|
|
|
|
|
|
|
|
s = txn2->Put(cfa, "ka2", "va2"); |
|
|
|
|
|
|
|
ASSERT_OK(s); |
|
|
|
|
|
|
|
s = txn2->Put(cfb, "kb2", "vb2"); |
|
|
|
|
|
|
|
ASSERT_OK(s); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// write prep section to wal
|
|
|
|
|
|
|
|
s = txn1->Prepare(); |
|
|
|
|
|
|
|
ASSERT_OK(s); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// our log should be in the heap
|
|
|
|
|
|
|
|
ASSERT_EQ(db_impl->TEST_FindMinLogContainingOutstandingPrep(), |
|
|
|
|
|
|
|
txn1->GetLogNumber()); |
|
|
|
|
|
|
|
ASSERT_EQ(db_impl->TEST_LogfileNumber(), txn1->GetLogNumber()); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// flush default cf to crate new log
|
|
|
|
|
|
|
|
s = db->Put(wopts, "foo", "bar"); |
|
|
|
|
|
|
|
ASSERT_OK(s); |
|
|
|
|
|
|
|
s = db_impl->TEST_FlushMemTable(true); |
|
|
|
|
|
|
|
ASSERT_OK(s); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// make sure we are on a new log
|
|
|
|
|
|
|
|
ASSERT_GT(db_impl->TEST_LogfileNumber(), txn1->GetLogNumber()); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// put txn2 prep section in this log
|
|
|
|
|
|
|
|
s = txn2->Prepare(); |
|
|
|
|
|
|
|
ASSERT_OK(s); |
|
|
|
|
|
|
|
ASSERT_EQ(db_impl->TEST_LogfileNumber(), txn2->GetLogNumber()); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// heap should still see first log
|
|
|
|
|
|
|
|
ASSERT_EQ(db_impl->TEST_FindMinLogContainingOutstandingPrep(), |
|
|
|
|
|
|
|
txn1->GetLogNumber()); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// commit txn1
|
|
|
|
|
|
|
|
s = txn1->Commit(); |
|
|
|
|
|
|
|
ASSERT_OK(s); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// heap should now show txn2s log
|
|
|
|
|
|
|
|
ASSERT_EQ(db_impl->TEST_FindMinLogContainingOutstandingPrep(), |
|
|
|
|
|
|
|
txn2->GetLogNumber()); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// we should see txn1s log refernced by the memtables
|
|
|
|
|
|
|
|
ASSERT_EQ(db_impl->TEST_FindMinPrepLogReferencedByMemTable(), |
|
|
|
|
|
|
|
txn1->GetLogNumber()); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// flush default cf to crate new log
|
|
|
|
|
|
|
|
s = db->Put(wopts, "foo", "bar2"); |
|
|
|
|
|
|
|
ASSERT_OK(s); |
|
|
|
|
|
|
|
s = db_impl->TEST_FlushMemTable(true); |
|
|
|
|
|
|
|
ASSERT_OK(s); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// make sure we are on a new log
|
|
|
|
|
|
|
|
ASSERT_GT(db_impl->TEST_LogfileNumber(), txn2->GetLogNumber()); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// commit txn2
|
|
|
|
|
|
|
|
s = txn2->Commit(); |
|
|
|
|
|
|
|
ASSERT_OK(s); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// heap should not show any logs
|
|
|
|
|
|
|
|
ASSERT_EQ(db_impl->TEST_FindMinLogContainingOutstandingPrep(), 0); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// should show the first txn log
|
|
|
|
|
|
|
|
ASSERT_EQ(db_impl->TEST_FindMinPrepLogReferencedByMemTable(), |
|
|
|
|
|
|
|
txn1->GetLogNumber()); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// flush only cfa memtable
|
|
|
|
|
|
|
|
s = db_impl->TEST_FlushMemTable(true, cfa); |
|
|
|
|
|
|
|
ASSERT_OK(s); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// should show the first txn log
|
|
|
|
|
|
|
|
ASSERT_EQ(db_impl->TEST_FindMinPrepLogReferencedByMemTable(), |
|
|
|
|
|
|
|
txn2->GetLogNumber()); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// flush only cfb memtable
|
|
|
|
|
|
|
|
s = db_impl->TEST_FlushMemTable(true, cfb); |
|
|
|
|
|
|
|
ASSERT_OK(s); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// should show not dependency on logs
|
|
|
|
|
|
|
|
ASSERT_EQ(db_impl->TEST_FindMinPrepLogReferencedByMemTable(), 0); |
|
|
|
|
|
|
|
ASSERT_EQ(db_impl->TEST_FindMinLogContainingOutstandingPrep(), 0); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
delete txn1; |
|
|
|
|
|
|
|
delete txn2; |
|
|
|
|
|
|
|
delete cfa; |
|
|
|
|
|
|
|
delete cfb; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
TEST_F(TransactionTest, FirstWriteTest) { |
|
|
|
TEST_F(TransactionTest, FirstWriteTest) { |
|
|
|
WriteOptions write_options; |
|
|
|
WriteOptions write_options; |
|
|
|
|
|
|
|
|
|
|
@ -1300,6 +1936,21 @@ TEST_F(TransactionTest, ReinitializeTest) { |
|
|
|
s = db->Get(read_options, "Y", &value); |
|
|
|
s = db->Get(read_options, "Y", &value); |
|
|
|
ASSERT_TRUE(s.IsNotFound()); |
|
|
|
ASSERT_TRUE(s.IsNotFound()); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
txn1 = db->BeginTransaction(write_options, txn_options, txn1); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
s = txn1->SetName("name"); |
|
|
|
|
|
|
|
ASSERT_OK(s); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
s = txn1->Prepare(); |
|
|
|
|
|
|
|
ASSERT_OK(s); |
|
|
|
|
|
|
|
s = txn1->Commit(); |
|
|
|
|
|
|
|
ASSERT_OK(s); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
txn1 = db->BeginTransaction(write_options, txn_options, txn1); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
s = txn1->SetName("name"); |
|
|
|
|
|
|
|
ASSERT_OK(s); |
|
|
|
|
|
|
|
|
|
|
|
delete txn1; |
|
|
|
delete txn1; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|