2010-07-12 17:53:02 -04:00
// distlock_test.h
2010-07-12 17:50:26 -04:00
/* Copyright 2009 10gen Inc.
*
* Licensed under the Apache License , Version 2.0 ( the " License " ) ;
* you may not use this file except in compliance with the License .
* You may obtain a copy of the License at
*
* http : //www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing , software
* distributed under the License is distributed on an " AS IS " BASIS ,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND , either express or implied .
* See the License for the specific language governing permissions and
* limitations under the License .
*/
2011-02-15 18:08:59 -05:00
# include <iostream>
2010-07-12 17:50:26 -04:00
# include "../pch.h"
# include "dbclient.h"
# include "distlock.h"
# include "../db/commands.h"
2011-02-15 18:08:59 -05:00
# include "../util/bson_util.h"
2011-03-02 11:56:47 -05:00
// Modify some config options for the RNG, since they cause MSVC to fail
# include <boost/config.hpp>
# if defined(BOOST_MSVC) && defined(BOOST_NO_MEMBER_TEMPLATE_FRIENDS)
# undef BOOST_NO_MEMBER_TEMPLATE_FRIENDS
# define BOOST_RNG_HACK
# endif
// Well, sort-of cross-platform RNG
2011-03-01 16:04:57 -05:00
# include <boost/random/mersenne_twister.hpp>
2011-03-02 11:56:47 -05:00
# ifdef BOOST_RNG_HACK
# define BOOST_NO_MEMBER_TEMPLATE_FRIENDS
# undef BOOST_RNG_HACK
# endif
2011-03-01 16:04:57 -05:00
# include <boost/random/uniform_int.hpp>
# include <boost/random/variate_generator.hpp>
2011-02-15 18:08:59 -05:00
// TODO: Make a method in BSONObj if useful, don't modify for now
# define string_field(obj, name, def) ( obj.hasField(name) ? obj[name].String() : def )
# define number_field(obj, name, def) ( obj.hasField(name) ? obj[name].Number() : def )
2010-07-12 17:50:26 -04:00
namespace mongo {
2011-01-04 00:40:41 -05:00
2011-02-15 18:08:59 -05:00
class TestDistLockWithSync : public Command {
2010-07-12 17:50:26 -04:00
public :
2011-02-15 18:08:59 -05:00
TestDistLockWithSync ( ) :
Command ( " _testDistLockWithSyncCluster " ) {
}
virtual void help ( stringstream & help ) const {
2010-07-12 17:50:26 -04:00
help < < " should not be calling this directly " < < endl ;
}
2011-01-04 00:40:41 -05:00
2011-02-15 18:08:59 -05:00
virtual bool slaveOk ( ) const {
return false ;
}
virtual bool adminOnly ( ) const {
return true ;
}
virtual LockType locktype ( ) const {
return NONE ;
}
2010-07-12 17:50:26 -04:00
2011-01-04 00:40:41 -05:00
static void runThread ( ) {
2011-02-15 18:08:59 -05:00
while ( keepGoing ) {
2011-05-03 11:21:49 -04:00
if ( current - > lock_try ( " test " ) ) {
2011-02-14 16:18:52 -05:00
count + + ;
2011-02-14 12:39:53 -05:00
int before = count ;
2011-02-15 18:08:59 -05:00
sleepmillis ( 3 ) ;
2011-02-14 12:39:53 -05:00
int after = count ;
2011-02-15 18:08:59 -05:00
if ( after ! = before ) {
error ( ) < < " before: " < < before < < " after: " < < after
< < endl ;
2011-02-14 12:39:53 -05:00
}
2011-02-15 18:08:59 -05:00
2010-07-12 17:50:26 -04:00
current - > unlock ( ) ;
}
}
}
2011-02-15 18:08:59 -05:00
2011-07-18 14:25:18 -04:00
bool run ( const string & , BSONObj & cmdObj , int , string & errmsg ,
2011-02-15 18:08:59 -05:00
BSONObjBuilder & result , bool ) {
2011-02-14 12:39:53 -05:00
Timer t ;
2011-02-15 18:08:59 -05:00
DistributedLock lk ( ConnectionString ( cmdObj [ " host " ] . String ( ) ,
2011-05-09 15:30:17 -04:00
ConnectionString : : SYNC ) , " testdistlockwithsync " , 0 , 0 ) ;
2010-07-12 17:50:26 -04:00
current = & lk ;
count = 0 ;
gotit = 0 ;
2011-02-14 12:39:53 -05:00
errors = 0 ;
2011-02-14 16:18:52 -05:00
keepGoing = true ;
2011-02-15 18:08:59 -05:00
2010-07-12 17:50:26 -04:00
vector < shared_ptr < boost : : thread > > l ;
2011-02-15 18:08:59 -05:00
for ( int i = 0 ; i < 4 ; i + + ) {
l . push_back (
shared_ptr < boost : : thread > ( new boost : : thread ( runThread ) ) ) ;
2010-07-12 17:50:26 -04:00
}
2011-02-15 18:08:59 -05:00
2011-02-14 16:18:52 -05:00
int secs = 10 ;
2011-02-15 18:08:59 -05:00
if ( cmdObj [ " secs " ] . isNumber ( ) )
2011-02-14 16:18:52 -05:00
secs = cmdObj [ " secs " ] . numberInt ( ) ;
2011-02-15 18:08:59 -05:00
sleepsecs ( secs ) ;
2011-02-14 16:18:52 -05:00
keepGoing = false ;
2011-02-15 18:08:59 -05:00
for ( unsigned i = 0 ; i < l . size ( ) ; i + + )
2010-07-12 17:50:26 -04:00
l [ i ] - > join ( ) ;
2011-02-14 12:39:53 -05:00
current = 0 ;
2011-02-15 18:08:59 -05:00
result . append ( " count " , count ) ;
result . append ( " gotit " , gotit ) ;
result . append ( " errors " , errors ) ;
result . append ( " timeMS " , t . millis ( ) ) ;
2011-01-04 00:40:41 -05:00
2011-02-14 16:18:52 -05:00
return errors = = 0 ;
2011-02-14 12:39:53 -05:00
}
2011-02-15 18:08:59 -05:00
2011-02-14 12:39:53 -05:00
// variables for test
2010-07-12 17:50:26 -04:00
static DistributedLock * current ;
static int gotit ;
2011-02-14 12:39:53 -05:00
static int errors ;
2011-02-14 16:18:52 -05:00
static AtomicUInt count ;
2011-02-15 18:08:59 -05:00
2011-02-14 16:18:52 -05:00
static bool keepGoing ;
2010-07-12 17:50:26 -04:00
} testDistLockWithSyncCmd ;
DistributedLock * TestDistLockWithSync : : current ;
2011-02-14 16:18:52 -05:00
AtomicUInt TestDistLockWithSync : : count ;
2010-07-12 17:50:26 -04:00
int TestDistLockWithSync : : gotit ;
2011-02-14 12:39:53 -05:00
int TestDistLockWithSync : : errors ;
2011-02-14 16:18:52 -05:00
bool TestDistLockWithSync : : keepGoing ;
2010-07-12 17:50:26 -04:00
2011-02-15 18:08:59 -05:00
class TestDistLockWithSkew : public Command {
public :
2011-03-04 16:54:37 -05:00
static const int logLvl = 1 ;
2011-02-15 18:08:59 -05:00
TestDistLockWithSkew ( ) :
Command ( " _testDistLockWithSkew " ) {
}
virtual void help ( stringstream & help ) const {
help < < " should not be calling this directly " < < endl ;
}
virtual bool slaveOk ( ) const {
return false ;
}
virtual bool adminOnly ( ) const {
return true ;
}
virtual LockType locktype ( ) const {
return NONE ;
}
void runThread ( ConnectionString & hostConn , unsigned threadId , unsigned seed ,
BSONObj & cmdObj , BSONObjBuilder & result ) {
stringstream ss ;
ss < < " thread- " < < threadId ;
setThreadName ( ss . str ( ) . c_str ( ) ) ;
// Lock name
string lockName = string_field ( cmdObj , " lockName " , this - > name + " _lock " ) ;
// Range of clock skew in diff threads
int skewRange = ( int ) number_field ( cmdObj , " skewRange " , 1 ) ;
// How long to wait with the lock
int threadWait = ( int ) number_field ( cmdObj , " threadWait " , 30 ) ;
if ( threadWait < = 0 ) threadWait = 1 ;
// Max amount of time (ms) a thread waits before checking the lock again
int threadSleep = ( int ) number_field ( cmdObj , " threadSleep " , 30 ) ;
if ( threadSleep < = 0 ) threadSleep = 1 ;
// How long until the lock is forced in ms, only compared locally
2011-03-01 12:28:54 -08:00
unsigned long long takeoverMS = ( unsigned long long ) number_field ( cmdObj , " takeoverMS " , 0 ) ;
2011-02-15 18:08:59 -05:00
// Whether or not we should hang some threads
int hangThreads = ( int ) number_field ( cmdObj , " hangThreads " , 0 ) ;
2011-03-01 16:04:57 -05:00
2011-03-02 11:56:47 -05:00
boost : : mt19937 gen ( ( boost : : mt19937 : : result_type ) seed ) ;
2011-03-01 16:04:57 -05:00
boost : : variate_generator < boost : : mt19937 & , boost : : uniform_int < > > randomSkew ( gen , boost : : uniform_int < > ( 0 , skewRange ) ) ;
boost : : variate_generator < boost : : mt19937 & , boost : : uniform_int < > > randomWait ( gen , boost : : uniform_int < > ( 1 , threadWait ) ) ;
boost : : variate_generator < boost : : mt19937 & , boost : : uniform_int < > > randomSleep ( gen , boost : : uniform_int < > ( 1 , threadSleep ) ) ;
2011-02-15 18:08:59 -05:00
int skew = 0 ;
if ( ! lock . get ( ) ) {
// Pick a skew, but the first two threads skew the whole range
if ( threadId = = 0 )
skew = - skewRange / 2 ;
else if ( threadId = = 1 )
skew = skewRange / 2 ;
2011-03-01 16:04:57 -05:00
else skew = randomSkew ( ) - ( skewRange / 2 ) ;
2011-02-15 18:08:59 -05:00
// Skew this thread
jsTimeVirtualThreadSkew ( skew ) ;
log ( ) < < " Initializing lock with skew of " < < skew < < " for thread " < < threadId < < endl ;
2011-05-09 15:30:17 -04:00
lock . reset ( new DistributedLock ( hostConn , lockName , takeoverMS , true ) ) ;
2011-02-15 18:08:59 -05:00
log ( ) < < " Skewed time " < < jsTime ( ) < < " for thread " < < threadId < < endl
< < " max wait (with lock: " < < threadWait < < " , after lock: " < < threadSleep < < " ) " < < endl
2011-05-09 15:30:17 -04:00
< < " takeover in " < < takeoverMS < < " (ms remote) " < < endl ;
2011-02-15 18:08:59 -05:00
}
DistributedLock * myLock = lock . get ( ) ;
bool errors = false ;
2011-05-03 11:21:49 -04:00
BSONObj lockObj ;
2011-02-15 18:08:59 -05:00
while ( keepGoing ) {
2011-03-04 16:54:37 -05:00
try {
2011-05-03 11:21:49 -04:00
if ( myLock - > lock_try ( " Testing distributed lock with skew. " , false , & lockObj ) ) {
2011-03-04 16:54:37 -05:00
2011-05-03 11:21:49 -04:00
log ( ) < < " **** Locked for thread " < < threadId < < " with ts " < < lockObj [ " ts " ] < < endl ;
2011-05-09 15:30:17 -04:00
if ( count % 2 = = 1 & & ! myLock - > lock_try ( " Testing lock re-entry. " , true ) ) {
errors = true ;
log ( ) < < " **** !Could not re-enter lock already held " < < endl ;
break ;
}
2011-05-03 11:21:49 -04:00
2011-05-09 15:30:17 -04:00
if ( count % 3 = = 1 & & myLock - > lock_try ( " Testing lock non-re-entry. " , false ) ) {
errors = true ;
log ( ) < < " **** !Invalid lock re-entry " < < endl ;
break ;
2011-05-03 11:21:49 -04:00
}
2011-03-04 16:54:37 -05:00
count + + ;
int before = count ;
int sleep = randomWait ( ) ;
sleepmillis ( sleep ) ;
int after = count ;
if ( after ! = before ) {
errors = true ;
log ( ) < < " **** !Bad increment while sleeping with lock for: " < < sleep < < " ms " < < endl ;
break ;
}
// Unlock only half the time...
if ( hangThreads = = 0 | | threadId % hangThreads ! = 0 ) {
2011-05-03 11:21:49 -04:00
log ( ) < < " **** Unlocking for thread " < < threadId < < " with ts " < < lockObj [ " ts " ] < < endl ;
myLock - > unlock ( & lockObj ) ;
2011-03-04 16:54:37 -05:00
}
else {
log ( ) < < " **** Not unlocking for thread " < < threadId < < endl ;
DistributedLock : : killPinger ( * myLock ) ;
// We're simulating a crashed process...
break ;
}
}
2011-02-15 18:08:59 -05:00
}
catch ( LockException & e ) {
2011-03-31 11:32:47 -04:00
log ( ) < < " *** !Could not try distributed lock. " < < causedBy ( e ) < < endl ;
2011-02-15 18:08:59 -05:00
break ;
}
2011-03-01 16:04:57 -05:00
sleepmillis ( randomSleep ( ) ) ;
2011-02-15 18:08:59 -05:00
}
result < < " errors " < < errors
< < " skew " < < skew
2011-05-09 15:30:17 -04:00
< < " takeover " < < ( long long ) takeoverMS
2011-02-15 18:08:59 -05:00
< < " localTimeout " < < ( takeoverMS > 0 ) ;
}
void test ( ConnectionString & hostConn , string & lockName , unsigned seed ) {
return ;
}
2011-07-18 14:25:18 -04:00
bool run ( const string & , BSONObj & cmdObj , int , string & errmsg ,
2011-02-15 18:08:59 -05:00
BSONObjBuilder & result , bool ) {
Timer t ;
ConnectionString hostConn ( cmdObj [ " host " ] . String ( ) ,
ConnectionString : : SYNC ) ;
unsigned seed = ( unsigned ) number_field ( cmdObj , " seed " , 0 ) ;
int numThreads = ( int ) number_field ( cmdObj , " numThreads " , 4 ) ;
int wait = ( int ) number_field ( cmdObj , " wait " , 10000 ) ;
log ( ) < < " Starting " < < this - > name < < " with - " < < endl
< < " seed: " < < seed < < endl
< < " numThreads: " < < numThreads < < endl
< < " total wait: " < < wait < < endl < < endl ;
// Skew host clocks if needed
try {
skewClocks ( hostConn , cmdObj ) ;
}
catch ( DBException e ) {
2011-03-31 11:32:47 -04:00
errmsg = str : : stream ( ) < < " Clocks could not be skewed. " < < causedBy ( e ) ;
2011-02-15 18:08:59 -05:00
return false ;
}
count = 0 ;
keepGoing = true ;
vector < shared_ptr < boost : : thread > > threads ;
vector < shared_ptr < BSONObjBuilder > > results ;
for ( int i = 0 ; i < numThreads ; i + + ) {
results . push_back ( shared_ptr < BSONObjBuilder > ( new BSONObjBuilder ( ) ) ) ;
threads . push_back ( shared_ptr < boost : : thread > ( new boost : : thread (
boost : : bind ( & TestDistLockWithSkew : : runThread , this ,
hostConn , ( unsigned ) i , seed + i , boost : : ref ( cmdObj ) ,
boost : : ref ( * ( results [ i ] . get ( ) ) ) ) ) ) ) ;
}
sleepsecs ( wait / 1000 ) ;
keepGoing = false ;
bool errors = false ;
for ( unsigned i = 0 ; i < threads . size ( ) ; i + + ) {
threads [ i ] - > join ( ) ;
errors = errors | | results [ i ] . get ( ) - > obj ( ) [ " errors " ] . Bool ( ) ;
}
result . append ( " count " , count ) ;
result . append ( " errors " , errors ) ;
result . append ( " timeMS " , t . millis ( ) ) ;
return ! errors ;
}
/**
* Skews the clocks of a remote cluster by a particular amount , specified by
* the " skewHosts " element in a BSONObj .
*/
static void skewClocks ( ConnectionString & cluster , BSONObj & cmdObj ) {
vector < long long > skew ;
if ( cmdObj . hasField ( " skewHosts " ) ) {
bsonArrToNumVector < long long > ( cmdObj [ " skewHosts " ] , skew ) ;
}
2011-03-04 16:54:37 -05:00
else {
log ( logLvl ) < < " No host clocks to skew. " < < endl ;
return ;
2011-02-15 18:08:59 -05:00
}
2011-03-04 16:54:37 -05:00
log ( logLvl ) < < " Skewing clocks of hosts " < < cluster < < endl ;
2011-02-15 18:08:59 -05:00
unsigned s = 0 ;
for ( vector < long long > : : iterator i = skew . begin ( ) ; i ! = skew . end ( ) ; + + i , s + + ) {
ConnectionString server ( cluster . getServers ( ) [ s ] ) ;
ScopedDbConnection conn ( server ) ;
BSONObj result ;
try {
bool success = conn - > runCommand ( string ( " admin " ) , BSON ( " _skewClockCommand " < < 1 < < " skew " < < * i ) , result ) ;
2011-03-31 11:32:47 -04:00
uassert ( 13678 , str : : stream ( ) < < " Could not communicate with server " < < server . toString ( ) < < " in cluster " < < cluster . toString ( ) < < " to change skew by " < < * i , success ) ;
2011-02-15 18:08:59 -05:00
2011-03-04 16:54:37 -05:00
log ( logLvl + 1 ) < < " Skewed host " < < server < < " clock by " < < * i < < endl ;
2011-02-15 18:08:59 -05:00
}
catch ( . . . ) {
conn . done ( ) ;
throw ;
}
conn . done ( ) ;
}
}
// variables for test
thread_specific_ptr < DistributedLock > lock ;
AtomicUInt count ;
bool keepGoing ;
} testDistLockWithSkewCmd ;
/**
* Utility command to virtually skew the clock of a mongo server a particular amount .
* This skews the clock globally , per - thread skew is also possible .
*/
class SkewClockCommand : public Command {
public :
SkewClockCommand ( ) :
Command ( " _skewClockCommand " ) {
}
virtual void help ( stringstream & help ) const {
help < < " should not be calling this directly " < < endl ;
}
virtual bool slaveOk ( ) const {
return false ;
}
virtual bool adminOnly ( ) const {
return true ;
}
virtual LockType locktype ( ) const {
return NONE ;
}
2011-07-18 14:25:18 -04:00
bool run ( const string & , BSONObj & cmdObj , int , string & errmsg ,
2011-02-15 18:08:59 -05:00
BSONObjBuilder & result , bool ) {
long long skew = ( long long ) number_field ( cmdObj , " skew " , 0 ) ;
log ( ) < < " Adjusting jsTime() clock skew to " < < skew < < endl ;
jsTimeVirtualSkew ( skew ) ;
log ( ) < < " JSTime adjusted, now is " < < jsTime ( ) < < endl ;
return true ;
}
} testSkewClockCommand ;
2010-07-12 17:50:26 -04:00
}
2011-02-15 18:08:59 -05:00