Boost logo

Boost :

Subject: [boost] [threads] adopt_lock_t question
From: Jens Finkhäuser (jens_at_[hidden])
Date: 2008-11-02 08:51:19


Hi!

  I've come across something that puzzles me. I've got code that,
condensed to relevant parts, looks like this:

    recursive_mutex m;
    lock_guard<recursive_mutex> l1(m);
    lock_guard<recursive_mutex> l2(m, adopt_lock_t());

  What happens is that if -DNDEBUG is set, the code passes. But if
-DDEBUG is set, l1's destructor aborts. Further investigation shows
that the destructor calls the mutexe's unlock() function (as
expected), which in turn tries to unlock it's internal mutex object -
in my case a pthread mutex.

  The difference between the compilation modes is that the return
value of that unlocking function is wrapped in a BOOST_VERIFY(), i.e.
assert() call.

  As I understand the documentation, my code is perfectly valid. It
may not make a large amount of sense to use adopt_lock_t as the nature
of recursive_mutex should be to not block in l2's constructor - but
that's a different matter.

  Now according to the documentation, lock_guard expects a Lockable,
recursive_mutex is a Lockable, and Lockable's unlock() specifies very
clearly what sort of conditions need to be met for the function to
work:

    Precondition:
      The current thread owns *this.
    Effects:
      Releases ownership by the current thread.
    Postcondition:
      The current thread no longer owns *this.
    Throws:
      Nothing

  So there you have it, in my case the current thread does not own
*this after l2's destructor ran - in fact, no thread does.

  Now there are a two inconsistencies here:

  1. The code works fine with other Lockable implementations, such as
     non-recursive boost mutexes.
  2. The behaviour is different depending on compilation mode. Yes,
     that's the idea behind asserts, but parts of the documentation
     suggest one behaviour is to be expected, other parts suggest the
     other is.

  From that I must draw the conclusion that either recursive_mutex and
the unlock() documentation is broken, or the non-recursive mutexes
are.

  Now conceptually, I'd expect unlock() to always succeed. That is, if
it's postcondition can be fulfilled, it should always, regardless of
compilation mode, exit cleanly. In that case the precondition docs
should read something like "*this is either owned by the current
thread, or by no thread at all.".

  I find that to be the case especially for recursive mutexes, as
they're intended to be locked/unlocked multiple times in the same
thread (though, technically, the calls should always be balanced).

  All of this is with version 1.35.0, but the docs for 1.36.0 don't
seem to be different in the relevant areas.

  Maybe I'm missing something vital here. If so, I'd greatly
appreciate any insight you guys might have.

  (Incidentally, I find it very hard to disable BOOST_VERIFY with a
BOOST_DISABLE_ASSERTS define. I'll have to dig deeper where the
re-enabling of asserts sneaks in, but on some machines that has no
effect, on others it solves my particular problem, regardless of how
correct or incorrect it might be.)

Thanks,
  Jens


Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk