[Development] QBasicMutex::lockInternal() race condition?

Thiago Macieira thiago.macieira at intel.com
Thu Sep 20 19:29:19 CEST 2012


On quinta-feira, 20 de setembro de 2012 16.53.23, Tony Van Eerd wrote:
> > Tony: I'd really appreciate giving the implementation a thorough once-
> > over.
> 
> I will try to find the time, although I'm already further down the rabbit
> hole than I should be.  I was really investigating what can be used in a
> static context, and what can't.  Would you recommend that client code use
> QBasicMutex when static mutexes are needed (which is a common use for
> mutexes).  Like the recent changes done for Qt internals?  

QBasicAtomic and QBasicMutex are PODs, but QBasicTimer isn't. Additionally, 
QElapsedTimer is POD.

But QBasicAtomic and QBasicMutex are private classes. You should not use them 
outside Qt code -- at least, not yet. For Qt 5.1, I want to make QBasicAtomic 
public and documented. QBasicMutex will require some more thinking.

Also note that QAtomicInt is fine as a static in C++11 mode, since its 
constructor is constexpr. But we can't depend on that in Qt code.

> (P.S. I noticed
> one of those had a static QBasicMutex inside a function, which gcc will
> then put a 'mutex' around...)

As you said in the next email, QBasicMutex is POD, so there's no compiler 
guard.

> P.S. although I agree that recursive mutex is often wrong, it is commonly
> used.  Possibly the most common, particularly for OOP and ex-java
> developers.  (And to me, Qt is very java-esque, no offense.)

Yeah, it usually means you could not resolve the recursion properly.

> > I've done it once and, after rewriting it, I came up with almost the
> > same
> > solutions. My implementation only differed that I added a spinlock, by
> > using
> > the lowest bit on the QMutexPrivate pointer.
> 
> did you use "testAndtestAndSet" ?
> 
> while (locked || !testAndSet(&locked, 0, 1))
>    yield-or-whatever;
>
> ie mostly spin on a relaxed read.  Only do the memory-barrier CAS when it
> "looks" unlocked, and thus has a chance of succeeding.

No, see qmutex.cpp in https://codereview.qt-project.org/33366
 
   while (!d_ptr.testAndSetAcquire(0, dummyLocked())) {
        QMutexData *copy = d_ptr.load();
        loopPause();
        copy = spinUnlocked(copy);

        if (d_ptr.testAndSetAcquire(copy, spinLocked(copy))) {

It always tries to go from unlocked to locked, even if it read the locked 
value (copy == spinLocked(copy)).

This is mostly because I wrote the original code with the 3-argument 
testAndSet:

    while (true) {
        loopPause();
        copy = spinUnlocked(copy);

        if (d_ptr.testAndSetAcquire(copy, spinLocked(copy), copy)) {

Which avoids the extra load from memory. It only uses the value that it has 
already got.

> In general, this problem seems the same as the pthread_once(), so various
> solutions are out there.  Lawrence Crowl had a good solution using
> thread-local-storage.  It is used in boost::call_once() for the non-Windows
> implementation.  The Windows impl is similar to QMutex in that it tries to
> avoid CreateEvent (like trying to avoid allocating the d_ptr) unless there
> is contention.  But the boost version has a small case where it never
> cleans up the event (which is OK for once(), since it tends to be a static
> that is never cleaned up anyhow).

This can only happen for timed locks when one thread gives up waiting at the 
moment the other unlocks. That's why Olivier's implementation has the 
possiblyUnlocked flag. That's why I applied a patch that removes timed locks 
from QBasicMutex.

In my implementation, that can't happen because the reference count is only 
modified under spinlock.

> I had a solution to that small case, but
> it took about 5 pages of code (lots of comments of course).  And used a
> list built across objects on the stacks of the waiting threads.  Which is
> scary if someone decides to terminate a thread.  But it showed that you
> could do everything with a single int, if you really wanted to.  I think I
> had a solution with 2 ints without the list...

I can only imagine what the solution might be. Or not.

Anyway, there's another possibility:
QBasicMutex can only be used as a static, never on the stack or as a member. 
Therefore, it's acceptable if it leaks the waiter structure, like the case you 
mentioned above. The leak would only manifest if someone is loading and 
unloading constantly a plugin. So a possibility is simply to keep the same 
waiter structure forever and leak it at the end of the execution.

QMutex has a destructor, so it can properly free it when it is destroyed.

-- 
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/20120920/36442358/attachment.sig>


More information about the Development mailing list