BDE 4.14.0 Production release
Loading...
Searching...
No Matches
bslmt_readerwriterlockassert

Macros

#define BSLMT_READERWRITERLOCKASSERT_IS_LOCKED(rwLock_p)   ((void) 0)
 
#define BSLMT_READERWRITERLOCKASSERT_IS_LOCKED_SAFE(rwLock_p)   ((void) 0)
 
#define BSLMT_READERWRITERLOCKASSERT_IS_LOCKED_OPT(rwLock_p)   ((void) 0)
 
#define BSLMT_READERWRITERLOCKASSERT_IS_LOCKED_READ(rwLock_p)   ((void) 0)
 
#define BSLMT_READERWRITERLOCKASSERT_IS_LOCKED_READ_SAFE(rwLock_p)    ((void) 0)
 
#define BSLMT_READERWRITERLOCKASSERT_IS_LOCKED_READ_OPT(rwLock_p)    ((void) 0)
 
#define BSLMT_READERWRITERLOCKASSERT_IS_LOCKED_WRITE(rwLock_p)   ((void) 0)
 
#define BSLMT_READERWRITERLOCKASSERT_IS_LOCKED_WRITE_SAFE(rwLock_p)    ((void) 0)
 
#define BSLMT_READERWRITERLOCKASSERT_IS_LOCKED_WRITE_OPT(rwLock_p)    ((void) 0)
 

Detailed Description

Outline

Purpose

Provide an assert macro for verifying reader-writer lock status.

Classes

Macros

See also
bslmt_lockassert, bslmt_readerwriterlock, bslmt_readerwritermutex

Description

This component provides macros for asserting that a reader-writer lock 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 lock 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. Note that the type of lock (pointer) passed to each of these macros is determined at compile-time. See {Requirements on the Lock Type} below.

The nine macros defined by the component are analogous to the macros defined by BSLS_ASSERT:

+---------------------------------------------------+------------------=+
| Macro | When Active |
+===================================================+===================+
|'BSLMT_READERWRITERLOCKASSERT_IS_LOCKED' | When |
|'BSLMT_READERWRITERLOCKASSERT_IS_LOCKED_READ' | 'BSLS_ASSERT' |
|'BSLMT_READERWRITERLOCKASSERT_IS_LOCKED_WRITE' | is active. |
+---------------------------------------------------+-------------------+
|'BSLMT_READERWRITERLOCKASSERT_IS_LOCKED_SAFE' | When |
|'BSLMT_READERWRITERLOCKASSERT_IS_LOCKED_READ_SAFE' | 'BSLS_ASSERT_SAFE'|
|'BSLMT_READERWRITERLOCKASSERT_IS_LOCKED_WRITE_SAFE'| is active. |
+---------------------------------------------------+-------------------+
|'BSLMT_READERWRITERLOCKASSERT_IS_LOCKED_OPT' | When |
|'BSLMT_READERWRITERLOCKASSERT_IS_LOCKED_READ_OPT' | 'BSLS_ASSERT_OPT' |
|'BSLMT_READERWRITERLOCKASSERT_IS_LOCKED_WRITE_OPT' | is active. |
+---------------------------------------------------+-------------------+

In build modes where any one of these macros is not active, the presence of the macros has no effect.

If any of these asserts are in effect and fail (because the reader-writer lock in question was unlocked), the behavior parallels the behavior of the assertion macros defined in bsls_assert.hbsls::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.

Caveat: False Positives

Preconditions on locks typically require that the lock exist and is held by the calling thread. Unfortunately, lock ownership is not recorded in the lock and cannot be confirmed. The absence of any lock when the calling thread should hold one is certainly a problem; however, the existence of a lock does not guarantee that the complete precondition is met.

Requirements on the Lock Type

This system of macros accept pointers to reader-write lock objects that provide the methods:

Two compatible classes are:

Although the required methods are typically const-qualified (i.e., "accessor" methods), that is not a requriement. Some client lock classes may implement these methods in terms of tryLock/unlock methods that require non-const access to the lock.

Usage

This section illustrates intended use of this component.

Example 1: Checking Consistency Within Private Methods

This example is an generalization of {bslmt_mutexassert |Example 1: Checking Consistency Within a Private Method}. In that example, a mutex was used to control access. Here, the (simple) mutex is replaced with a bslmt::ReaderWriterLock that is allows multiple concurrent access to the queue when conditions allow.

Sometimes multithreaded code is written such that the author of a function requires that a caller has already acquired a lock. The BSLMT_READERWRITERLOCKASSERT_IS_LOCKED* family of assertions allows the programmers to detect, using defensive programming techniques, if the required lock has not been acquired.

