From 2e72a8b19ea6c674fb4777860dac50faa5d387e6 Mon Sep 17 00:00:00 2001 From: Thiago Macieira Date: Thu, 27 Jan 2011 11:49:49 +0100 Subject: Restore Qt 4.6 behaviour: exec() always enters the event loop. In Qt 4.6 as well as 4.7's QCoreApplication and QEventLoop, calling exec() always enters the event loop, even if you had tried to quit()/exit() it before entering, with one exception (noted in the unit tests; this difference has been in Qt since at least Qt 4.2). Add unit tests to ensure all of the three classes have the same behaviour. Decide if we want to match the behaviours in Qt 4.8. Reviewed-by: Bradley T. Hughes --- src/corelib/thread/qthread.cpp | 5 +- .../auto/qcoreapplication/tst_qcoreapplication.cpp | 45 +++++ tests/auto/qeventloop/tst_qeventloop.cpp | 41 ++++- tests/auto/qthread/tst_qthread.cpp | 191 +++++++++++++++------ 4 files changed, 221 insertions(+), 61 deletions(-) diff --git a/src/corelib/thread/qthread.cpp b/src/corelib/thread/qthread.cpp index f368192..f4bfa5d 100644 --- a/src/corelib/thread/qthread.cpp +++ b/src/corelib/thread/qthread.cpp @@ -482,10 +482,7 @@ int QThread::exec() Q_D(QThread); QMutexLocker locker(&d->mutex); d->data->quitNow = false; - if (d->exited) { - d->exited = false; - return d->returnCode; - } + d->exited = false; locker.unlock(); QEventLoop eventLoop; diff --git a/tests/auto/qcoreapplication/tst_qcoreapplication.cpp b/tests/auto/qcoreapplication/tst_qcoreapplication.cpp index 95055d1..bc69461 100644 --- a/tests/auto/qcoreapplication/tst_qcoreapplication.cpp +++ b/tests/auto/qcoreapplication/tst_qcoreapplication.cpp @@ -59,6 +59,9 @@ private slots: void applicationPid(); void globalPostedEventsCount(); void processEventsAlwaysSendsPostedEvents(); + void reexec(); + void execAfterExit(); + void eventLoopExecAfterExit(); }; class EventSpy : public QObject @@ -524,5 +527,47 @@ void tst_QCoreApplication::processEventsAlwaysSendsPostedEvents() } while (t.elapsed() < 3000); } +void tst_QCoreApplication::reexec() +{ + int argc = 1; + char *argv[] = { "tst_qcoreapplication" }; + QCoreApplication app(argc, argv); + + // exec once + QMetaObject::invokeMethod(&app, "quit", Qt::QueuedConnection); + QCOMPARE(app.exec(), 0); + + // and again + QMetaObject::invokeMethod(&app, "quit", Qt::QueuedConnection); + QCOMPARE(app.exec(), 0); +} + +void tst_QCoreApplication::execAfterExit() +{ + int argc = 1; + char *argv[] = { "tst_qcoreapplication" }; + QCoreApplication app(argc, argv); + + app.exit(1); + QMetaObject::invokeMethod(&app, "quit", Qt::QueuedConnection); + QCOMPARE(app.exec(), 0); +} + +void tst_QCoreApplication::eventLoopExecAfterExit() +{ + int argc = 1; + char *argv[] = { "tst_qcoreapplication" }; + QCoreApplication app(argc, argv); + + // exec once and exit + QMetaObject::invokeMethod(&app, "quit", Qt::QueuedConnection); + QCOMPARE(app.exec(), 0); + + // and again, but this time using a QEventLoop + QEventLoop loop; + QMetaObject::invokeMethod(&loop, "quit", Qt::QueuedConnection); + QCOMPARE(loop.exec(), 0); +} + QTEST_APPLESS_MAIN(tst_QCoreApplication) #include "tst_qcoreapplication.moc" diff --git a/tests/auto/qeventloop/tst_qeventloop.cpp b/tests/auto/qeventloop/tst_qeventloop.cpp index 7af722f..6860f19 100644 --- a/tests/auto/qeventloop/tst_qeventloop.cpp +++ b/tests/auto/qeventloop/tst_qeventloop.cpp @@ -112,6 +112,10 @@ signals: public: QMutex mutex; QWaitCondition cond; + volatile int result1; + volatile int result2; + MultipleExecThread() : result1(0xdead), result2(0xbeef) {} + void run() { QMutexLocker locker(&mutex); @@ -124,13 +128,13 @@ public: connect(&timer, SIGNAL(timeout()), SLOT(quit()), Qt::DirectConnection); timer.setInterval(1000); timer.start(); - (void) exec(); + result1 = exec(); // this should return immediately, since exit() has been called cond.wakeOne(); cond.wait(&mutex); QEventLoop eventLoop; - (void) eventLoop.exec(); + result2 = eventLoop.exec(); } }; @@ -197,7 +201,9 @@ private slots: void symbianNestedActiveSchedulerLoop(); void processEvents(); void exec(); + void reexec(); void exit(); + void execAfterExit(); void wakeUp(); void quit(); void processEventsExcludeSocket(); @@ -398,7 +404,9 @@ void tst_QEventLoop::exec() } { - // calling exec() after exit()/quit() should return immediately + // calling QEventLoop::exec() after a thread loop has exit()ed should return immediately + // Note: this behaviour differs from QCoreApplication and QEventLoop + // see tst_QCoreApplication::eventLoopExecAfterExit, tst_QEventLoop::reexec MultipleExecThread thread; // start thread and wait for checkpoint @@ -411,6 +419,8 @@ void tst_QEventLoop::exec() thread.cond.wakeOne(); thread.cond.wait(&thread.mutex); QVERIFY(spy.count() > 0); + int v = thread.result1; + QCOMPARE(v, 0); // exec should return immediately spy.clear(); @@ -418,6 +428,8 @@ void tst_QEventLoop::exec() thread.mutex.unlock(); thread.wait(); QCOMPARE(spy.count(), 0); + v = thread.result2; + QCOMPARE(v, -1); } { @@ -462,9 +474,32 @@ void tst_QEventLoop::exec() #endif } +void tst_QEventLoop::reexec() +{ + QEventLoop loop; + + // exec once + QMetaObject::invokeMethod(&loop, "quit", Qt::QueuedConnection); + QCOMPARE(loop.exec(), 0); + + // and again + QMetaObject::invokeMethod(&loop, "quit", Qt::QueuedConnection); + QCOMPARE(loop.exec(), 0); +} + void tst_QEventLoop::exit() { DEPENDS_ON(exec()); } +void tst_QEventLoop::execAfterExit() +{ + QEventLoop loop; + EventLoopExiter obj(&loop); + + QMetaObject::invokeMethod(&obj, "exit", Qt::QueuedConnection); + loop.exit(1); + QCOMPARE(loop.exec(), 0); +} + void tst_QEventLoop::wakeUp() { EventLoopThread thread; diff --git a/tests/auto/qthread/tst_qthread.cpp b/tests/auto/qthread/tst_qthread.cpp index e6bf9ce..c7036e4 100644 --- a/tests/auto/qthread/tst_qthread.cpp +++ b/tests/auto/qthread/tst_qthread.cpp @@ -86,6 +86,7 @@ private slots: void start(); void terminate(); void quit(); + void execAfterQuit(); void wait(); void started(); void finished(); @@ -265,6 +266,34 @@ public: } }; +class ExecAfterQuitThreadHelper: public QObject +{ + Q_OBJECT + QThread *thr; +public: + ExecAfterQuitThreadHelper(QThread *thr) : thr(thr) {} +public slots: + void doIt() { thr->exit(0); } +}; + +class ExecAfterQuitThread: public QThread +{ +public: + int returnValue; + void run() + { + ExecAfterQuitThreadHelper obj(this); + + QMetaObject::invokeMethod(&obj, "doIt", Qt::QueuedConnection); + exit(1); + + // returnValue will be either 0 or 1, depending on which of the two + // above take effect. The correct value is 0, since exit(1) before + // exec() should have no effect + returnValue = exec(); + } +}; + tst_QThread::tst_QThread() { @@ -424,34 +453,52 @@ void tst_QThread::stackSize() void tst_QThread::exit() { - Exit_Thread thread; - thread.object = new Exit_Object; - thread.object->moveToThread(&thread); - thread.code = 42; - thread.result = 0; - QVERIFY(!thread.isFinished()); - QVERIFY(!thread.isRunning()); - QMutexLocker locker(&thread.mutex); - thread.start(); - QVERIFY(thread.isRunning()); - QVERIFY(!thread.isFinished()); - thread.cond.wait(locker.mutex()); - QVERIFY(thread.wait(five_minutes)); - QVERIFY(thread.isFinished()); - QVERIFY(!thread.isRunning()); - QCOMPARE(thread.result, thread.code); - delete thread.object; + { + Exit_Thread thread; + thread.object = new Exit_Object; + thread.object->moveToThread(&thread); + thread.code = 42; + thread.result = 0; + QVERIFY(!thread.isFinished()); + QVERIFY(!thread.isRunning()); - Exit_Thread thread2; - thread2.object = 0; - thread2.code = 53; - thread2.result = 0; - QMutexLocker locker2(&thread2.mutex); - thread2.start(); - thread2.exit(thread2.code); - thread2.cond.wait(locker2.mutex()); - QVERIFY(thread2.wait(five_minutes)); - QCOMPARE(thread2.result, thread2.code); + QMutexLocker locker(&thread.mutex); + thread.start(); + QVERIFY(thread.isRunning()); + QVERIFY(!thread.isFinished()); + // but the thread is not running the event loop yet (the mutex is locked) + + // start the event loop + thread.cond.wait(locker.mutex()); + + // the Exit_Object above will cause the thread to exit + QVERIFY(thread.wait(five_minutes)); + QVERIFY(thread.isFinished()); + QVERIFY(!thread.isRunning()); + QCOMPARE(thread.result, thread.code); + delete thread.object; + } + + { + Exit_Thread thread2; + thread2.object = 0; + thread2.code = 53; + thread2.result = 0; + QMutexLocker locker2(&thread2.mutex); + thread2.start(); + + // the mutex is locked, so the thread has *not* started running the event loop yet + // this will do nothing: + thread2.exit(thread2.code); + + // the thread will now start running + thread2.cond.wait(locker2.mutex()); + + // this will cause it to exit now + thread2.exit(++thread2.code); + QVERIFY(thread2.wait(five_minutes)); + QCOMPARE(thread2.result, thread2.code); + } } void tst_QThread::start() @@ -498,32 +545,59 @@ void tst_QThread::terminate() void tst_QThread::quit() { - Quit_Thread thread; - thread.object = new Quit_Object; - thread.object->moveToThread(&thread); - thread.result = -1; - QVERIFY(!thread.isFinished()); - QVERIFY(!thread.isRunning()); - QMutexLocker locker(&thread.mutex); - thread.start(); - QVERIFY(thread.isRunning()); - QVERIFY(!thread.isFinished()); - thread.cond.wait(locker.mutex()); - QVERIFY(thread.wait(five_minutes)); - QVERIFY(thread.isFinished()); - QVERIFY(!thread.isRunning()); - QCOMPARE(thread.result, 0); - delete thread.object; + // very similar to exit() above + { + Quit_Thread thread; + thread.object = new Quit_Object; + thread.object->moveToThread(&thread); + thread.result = -1; + QVERIFY(!thread.isFinished()); + QVERIFY(!thread.isRunning()); - Quit_Thread thread2; - thread2.object = 0; - thread2.result = -1; - QMutexLocker locker2(&thread2.mutex); - thread2.start(); - thread2.quit(); - thread2.cond.wait(locker2.mutex()); - QVERIFY(thread2.wait(five_minutes)); - QCOMPARE(thread2.result, 0); + // start the thread, but keep the event loop from starting + // (while the mutex is locked) + QMutexLocker locker(&thread.mutex); + thread.start(); + QVERIFY(thread.isRunning()); + QVERIFY(!thread.isFinished()); + + // unlock the mutex and let the event loop run + // the Quit_Object above will cause the thread to quit + thread.cond.wait(locker.mutex()); + QVERIFY(thread.wait(five_minutes)); + QVERIFY(thread.isFinished()); + QVERIFY(!thread.isRunning()); + QCOMPARE(thread.result, 0); + delete thread.object; + } + + { + Quit_Thread thread2; + thread2.object = 0; + thread2.result = -1; + + // start the thread, but keep the event loop from starting + // (while the mutex is locked) + QMutexLocker locker2(&thread2.mutex); + thread2.start(); + thread2.quit(); // does nothing, the event loop is not running! + + // unlock the mutex and let the event loop run + thread2.cond.wait(locker2.mutex()); + + // there's no Quit_Object so it won't quit on its own + thread2.quit(); + QVERIFY(thread2.wait(five_minutes)); + QCOMPARE(thread2.result, 0); + } +} + +void tst_QThread::execAfterQuit() +{ + ExecAfterQuitThread thread; + thread.start(); + QVERIFY(thread.wait()); + QCOMPARE(thread.returnValue, 0); } void tst_QThread::wait() @@ -994,8 +1068,17 @@ void tst_QThread::QTBUG15378_exitAndExec() Thread thread; thread.value = 0; thread.start(); - thread.exit(556); - thread.sem1.release(); //should exit the first loop + thread.exit(42); // will do nothing, this value should not appear + thread.sem1.release(); //should enter the first loop + + Exit_Object *exit_object = new Exit_Object; + exit_object->code = 556; + exit_object->thread = &thread; + QMetaObject::invokeMethod(exit_object, "slot", Qt::QueuedConnection); + exit_object->deleteLater(); + exit_object->moveToThread(&thread); // should exit the first loop + exit_object = 0; + thread.sem2.acquire(); int v = thread.value; QCOMPARE(v, 556); -- cgit v0.12