@ -3,8 +3,9 @@
// COPYING file in the root directory) and Apache 2.0 License
// (found in the LICENSE.Apache file in the root directory).
# include <cstdint>
# include <functional>
# include <memory>
# include <string>
# include <thread>
@ -27,49 +28,54 @@ class OptimisticTransactionTest
: public testing : : Test ,
public testing : : WithParamInterface < OccValidationPolicy > {
public :
OptimisticTransactionDB * txn_db ;
std : : unique_ptr < OptimisticTransactionDB > txn_db ;
std : : string dbname ;
Options options ;
OptimisticTransactionDBOptions occ_opts ;
OptimisticTransactionTest ( ) {
options . create_if_missing = true ;
options . max_write_buffer_number = 2 ;
options . max_write_buffer_size_to_maintain = 2 * Arena : : kInlineSize ;
options . merge_operator . reset ( new TestPutOperator ( ) ) ;
occ_opts . validate_policy = GetParam ( ) ;
dbname = test : : PerThreadDBPath ( " optimistic_transaction_testdb " ) ;
EXPECT_OK ( DestroyDB ( dbname , options ) ) ;
Open ( ) ;
}
~ OptimisticTransactionTest ( ) override {
delete txn_db ;
EXPECT_OK ( txn_db - > Close ( ) ) ;
txn_db . reset ( ) ;
EXPECT_OK ( DestroyDB ( dbname , options ) ) ;
}
void Reopen ( ) {
delete txn_db ;
txn_db = nullptr ;
txn_db . reset ( ) ;
Open ( ) ;
}
private :
void Open ( ) {
static void OpenImpl ( const Options & options ,
const OptimisticTransactionDBOptions & occ_opts ,
const std : : string & dbname ,
std : : unique_ptr < OptimisticTransactionDB > * txn_db ) {
ColumnFamilyOptions cf_options ( options ) ;
OptimisticTransactionDBOptions occ_opts ;
occ_opts . validate_policy = GetParam ( ) ;
std : : vector < ColumnFamilyDescriptor > column_families ;
std : : vector < ColumnFamilyHandle * > handles ;
column_families . push_back (
ColumnFamilyDescriptor ( kDefaultColumnFamilyName , cf_options ) ) ;
Status s =
OptimisticTransactionDB : : Open ( DBOptions ( options ) , occ_opts , dbname ,
column_families , & handles , & txn_db ) ;
OptimisticTransactionDB * raw_txn_db = nullptr ;
Status s = OptimisticTransactionDB : : Open (
options , occ_opts , dbname , column_families , & handles , & raw_txn_db ) ;
ASSERT_OK ( s ) ;
ASSERT_NE ( txn_db , nullptr ) ;
ASSERT_NE ( raw_txn_db , nullptr ) ;
txn_db - > reset ( raw_txn_db ) ;
ASSERT_EQ ( handles . size ( ) , 1 ) ;
delete handles [ 0 ] ;
}
private :
void Open ( ) { OpenImpl ( options , occ_opts , dbname , & txn_db ) ; }
} ;
TEST_P ( OptimisticTransactionTest , SuccessTest ) {
@ -616,8 +622,11 @@ TEST_P(OptimisticTransactionTest, ColumnFamiliesTest) {
delete cfa ;
delete cfb ;
delete txn_db ;
txn_db = nullptr ;
txn_db . reset ( ) ;
OptimisticTransactionDBOptions my_occ_opts = occ_opts ;
const size_t bucket_count = 500 ;
my_occ_opts . shared_lock_buckets = MakeSharedOccLockBuckets ( bucket_count ) ;
// open DB with three column families
std : : vector < ColumnFamilyDescriptor > column_families ;
@ -630,10 +639,11 @@ TEST_P(OptimisticTransactionTest, ColumnFamiliesTest) {
column_families . push_back (
ColumnFamilyDescriptor ( " CFB " , ColumnFamilyOptions ( ) ) ) ;
std : : vector < ColumnFamilyHandle * > handles ;
ASSERT_OK ( OptimisticTransactionDB : : Open ( options , dbname , column_families ,
& handles , & txn_db ) ) ;
assert ( txn_db ! = nullptr ) ;
ASSERT_NE ( txn_db , nullptr ) ;
OptimisticTransactionDB * raw_txn_db = nullptr ;
ASSERT_OK ( OptimisticTransactionDB : : Open (
options , my_occ_opts , dbname , column_families , & handles , & raw_txn_db ) ) ;
ASSERT_NE ( raw_txn_db , nullptr ) ;
txn_db . reset ( raw_txn_db ) ;
Transaction * txn = txn_db - > BeginTransaction ( write_options ) ;
ASSERT_NE ( txn , nullptr ) ;
@ -694,6 +704,7 @@ TEST_P(OptimisticTransactionTest, ColumnFamiliesTest) {
delete txn ;
delete txn2 ;
// ** MultiGet **
txn = txn_db - > BeginTransaction ( write_options , txn_options ) ;
snapshot_read_options . snapshot = txn - > GetSnapshot ( ) ;
@ -745,11 +756,162 @@ TEST_P(OptimisticTransactionTest, ColumnFamiliesTest) {
s = txn2 - > Commit ( ) ;
ASSERT_TRUE ( s . IsBusy ( ) ) ;
delete txn ;
delete txn2 ;
// ** Test independence and/or sharing of lock buckets across CFs and DBs **
if ( my_occ_opts . validate_policy = = OccValidationPolicy : : kValidateParallel ) {
struct SeenStat {
uint64_t rolling_hash = 0 ;
uintptr_t min = 0 ;
uintptr_t max = 0 ;
} ;
SeenStat cur_seen ;
ROCKSDB_NAMESPACE : : SyncPoint : : GetInstance ( ) - > SetCallBack (
" OptimisticTransaction::CommitWithParallelValidate::lock_bucket_ptr " ,
[ & ] ( void * arg ) {
// Hash the pointer
cur_seen . rolling_hash = Hash64 ( reinterpret_cast < char * > ( & arg ) ,
sizeof ( arg ) , cur_seen . rolling_hash ) ;
uintptr_t val = reinterpret_cast < uintptr_t > ( arg ) ;
if ( cur_seen . min = = 0 | | val < cur_seen . min ) {
cur_seen . min = val ;
}
if ( cur_seen . max = = 0 | | val > cur_seen . max ) {
cur_seen . max = val ;
}
} ) ;
ROCKSDB_NAMESPACE : : SyncPoint : : GetInstance ( ) - > EnableProcessing ( ) ;
// Another db sharing lock buckets
auto shared_dbname =
test : : PerThreadDBPath ( " optimistic_transaction_testdb_shared " ) ;
std : : unique_ptr < OptimisticTransactionDB > shared_txn_db = nullptr ;
OpenImpl ( options , my_occ_opts , shared_dbname , & shared_txn_db ) ;
// Another db not sharing lock buckets
auto nonshared_dbname =
test : : PerThreadDBPath ( " optimistic_transaction_testdb_nonshared " ) ;
std : : unique_ptr < OptimisticTransactionDB > nonshared_txn_db = nullptr ;
my_occ_opts . occ_lock_buckets = bucket_count ;
my_occ_opts . shared_lock_buckets = nullptr ;
OpenImpl ( options , my_occ_opts , nonshared_dbname , & nonshared_txn_db ) ;
// Plenty of keys to avoid randomly hitting the same hash sequence
std : : array < std : : string , 30 > keys ;
for ( size_t i = 0 ; i < keys . size ( ) ; + + i ) {
keys [ i ] = std : : to_string ( i ) ;
}
// Get a baseline pattern of bucket accesses
cur_seen = { } ;
txn = txn_db - > BeginTransaction ( write_options , txn_options ) ;
for ( const auto & key : keys ) {
txn - > Put ( handles [ 0 ] , key , " blah " ) ;
}
ASSERT_OK ( txn - > Commit ( ) ) ;
// Sufficiently large hash coverage of the space
const uintptr_t min_span_bytes = sizeof ( port : : Mutex ) * bucket_count / 2 ;
ASSERT_GT ( cur_seen . max - cur_seen . min , min_span_bytes ) ;
// Save
SeenStat base_seen = cur_seen ;
// Verify it is repeatable
cur_seen = { } ;
txn = txn_db - > BeginTransaction ( write_options , txn_options , txn ) ;
for ( const auto & key : keys ) {
txn - > Put ( handles [ 0 ] , key , " moo " ) ;
}
ASSERT_OK ( txn - > Commit ( ) ) ;
ASSERT_EQ ( cur_seen . rolling_hash , base_seen . rolling_hash ) ;
ASSERT_EQ ( cur_seen . min , base_seen . min ) ;
ASSERT_EQ ( cur_seen . max , base_seen . max ) ;
// Try another CF
cur_seen = { } ;
txn = txn_db - > BeginTransaction ( write_options , txn_options , txn ) ;
for ( const auto & key : keys ) {
txn - > Put ( handles [ 1 ] , key , " blah " ) ;
}
ASSERT_OK ( txn - > Commit ( ) ) ;
// Different access pattern (different hash seed)
ASSERT_NE ( cur_seen . rolling_hash , base_seen . rolling_hash ) ;
// Same pointer space
ASSERT_LT ( cur_seen . min , base_seen . max ) ;
ASSERT_GT ( cur_seen . max , base_seen . min ) ;
// Sufficiently large hash coverage of the space
ASSERT_GT ( cur_seen . max - cur_seen . min , min_span_bytes ) ;
// Save
SeenStat cf1_seen = cur_seen ;
// And another CF
cur_seen = { } ;
txn = txn_db - > BeginTransaction ( write_options , txn_options , txn ) ;
for ( const auto & key : keys ) {
txn - > Put ( handles [ 2 ] , key , " blah " ) ;
}
ASSERT_OK ( txn - > Commit ( ) ) ;
// Different access pattern (different hash seed)
ASSERT_NE ( cur_seen . rolling_hash , base_seen . rolling_hash ) ;
ASSERT_NE ( cur_seen . rolling_hash , cf1_seen . rolling_hash ) ;
// Same pointer space
ASSERT_LT ( cur_seen . min , base_seen . max ) ;
ASSERT_GT ( cur_seen . max , base_seen . min ) ;
// Sufficiently large hash coverage of the space
ASSERT_GT ( cur_seen . max - cur_seen . min , min_span_bytes ) ;
// And DB with shared lock buckets
cur_seen = { } ;
delete txn ;
txn = shared_txn_db - > BeginTransaction ( write_options , txn_options ) ;
for ( const auto & key : keys ) {
txn - > Put ( key , " blah " ) ;
}
ASSERT_OK ( txn - > Commit ( ) ) ;
// Different access pattern (different hash seed)
ASSERT_NE ( cur_seen . rolling_hash , base_seen . rolling_hash ) ;
ASSERT_NE ( cur_seen . rolling_hash , cf1_seen . rolling_hash ) ;
// Same pointer space
ASSERT_LT ( cur_seen . min , base_seen . max ) ;
ASSERT_GT ( cur_seen . max , base_seen . min ) ;
// Sufficiently large hash coverage of the space
ASSERT_GT ( cur_seen . max - cur_seen . min , min_span_bytes ) ;
// And DB with distinct lock buckets
cur_seen = { } ;
delete txn ;
txn = nonshared_txn_db - > BeginTransaction ( write_options , txn_options ) ;
for ( const auto & key : keys ) {
txn - > Put ( key , " blah " ) ;
}
ASSERT_OK ( txn - > Commit ( ) ) ;
// Different access pattern (different hash seed)
ASSERT_NE ( cur_seen . rolling_hash , base_seen . rolling_hash ) ;
ASSERT_NE ( cur_seen . rolling_hash , cf1_seen . rolling_hash ) ;
// Different pointer space
ASSERT_TRUE ( cur_seen . min > base_seen . max | | cur_seen . max < base_seen . min ) ;
// Sufficiently large hash coverage of the space
ASSERT_GT ( cur_seen . max - cur_seen . min , min_span_bytes ) ;
delete txn ;
ROCKSDB_NAMESPACE : : SyncPoint : : GetInstance ( ) - > DisableProcessing ( ) ;
}
// ** Test dropping column family before committing, or even creating txn **
txn = txn_db - > BeginTransaction ( write_options , txn_options ) ;
ASSERT_OK ( txn - > Delete ( handles [ 1 ] , " AAA " ) ) ;
s = txn_db - > DropColumnFamily ( handles [ 1 ] ) ;
ASSERT_OK ( s ) ;
s = txn_db - > DropColumnFamily ( handles [ 2 ] ) ;
ASSERT_OK ( s ) ;
ASSERT_NOK ( txn - > Commit ( ) ) ;
txn2 = txn_db - > BeginTransaction ( write_options , txn_options ) ;
ASSERT_OK ( txn2 - > Delete ( handles [ 2 ] , " AAA " ) ) ;
ASSERT_NOK ( txn2 - > Commit ( ) ) ;
delete txn ;
delete txn2 ;
@ -1402,7 +1564,7 @@ TEST_P(OptimisticTransactionTest, OptimisticTransactionStressTest) {
std : : function < void ( ) > call_inserter = [ & ] {
ASSERT_OK ( OptimisticTransactionStressTestInserter (
txn_db , num_transactions_per_thread , num_sets , num_keys_per_set ) ) ;
txn_db . get ( ) , num_transactions_per_thread , num_sets , num_keys_per_set ) ) ;
} ;
// Create N threads that use RandomTransactionInserter to write
@ -1417,7 +1579,7 @@ TEST_P(OptimisticTransactionTest, OptimisticTransactionStressTest) {
}
// Verify that data is consistent
Status s = RandomTransactionInserter : : Verify ( txn_db , num_sets ) ;
Status s = RandomTransactionInserter : : Verify ( txn_db . get ( ) , num_sets ) ;
ASSERT_OK ( s ) ;
}
@ -1469,6 +1631,19 @@ INSTANTIATE_TEST_CASE_P(
testing : : Values ( OccValidationPolicy : : kValidateSerial ,
OccValidationPolicy : : kValidateParallel ) ) ;
TEST ( OccLockBucketsTest , CacheAligned ) {
// Typical x86_64 is 40 byte mutex, 64 byte cache line
if ( sizeof ( port : : Mutex ) > = sizeof ( CacheAlignedWrapper < port : : Mutex > ) ) {
ROCKSDB_GTEST_BYPASS ( " Test requires mutex smaller than cache line " ) ;
return ;
}
auto buckets_unaligned = MakeSharedOccLockBuckets ( 100 , false ) ;
auto buckets_aligned = MakeSharedOccLockBuckets ( 100 , true ) ;
// Save at least one byte per bucket
ASSERT_LE ( buckets_unaligned - > ApproximateMemoryUsage ( ) + 100 ,
buckets_aligned - > ApproximateMemoryUsage ( ) ) ;
}
} // namespace ROCKSDB_NAMESPACE
int main ( int argc , char * * argv ) {
@ -1476,4 +1651,3 @@ int main(int argc, char** argv) {
: : testing : : InitGoogleTest ( & argc , argv ) ;
return RUN_ALL_TESTS ( ) ;
}