@ -5,6 +5,7 @@
# ifndef ROCKSDB_LITE
# ifndef ROCKSDB_LITE
# include <algorithm>
# include <string>
# include <string>
# include <thread>
# include <thread>
@ -17,6 +18,7 @@
# include "util/fault_injection_test_env.h"
# include "util/fault_injection_test_env.h"
# include "util/logging.h"
# include "util/logging.h"
# include "util/random.h"
# include "util/random.h"
# include "util/string_util.h"
# include "util/sync_point.h"
# include "util/sync_point.h"
# include "util/testharness.h"
# include "util/testharness.h"
# include "util/testutil.h"
# include "util/testutil.h"
@ -219,16 +221,16 @@ TEST_P(TransactionTest, WaitingTxn) {
auto cf_iterator = lock_data . begin ( ) ;
auto cf_iterator = lock_data . begin ( ) ;
// Column family is 0 (default ).
// Column family is 1 (cfa ).
ASSERT_EQ ( cf_iterator - > first , 0 ) ;
ASSERT_EQ ( cf_iterator - > first , 1 ) ;
// The locked key is "foo" and is locked by txn1
// The locked key is "foo" and is locked by txn1
ASSERT_EQ ( cf_iterator - > second . key , " foo " ) ;
ASSERT_EQ ( cf_iterator - > second . key , " foo " ) ;
ASSERT_EQ ( cf_iterator - > second . id , txn1 - > GetID ( ) ) ;
ASSERT_EQ ( cf_iterator - > second . id , txn1 - > GetID ( ) ) ;
cf_iterator + + ;
cf_iterator + + ;
// Column family is 1 (cfa ).
// Column family is 0 (default ).
ASSERT_EQ ( cf_iterator - > first , 1 ) ;
ASSERT_EQ ( cf_iterator - > first , 0 ) ;
// The locked key is "foo" and is locked by txn1
// The locked key is "foo" and is locked by txn1
ASSERT_EQ ( cf_iterator - > second . key , " foo " ) ;
ASSERT_EQ ( cf_iterator - > second . key , " foo " ) ;
ASSERT_EQ ( cf_iterator - > second . id , txn1 - > GetID ( ) ) ;
ASSERT_EQ ( cf_iterator - > second . id , txn1 - > GetID ( ) ) ;
@ -247,6 +249,124 @@ TEST_P(TransactionTest, WaitingTxn) {
delete txn2 ;
delete txn2 ;
}
}
TEST_P ( TransactionTest , DeadlockCycle ) {
WriteOptions write_options ;
ReadOptions read_options ;
TransactionOptions txn_options ;
const uint32_t kMaxCycleLength = 50 ;
txn_options . lock_timeout = 1000000 ;
txn_options . deadlock_detect = true ;
for ( uint32_t len = 2 ; len < kMaxCycleLength ; len + + ) {
// Set up a long wait for chain like this:
//
// T1 -> T2 -> T3 -> ... -> Tlen
std : : vector < Transaction * > txns ( len ) ;
for ( uint32_t i = 0 ; i < len ; i + + ) {
txns [ i ] = db - > BeginTransaction ( write_options , txn_options ) ;
ASSERT_TRUE ( txns [ i ] ) ;
auto s = txns [ i ] - > GetForUpdate ( read_options , ToString ( i ) , nullptr ) ;
ASSERT_OK ( s ) ;
}
std : : atomic < uint32_t > checkpoints ( 0 ) ;
rocksdb : : SyncPoint : : GetInstance ( ) - > SetCallBack (
" TransactionLockMgr::AcquireWithTimeout:WaitingTxn " ,
[ & ] ( void * arg ) { checkpoints . fetch_add ( 1 ) ; } ) ;
rocksdb : : SyncPoint : : GetInstance ( ) - > EnableProcessing ( ) ;
// We want the last transaction in the chain to block and hold everyone
// back.
std : : vector < std : : thread > threads ;
for ( uint32_t i = 0 ; i < len - 1 ; i + + ) {
std : : function < void ( ) > blocking_thread = [ & , i ] {
auto s =
txns [ i ] - > GetForUpdate ( read_options , ToString ( i + 1 ) , nullptr ) ;
ASSERT_OK ( s ) ;
txns [ i ] - > Rollback ( ) ;
delete txns [ i ] ;
} ;
threads . emplace_back ( blocking_thread ) ;
}
// Wait until all threads are waiting on each other.
while ( checkpoints . load ( ) ! = len - 1 ) {
/* sleep override */
std : : this_thread : : sleep_for ( std : : chrono : : milliseconds ( 100 ) ) ;
}
rocksdb : : SyncPoint : : GetInstance ( ) - > DisableProcessing ( ) ;
rocksdb : : SyncPoint : : GetInstance ( ) - > ClearAllCallBacks ( ) ;
// Complete the cycle Tlen -> T1
auto s = txns [ len - 1 ] - > GetForUpdate ( read_options , " 0 " , nullptr ) ;
ASSERT_TRUE ( s . IsDeadlock ( ) ) ;
// Rollback the last transaction.
txns [ len - 1 ] - > Rollback ( ) ;
delete txns [ len - 1 ] ;
for ( auto & t : threads ) {
t . join ( ) ;
}
}
}
TEST_P ( TransactionTest , DeadlockStress ) {
const uint32_t NUM_TXN_THREADS = 10 ;
const uint32_t NUM_KEYS = 100 ;
const uint32_t NUM_ITERS = 100000 ;
WriteOptions write_options ;
ReadOptions read_options ;
TransactionOptions txn_options ;
txn_options . lock_timeout = 1000000 ;
txn_options . deadlock_detect = true ;
std : : vector < std : : string > keys ;
for ( uint32_t i = 0 ; i < NUM_KEYS ; i + + ) {
db - > Put ( write_options , Slice ( ToString ( i ) ) , Slice ( " " ) ) ;
keys . push_back ( ToString ( i ) ) ;
}
size_t tid = std : : hash < std : : thread : : id > ( ) ( std : : this_thread : : get_id ( ) ) ;
Random rnd ( static_cast < uint32_t > ( tid ) ) ;
std : : function < void ( uint32_t ) > stress_thread = [ & ] ( uint32_t seed ) {
std : : default_random_engine g ( seed ) ;
Transaction * txn ;
for ( uint32_t i = 0 ; i < NUM_ITERS ; i + + ) {
txn = db - > BeginTransaction ( write_options , txn_options ) ;
auto random_keys = keys ;
std : : shuffle ( random_keys . begin ( ) , random_keys . end ( ) , g ) ;
// Lock keys in random order.
for ( const auto & k : random_keys ) {
auto s = txn - > GetForUpdate ( read_options , k , nullptr ) ;
if ( ! s . ok ( ) ) {
ASSERT_TRUE ( s . IsDeadlock ( ) ) ;
txn - > Rollback ( ) ;
break ;
}
}
delete txn ;
}
} ;
std : : vector < std : : thread > threads ;
for ( uint32_t i = 0 ; i < NUM_TXN_THREADS ; i + + ) {
threads . emplace_back ( stress_thread , rnd . Next ( ) ) ;
}
for ( auto & t : threads ) {
t . join ( ) ;
}
}
TEST_P ( TransactionTest , CommitTimeBatchFailTest ) {
TEST_P ( TransactionTest , CommitTimeBatchFailTest ) {
WriteOptions write_options ;
WriteOptions write_options ;
TransactionOptions txn_options ;
TransactionOptions txn_options ;