Suppose we have a fully thread-safe queue that contains int values, and is guarded by an internal bslmt::ReaderWriterLock object.

First, we define the container class:

/// This `class` provides a fully *thread-safe* unidirectional queue of
/// `int` values. See {`bsls_glossary`|Fully Thread-Safe}. All public
/// methods operate as single, atomic actions.
class MyThreadSafeQueue {
// DATA
bsl::deque<int> d_deque; // underlying non-*thread-safe*
// standard container
d_rwLock; // coordinate thread access
// PRIVATE MANIPULATOR
// 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_rwLock` is locked for writing.
int popImp(int *result);
// PRIVATE ACCESSOR
/// Return a `bsl::pair<int, int>` containing the number of elements
/// and the mean value of the elements of this queue. The mean
/// values is set to `DBL_MIN` if the number of elements is 0. The
/// behavior is undefined unless the call has locked this queue
/// (either a read lock or write lock).
bsl::pair<int, double> getStats() const;
public:
// ...
// MANIPULATORS
/// 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.
int pop(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 popAll(bsl::vector<int> *result);
/// Remove all elements from this queue if their mean exceeds the
/// specified `limit`.
void purgeAll(double limit);
/// Push the specified `value` onto this queue.
void push(int value);
/// Push the values from the specified `first` (inclusive) to the
/// specified `last` (exclusive) onto this queue.
template <class INPUT_ITER>
void pushRange(const INPUT_ITER& first, const INPUT_ITER& last);
// ACCESSORS
/// Return the mean value of the elements of this queue.
double mean() const;
/// Return the number of elements in this queue.
bsl::size_t numElements() const;
};
Definition bslstl_deque.h:772
Definition bslstl_pair.h:1210
Definition bslstl_vector.h:1025
Definition bslmt_readerwriterlock.h:294

Notice that this version of the MyThreadSafeQueue class has two public accessors, numElements and mean, and an additional manipulator, purgeAll.

Then, we implement most of the manipulators:

// PRIVATE MANIPULATOR
int MyThreadSafeQueue::popImp(int *result)
{
if (d_deque.empty()) {
return -1; // RETURN
}
else {
*result = d_deque.front();
d_deque.pop_front();
return 0; // RETURN
}
}
// MANIPULATORS
int MyThreadSafeQueue::pop(int *result)
{
BSLS_ASSERT(result);
d_rwLock.lockWrite();
int rc = popImp(result);
d_rwLock.unlockWrite();
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_rwLock.lockWrite();
d_deque.push_back(value);
d_rwLock.unlockWrite();
}
template <class INPUT_ITER>
void MyThreadSafeQueue::pushRange(const INPUT_ITER& first,
const INPUT_ITER& last)
{
d_rwLock.lockWrite();
d_deque.insert(d_deque.begin(), first, last);
d_rwLock.unlockWrite();
}
iterator begin() BSLS_KEYWORD_NOEXCEPT
Definition bslstl_vector.h:2511
void resize(size_type newSize)
Definition bslstl_vector.h:3616
#define BSLMT_READERWRITERLOCKASSERT_IS_LOCKED_WRITE(rwLock_p)
Definition bslmt_readerwriterlockassert.h:488
#define BSLS_ASSERT(X)
Definition bsls_assert.h:1804
bsl::size_t size(const TYPE &array)
Return the number of elements in the specified array.
T::iterator begin(T &container)
Definition bslstl_iterator.h:1495

Notice that these implementations are identical to those shown in {bslmt_mutexassert |Example 1} except that the lock calls to the bslmt::Mutex there have been changed here to lockWrite calls on a bslmt::ReaderWriterLock. Both operations provide exclusive access to the container.

Also notice that, having learned the lesson of {bslmt_mutexassert |Example 1}, we were careful to acquire a write lock for the duration of each of these operation and to check the precondition of the private popImp method by using the BSLMT_READERWRITERLOCKASSERT_IS_LOCKED_WRITE macro.

Finally notice that we use the "normal" flavor of the macro (rather than the *_SAFE version) because this test is not particularly expensive.

Next, we implement the accessor methods of the container:

// ACCESSORS
double MyThreadSafeQueue::mean() const
{
d_rwLock.lockRead();
bsl::pair<int, double> result = getStats();
d_rwLock.unlockRead();
return result.second;
}
bsl::size_t MyThreadSafeQueue::numElements() const
{
d_rwLock.lockRead();
bsl::size_t numElements = d_deque.size();
d_rwLock.unlockRead();
return numElements;
}
TYPE second
Definition bslstl_pair.h:908

Notice that each of these methods acquire a read lock for the duration of the operation. These locks allow shared access provided that the container is not changed, a reasonable assumption for these const-qualified methods.

Also notice that the bulk of the work of mean is done by the private method getStats. One's might except the private method to confirm that a lock was acquired by using the BSLMT_READERWRITERLOCKASSERT_IS_LOCKED_READ macro; however, the reason for creating that private method is so that it can be reused by the purgeAll method, a non-const method that requires a write lock. Thus, getStats is an occassion to use the BSLMT_READERWRITERLOCKASSERT_IS_LOCKED check (for either a read lock or a write lock).

// PRIVATE ACCESSORS
bsl::pair<int, double> MyThreadSafeQueue::getStats() const
{
int numElements = d_deque.size();
if (0 == numElements) {
return bsl::make_pair(numElements, DBL_MIN); // RETURN
}
int sum = bsl::accumulate(d_deque.cbegin(), d_deque.cend(), 0);
double mean = static_cast<double>(sum)
/ static_cast<double>(numElements);
return bsl::make_pair(numElements, mean);
}
#define BSLMT_READERWRITERLOCKASSERT_IS_LOCKED(rwLock_p)
Definition bslmt_readerwriterlockassert.h:410

Next, we implement the purgeAll method:

void MyThreadSafeQueue::purgeAll(double limit)
{
d_rwLock.lockWrite();
bsl::pair<int, double> results = getStats(); // requires some lock
if (0 < results.first && limit < results.second) {
for (int i = 0; i < results.first; ++i) {
int dummy;
int rc = popImp(&dummy); // requires a write lock
assert(0 == rc);
}
}
d_rwLock.unlockWrite();
}
TYPE first
Definition bslstl_pair.h:605

Finally, we confirm that our accessors work as expected:

/// Exercise the added methods of the `MyThreadSafeQueue` class.
void testEnhancedThreadSafeQueue()
{
MyThreadSafeQueue queue;
const int rawData[] = { 17, 3, -20, 7, 28 };
enum { k_RAW_DATA_LENGTH = sizeof rawData / sizeof *rawData };
queue.pushRange(rawData + 0, rawData + k_RAW_DATA_LENGTH);
assert(5 == queue.numElements());
assert(7 == queue.mean());
queue.push(100000);
queue.purgeAll(10);
assertV(queue.numElements(), 0 == queue.numElements());
assertV(queue.mean() , DBL_MIN == queue.mean());
}

Macro Definition Documentation

◆ BSLMT_READERWRITERLOCKASSERT_IS_LOCKED

#define BSLMT_READERWRITERLOCKASSERT_IS_LOCKED (   rwLock_p)    ((void) 0)

◆ BSLMT_READERWRITERLOCKASSERT_IS_LOCKED_OPT

#define BSLMT_READERWRITERLOCKASSERT_IS_LOCKED_OPT (   rwLock_p)    ((void) 0)

◆ BSLMT_READERWRITERLOCKASSERT_IS_LOCKED_READ

#define BSLMT_READERWRITERLOCKASSERT_IS_LOCKED_READ (   rwLock_p)    ((void) 0)

◆ BSLMT_READERWRITERLOCKASSERT_IS_LOCKED_READ_OPT

#define BSLMT_READERWRITERLOCKASSERT_IS_LOCKED_READ_OPT (   rwLock_p)     ((void) 0)

◆ BSLMT_READERWRITERLOCKASSERT_IS_LOCKED_READ_SAFE

#define BSLMT_READERWRITERLOCKASSERT_IS_LOCKED_READ_SAFE (   rwLock_p)     ((void) 0)

◆ BSLMT_READERWRITERLOCKASSERT_IS_LOCKED_SAFE

#define BSLMT_READERWRITERLOCKASSERT_IS_LOCKED_SAFE (   rwLock_p)    ((void) 0)

◆ BSLMT_READERWRITERLOCKASSERT_IS_LOCKED_WRITE

#define BSLMT_READERWRITERLOCKASSERT_IS_LOCKED_WRITE (   rwLock_p)    ((void) 0)

◆ BSLMT_READERWRITERLOCKASSERT_IS_LOCKED_WRITE_OPT

#define BSLMT_READERWRITERLOCKASSERT_IS_LOCKED_WRITE_OPT (   rwLock_p)     ((void) 0)

◆ BSLMT_READERWRITERLOCKASSERT_IS_LOCKED_WRITE_SAFE

#define BSLMT_READERWRITERLOCKASSERT_IS_LOCKED_WRITE_SAFE (   rwLock_p)     ((void) 0)