// ********************************************************************** // // Copyright (c) 2003 // ZeroC, Inc. // Billerica, MA, USA // // All Rights Reserved. // // Ice is free software; you can redistribute it and/or modify it under // the terms of the GNU General Public License version 2 as published by // the Free Software Foundation. // // ********************************************************************** #include #include #include using namespace std; using namespace IceUtil; static const std::string rwRecMutexTestName("read/write recursive mutex"); class RWRecMutexTestThread : public Thread { public: RWRecMutexTestThread(RWRecMutex& m) : _mutex(m), _tryLock(false) { } void waitTryLock() { Mutex::Lock lock(_tryLockMutex); while(!_tryLock) { _tryLockCond.wait(lock); } } protected: RWRecMutex& _mutex; bool _tryLock; // // Use native Condition variable here, not Monitor. // Cond _tryLockCond; Mutex _tryLockMutex; }; class RWRecMutexReadTestThread : public RWRecMutexTestThread { public: RWRecMutexReadTestThread(RWRecMutex& m) : RWRecMutexTestThread(m) { } virtual void run() { RWRecMutex::TryRLock tlock(_mutex); test(tlock.acquired()); { Mutex::Lock lock(_tryLockMutex); _tryLock = true; } _tryLockCond.signal(); RWRecMutex::RLock lock(_mutex); } }; class RWRecMutexReadTestThread2 : public RWRecMutexTestThread { public: RWRecMutexReadTestThread2(RWRecMutex& m) : RWRecMutexTestThread(m) { } virtual void run() { RWRecMutex::TryRLock tlock(_mutex); test(!tlock.acquired()); { Mutex::Lock lock(_tryLockMutex); _tryLock = true; } _tryLockCond.signal(); RWRecMutex::RLock lock(_mutex); } }; class RWRecMutexUpgradeReadThread : public Thread { public: RWRecMutexUpgradeReadThread(RWRecMutex& m) : _mutex(m), _unlock(false), _lock(false) { } virtual void run() { RWRecMutex::RLock lock(_mutex); signalLock(); waitUnlock(); } void waitUnlock() { Mutex::Lock lock(_unlockMutex); while(!_unlock) { _unlockCond.wait(lock); } } void signalUnlock() { Mutex::Lock lock(_unlockMutex); _unlock = true; _unlockCond.signal(); } void signalLock() { Mutex::Lock lock(_lockMutex); _lock = true; _lockCond.signal(); } void waitLock() { Mutex::Lock lock(_lockMutex); while(!_lock) { _lockCond.wait(lock); } } private: RWRecMutex& _mutex; // // Use native Condition variable here, not Monitor. // Cond _unlockCond; Mutex _unlockMutex; bool _unlock; Cond _lockCond; Mutex _lockMutex; bool _lock; }; typedef Handle RWRecMutexUpgradeReadThreadPtr; class RWRecMutexUpgradeTestThread : public Thread { public: RWRecMutexUpgradeTestThread(RWRecMutex& m) : _mutex(m), _lock(false), _upgradeAcquired(false) { } virtual void run() { RWRecMutex::RLock lock(_mutex); signalLock(); lock.upgrade(); _upgradeAcquired = true; } void signalLock() { Mutex::Lock lock(_lockMutex); _lock = true; _lockCond.signal(); } void waitLock() { Mutex::Lock lock(_lockMutex); while(!_lock) { _lockCond.wait(lock); } } bool upgradeAcquired() const { return _upgradeAcquired; } private: RWRecMutex& _mutex; // // Use native Condition variable here, not Monitor. // Cond _lockCond; Mutex _lockMutex; bool _lock; bool _upgradeAcquired; }; typedef Handle RWRecMutexUpgradeTestThreadPtr; class RWRecMutexWriteTestThread : public RWRecMutexTestThread { public: RWRecMutexWriteTestThread(RWRecMutex& m) : RWRecMutexTestThread(m) { } virtual void run() { RWRecMutex::TryWLock tlock(_mutex); test(!tlock.acquired()); { Mutex::Lock lock(_tryLockMutex); _tryLock = true; } _tryLockCond.signal(); RWRecMutex::WLock lock(_mutex); } }; typedef Handle RWRecMutexTestThreadPtr; RWRecMutexTest::RWRecMutexTest() : TestBase(rwRecMutexTestName) { } void RWRecMutexTest::run() { RWRecMutex mutex; RWRecMutexTestThreadPtr t; ThreadControl control; // TEST: TryLock (read) { RWRecMutex::RLock rlock(mutex); // RLock testing test(rlock.acquired()); try { rlock.acquire(); test(false); } catch(const ThreadLockedException&) { // Expected } try { rlock.tryAcquire(); test(false); } catch(const ThreadLockedException&) { // Expected } test(rlock.acquired()); rlock.release(); test(!rlock.acquired()); try { rlock.release(); test(false); } catch(const ThreadLockedException&) { // Expected } test(rlock.tryAcquire() == true); test(rlock.acquired()); RWRecMutex::RLock rlock2(mutex); RWRecMutex::TryRLock trlock(mutex); test(trlock.acquired()); RWRecMutex::TryWLock twlock(mutex); test(!twlock.acquired()); RWRecMutex::TryWLock twlock2(mutex, Time::milliSeconds(10)); test(!twlock2.acquired()); } // TEST: TryLock (write) { RWRecMutex::WLock wlock(mutex); // WLock testing test(wlock.acquired()); try { wlock.acquire(); test(false); } catch(const ThreadLockedException&) { // Expected } try { wlock.tryAcquire(); test(false); } catch(const ThreadLockedException&) { // Expected } test(wlock.acquired()); wlock.release(); test(!wlock.acquired()); try { wlock.release(); test(false); } catch(const ThreadLockedException&) { // Expected } test(wlock.tryAcquire() == true); test(wlock.acquired()); RWRecMutex::TryRLock trlock(mutex); test(!trlock.acquired()); RWRecMutex::TryRLock trlock2(mutex, Time::milliSeconds(10)); test(!trlock2.acquired()); RWRecMutex::TryWLock twlock(mutex); test(twlock.acquired()); RWRecMutex::TryWLock twlock2(mutex, Time::milliSeconds(10)); test(twlock2.acquired()); } // TEST: read lock { RWRecMutex::RLock rlock(mutex); // TEST: Start thread, try to acquire the mutex. t = new RWRecMutexReadTestThread(mutex); control = t->start(); // TEST: Wait until the tryLock has been tested. t->waitTryLock(); } // // TEST: Once the mutex has been released, the thread should // acquire the mutex and then terminate. // control.join(); // TEST: write lock { RWRecMutex::WLock wlock(mutex); // TEST: Start thread, try to acquire the mutex. t = new RWRecMutexWriteTestThread(mutex); control = t->start(); // TEST: Wait until the tryLock has been tested. t->waitTryLock(); } // // TEST: Once the mutex has been released, the thread should // acquire the mutex and then terminate. // control.join(); // TEST: Lock precedence. Writers have precedence over readers. { RWRecMutex::RLock rlock(mutex); // Start thread that tries to acquire write lock t = new RWRecMutexWriteTestThread(mutex); control = t->start(); // TEST: Wait until the tryLock has been tested. The thread is // now waiting on a write lock. t->waitTryLock(); // It's necessary for a small sleep here to ensure that the // thread is actually waiting on a write lock. ThreadControl::sleep(Time::seconds(1)); RWRecMutex::TryRLock trlock(mutex); test(!trlock.acquired()); } // // TEST: Once the mutex has been released, the thread should // acquire the mutex and then terminate. // control.join(); // TEST: Lock upgrading. { RWRecMutex::RLock rlock(mutex); // // Mutex now holds a write lock. // mutex.upgrade(); // Start thread that tries to acquire write lock t = new RWRecMutexReadTestThread2(mutex); control = t->start(); // TEST: Wait until the tryLock has been tested. The thread is // now waiting on a read lock. t->waitTryLock(); // It's necessary for a small sleep here to ensure that the // thread is actually waiting on a read lock. ThreadControl::sleep(Time::seconds(1)); } // // TEST: Once the mutex has been released, the thread should // acquire the mutex and then terminate. // control.join(); // // COMPILERBUG: Under Linux with gcc this causes the Mutex test to // fail for some unknown reason. After spending more than enough // time looking into this problem it was decided that this is a // compiler bug of some sort. // #if !(defined(__linux) && defined(__GNUC__)) // TEST: Lock upgrading. This time a reader thread is started // first. { RWRecMutexUpgradeReadThreadPtr t1 = new RWRecMutexUpgradeReadThread(mutex); control = t1->start(); // Wait for the thread to acquire the read lock. t1->waitLock(); // Spawn a thread to try acquiring the lock RWRecMutexUpgradeTestThreadPtr t2 = new RWRecMutexUpgradeTestThread(mutex); ThreadControl control2 = t2->start(); t2->waitLock(); // // Small sleep to find out whether the thread actually // terminates (which means that the write lock upgrade was // mistakenly acquired). // ThreadControl::sleep(Time::seconds(1)); test(!t2->upgradeAcquired()); // // A read lock at this point should fail. // RWRecMutex::TryRLock trlock(mutex); test(!trlock.acquired()); // // As should a write lock. // RWRecMutex::TryWLock twlock(mutex); test(!twlock.acquired()); // // Once the read lock is released then the upgrade should // succeed & the thread should terminate. // t1->signalUnlock(); control2.join(); control.join(); // // Now both a read & write lock should be available. // { RWRecMutex::WLock rlock2(mutex); } { RWRecMutex::RLock rlock2(mutex); } } #endif }