Quick Links:

bal | bbl | bdl | bsl

Namespaces

Component bslmt_mutexassert
[Package bslmt]

Provide an assert macro for verifying that a mutex is locked. More...

Namespaces

namespace  bslmt

Detailed Description

Outline
Purpose:
Provide an assert macro for verifying that a mutex is locked.
Classes:
Macros:
BSLMT_MUTEXASSERT_IS_LOCKED verify a mutex is locked in non-opt modes
BSLMT_MUTEXASSERT_IS_LOCKED_SAFE verify a mutex is locked in safe mode
BSLMT_MUTEXASSERT_IS_LOCKED_OPT verify a mutex is locked in all modes
See also:
Component bslmt_mutex
Description:
This component provides macros for asserting that a mutex is locked. It does not distinguish between locks held by the current thread or other threads. If the macro is active in the current build mode, when the macro is called, if the supplied mutex is unlocked, the assert handler installed for BSLS_ASSERT will be called. The assert handler installed by default will report an error and abort the task.
The three macros defined by the component are analogous to the macros defined by BSLS_ASSERT:
  • BSLMT_MUTEXASSERT_IS_LOCKED: active when BSLS_ASSERT is active
  • BSLMT_MUTEXASSERT_IS_LOCKED_SAFE: active when BSLS_ASSERT_SAFE is active
  • BSLMT_MUTEXASSERT_IS_LOCKED_OPT: active when BSLS_ASSERT_OPT is active
In build modes where any one of these macros is not active, calling it will have no effect.
If any of these asserts are in effect and fail (because the mutex in question was unlocked), the behavior parallels the behavior of the assertion macros defined in bsls_assert.h -- bsls::Assert::invokeHandler is called, with a source code expression, the name of the source file, and the line number in the source file where the macro was called. If the default handler is installed, this will result in an error message and an abort.
Usage:
This section illustrates intended use of this component.
Example 1: Checking Consistency Within a Private Method:
Sometimes multithreaded code is written such that the author of a function requires that a caller has already acquired a mutex. The BSLMT_MUTEXASSERT_IS_LOCKED* family of assertions allows the programmers to verify, using defensive programming techniques, that the mutex in question is indeed locked.
Suppose we have a fully thread-safe queue that contains int values, and is guarded by an internal mutex. We can use BSLMT_MUTEXASSERT_IS_LOCKED_SAFE to ensure (in appropriate build modes) that proper internal locking of the mutex is taking place.
First, we define the container:
  class MyThreadSafeQueue {
      // This 'class' provides a fully *thread-safe* unidirectional queue of
      // 'int' values.  See {'bsls_glossary'|Fully Thread-Safe}.  All public
      // manipulators operate as single, atomic actions.

      // DATA
      bsl::deque<int>      d_deque;    // underlying non-*thread-safe*
                                       // standard container

      mutable bslmt::Mutex  d_mutex;    // mutex to provide thread safety

      // PRIVATE MANIPULATOR
      int popImp(int *result);
          // Assign the value at the front of the queue to the specified
          // '*result', and remove the value at the front of the queue;
          // return 0 if the queue was not initially empty, and a non-zero
          // value (with no effect) otherwise.  The behavior is undefined
          // unless 'd_mutex' is locked.

    public:
      // ...

      // MANIPULATORS
      int pop(int *result);
          // Assign the value at the front of the queue to the specified
          // '*result', and remove the value at the front of the queue;
          // return 0 if the queue was not initially empty, and a non-zero
          // value (with no effect) otherwise.

      void popAll(bsl::vector<int> *result);
          // Assign the values of all the elements from this queue, in order,
          // to the specified '*result', and remove them from this queue.
          // Any previous contents of '*result' are discarded.  Note that, as
          // with the other public manipulators, this entire operation occurs
          // as a single, atomic action.

      void push(int value);
          // ...

      template <class INPUT_ITER>
      void pushRange(const INPUT_ITER& first, const INPUT_ITER& last);
          // ...
  };
