[Development] QThread::finished() race → fixing QThread
Thiago Macieira
thiago.macieira at intel.com
Thu Nov 3 11:37:25 CET 2011
Hello
First of all, required reading:
http://labs.qt.nokia.com/2010/06/17/youre-doing-it-wrong/
== Background ==
Today, I was told that there was a timing sensitive test in QDBusPendingCall's
unit testing. Looking at the code, it was something like this:
connect(&thread, SIGNAL(finished()), &loop, SLOT(quit()));
thread.start();
loop.exec();
QVERIFY(!thread.isRunning());
Technically speaking, there's a race condition in *starting* the event loop
above, but that's an error of the code above. Let's ignore it for the moment.
The problem we saw was QVERIFY would fail. If we look at the apparent causal
sequence of events, we have:
event loop exited
because
finished() signal was emitted
because
thread's run() function exited
But isRunning() is still true. The reason for this is that there's a race
condition in QThreadPrivate::finish, which emits the finished() signal before it
sets the isRunning variable to false. The fix looks simple: move the code
around.
Is it really?
== Larger implications ==
The crux of the issue is that finished() is emitted by QThreadPrivate::finish(),
which is run from *inside* the thread that is finishing. Depending on how you
interpret finished(), that is a contradiction. If you interpret it as "the
thread has finished", the fact that it's emitted from inside the thread means
the thread was still running when it was emitted, which means it wasn't
finished. If you interpret it as "the user code in the run() function has
finished", then it's fine.
So on IRC we discussed this and came to no conclusion yet. More issues came to
light in the discussion:
- if finished() is emitted from inside the thread, are there other race
conditions possible? For example, imagine that the slot connected to finished()
does start() again.
- if we move the finished() from the thread to the QThread's owning thread, we
ensure that the thread *has* finished, but we're likely to break existing code.
After Brad's blog came out, people began writing code like:
MyObject *obj = new MyObject(...);
QThread *thread = new QThread(...);
connect(...);
obj->moveToThread(thread);
thread->start();
(in http://lists.qt.nokia.com/pipermail/qt-interest/2009-August/011022.html )
where is the object deleted? Some might write:
connect(thread, SIGNAL(finished()), obj, SLOT(deleteLater()));
This code and other cleanup tasks that people may have will no longer work if
we move the finished() to really mean the thread has finished: the slot
invocations would be queued and never run.
Also, finished() would not be emitted at all if people had written
thread->moveToThread(thread);
but on IRC we all agreed we don't care about this case.
== What to do? ==
We have a couple of options before us:
1) leave QThread unchanged, update the docs to say that finished() means the
user code has finished, but the thread itself might still be running and
performing clean up tasks. If synchronisation is required, one should call
QThread::wait()
2) move the code around and update the documentation as above
3) make it so that finished() is emitted only after the thread has finished;
maybe add a QThread::aboutToFinish() signal that is emitted from inside the
thread like the current finished() is.
In the cases of 1 and 2 above, we can also deprecate QThread and introduce a
new class with the proper semantics.
In general, we believe we need to do some work for QThread anyway. Ever since
we told people they were doing it wrong, we have failed to update the
documentation to provide the "right way" to do and we still have no
convenience for the common, right way we propose people use.
--
Thiago Macieira - thiago.macieira (AT) intel.com
Software Architect - Intel Open Source Technology Center
Intel Sweden AB - Registration Number: 556189-6027
Knarrarnäsgatan 15, 164 40 Kista, Stockholm, Sweden
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 190 bytes
Desc: This is a digitally signed message part.
URL: <http://lists.qt-project.org/pipermail/development/attachments/20111103/1e673ff4/attachment.sig>
More information about the Development
mailing list