tests/auto/qreadwritelock/tst_qreadwritelock.cpp
changeset 0 1918ee327afb
child 4 3b1da2848fc7
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/auto/qreadwritelock/tst_qreadwritelock.cpp	Mon Jan 11 14:00:40 2010 +0000
@@ -0,0 +1,1127 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the test suite of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** 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, Nokia gives you certain additional
+** rights.  These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+
+#include <QtTest/QtTest>
+#include <qcoreapplication.h>
+
+
+#include <qreadwritelock.h>
+#include <qmutex.h>
+#include <qthread.h>
+#include <qwaitcondition.h>
+
+#ifdef Q_OS_UNIX
+#include <unistd.h>
+#endif
+#if defined(Q_OS_WIN32) || defined(Q_OS_WINCE)
+#include <windows.h>
+#define sleep(X) Sleep(X)
+#endif
+
+//on solaris, threads that loop one 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 <stdio.h>
+
+//TESTED_CLASS=
+//TESTED_FILES=
+
+class tst_QReadWriteLock : public QObject
+{
+    Q_OBJECT
+public:
+    tst_QReadWriteLock();
+    virtual ~tst_QReadWriteLock();
+
+
+/*
+    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();
+};
+
+tst_QReadWriteLock::tst_QReadWriteLock()
+{
+
+}
+
+tst_QReadWriteLock::~tst_QReadWriteLock()
+{
+
+}
+
+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<runs; ++i) {
+        rwlock.lockForRead();
+        rwlock.unlock();
+    }
+}
+
+void tst_QReadWriteLock::writeLockUnlockLoop()
+{
+    QReadWriteLock rwlock;
+    int runs=10000;
+    int i;
+    for (i=0; i<runs; ++i) {
+        rwlock.lockForWrite();
+        rwlock.unlock();
+    }
+}
+
+
+void tst_QReadWriteLock::readLockLoop()
+{
+    QReadWriteLock rwlock;
+    int runs=10000;
+    int i;
+    for (i=0; i<runs; ++i) {
+        rwlock.lockForRead();
+    }
+    for (i=0; i<runs; ++i) {
+        rwlock.unlock();
+    }
+}
+
+void tst_QReadWriteLock::writeLockLoop()
+{
+    /*
+        If you include this, the test should print one line
+        and then block.
+    */
+#if 0
+    QReadWriteLock rwlock;
+    int runs=10000;
+    int i;
+    for (i=0; i<runs; ++i) {
+        rwlock.lockForWrite();
+        qDebug("I am going to block now.");
+    }
+#endif
+}
+
+void tst_QReadWriteLock::readWriteLockUnlockLoop()
+{
+    QReadWriteLock rwlock;
+    int runs=10000;
+    int i;
+    for (i=0; i<runs; ++i) {
+        rwlock.lockForRead();
+        rwlock.unlock();
+        rwlock.lockForWrite();
+        rwlock.unlock();
+    }
+
+}
+
+QAtomicInt lockCount(0);
+QReadWriteLock readWriteLock;
+QSemaphore testsTurn;
+QSemaphore threadsTurn;
+
+
+void tst_QReadWriteLock::tryReadLock()
+{
+    QReadWriteLock rwlock;
+    QVERIFY(rwlock.tryLockForRead());
+    rwlock.unlock();
+    QVERIFY(rwlock.tryLockForRead());
+    rwlock.unlock();
+
+    rwlock.lockForRead();
+    rwlock.lockForRead();
+    QVERIFY(rwlock.tryLockForRead());
+    rwlock.unlock();
+    rwlock.unlock();
+    rwlock.unlock();
+
+    rwlock.lockForWrite();
+    QVERIFY(!rwlock.tryLockForRead());
+    rwlock.unlock();
+
+    // functionality test
+    {
+        class Thread : public QThread
+        {
+        public:
+            void run()
+            {
+                testsTurn.release();
+
+                threadsTurn.acquire();
+                QVERIFY(!readWriteLock.tryLockForRead());
+                testsTurn.release();
+
+                threadsTurn.acquire();
+                QVERIFY(readWriteLock.tryLockForRead());
+                lockCount.ref();
+                QVERIFY(readWriteLock.tryLockForRead());
+                lockCount.ref();
+                lockCount.deref();
+                readWriteLock.unlock();
+                lockCount.deref();
+                readWriteLock.unlock();
+                testsTurn.release();
+
+                threadsTurn.acquire();
+                QTime timer;
+                timer.start();
+                QVERIFY(!readWriteLock.tryLockForRead(1000));
+                QVERIFY(timer.elapsed() >= 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:
+            void run()
+            {
+                testsTurn.release();
+
+                threadsTurn.acquire();
+                Q_ASSERT(!readWriteLock.tryLockForWrite());
+                testsTurn.release();
+
+                threadsTurn.acquire();
+                Q_ASSERT(readWriteLock.tryLockForWrite());
+                Q_ASSERT(lockCount.testAndSetRelaxed(0, 1));
+                Q_ASSERT(lockCount.testAndSetRelaxed(1, 0));
+                readWriteLock.unlock();
+                testsTurn.release();
+
+                threadsTurn.acquire();
+                Q_ASSERT(!readWriteLock.tryLockForWrite(1000));
+                testsTurn.release();
+
+                threadsTurn.acquire();
+                Q_ASSERT(readWriteLock.tryLockForWrite(1000));
+                Q_ASSERT(lockCount.testAndSetRelaxed(0, 1));
+                Q_ASSERT(lockCount.testAndSetRelaxed(1, 0));
+                readWriteLock.unlock();
+                testsTurn.release();
+
+                threadsTurn.acquire();
+            }
+        };
+
+        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();
+    }
+}
+
+bool threadDone;
+volatile bool 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==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==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()<runTime)  {
+            testRwlock.lockForRead();
+            if(print) printf("reading\n");
+            if (holdTime) msleep(holdTime);
+            testRwlock.unlock();
+            if (waitTime) msleep(waitTime);
+        }
+    }
+};
+
+/*
+    for(runTime msecs)
+        write-lock
+        msleep(holdTime msecs)
+        release lock
+        msleep(waitTime msecs)
+*/
+class WriteLockLoopThread : public QThread
+{
+public:
+    QReadWriteLock &testRwlock;
+    int runTime;
+    int holdTime;
+    int waitTime;
+    bool print;
+    QTime t;
+    inline WriteLockLoopThread(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() < runTime)  {
+            testRwlock.lockForWrite();
+            if (print) printf(".");
+            if (holdTime) msleep(holdTime);
+            testRwlock.unlock();
+            if (waitTime) msleep(waitTime);
+        }
+    }
+};
+
+volatile int count=0;
+
+/*
+    for(runTime msecs)
+        write-lock
+        count to maxval
+        set count to 0
+        release lock
+        msleep waitTime
+*/
+class WriteLockCountThread : public QThread
+{
+public:
+    QReadWriteLock &testRwlock;
+    int runTime;
+    int waitTime;
+    int maxval;
+    QTime t;
+    inline WriteLockCountThread(QReadWriteLock &l, int runTime, int waitTime, int maxval)
+    :testRwlock(l)
+    ,runTime(runTime)
+    ,waitTime(waitTime)
+    ,maxval(maxval)
+    { }
+    void run()
+    {
+        t.start();
+        while (t.elapsed() < runTime)  {
+            testRwlock.lockForWrite();
+            if(count)
+                qFatal("Non-zero count at start of write! (%d)",count );
+//            printf(".");
+            int i;
+            for(i=0; i<maxval; ++i) {
+                volatile int lc=count;
+                ++lc;
+                count=lc;
+            }
+            count=0;
+            testRwlock.unlock();
+            msleep(waitTime);
+        }
+    }
+};
+
+/*
+    for(runTime msecs)
+        read-lock
+        verify count==0
+        release lock
+        msleep waitTime
+*/
+class ReadLockCountThread : public QThread
+{
+public:
+    QReadWriteLock &testRwlock;
+    int runTime;
+    int waitTime;
+    QTime t;
+    inline ReadLockCountThread(QReadWriteLock &l, int runTime, int waitTime)
+    :testRwlock(l)
+    ,runTime(runTime)
+    ,waitTime(waitTime)
+    { }
+    void run()
+    {
+        t.start();
+        while (t.elapsed() < runTime)  {
+            testRwlock.lockForRead();
+            if(count)
+                qFatal("Non-zero count at Read! (%d)",count );
+            testRwlock.unlock();
+            msleep(waitTime);
+        }
+    }
+};
+
+
+/*
+    A writer aquires a read-lock, a reader locks
+    the writer releases the lock, the reader gets the lock
+*/
+void tst_QReadWriteLock::readLockBlockRelease()
+{
+    QReadWriteLock testLock;
+    testLock.lockForWrite();
+    threadDone=false;
+    ReadLockThread rlt(testLock);
+    rlt.start();
+    sleep(1);
+    testLock.unlock();
+    rlt.wait();
+    QVERIFY(threadDone);
+}
+
+/*
+    writer1 aquires a read-lock, writer2 blocks,
+    writer1 releases the lock, writer2 gets the lock
+*/
+void tst_QReadWriteLock::writeLockBlockRelease()
+{
+    QReadWriteLock testLock;
+    testLock.lockForWrite();
+    threadDone=false;
+    WriteLockThread wlt(testLock);
+    wlt.start();
+    sleep(1);
+    testLock.unlock();
+    wlt.wait();
+    QVERIFY(threadDone);
+}
+/*
+    Two readers aquire a read-lock, one writer attempts a write block,
+    the readers release their locks, the writer gets the lock.
+*/
+void tst_QReadWriteLock::multipleReadersBlockRelease()
+{
+
+    QReadWriteLock testLock;
+    release=false;
+    threadDone=false;
+    ReadLockReleasableThread rlt1(testLock);
+    ReadLockReleasableThread rlt2(testLock);
+    rlt1.start();
+    rlt2.start();
+    sleep(1);
+    WriteLockThread wlt(testLock);
+    wlt.start();
+    sleep(1);
+    release=true;
+    wlt.wait();
+    rlt1.wait();
+    rlt2.wait();
+    QVERIFY(threadDone);
+}
+
+/*
+    Multiple readers locks and unlocks a lock.
+*/
+void tst_QReadWriteLock::multipleReadersLoop()
+{
+    int time=500;
+    int hold=250;
+    int wait=0;
+#if defined (Q_OS_HPUX)
+    const int numthreads=50;
+#elif defined(Q_OS_VXWORKS)
+    const int numthreads=40;
+#else
+    const int numthreads=75;
+#endif
+    QReadWriteLock testLock;
+    ReadLockLoopThread *threads[numthreads];
+    int i;
+    for (i=0; i<numthreads; ++i)
+        threads[i] = new ReadLockLoopThread(testLock, time, hold, wait);
+    for (i=0; i<numthreads; ++i)
+        threads[i]->start();
+    for (i=0; i<numthreads; ++i)
+        threads[i]->wait();
+    for (i=0; i<numthreads; ++i)
+        delete threads[i];
+}
+
+/*
+    Multiple writers locks and unlocks a lock.
+*/
+void tst_QReadWriteLock::multipleWritersLoop()
+{
+        int time=500;
+        int wait=0;
+        int hold=0;
+        const int numthreads=50;
+        QReadWriteLock testLock;
+        WriteLockLoopThread *threads[numthreads];
+        int i;
+        for (i=0; i<numthreads; ++i)
+            threads[i] = new WriteLockLoopThread(testLock, time, hold, wait);
+        for (i=0; i<numthreads; ++i)
+            threads[i]->start();
+        for (i=0; i<numthreads; ++i)
+            threads[i]->wait();
+        for (i=0; i<numthreads; ++i)
+            delete threads[i];
+}
+
+/*
+    Multiple readers and writers locks and unlocks a lock.
+*/
+void tst_QReadWriteLock::multipleReadersWritersLoop()
+{
+        //int time=INT_MAX;
+        int time=10000;
+        int readerThreads=20;
+        int readerWait=0;
+        int readerHold=1;
+
+        int writerThreads=2;
+        int writerWait=500;
+        int writerHold=50;
+
+        QReadWriteLock testLock;
+        ReadLockLoopThread  *readers[1024];
+        WriteLockLoopThread *writers[1024];
+        int i;
+
+        for (i=0; i<readerThreads; ++i)
+            readers[i] = new ReadLockLoopThread(testLock, time, readerHold, readerWait, false);
+        for (i=0; i<writerThreads; ++i)
+            writers[i] = new WriteLockLoopThread(testLock, time, writerHold, writerWait, false);
+
+        for (i=0; i<readerThreads; ++i)
+            readers[i]->start(QThread::NormalPriority);
+        for (i=0; i<writerThreads; ++i)
+            writers[i]->start(QThread::IdlePriority);
+
+        for (i=0; i<readerThreads; ++i)
+            readers[i]->wait();
+        for (i=0; i<writerThreads; ++i)
+            writers[i]->wait();
+
+        for (i=0; i<readerThreads; ++i)
+            delete readers[i];
+        for (i=0; i<writerThreads; ++i)
+            delete writers[i];
+}
+
+/*
+    Writers increment a variable from 0 to maxval, then reset it to 0.
+    Readers verify that the variable remains at 0.
+*/
+void tst_QReadWriteLock::countingTest()
+{
+        //int time=INT_MAX;
+        int time=10000;
+        int readerThreads=20;
+        int readerWait=1;
+
+        int writerThreads=3;
+        int writerWait=150;
+        int maxval=10000;
+
+        QReadWriteLock testLock;
+        ReadLockCountThread  *readers[1024];
+        WriteLockCountThread *writers[1024];
+        int i;
+
+        for (i=0; i<readerThreads; ++i)
+            readers[i] = new ReadLockCountThread(testLock, time,  readerWait);
+        for (i=0; i<writerThreads; ++i)
+            writers[i] = new WriteLockCountThread(testLock, time,  writerWait, maxval);
+
+        for (i=0; i<readerThreads; ++i)
+            readers[i]->start(QThread::NormalPriority);
+        for (i=0; i<writerThreads; ++i)
+            writers[i]->start(QThread::LowestPriority);
+
+        for (i=0; i<readerThreads; ++i)
+            readers[i]->wait();
+        for (i=0; i<writerThreads; ++i)
+            writers[i]->wait();
+
+        for (i=0; i<readerThreads; ++i)
+            delete readers[i];
+        for (i=0; i<writerThreads; ++i)
+            delete writers[i];
+}
+
+void tst_QReadWriteLock::limitedReaders()
+{
+
+};
+
+/*
+    Test a race-condition that may happen if one thread is in unlock() while
+    another thread deletes the rw-lock.
+
+    MainThread              DeleteOnUnlockThread
+
+    write-lock
+    unlock
+      |                     write-lock
+      |                     unlock
+      |                     delete lock
+    deref d inside unlock
+*/
+class DeleteOnUnlockThread : public QThread
+{
+public:
+    DeleteOnUnlockThread(QReadWriteLock **lock, QWaitCondition *startup, QMutex *waitMutex)
+    :m_lock(lock), m_startup(startup), m_waitMutex(waitMutex) {}
+    void run()
+    {
+        m_waitMutex->lock();
+        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() <millisecs)
+        {
+            ++count;
+        }
+    }
+    {
+        QReadWriteLock rwlock;
+        QTime t;
+        t.start();
+        while(t.elapsed() <millisecs)
+        {
+            rwlock.lockForRead();
+            rwlock.unlock();
+            ++read;
+        }
+    }
+    {
+        QReadWriteLock rwlock;
+        QTime t;
+        t.start();
+        while(t.elapsed() <millisecs)
+        {
+            rwlock.lockForWrite();
+            rwlock.unlock();
+            ++write;
+        }
+    }
+
+    qDebug("during %d millisecs:", millisecs);
+    qDebug("counted to %u", count);
+    qDebug("%u uncontended read locks/unlocks", read);
+    qDebug("%u uncontended write locks/unlocks", write);
+}
+
+enum { RecursiveLockCount = 10 };
+
+void tst_QReadWriteLock::recursiveReadLock()
+{
+    // thread to attempt locking for writing while the test recursively locks for reading
+    class RecursiveReadLockThread : public QThread
+    {
+    public:
+        QReadWriteLock *lock;
+        bool tryLockForWriteResult;
+
+        void run()
+        {
+            testsTurn.release();
+
+            // test is recursively locking for writing
+            for (int i = 0; i < RecursiveLockCount; ++i) {
+                threadsTurn.acquire();
+                tryLockForWriteResult = lock->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"