/**************************************************************************** ** ** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). ** Contact: http://www.qt-project.org/legal ** ** This file is part of the test suite of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and Digia. For licensing terms and ** conditions see http://qt.digia.com/licensing. For further information ** use the contact form at http://qt.digia.com/contact-us. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 2.1 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 2.1 requirements ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** In addition, as a special exception, Digia gives you certain additional ** rights. These rights are described in the Digia Qt LGPL Exception ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 3.0 as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL included in the ** packaging of this file. Please review the following information to ** ensure the GNU General Public License version 3.0 requirements will be ** met: http://www.gnu.org/copyleft/gpl.html. ** ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include #include #include #include #include #include #ifdef Q_OS_UNIX #include #endif #if defined(Q_OS_WIN32) || defined(Q_OS_WINCE) #include #define sleep(X) Sleep(X) #endif //on solaris, threads that loop on the release bool variable //needs to sleep more than 1 usec. #ifdef Q_OS_SOLARIS # define RWTESTSLEEP usleep(10); #else # define RWTESTSLEEP usleep(1); #endif #include class tst_QReadWriteLock : public QObject { Q_OBJECT /* Singlethreaded tests */ private slots: void constructDestruct(); void readLockUnlock(); void writeLockUnlock(); void readLockUnlockLoop(); void writeLockUnlockLoop(); void readLockLoop(); void writeLockLoop(); void readWriteLockUnlockLoop(); void tryReadLock(); void tryWriteLock(); /* Multithreaded tests */ private slots: void readLockBlockRelease(); void writeLockBlockRelease(); void multipleReadersBlockRelease(); void multipleReadersLoop(); void multipleWritersLoop(); void multipleReadersWritersLoop(); void countingTest(); void limitedReaders(); void deleteOnUnlock(); /* Performance tests */ private slots: void uncontendedLocks(); // recursive locking tests void recursiveReadLock(); void recursiveWriteLock(); }; void tst_QReadWriteLock::constructDestruct() { { QReadWriteLock rwlock; } } void tst_QReadWriteLock::readLockUnlock() { QReadWriteLock rwlock; rwlock.lockForRead(); rwlock.unlock(); } void tst_QReadWriteLock::writeLockUnlock() { QReadWriteLock rwlock; rwlock.lockForWrite(); rwlock.unlock(); } void tst_QReadWriteLock::readLockUnlockLoop() { QReadWriteLock rwlock; int runs=10000; int i; for (i=0; i= 1000); testsTurn.release(); threadsTurn.acquire(); timer.start(); QVERIFY(readWriteLock.tryLockForRead(1000)); QVERIFY(timer.elapsed() <= 1000); lockCount.ref(); QVERIFY(readWriteLock.tryLockForRead(1000)); lockCount.ref(); lockCount.deref(); readWriteLock.unlock(); lockCount.deref(); readWriteLock.unlock(); testsTurn.release(); threadsTurn.acquire(); } }; Thread thread; thread.start(); testsTurn.acquire(); readWriteLock.lockForWrite(); QVERIFY(lockCount.testAndSetRelaxed(0, 1)); threadsTurn.release(); testsTurn.acquire(); QVERIFY(lockCount.testAndSetRelaxed(1, 0)); readWriteLock.unlock(); threadsTurn.release(); testsTurn.acquire(); readWriteLock.lockForWrite(); QVERIFY(lockCount.testAndSetRelaxed(0, 1)); threadsTurn.release(); testsTurn.acquire(); QVERIFY(lockCount.testAndSetRelaxed(1, 0)); readWriteLock.unlock(); threadsTurn.release(); // stop thread testsTurn.acquire(); threadsTurn.release(); thread.wait(); } } void tst_QReadWriteLock::tryWriteLock() { { QReadWriteLock rwlock; QVERIFY(rwlock.tryLockForWrite()); rwlock.unlock(); QVERIFY(rwlock.tryLockForWrite()); rwlock.unlock(); rwlock.lockForWrite(); QVERIFY(!rwlock.tryLockForWrite()); QVERIFY(!rwlock.tryLockForWrite()); rwlock.unlock(); rwlock.lockForRead(); QVERIFY(!rwlock.tryLockForWrite()); rwlock.unlock(); } { QReadWriteLock rwlock(QReadWriteLock::Recursive); QVERIFY(rwlock.tryLockForWrite()); rwlock.unlock(); QVERIFY(rwlock.tryLockForWrite()); rwlock.unlock(); rwlock.lockForWrite(); QVERIFY(rwlock.tryLockForWrite()); QVERIFY(rwlock.tryLockForWrite()); rwlock.unlock(); rwlock.unlock(); rwlock.unlock(); rwlock.lockForRead(); QVERIFY(!rwlock.tryLockForWrite()); rwlock.unlock(); } // functionality test { class Thread : public QThread { public: Thread() : failureCount(0) { } void run() { testsTurn.release(); threadsTurn.acquire(); if (readWriteLock.tryLockForWrite()) failureCount++; testsTurn.release(); threadsTurn.acquire(); if (!readWriteLock.tryLockForWrite()) failureCount++; if (!lockCount.testAndSetRelaxed(0, 1)) failureCount++; if (!lockCount.testAndSetRelaxed(1, 0)) failureCount++; readWriteLock.unlock(); testsTurn.release(); threadsTurn.acquire(); if (readWriteLock.tryLockForWrite(1000)) failureCount++; testsTurn.release(); threadsTurn.acquire(); if (!readWriteLock.tryLockForWrite(1000)) failureCount++; if (!lockCount.testAndSetRelaxed(0, 1)) failureCount++; if (!lockCount.testAndSetRelaxed(1, 0)) failureCount++; readWriteLock.unlock(); testsTurn.release(); threadsTurn.acquire(); } int failureCount; }; Thread thread; thread.start(); testsTurn.acquire(); readWriteLock.lockForRead(); lockCount.ref(); threadsTurn.release(); testsTurn.acquire(); lockCount.deref(); readWriteLock.unlock(); threadsTurn.release(); testsTurn.acquire(); readWriteLock.lockForRead(); lockCount.ref(); threadsTurn.release(); testsTurn.acquire(); lockCount.deref(); readWriteLock.unlock(); threadsTurn.release(); // stop thread testsTurn.acquire(); threadsTurn.release(); thread.wait(); QCOMPARE(thread.failureCount, 0); } } bool threadDone; QAtomicInt release; /* write-lock unlock set threadone */ class WriteLockThread : public QThread { public: QReadWriteLock &testRwlock; inline WriteLockThread(QReadWriteLock &l) : testRwlock(l) { } void run() { testRwlock.lockForWrite(); testRwlock.unlock(); threadDone=true; } }; /* read-lock unlock set threadone */ class ReadLockThread : public QThread { public: QReadWriteLock &testRwlock; inline ReadLockThread(QReadWriteLock &l) : testRwlock(l) { } void run() { testRwlock.lockForRead(); testRwlock.unlock(); threadDone=true; } }; /* write-lock wait for release==true unlock */ class WriteLockReleasableThread : public QThread { public: QReadWriteLock &testRwlock; inline WriteLockReleasableThread(QReadWriteLock &l) : testRwlock(l) { } void run() { testRwlock.lockForWrite(); while(release.load()==false) { RWTESTSLEEP } testRwlock.unlock(); } }; /* read-lock wait for release==true unlock */ class ReadLockReleasableThread : public QThread { public: QReadWriteLock &testRwlock; inline ReadLockReleasableThread(QReadWriteLock &l) : testRwlock(l) { } void run() { testRwlock.lockForRead(); while(release.load()==false) { RWTESTSLEEP } testRwlock.unlock(); } }; /* for(runTime msecs) read-lock msleep(holdTime msecs) release lock msleep(waitTime msecs) */ class ReadLockLoopThread : public QThread { public: QReadWriteLock &testRwlock; int runTime; int holdTime; int waitTime; bool print; QTime t; inline ReadLockLoopThread(QReadWriteLock &l, int runTime, int holdTime=0, int waitTime=0, bool print=false) :testRwlock(l) ,runTime(runTime) ,holdTime(holdTime) ,waitTime(waitTime) ,print(print) { } void run() { t.start(); while (t.elapsed()start(); for (i=0; iwait(); for (i=0; istart(); for (i=0; iwait(); for (i=0; istart(QThread::NormalPriority); for (i=0; istart(QThread::IdlePriority); for (i=0; iwait(); for (i=0; iwait(); for (i=0; istart(QThread::NormalPriority); for (i=0; istart(QThread::LowestPriority); for (i=0; iwait(); for (i=0; iwait(); for (i=0; ilock(); m_startup->wakeAll(); m_waitMutex->unlock(); // DeleteOnUnlockThread and the main thread will race from this point (*m_lock)->lockForWrite(); (*m_lock)->unlock(); delete *m_lock; } private: QReadWriteLock **m_lock; QWaitCondition *m_startup; QMutex *m_waitMutex; }; void tst_QReadWriteLock::deleteOnUnlock() { QReadWriteLock *lock = 0; QWaitCondition startup; QMutex waitMutex; DeleteOnUnlockThread thread2(&lock, &startup, &waitMutex); QTime t; t.start(); while(t.elapsed() < 4000) { lock = new QReadWriteLock(); waitMutex.lock(); lock->lockForWrite(); thread2.start(); startup.wait(&waitMutex); waitMutex.unlock(); // DeleteOnUnlockThread and the main thread will race from this point lock->unlock(); thread2.wait(); } } void tst_QReadWriteLock::uncontendedLocks() { uint read=0; uint write=0; uint count=0; int millisecs=1000; { QTime t; t.start(); while(t.elapsed() tryLockForWrite(); testsTurn.release(); } // test is releasing recursive write lock for (int i = 0; i < RecursiveLockCount - 1; ++i) { threadsTurn.acquire(); tryLockForWriteResult = lock->tryLockForWrite(); testsTurn.release(); } // after final unlock in test, we should get the lock threadsTurn.acquire(); tryLockForWriteResult = lock->tryLockForWrite(); testsTurn.release(); // cleanup threadsTurn.acquire(); lock->unlock(); testsTurn.release(); // test will lockForRead(), then we will lockForWrite() // (and block), purpose is to ensure that the test can // recursive lockForRead() even with a waiting writer threadsTurn.acquire(); // testsTurn.release(); // ### do not release here, the test uses tryAcquire() lock->lockForWrite(); lock->unlock(); } }; // init QReadWriteLock lock(QReadWriteLock::Recursive); RecursiveReadLockThread thread; thread.lock = &lock; thread.start(); testsTurn.acquire(); // verify that we can get multiple read locks in the same thread for (int i = 0; i < RecursiveLockCount; ++i) { QVERIFY(lock.tryLockForRead()); threadsTurn.release(); testsTurn.acquire(); QVERIFY(!thread.tryLockForWriteResult); } // have to unlock the same number of times that we locked for (int i = 0;i < RecursiveLockCount - 1; ++i) { lock.unlock(); threadsTurn.release(); testsTurn.acquire(); QVERIFY(!thread.tryLockForWriteResult); } // after the final unlock, we should be able to get the write lock lock.unlock(); threadsTurn.release(); testsTurn.acquire(); QVERIFY(thread.tryLockForWriteResult); threadsTurn.release(); // check that recursive read locking works even when we have a waiting writer testsTurn.acquire(); QVERIFY(lock.tryLockForRead()); threadsTurn.release(); testsTurn.tryAcquire(1, 1000); QVERIFY(lock.tryLockForRead()); lock.unlock(); lock.unlock(); // cleanup QVERIFY(thread.wait()); } void tst_QReadWriteLock::recursiveWriteLock() { // thread to attempt locking for reading while the test recursively locks for writing class RecursiveWriteLockThread : public QThread { public: QReadWriteLock *lock; bool tryLockForReadResult; void run() { testsTurn.release(); // test is recursively locking for writing for (int i = 0; i < RecursiveLockCount; ++i) { threadsTurn.acquire(); tryLockForReadResult = lock->tryLockForRead(); testsTurn.release(); } // test is releasing recursive write lock for (int i = 0; i < RecursiveLockCount - 1; ++i) { threadsTurn.acquire(); tryLockForReadResult = lock->tryLockForRead(); testsTurn.release(); } // after final unlock in test, we should get the lock threadsTurn.acquire(); tryLockForReadResult = lock->tryLockForRead(); testsTurn.release(); // cleanup lock->unlock(); } }; // init QReadWriteLock lock(QReadWriteLock::Recursive); RecursiveWriteLockThread thread; thread.lock = &lock; thread.start(); testsTurn.acquire(); // verify that we can get multiple read locks in the same thread for (int i = 0; i < RecursiveLockCount; ++i) { QVERIFY(lock.tryLockForWrite()); threadsTurn.release(); testsTurn.acquire(); QVERIFY(!thread.tryLockForReadResult); } // have to unlock the same number of times that we locked for (int i = 0;i < RecursiveLockCount - 1; ++i) { lock.unlock(); threadsTurn.release(); testsTurn.acquire(); QVERIFY(!thread.tryLockForReadResult); } // after the final unlock, thread should be able to get the read lock lock.unlock(); threadsTurn.release(); testsTurn.acquire(); QVERIFY(thread.tryLockForReadResult); // cleanup QVERIFY(thread.wait()); } QTEST_MAIN(tst_QReadWriteLock) #include "tst_qreadwritelock.moc"