Notice that our public manipulators have two forms: push/pop a single element, and push/pop a collection of elements. Popping even a single element is non-trivial, so we factor this operation into a non-*thread-safe* private manipulator that performs the pop, and is used in both public pop methods. This private manipulator requires that the mutex be locked, but cannot lock the mutex itself, since the correctness of popAll demands that all of the pops be collectively performed using a single mutex lock/unlock.
Then, we define the private manipulator:
  // PRIVATE MANIPULATOR
  int MyThreadSafeQueue::popImp(int *result)
  {
      BSLMT_MUTEXASSERT_IS_LOCKED_SAFE(&d_mutex);

      if (d_deque.empty()) {
          return -1;                                                // RETURN
      }
      else {
          *result = d_deque.front();
          d_deque.pop_front();
          return 0;                                                 // RETURN
      }
  }
Notice that, on the very first line, the private manipulator verifies, as a precondition check, that the mutex has been acquired, using one of the BSLMT_MUTEXASSERT_IS_LOCKED* macros. We use the ...IS_LOCKED_SAFE... version of the macro so that the check, which on some platforms is as expensive as locking the mutex, is performed in only the safe build mode.
Next, we define the public manipulators; each of which must acquire a lock on the mutex (note that there is a bug in popAll):
  // MANIPULATORS
  int MyThreadSafeQueue::pop(int *result)
  {
      BSLS_ASSERT(result);

      d_mutex.lock();
      int rc = popImp(result);
      d_mutex.unlock();
      return rc;
  }

  void MyThreadSafeQueue::popAll(bsl::vector<int> *result)
  {
      BSLS_ASSERT(result);

      const int size = static_cast<int>(d_deque.size());
      result->resize(size);
      int *begin = result->begin();
      for (int index = 0; index < size; ++index) {
          int rc = popImp(&begin[index]);
          BSLS_ASSERT(0 == rc);
      }
  }

  void MyThreadSafeQueue::push(int value)
  {
      d_mutex.lock();
      d_deque.push_back(value);
      d_mutex.unlock();
  }

  template <class INPUT_ITER>
  void MyThreadSafeQueue::pushRange(const INPUT_ITER& first,
                                    const INPUT_ITER& last)
  {
      d_mutex.lock();
      d_deque.insert(d_deque.begin(), first, last);
      d_mutex.unlock();
  }
Notice that, in popAll, we forgot to lock/unlock the mutex!
Then, in our function example2Function, we make use of our class to create and exercise a MyThreadSafeQueue object:
  void testThreadSafeQueue(bsl::ostream& stream)
  {
      MyThreadSafeQueue queue;
Next, we populate the queue using pushRange:
      const int rawData[] = { 17, 3, 21, -19, 4, 87, 29, 3, 101, 31, 36 };
      enum { k_RAW_DATA_LENGTH = sizeof rawData / sizeof *rawData };

      queue.pushRange(rawData + 0, rawData + k_RAW_DATA_LENGTH);
Then, we pop a few items off the front of the queue and verify their values:
      int value = -1;

      assert(0 == queue.pop(&value));    assert(17 == value);
      assert(0 == queue.pop(&value));    assert( 3 == value);
      assert(0 == queue.pop(&value));    assert(21 == value);
Next, we attempt to empty the queue with popAll, which, if built in safe mode, would fail because it neglects to lock the mutex:
      bsl::vector<int> v;
      queue.popAll(&v);

      stream << "Remaining raw numbers: ";
      for (bsl::size_t ti = 0; ti < v.size(); ++ti) {
          stream << (ti ? ", " : "") << v[ti];
      }
      stream << bsl::endl;
  }
Then, we build in non-safe mode and run:
  Remaining raw numbers: -19, 4, 87, 29, 3, 101, 31, 36
Notice that, since the test case is being run in a single thread and our check is disabled, the bug where the mutex was not acquired does not manifest itself in a visible error, and we observe the seemingly correct output.
Now, we build in safe mode (which enables our check), run the program (which calls example2Function), and observe that, when we call popAll, the BSLMT_MUTEXASSERT_IS_LOCKED_SAFE(&d_mutex) macro issues an error message and aborts:
  Assertion failed: BSLMT_MUTEXASSERT_IS_LOCKED_SAFE(&d_mutex),
  file bslmt_mutexassertislocked.t.cpp, line 137 Aborted (core dumped)
Finally, note that the message printed above and the subsequent aborting of the program were the result of a call to bsls::Assert::invokeHandler, which in this case was configured (by default) to call bsls::Assert::failAbort. Other handlers may be installed that produce different results, but in all cases should prevent the program from proceeding normally.