// bslmt_readerwriterlockassert.h -*-C++-*- #ifndef INCLUDED_BSLMT_READERWRITERLOCKASSERT #define INCLUDED_BSLMT_READERWRITERLOCKASSERT #include <bsls_ident.h> BSLS_IDENT("$Id: $") //@PURPOSE: Provide an assert macro for verifying reader-writer lock status. // //@CLASSES: // //@MACROS: // BSLMT_READERWRITERLOCKASSERT_IS_LOCKED: test in non-opt modes // BSLMT_READERWRITERLOCKASSERT_IS_LOCKED_SAFE: test in safe mode // BSLMT_READERWRITERLOCKASSERT_IS_LOCKED_OPT: test in all modes // BSLMT_READERWRITERLOCKASSERT_IS_LOCKED_READ: test in non-opt modes // BSLMT_READERWRITERLOCKASSERT_IS_LOCKED_READ_SAFE: test in safe mode // BSLMT_READERWRITERLOCKASSERT_IS_LOCKED_READ_OPT: test in all modes // BSLMT_READERWRITERLOCKASSERT_IS_LOCKED_WRITE: test in non-opt modes // BSLMT_READERWRITERLOCKASSERT_IS_LOCKED_WRITE_SAFE: test in safe mode // BSLMT_READERWRITERLOCKASSERT_IS_LOCKED_WRITE_OPT: test in all modes // //@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.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. // ///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: //: o 'isLocked', //: o 'isLockedRead', and //: o 'isLockedWrite' // // Two compatible classes are: //: o 'bslmt::ReaderWriterLock' and //: o 'bslmt::ReaderWriterMutex' // // 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: //.. // class MyThreadSafeQueue { // // 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. // // // DATA // bsl::deque<int> d_deque; // underlying non-*thread-safe* // // standard container // // mutable bslmt::ReaderWriterLock // d_rwLock; // coordinate thread access // // // 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_rwLock' is locked for writing. // // // PRIVATE ACCESSOR // bsl::pair<int, double> getStats() const; // // 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). // // 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 purgeAll(double limit); // // Remove all elements from this queue if their mean exceeds the // // specified 'limit'. // // void push(int value); // // Push the specified 'value' onto this queue. // // template <class INPUT_ITER> // void pushRange(const INPUT_ITER& first, const INPUT_ITER& last); // // Push the values from the specified 'first' (inclusive) to the // // specified 'last' (exclusive) onto this queue. // // // ACCESSORS // double mean() const; // // Return the mean value of the elements of this queue. // // bsl::size_t numElements() const; // // Return the number of elements in this queue. // }; //.. // 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) // { // BSLMT_READERWRITERLOCKASSERT_IS_LOCKED_WRITE(&d_rwLock); // // 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(); // } //.. // 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; // } //.. // 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 // { // BSLMT_READERWRITERLOCKASSERT_IS_LOCKED(&d_rwLock); // // 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); // } //.. // 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(); // } //.. // Finally, we confirm that our accessors work as expected: //.. // void testEnhancedThreadSafeQueue() // // Exercise the added methods of the 'MyThreadSafeQueue' class. // { // 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()); // } //.. #include <bslscm_version.h> #include <bsls_assert.h> #include <bsl_string.h> // 'std::strcmp' // ---------------------------------------------------------------------------- #if defined(BSLS_ASSERT_IS_ACTIVE) #define BSLMT_READERWRITERLOCKASSERT_IS_LOCKED(rwLock_p) do { \ bslmt::ReaderWriterLockAssert_Imp::assertIsLocked( \ (rwLock_p), \ "BSLMT_READERWRITERLOCKASSERT_IS_LOCKED(" #rwLock_p ")", \ __FILE__, \ __LINE__, \ bsls::Assert::k_LEVEL_ASSERT); } while (false) #else #define BSLMT_READERWRITERLOCKASSERT_IS_LOCKED(rwLock_p) ((void) 0) #endif #if defined(BSLS_ASSERT_SAFE_IS_ACTIVE) #define BSLMT_READERWRITERLOCKASSERT_IS_LOCKED_SAFE(rwLock_p) do { \ bslmt::ReaderWriterLockAssert_Imp::assertIsLocked( \ (rwLock_p), \ "BSLMT_READERWRITERLOCKASSERT_IS_LOCKED_SAFE(" #rwLock_p ")", \ __FILE__, \ __LINE__, \ bsls::Assert::k_LEVEL_SAFE); } while (false) #else #define BSLMT_READERWRITERLOCKASSERT_IS_LOCKED_SAFE(rwLock_p) ((void) 0) #endif #if defined(BSLS_ASSERT_OPT_IS_ACTIVE) #define BSLMT_READERWRITERLOCKASSERT_IS_LOCKED_OPT(rwLock_p) do { \ bslmt::ReaderWriterLockAssert_Imp::assertIsLocked( \ (rwLock_p), \ "BSLMT_READERWRITERLOCKASSERT_IS_LOCKED_OPT(" #rwLock_p ")", \ __FILE__, \ __LINE__, \ bsls::Assert::k_LEVEL_OPT); } while (false) #else #define BSLMT_READERWRITERLOCKASSERT_IS_LOCKED_OPT(rwLock_p) ((void) 0) #endif // ---------------------------------------------------------------------------- #if defined(BSLS_ASSERT_IS_ACTIVE) #define BSLMT_READERWRITERLOCKASSERT_IS_LOCKED_READ(rwLock_p) do { \ bslmt::ReaderWriterLockAssert_Imp::assertIsLockedRead( \ (rwLock_p), \ "BSLMT_READERWRITERLOCKASSERT_IS_LOCKED_READ(" #rwLock_p ")", \ __FILE__, \ __LINE__, \ bsls::Assert::k_LEVEL_ASSERT); } while (false) #else #define BSLMT_READERWRITERLOCKASSERT_IS_LOCKED_READ(rwLock_p) ((void) 0) #endif #if defined(BSLS_ASSERT_SAFE_IS_ACTIVE) #define BSLMT_READERWRITERLOCKASSERT_IS_LOCKED_READ_SAFE(rwLock_p) do { \ bslmt::ReaderWriterLockAssert_Imp::assertIsLockedRead( \ (rwLock_p), \ "BSLMT_READERWRITERLOCKASSERT_IS_LOCKED_READ_SAFE(" #rwLock_p ")", \ __FILE__, \ __LINE__, \ bsls::Assert::k_LEVEL_SAFE); } while (false) #else #define BSLMT_READERWRITERLOCKASSERT_IS_LOCKED_READ_SAFE(rwLock_p) \ ((void) 0) #endif #if defined(BSLS_ASSERT_OPT_IS_ACTIVE) #define BSLMT_READERWRITERLOCKASSERT_IS_LOCKED_READ_OPT(rwLock_p) do { \ bslmt::ReaderWriterLockAssert_Imp::assertIsLockedRead( \ (rwLock_p), \ "BSLMT_READERWRITERLOCKASSERT_IS_LOCKED_READ_OPT(" #rwLock_p ")", \ __FILE__, \ __LINE__, \ bsls::Assert::k_LEVEL_OPT); } while (false) #else #define BSLMT_READERWRITERLOCKASSERT_IS_LOCKED_READ_OPT(rwLock_p) \ ((void) 0) #endif // ---------------------------------------------------------------------------- #if defined(BSLS_ASSERT_IS_ACTIVE) #define BSLMT_READERWRITERLOCKASSERT_IS_LOCKED_WRITE(rwLock_p) do { \ bslmt::ReaderWriterLockAssert_Imp::assertIsLockedWrite( \ (rwLock_p), \ "BSLMT_READERWRITERLOCKASSERT_IS_LOCKED_WRITE(" #rwLock_p ")", \ __FILE__, \ __LINE__, \ bsls::Assert::k_LEVEL_ASSERT); } while (false) #else #define BSLMT_READERWRITERLOCKASSERT_IS_LOCKED_WRITE(rwLock_p) ((void) 0) #endif #if defined(BSLS_ASSERT_SAFE_IS_ACTIVE) #define BSLMT_READERWRITERLOCKASSERT_IS_LOCKED_WRITE_SAFE(rwLock_p) do { \ bslmt::ReaderWriterLockAssert_Imp::assertIsLockedWrite( \ (rwLock_p), \ "BSLMT_READERWRITERLOCKASSERT_IS_LOCKED_WRITE_SAFE(" #rwLock_p ")", \ __FILE__, \ __LINE__, \ bsls::Assert::k_LEVEL_SAFE); } while (false) #else #define BSLMT_READERWRITERLOCKASSERT_IS_LOCKED_WRITE_SAFE(rwLock_p) \ ((void) 0) #endif #if defined(BSLS_ASSERT_OPT_IS_ACTIVE) #define BSLMT_READERWRITERLOCKASSERT_IS_LOCKED_WRITE_OPT(rwLock_p) do { \ bslmt::ReaderWriterLockAssert_Imp::assertIsLockedWrite( \ (rwLock_p), \ "BSLMT_READERWRITERLOCKASSERT_IS_LOCKED_WRITE_OPT(" #rwLock_p ")", \ __FILE__, \ __LINE__, \ bsls::Assert::k_LEVEL_OPT); } while (false) #else #define BSLMT_READERWRITERLOCKASSERT_IS_LOCKED_WRITE_OPT(rwLock_p) \ ((void) 0) #endif // ---------------------------------------------------------------------------- namespace BloombergLP { namespace bslmt { // ================================ // class ReaderWriterLockAssert_Imp // ================================ struct ReaderWriterLockAssert_Imp { // This 'struct' provides a (component private) namespace for // implementation functions of the assert macros defined in this component. // This class should *not* be used directly in client code. // CLASS METHODS template <class RW_LOCK> static void assertIsLocked(RW_LOCK *rwLock, const char *text, const char *file, int line, const char *level); // If the specified 'rwLock' is not locked (i.e., neither a read lock // or a write lock), call 'bsls::Assert::invokeHandler' with the // specified 'text', 'file', 'line', and 'level', where 'text' is text // describing the assertion being performed, 'file' is the name of the // source file that called the macro, 'line' is the line number in the // file where the macro was called, and 'level' is one of the // 'bslsAssert::k_LEVEL_*' string literals. This function is intended // to implement 'BSLMT_READERWRITERLOCKASSERT_IS_LOCKED', // 'BSLMT_READERWRITERLOCKASSERT_IS_LOCKED_SAFE', and // 'BSLMT_READERWRITERLOCKASSERT_IS_LOCKED_OPT' and should not // otherwise be called directly. template <class RW_LOCK> static void assertIsLockedRead(RW_LOCK *rwLock, const char *text, const char *file, int line, const char *level); // If the specified 'rwLock' is not locked for reading, call // 'bsls::Assert::invokeHandler' with the specified 'text', 'file', // 'line', and 'level', where 'text' is text describing the assertion // being performed, 'file' is the name of the source file that called // the macro, 'line' is the line number in the file where the macro was // called, and 'level' is one of the 'bslsAssert::k_LEVEL_*' string // literals. This function is intended to implement // 'BSLMT_READERWRITERLOCKASSERT_IS_LOCKED_READ', // 'BSLMT_READERWRITERLOCKASSERT_IS_LOCKED_READ_SAFE', and // 'BSLMT_READERWRITERLOCKASSERT_IS_LOCKED_READ_OPT' and should not // otherwise be called directly. template <class RW_LOCK> static void assertIsLockedWrite(RW_LOCK *rwLock, const char *text, const char *file, int line, const char *level); // If the specified 'rwLock' is not locked for writing, call // 'bsls::Assert::invokeHandler' with the specified 'text', 'file', // 'line', and 'level', where 'text' is text describing the assertion // being performed, 'file' is the name of the source file that called // the macro, 'line' is the line number in the file where the macro was // called, and 'level' is one of the 'bsls::Assert::k_LEVEL_*' string // literals. This function is intended to implement // 'BSLMT_READERWRITERLOCKASSERT_IS_LOCKED_READ', // 'BSLMT_READERWRITERLOCKASSERT_IS_LOCKED_READ_SAFE', and // 'BSLMT_READERWRITERLOCKASSERT_IS_LOCKED_READ_OPT' and should not // otherwise be called directly. static bool isValidLevel(const char *level); // Return 'true' if the specified 'level' compares equal to one of the // 'bsls::Assert::k_LEVEL_*' string literals, and 'false' otherwise. }; // ============================================================================ // INLINE DEFINITIONS // ============================================================================ // -------------------------------- // class ReaderWriterLockAssert_Imp // -------------------------------- // CLASS METHODS template <class RW_LOCK> inline void ReaderWriterLockAssert_Imp::assertIsLocked(RW_LOCK *rwLock, const char *text, const char *file, int line, const char *level) { BSLS_ASSERT(rwLock); BSLS_ASSERT(text); BSLS_ASSERT(file); BSLS_ASSERT(level); BSLS_ASSERT(isValidLevel(level)); if (!rwLock->isLocked()) { bsls::AssertViolation violation(text, file, line, level); bsls::Assert::invokeHandler(violation); } } template <class RW_LOCK> inline void ReaderWriterLockAssert_Imp::assertIsLockedRead(RW_LOCK *rwLock, const char *text, const char *file, int line, const char *level) { BSLS_ASSERT(rwLock); BSLS_ASSERT(text); BSLS_ASSERT(file); BSLS_ASSERT(level); BSLS_ASSERT(isValidLevel(level)); if (!rwLock->isLockedRead()) { bsls::AssertViolation violation(text, file, line, level); bsls::Assert::invokeHandler(violation); } } template <class RW_LOCK> inline void ReaderWriterLockAssert_Imp::assertIsLockedWrite(RW_LOCK *rwLock, const char *text, const char *file, int line, const char *level) { BSLS_ASSERT(rwLock); BSLS_ASSERT(text); BSLS_ASSERT(file); BSLS_ASSERT(level); BSLS_ASSERT(isValidLevel(level)); if (!rwLock->isLockedWrite()) { bsls::AssertViolation violation(text, file, line, level); bsls::Assert::invokeHandler(violation); } } inline bool ReaderWriterLockAssert_Imp::isValidLevel(const char *level) { BSLS_ASSERT(level); return 0 == std::strcmp(level, bsls::Assert::k_LEVEL_SAFE) || 0 == std::strcmp(level, bsls::Assert::k_LEVEL_OPT) || 0 == std::strcmp(level, bsls::Assert::k_LEVEL_ASSERT) || 0 == std::strcmp(level, bsls::Assert::k_LEVEL_INVOKE); } } // close package namespace } // close enterprise namespace #endif // ---------------------------------------------------------------------------- // Copyright 2019 Bloomberg Finance L.P. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // ----------------------------- END-OF-FILE ----------------------------------