From 3b237267fb42ea2f7a96c6eaaca64eee849bbce2 Mon Sep 17 00:00:00 2001 From: Michael Glassford Date: Tue, 29 Mar 2005 21:46:38 +0000 Subject: [PATCH] Added more debugging aids, assertions. Corrected errors, including hangs, at the cost or making the read-write mutex even larger. The whole read-write mutex is too complicated; the design needs to be revisited. [SVN r27874] --- .../boost/thread/detail/read_write_lock.hpp | 8 +- include/boost/thread/read_write_mutex.hpp | 61 +- src/read_write_mutex.cpp | 1166 ++++++++++++----- 3 files changed, 907 insertions(+), 328 deletions(-) diff --git a/include/boost/thread/detail/read_write_lock.hpp b/include/boost/thread/detail/read_write_lock.hpp index 01544184..2116a5de 100644 --- a/include/boost/thread/detail/read_write_lock.hpp +++ b/include/boost/thread/detail/read_write_lock.hpp @@ -1,5 +1,5 @@ // Copyright (C) 2002-2003 -// David Moore +// David Moore, Michael Glassford // // Original scoped_lock implementation // Copyright (C) 2001 @@ -174,7 +174,7 @@ public: m_state = read_write_lock_state::write_locked; } - //If allow_unlock = true, set_lock always succeedes and + //If allow_unlock = true, set_lock always succeeds and //the function result indicates whether an unlock was required. //If allow_unlock = false, set_lock may fail; //the function result indicates whether it succeeded. @@ -448,7 +448,7 @@ public: return read_write_lock_ops::try_promote(m_mutex) ? (m_state = read_write_lock_state::write_locked, true) : false; } - //If allow_unlock = true, set_lock always succeedes and + //If allow_unlock = true, set_lock always succeeds and //the function result indicates whether an unlock was required. //If allow_unlock = false, set_lock may fail; //the function result indicates whether it succeeded. @@ -806,7 +806,7 @@ public: return read_write_lock_ops::timed_promote(m_mutex, xt) ? (m_state = read_write_lock_state::write_locked, true) : false; } - //If allow_unlock = true, set_lock always succeedes and + //If allow_unlock = true, set_lock always succeeds and //the function result indicates whether an unlock was required. //If allow_unlock = false, set_lock may fail; //the function result indicates whether it succeeded. diff --git a/include/boost/thread/read_write_mutex.hpp b/include/boost/thread/read_write_mutex.hpp index 94f4d794..0f369a63 100644 --- a/include/boost/thread/read_write_mutex.hpp +++ b/include/boost/thread/read_write_mutex.hpp @@ -1,5 +1,5 @@ // Copyright (C) 2002-2003 -// David Moore, William E. Kempf +// David Moore, William E. Kempf, Michael Glassford // // Permission to use, copy, modify, distribute and sell this software // and its documentation for any purpose is hereby granted without fee, @@ -33,7 +33,7 @@ namespace read_write_scheduling_policy { writer_priority, //Prefer writers; can starve readers reader_priority, //Prefer readers; can starve writers alternating_many_reads, //Alternate readers and writers; before a writer, release all queued readers - alternating_single_read //Alternate readers and writers; before a writer, release only on queued reader + alternating_single_read //Alternate readers and writers; before a writer, release only one queued reader }; } // namespace read_write_scheduling_policy @@ -51,27 +51,26 @@ struct read_write_mutex_impl typedef detail::thread::scoped_try_lock scoped_try_lock; typedef detail::thread::scoped_timed_lock scoped_timed_lock; - read_write_mutex_impl(read_write_scheduling_policy::read_write_scheduling_policy_enum sp) - : m_num_waiting_writers(0), - m_num_waiting_readers(0), - m_num_readers_to_wake(0), - m_state_waiting_promotion(false), - m_state(0), - m_sp(sp), - m_readers_next(true) { } + read_write_mutex_impl(read_write_scheduling_policy::read_write_scheduling_policy_enum sp); + ~read_write_mutex_impl(); Mutex m_prot; + + const read_write_scheduling_policy::read_write_scheduling_policy_enum m_sp; + int m_state; //-1 = write lock; 0 = unlocked; >0 = read locked + boost::condition m_waiting_writers; boost::condition m_waiting_readers; + boost::condition m_waiting_promotion; int m_num_waiting_writers; int m_num_waiting_readers; - int m_num_readers_to_wake; - boost::condition m_waiting_promotion; bool m_state_waiting_promotion; - int m_state; // -1 = excl locked - // 0 = unlocked - // 1-> INT_MAX - shared locked - const read_write_scheduling_policy::read_write_scheduling_policy_enum m_sp; + + int m_num_waking_writers; + int m_num_waking_readers; + int m_num_max_waking_writers; //Debug only + int m_num_max_waking_readers; //Debug only + bool m_readers_next; void do_read_lock(); @@ -96,12 +95,20 @@ struct read_write_mutex_impl private: - void do_unlock_scheduling_impl(); - void do_timeout_scheduling_impl(); - void do_demote_scheduling_impl(); - void do_scheduling_impl(); - bool do_demote_to_read_lock_impl(); + + enum scheduling_reason + { + scheduling_reason_unlock, + scheduling_reason_timeout, + scheduling_reason_demote + }; + + void do_scheduling_impl(const scheduling_reason reason); + bool do_wake_one_reader(void); + bool do_wake_all_readers(void); + bool do_wake_writer(void); + bool waker_exists(void); }; } // namespace detail @@ -112,8 +119,8 @@ class BOOST_THREAD_DECL read_write_mutex : private noncopyable { public: - read_write_mutex(read_write_scheduling_policy::read_write_scheduling_policy_enum sp) : m_impl(sp) { } - ~read_write_mutex() { } + read_write_mutex(read_write_scheduling_policy::read_write_scheduling_policy_enum sp); + ~read_write_mutex(); read_write_scheduling_policy::read_write_scheduling_policy_enum policy() const { return m_impl.m_sp; } @@ -151,8 +158,8 @@ class BOOST_THREAD_DECL try_read_write_mutex : private noncopyable { public: - try_read_write_mutex(read_write_scheduling_policy::read_write_scheduling_policy_enum sp) : m_impl(sp) { } - ~try_read_write_mutex() { } + try_read_write_mutex(read_write_scheduling_policy::read_write_scheduling_policy_enum sp); + ~try_read_write_mutex(); read_write_scheduling_policy::read_write_scheduling_policy_enum policy() const { return m_impl.m_sp; } @@ -201,8 +208,8 @@ class BOOST_THREAD_DECL timed_read_write_mutex : private noncopyable { public: - timed_read_write_mutex(read_write_scheduling_policy::read_write_scheduling_policy_enum sp) : m_impl(sp) { } - ~timed_read_write_mutex() { } + timed_read_write_mutex(read_write_scheduling_policy::read_write_scheduling_policy_enum sp); + ~timed_read_write_mutex(); read_write_scheduling_policy::read_write_scheduling_policy_enum policy() const { return m_impl.m_sp; } diff --git a/src/read_write_mutex.cpp b/src/read_write_mutex.cpp index 17a5a4f6..5ab67a20 100644 --- a/src/read_write_mutex.cpp +++ b/src/read_write_mutex.cpp @@ -1,5 +1,5 @@ // Copyright (C) 2002-2003 -// David Moore, William E. Kempf +// David Moore, William E. Kempf, Michael Glassford // // Permission to use, copy, modify, distribute and sell this software // and its documentation for any purpose is hereby granted without fee, @@ -9,22 +9,107 @@ // about the suitability of this software for any purpose. // It is provided "as is" without express or implied warranty. +/* +PROBLEMS: + +The algorithms are not exception safe. For instance, if conditon::wait() +or another call throws an exception, the lock state and other state data +are not appropriately adjusted. + +A harder problem to fix is that, if a thread is killed while inside the +read-write mutex functions (for example, while waiting), +bad things happen. +*/ + #include #include #include #include -#ifdef BOOST_HAS_WINTHREADS - #include - #include +#if !defined(BOOST_NO_STRINGSTREAM) +# include +#endif - #if !((_WIN32_WINNT >= 0x0400) || (_WIN32_WINDOWS > 0x0400)) +#ifdef BOOST_HAS_WINTHREADS +# include +# include + +# if !((_WIN32_WINNT >= 0x0400) || (_WIN32_WINDOWS > 0x0400)) inline bool IsDebuggerPresent(void) { return false; } - #endif + inline void OutputDebugStringA(LPCTSTR) + { + } +# endif + +# if defined(BOOST_READ_WRITE_MUTEX_USE_TRACE) && !defined(BOOST_NO_STRINGSTREAM) + inline void DoTrace( + const char* message, + int state, + int num_waiting_writers, + int num_waiting_readers, + bool state_waiting_promotion, + int num_waking_writers, + int num_waking_readers, + int num_max_waking_writers, + int num_max_waking_readers, + bool readers_next + ) + { + std::ostringstream stream; + stream + << std::endl + << "***** " + << std::hex << GetCurrentThreadId() << std::dec << " " + << message << " " + << state << " " + << "[" + << num_waiting_writers << " " + << num_waking_writers << " " + << num_max_waking_writers + << "] [" + << num_waiting_readers << " " + << num_waking_readers << " " + << num_max_waking_readers + << "]" << " " + << state_waiting_promotion << " " + << readers_next + << std::endl; + ::OutputDebugStringA(stream.str().c_str()); + } +# else + inline void DoTrace( + const char* message, + int state, + int num_waiting_writers, + int num_waiting_readers, + bool state_waiting_promotion, + int num_waking_writers, + int num_waking_readers, + int num_max_waking_writers, + int num_max_waking_readers, + bool readers_next + ) + { + } +# endif + +# define BOOST_READ_WRITE_MUTEX_TRACE(message) \ + DoTrace( \ + message, \ + m_state, \ + m_num_waiting_writers, \ + m_num_waiting_readers, \ + m_state_waiting_promotion, \ + m_num_waking_writers, \ + m_num_waking_readers, \ + m_num_max_waking_writers, \ + m_num_max_waking_readers, \ + m_readers_next \ + ) #endif #if defined(BOOST_ASSERT) @@ -33,6 +118,31 @@ # define BOOST_ASSERT_ELSE(expr) if (false) {} else #endif +//The following macro checks for invalid loop conditions +//by checking for wait loops that loop more than once. +//Please note that this does not necessarily indicate any +//kind of error; there are several valid reasons for +//a wait loop to loop more than once. For instance: +//1) if a condition signals a spurious wakeup, +// it should wait again; +//2) if a several waiting threads (e.g. readers) are +// notified, and the first to wake changes conditions +// so that the others should no longer wake (e.g. +// by promoting itself to a writer), the wait loops +// of the other threads, when they wake, will loop again +// and they will wait again. +//For this reason, the BOOST_ASSERT_LOOP_COUNT is only +//enabled when specifically requested by #defining +//the BOOST_READ_WRITE_MUTEX_TEST_LOOP_COUNTS macro. + +#if defined(BOOST_READ_WRITE_MUTEX_TEST_LOOP_COUNTS) +# define BOOST_DEFINE_LOOP_COUNT int loop_count = 0; +# define BOOST_ASSERT_LOOP_COUNT() BOOST_ASSERT(++loop_count == 1) +#else +# define BOOST_DEFINE_LOOP_COUNT do {} while(false); +# define BOOST_ASSERT_LOOP_COUNT() do {} while(false) +#endif + bool boost_error(char const* expr, char const* func, char const* file, long line) { #if WINVER @@ -58,17 +168,19 @@ namespace boost { namespace detail { namespace thread { -inline bool valid_lock(int state) +//---------------------------------------- + +inline bool valid_lock_state(int state) { return (state >= 0) || (state == -1); } -inline bool valid_read_write_lock(int state) +inline bool valid_read_write_locked(int state) { return state != 0; } -inline bool valid_read_lock(int state) +inline bool valid_read_locked(int state) { return state > 0; } @@ -78,7 +190,7 @@ inline bool valid_read_lockable(int state) return state >= 0; } -inline bool valid_write_lock(int state) +inline bool valid_write_locked(int state) { return state == -1; } @@ -88,53 +200,260 @@ inline bool valid_write_lockable(int state) return state == 0; } +inline bool valid_promotable(int state) +{ + return state == 1; +} + +inline bool valid_unlocked(int state) +{ + return state == 0; +} + +//---------------------------------------- + +class adjust_state +{ +public: + + adjust_state(bool& state, bool adjust = true) + : state_(state) + , adjust_(adjust) + { + if (adjust_) + state_ = true; + } + + ~adjust_state(void) + { + adjust_now(); + } + + void set_adjust(bool adjust) + { + adjust_ = adjust; + } + + void adjust_now(void) + { + if (adjust_) + { + BOOST_ASSERT(state_); + state_ = false; + } + else + { + BOOST_ASSERT(!state_); + } + adjust_ = false; + } + +private: + + bool& state_; + bool adjust_; +}; + +//---------------------------------------- + +class adjust_count +{ +public: + + adjust_count(int& count, bool adjust = true) + : count_(count) + , adjust_(adjust) + { + if (adjust_) + ++count_; + } + + ~adjust_count(void) + { + adjust_now(); + } + + void set_adjust(bool adjust) + { + adjust_ = adjust; + } + + void adjust_now(void) + { + if (adjust_) + { + BOOST_ASSERT(count_ > 0); + if (count_ > 0) + --count_; + } + else + { + BOOST_ASSERT(count_ >= 0); + } + adjust_ = false; + } + +private: + + int& count_; + bool adjust_; +}; + +//---------------------------------------- + +/* +Because of the possibility that threads that call +timed_wait may timeout instead of waking, even after +they have been notified, the counters m_num_waking_writers +and m_num_waking_readers cannot be exact, but rather keep +track of the minimum number of writers and readers (respectively) +that are waking. For this reason, adjust_count may decrement +too many times. The max_count mechanism is an attempt to +keep track of the maximum as well. +*/ + +class adjust_dual_count +{ +public: + + adjust_dual_count(int& count, int& max_count, bool adjust = true) + : count_(count) + , max_count_(max_count) + , adjust_(adjust) + { + BOOST_ASSERT(&max_count_ != &count_); + BOOST_ASSERT(max_count_ >= count_); + + if (adjust_) + { + ++count_; + ++max_count_; + } + } + + ~adjust_dual_count(void) + { + adjust_now(); + } + + void set_adjust(bool adjust) + { + adjust_ = adjust; + } + + void adjust_now(void) + { + BOOST_ASSERT(max_count_ >= count_); + + if (adjust_) + { + BOOST_ASSERT(max_count_ > 0); + if (count_ > 0) + --count_; + if (max_count_ > 0) + --max_count_; + } + else + { + BOOST_ASSERT(max_count_ >= 0); + } + adjust_ = false; + } + +private: + + int& count_; + int& max_count_; + bool adjust_; +}; + +//---------------------------------------- + +template +read_write_mutex_impl::read_write_mutex_impl(read_write_scheduling_policy::read_write_scheduling_policy_enum sp) + : m_num_waiting_writers(0) + , m_num_waiting_readers(0) + , m_num_waking_writers(0) + , m_num_max_waking_writers(0) + , m_num_waking_readers(0) + , m_num_max_waking_readers(0) + , m_state_waiting_promotion(false) + , m_state(0) + , m_sp(sp) + , m_readers_next(true) +{} + +template +read_write_mutex_impl::~read_write_mutex_impl() +{ + BOOST_ASSERT(valid_unlocked(m_state)); + + BOOST_ASSERT(m_num_waiting_writers == 0); + BOOST_ASSERT(m_num_waiting_readers == 0); + BOOST_ASSERT(!m_state_waiting_promotion); + + BOOST_ASSERT(m_num_waking_writers == 0); + BOOST_ASSERT(m_num_max_waking_writers == 0); + BOOST_ASSERT(m_num_waking_readers == 0); + BOOST_ASSERT(m_num_max_waking_readers == 0); +} + template void read_write_mutex_impl::do_read_lock() { typename Mutex::scoped_lock l(m_prot); - BOOST_ASSERT(valid_lock(m_state)); + BOOST_READ_WRITE_MUTEX_TRACE("do_read_lock() enter"); + BOOST_ASSERT(valid_lock_state(m_state)); if (m_sp == read_write_scheduling_policy::reader_priority) { //Reader priority: wait while write-locked - int loop_count = 0; + BOOST_DEFINE_LOOP_COUNT; + adjust_dual_count adjust_waking(m_num_waking_readers, m_num_max_waking_readers, false); while (m_state == -1) { - BOOST_ASSERT(++loop_count == 1); //Check for invalid loop conditions (but will also detect spurious wakeups) - ++m_num_waiting_readers; + BOOST_ASSERT_LOOP_COUNT(); //See note at BOOST_ASSERT_LOOP_COUNT definition above + BOOST_ASSERT(waker_exists()); //There should be someone to wake us up + adjust_count adjust_waiting(m_num_waiting_readers); + adjust_waking.set_adjust(true); m_waiting_readers.wait(l); - --m_num_waiting_readers; - } + adjust_waking.adjust_now(); + }; } else if (m_sp == read_write_scheduling_policy::writer_priority) { //Writer priority: wait while write-locked or while writers are waiting - int loop_count = 0; - while (m_state == -1 || m_num_waiting_writers > 0) + BOOST_DEFINE_LOOP_COUNT; + adjust_dual_count adjust_waking(m_num_waking_readers, m_num_max_waking_readers, false); +//: if (m_num_waiting_writers > 0 && m_num_waking_writers == 0) +//: do_wake_one_writer(); + while (m_state == -1 || (m_num_waking_writers > 0) || (m_num_waiting_writers > 0 && waker_exists())) { - BOOST_ASSERT(++loop_count == 1); //Check for invalid loop conditions (but will also detect spurious wakeups) - ++m_num_waiting_readers; + BOOST_ASSERT_LOOP_COUNT(); //See note at BOOST_ASSERT_LOOP_COUNT definition above + BOOST_ASSERT(waker_exists()); //There should be someone to wake us up + adjust_count adjust_waiting(m_num_waiting_readers); + adjust_waking.set_adjust(true); m_waiting_readers.wait(l); - --m_num_waiting_readers; + adjust_waking.adjust_now(); } } else BOOST_ASSERT_ELSE(m_sp == read_write_scheduling_policy::alternating_single_read || m_sp == read_write_scheduling_policy::alternating_many_reads) { //Alternating priority: wait while write-locked or while not readers' turn - int loop_count = 0; - while (m_state == -1 || m_num_readers_to_wake == 0) + BOOST_DEFINE_LOOP_COUNT; + adjust_dual_count adjust_waking(m_num_waking_readers, m_num_max_waking_readers, false); + while (m_state == -1 || (m_num_waiting_writers > 0 && m_num_waking_readers == 0 && waker_exists())) { - BOOST_ASSERT(++loop_count == 1); //Check for invalid loop conditions (but will also detect spurious wakeups) - ++m_num_waiting_readers; + BOOST_ASSERT_LOOP_COUNT(); //See note at BOOST_ASSERT_LOOP_COUNT definition above + BOOST_ASSERT(waker_exists()); //There should be someone to wake us up + adjust_count adjust_waiting(m_num_waiting_readers); + adjust_waking.set_adjust(true); m_waiting_readers.wait(l); - --m_num_waiting_readers; + adjust_waking.adjust_now(); } - - BOOST_ASSERT(m_num_readers_to_wake > 0); - --m_num_readers_to_wake; } //Obtain a read lock @@ -154,43 +473,49 @@ void read_write_mutex_impl::do_read_lock() m_readers_next = false; - BOOST_ASSERT(valid_read_lock(m_state)); + BOOST_ASSERT(valid_read_locked(m_state)); + BOOST_READ_WRITE_MUTEX_TRACE("do_read_lock() exit"); } template void read_write_mutex_impl::do_write_lock() { typename Mutex::scoped_lock l(m_prot); - BOOST_ASSERT(valid_lock(m_state)); + BOOST_READ_WRITE_MUTEX_TRACE("do_write_lock() enter"); + BOOST_ASSERT(valid_lock_state(m_state)); if (m_sp == read_write_scheduling_policy::reader_priority) { //Reader priority: wait while locked or while readers are waiting - int loop_count = 0; - while (m_state != 0 || m_num_waiting_readers > 0) + BOOST_DEFINE_LOOP_COUNT; + adjust_dual_count adjust_waking(m_num_waking_writers, m_num_max_waking_writers, false); +//: if (m_num_waiting_readers > 0 && m_num_waking_readers == 0) +//: do_wake_all_readers(); + while (m_state != 0 || (m_num_waking_readers > 0) || (m_num_waiting_readers > 0 && waker_exists())) { - BOOST_ASSERT(++loop_count == 1); //Check for invalid loop conditions (but will also detect spurious wakeups) - ++m_num_waiting_writers; + BOOST_ASSERT_LOOP_COUNT(); //See note at BOOST_ASSERT_LOOP_COUNT definition above + BOOST_ASSERT(waker_exists()); //There should be someone to wake us up + adjust_count adjust_waiting(m_num_waiting_writers); + adjust_waking.set_adjust(true); m_waiting_writers.wait(l); - --m_num_waiting_writers; + adjust_waking.adjust_now(); } } else if (m_sp == read_write_scheduling_policy::writer_priority) { - //Shut down extra readers that were scheduled only because of no waiting writers - - m_num_readers_to_wake = 0; - //Writer priority: wait while locked - int loop_count = 0; + BOOST_DEFINE_LOOP_COUNT; + adjust_dual_count adjust_waking(m_num_waking_writers, m_num_max_waking_writers, false); while (m_state != 0) { - BOOST_ASSERT(++loop_count == 1); //Check for invalid loop conditions (but will also detect spurious wakeups) - ++m_num_waiting_writers; + BOOST_ASSERT_LOOP_COUNT(); //See note at BOOST_ASSERT_LOOP_COUNT definition above + BOOST_ASSERT(waker_exists()); //There should be someone to wake us up + adjust_count adjust_waiting(m_num_waiting_writers); + adjust_waking.set_adjust(true); m_waiting_writers.wait(l); - --m_num_waiting_writers; + adjust_waking.adjust_now(); } } else BOOST_ASSERT_ELSE(m_sp == read_write_scheduling_policy::alternating_single_read || m_sp == read_write_scheduling_policy::alternating_many_reads) @@ -198,17 +523,20 @@ void read_write_mutex_impl::do_write_lock() //Shut down extra readers that were scheduled only because of no waiting writers if (m_sp == read_write_scheduling_policy::alternating_single_read && m_num_waiting_writers == 0) - m_num_readers_to_wake = (m_readers_next && m_num_readers_to_wake > 0) ? 1 : 0; + m_num_waking_readers = (m_readers_next && m_num_waking_readers > 0) ? 1 : 0; //Alternating priority: wait while locked or while not writers' turn - int loop_count = 0; - while (m_state != 0 || m_num_readers_to_wake > 0) + BOOST_DEFINE_LOOP_COUNT; + adjust_dual_count adjust_waking(m_num_waking_writers, m_num_max_waking_writers, false); + while (m_state != 0 || (m_num_waking_readers > 0 && waker_exists())) { - BOOST_ASSERT(++loop_count == 1); //Check for invalid loop conditions (but will also detect spurious wakeups) - ++m_num_waiting_writers; + BOOST_ASSERT_LOOP_COUNT(); //See note at BOOST_ASSERT_LOOP_COUNT definition above + BOOST_ASSERT(waker_exists()); //There should be someone to wake us up + adjust_count adjust_waiting(m_num_waiting_writers); + adjust_waking.set_adjust(true); m_waiting_writers.wait(l); - --m_num_waiting_writers; + adjust_waking.adjust_now(); } } @@ -222,17 +550,22 @@ void read_write_mutex_impl::do_write_lock() m_readers_next = true; - BOOST_ASSERT(valid_write_lock(m_state)); + BOOST_ASSERT(valid_write_locked(m_state)); + BOOST_READ_WRITE_MUTEX_TRACE("do_write_lock() exit"); } template bool read_write_mutex_impl::do_try_read_lock() { typename Mutex::scoped_try_lock l(m_prot); - BOOST_ASSERT(valid_lock(m_state)); + BOOST_ASSERT(valid_lock_state(m_state)); + BOOST_READ_WRITE_MUTEX_TRACE("do_try_read_lock() enter"); if (!l.locked()) + { + BOOST_READ_WRITE_MUTEX_TRACE("do_try_read_lock() exit 1"); return false; + } bool fail; @@ -249,13 +582,7 @@ bool read_write_mutex_impl::do_try_read_lock() else BOOST_ASSERT_ELSE(m_sp == read_write_scheduling_policy::alternating_single_read || m_sp == read_write_scheduling_policy::alternating_many_reads) { //Alternating priority: fail if write-locked or if not readers' turn - fail = (m_state == -1 || m_num_readers_to_wake == 0); - - if (!fail) - { - BOOST_ASSERT(m_num_readers_to_wake > 0); - --m_num_readers_to_wake; - } + fail = (m_state == -1 || (m_num_waiting_writers > 0 && m_num_waking_readers == 0)); } if (!fail) @@ -270,16 +597,17 @@ bool read_write_mutex_impl::do_try_read_lock() m_readers_next = false; - BOOST_ASSERT(valid_read_lock(m_state)); + BOOST_ASSERT(valid_read_locked(m_state)); //Should be read-locked } else { - BOOST_ASSERT(valid_write_lock(m_state) || m_num_waiting_writers > 0); + BOOST_ASSERT(valid_write_locked(m_state) || m_num_waiting_writers > 0); //Should be write-locked or //writer should be waiting } + BOOST_READ_WRITE_MUTEX_TRACE("do_try_read_lock() exit 2"); return !fail; } @@ -287,10 +615,14 @@ template bool read_write_mutex_impl::do_try_write_lock() { typename Mutex::scoped_try_lock l(m_prot); - BOOST_ASSERT(valid_lock(m_state)); + BOOST_READ_WRITE_MUTEX_TRACE("do_try_write_lock() enter"); + BOOST_ASSERT(valid_lock_state(m_state)); if (!l.locked()) + { + BOOST_READ_WRITE_MUTEX_TRACE("do_try_write_lock() exit 1"); return false; + } bool fail; @@ -307,7 +639,7 @@ bool read_write_mutex_impl::do_try_write_lock() else BOOST_ASSERT_ELSE(m_sp == read_write_scheduling_policy::alternating_single_read || m_sp == read_write_scheduling_policy::alternating_many_reads) { //Alternating priority: fail if locked or if not writers' turn - fail = (m_state != 0 || m_num_readers_to_wake > 0); + fail = (m_state != 0 || m_num_waking_readers > 0); } if (!fail) @@ -322,16 +654,17 @@ bool read_write_mutex_impl::do_try_write_lock() m_readers_next = true; - BOOST_ASSERT(valid_write_lock(m_state)); + BOOST_ASSERT(valid_write_locked(m_state)); //Should be write-locked } else { - BOOST_ASSERT(valid_read_write_lock(m_state) || m_num_readers_to_wake > 0); + BOOST_ASSERT(valid_read_write_locked(m_state) || m_num_waiting_readers > 0); //Should be read-locked or write-locked, or - //reader should be waking + //reader should be waiting } + BOOST_READ_WRITE_MUTEX_TRACE("do_try_write_lock() exit 2"); return !fail; } @@ -339,10 +672,14 @@ template bool read_write_mutex_impl::do_timed_read_lock(const boost::xtime &xt) { typename Mutex::scoped_timed_lock l(m_prot, xt); - BOOST_ASSERT(valid_lock(m_state)); + BOOST_READ_WRITE_MUTEX_TRACE("do_timed_read_lock() enter"); + BOOST_ASSERT(valid_lock_state(m_state)); if (!l.locked()) + { + BOOST_READ_WRITE_MUTEX_TRACE("do_timed_read_lock() exit 1"); return false; + } bool fail = false; @@ -350,60 +687,65 @@ bool read_write_mutex_impl::do_timed_read_lock(const boost::xtime &xt) { //Reader priority: wait while write-locked - int loop_count = 0; + BOOST_DEFINE_LOOP_COUNT; + adjust_dual_count adjust_waking(m_num_waking_readers, m_num_max_waking_readers, false); while (m_state == -1) { - BOOST_ASSERT(++loop_count == 1); //Check for invalid loop conditions (but will also detect spurious wakeups) - ++m_num_waiting_readers; + BOOST_ASSERT_LOOP_COUNT(); //See note at BOOST_ASSERT_LOOP_COUNT definition above + BOOST_ASSERT(waker_exists()); //There should be someone to wake us up + adjust_count adjust_waiting(m_num_waiting_readers); + adjust_waking.set_adjust(true); if (!m_waiting_readers.timed_wait(l, xt)) { - --m_num_waiting_readers; + ++m_num_max_waking_readers; fail = true; break; } - --m_num_waiting_readers; + adjust_waking.adjust_now(); } } else if (m_sp == read_write_scheduling_policy::writer_priority) { //Writer priority: wait while write-locked or while writers are waiting - int loop_count = 0; - while (m_state == -1 || m_num_waiting_writers > 0) + BOOST_DEFINE_LOOP_COUNT; + adjust_dual_count adjust_waking(m_num_waking_readers, m_num_max_waking_readers, false); +//: if (m_num_waiting_writers > 0 && m_num_waking_writers == 0) +//: do_wake_one_writer(); + while (m_state == -1 || (m_num_waking_writers > 0) || (m_num_waiting_writers > 0 && waker_exists())) { - BOOST_ASSERT(++loop_count == 1); //Check for invalid loop conditions (but will also detect spurious wakeups) - ++m_num_waiting_readers; + BOOST_ASSERT_LOOP_COUNT(); //See note at BOOST_ASSERT_LOOP_COUNT definition above + BOOST_ASSERT(waker_exists()); //There should be someone to wake us up + adjust_count adjust_waiting(m_num_waiting_readers); + adjust_waking.set_adjust(true); if (!m_waiting_readers.timed_wait(l, xt)) { - --m_num_waiting_readers; + ++m_num_max_waking_readers; fail = true; break; } - --m_num_waiting_readers; + adjust_waking.adjust_now(); } } else BOOST_ASSERT_ELSE(m_sp == read_write_scheduling_policy::alternating_single_read || m_sp == read_write_scheduling_policy::alternating_many_reads) { //Alternating priority: wait while write-locked or while not readers' turn - int loop_count = 0; - while (m_state == -1 || m_num_readers_to_wake == 0) + BOOST_DEFINE_LOOP_COUNT; + while (m_state == -1 || (m_num_waiting_writers > 0 && m_num_waking_readers == 0 && waker_exists())) { - BOOST_ASSERT(++loop_count == 1); //Check for invalid loop conditions (but will also detect spurious wakeups) - ++m_num_waiting_readers; + adjust_dual_count adjust_waking(m_num_waking_readers, m_num_max_waking_readers, false); + BOOST_ASSERT_LOOP_COUNT(); //See note at BOOST_ASSERT_LOOP_COUNT definition above + BOOST_ASSERT(waker_exists()); //There should be someone to wake us up + adjust_count adjust_waiting(m_num_waiting_readers); + adjust_waking.set_adjust(true); if (!m_waiting_readers.timed_wait(l, xt)) { - --m_num_waiting_readers; + ++m_num_max_waking_readers; fail = true; break; } - --m_num_waiting_readers; - } - - if (!fail) - { - BOOST_ASSERT(m_num_readers_to_wake > 0); - --m_num_readers_to_wake; + adjust_waking.adjust_now(); } } @@ -419,35 +761,17 @@ bool read_write_mutex_impl::do_timed_read_lock(const boost::xtime &xt) m_readers_next = false; - BOOST_ASSERT(valid_read_lock(m_state)); + BOOST_ASSERT(valid_read_locked(m_state)); //Should be read-locked } else { - if (m_num_readers_to_wake > 0) - { - //If there were readers scheduled to wake, - //decrement the number in case we were that reader. - //If only one was scheduled to wake, the scheduling - //algorithm will schedule another if one is available; - //if more than one, one fewer reader will run before - //the scheduling algorithm is called again. This last - //case is not ideal, especially if a lot of waiting - //readers timeout, but without knowing whether - //we were actually one of the readers that was - //scheduled to wake it's difficult to come up - //with a better plan. - --m_num_readers_to_wake; - } - - if (m_state == 0) - { - //If there is no thread with a lock that will - //call do_scheduling_impl() when it unlocks, call it ourselves - do_timeout_scheduling_impl(); - } + //In case there is no thread with a lock that will + //call do_scheduling_impl() when it unlocks, call it ourselves + do_scheduling_impl(scheduling_reason_timeout); } + BOOST_READ_WRITE_MUTEX_TRACE("do_timed_read_lock() exit 2"); return !fail; } @@ -455,10 +779,14 @@ template bool read_write_mutex_impl::do_timed_write_lock(const boost::xtime &xt) { typename Mutex::scoped_timed_lock l(m_prot, xt); - BOOST_ASSERT(valid_lock(m_state)); + BOOST_READ_WRITE_MUTEX_TRACE("do_timed_write_lock() enter"); + BOOST_ASSERT(valid_lock_state(m_state)); if (!l.locked()) + { + BOOST_READ_WRITE_MUTEX_TRACE("do_timed_write_lock() exit 1"); return false; + } bool fail = false; @@ -466,40 +794,44 @@ bool read_write_mutex_impl::do_timed_write_lock(const boost::xtime &xt) { //Reader priority: wait while locked or while readers are waiting - int loop_count = 0; - while (m_state != 0 || m_num_waiting_readers > 0) + BOOST_DEFINE_LOOP_COUNT; + adjust_dual_count adjust_waking(m_num_waking_writers, m_num_max_waking_writers, false); +//: if (m_num_waiting_readers > 0 && m_num_waking_readers == 0) +//: do_wake_all_readers(); + while (m_state != 0 || (m_num_waking_readers > 0) || (m_num_waiting_readers > 0 && waker_exists())) { - BOOST_ASSERT(++loop_count == 1); //Check for invalid loop conditions (but will also detect spurious wakeups) - ++m_num_waiting_writers; + BOOST_ASSERT_LOOP_COUNT(); //See note at BOOST_ASSERT_LOOP_COUNT definition above + BOOST_ASSERT(waker_exists()); //There should be someone to wake us up + adjust_count adjust_waiting(m_num_waiting_writers); + adjust_waking.set_adjust(true); if (!m_waiting_writers.timed_wait(l, xt)) { - --m_num_waiting_writers; + ++m_num_max_waking_writers; fail = true; break; } - --m_num_waiting_writers; + adjust_waking.adjust_now(); } } else if (m_sp == read_write_scheduling_policy::writer_priority) { - //Shut down extra readers that were scheduled only because of no waiting writers - - m_num_readers_to_wake = 0; - //Writer priority: wait while locked - int loop_count = 0; + BOOST_DEFINE_LOOP_COUNT; + adjust_dual_count adjust_waking(m_num_waking_writers, m_num_max_waking_writers, false); while (m_state != 0) { - BOOST_ASSERT(++loop_count == 1); //Check for invalid loop conditions (but will also detect spurious wakeups) - ++m_num_waiting_writers; + BOOST_ASSERT_LOOP_COUNT(); //See note at BOOST_ASSERT_LOOP_COUNT definition above + BOOST_ASSERT(waker_exists()); //There should be someone to wake us up + adjust_count adjust_waiting(m_num_waiting_writers); + adjust_waking.set_adjust(true); if (!m_waiting_writers.timed_wait(l, xt)) { - --m_num_waiting_writers; + ++m_num_max_waking_writers; fail = true; break; } - --m_num_waiting_writers; + adjust_waking.adjust_now(); } } else BOOST_ASSERT_ELSE(m_sp == read_write_scheduling_policy::alternating_single_read || m_sp == read_write_scheduling_policy::alternating_many_reads) @@ -507,22 +839,25 @@ bool read_write_mutex_impl::do_timed_write_lock(const boost::xtime &xt) //Shut down extra readers that were scheduled only because of no waiting writers if (m_sp == read_write_scheduling_policy::alternating_single_read && m_num_waiting_writers == 0) - m_num_readers_to_wake = (m_readers_next && m_num_readers_to_wake > 0) ? 1 : 0; + m_num_waking_readers = (m_readers_next && m_num_waking_readers > 0) ? 1 : 0; //Alternating priority: wait while locked or while not writers' turn - int loop_count = 0; - while (m_state != 0 || m_num_readers_to_wake > 0) + BOOST_DEFINE_LOOP_COUNT; + adjust_dual_count adjust_waking(m_num_waking_writers, m_num_max_waking_writers, false); + while (m_state != 0 || (m_num_waking_readers > 0 && waker_exists())) { - BOOST_ASSERT(++loop_count == 1); //Check for invalid loop conditions (but will also detect spurious wakeups) - ++m_num_waiting_writers; + BOOST_ASSERT_LOOP_COUNT(); //See note at BOOST_ASSERT_LOOP_COUNT definition above + BOOST_ASSERT(waker_exists()); //There should be someone to wake us up + adjust_count adjust_waiting(m_num_waiting_writers); + adjust_waking.set_adjust(true); if (!m_waiting_writers.timed_wait(l, xt)) { - --m_num_waiting_writers; + ++m_num_max_waking_writers; fail = true; break; } - --m_num_waiting_writers; + adjust_waking.adjust_now(); } } @@ -538,19 +873,17 @@ bool read_write_mutex_impl::do_timed_write_lock(const boost::xtime &xt) m_readers_next = true; - BOOST_ASSERT(valid_write_lock(m_state)); + BOOST_ASSERT(valid_write_locked(m_state)); //Should be write-locked } else { - if (m_state == 0) - { - //If there is no thread with a lock that will - //call do_scheduling_impl() when it unlocks, call it ourselves - do_timeout_scheduling_impl(); - } + //In case there is no thread with a lock that will + //call do_scheduling_impl() when it unlocks, call it ourselves + do_scheduling_impl(scheduling_reason_timeout); } + BOOST_READ_WRITE_MUTEX_TRACE("do_timed_write_lock() exit 2"); return !fail; } @@ -558,39 +891,43 @@ template void read_write_mutex_impl::do_read_unlock() { typename Mutex::scoped_lock l(m_prot); - BOOST_ASSERT(valid_read_lock(m_state)); + BOOST_READ_WRITE_MUTEX_TRACE("do_read_unlock() enter"); + BOOST_ASSERT(valid_read_locked(m_state)); if (m_state > 0) --m_state; else //not read-locked throw lock_error(); - if (m_state == 0) - do_unlock_scheduling_impl(); + do_scheduling_impl(scheduling_reason_unlock); - BOOST_ASSERT(valid_lock(m_state)); + BOOST_ASSERT(valid_read_locked(m_state) || valid_unlocked(m_state)); + BOOST_READ_WRITE_MUTEX_TRACE("do_read_unlock() exit"); } template void read_write_mutex_impl::do_write_unlock() { typename Mutex::scoped_lock l(m_prot); - BOOST_ASSERT(valid_write_lock(m_state)); + BOOST_READ_WRITE_MUTEX_TRACE("do_write_unlock() enter"); + BOOST_ASSERT(valid_write_locked(m_state)); if (m_state == -1) m_state = 0; else BOOST_ASSERT_ELSE(m_state >= 0) throw lock_error(); // Trying to release a reader-locked or unlocked mutex??? - do_unlock_scheduling_impl(); + do_scheduling_impl(scheduling_reason_unlock); - BOOST_ASSERT(valid_lock(m_state)); + BOOST_ASSERT(valid_unlocked(m_state)); + BOOST_READ_WRITE_MUTEX_TRACE("do_write_unlock() exit"); } template bool read_write_mutex_impl::do_demote_to_read_lock_impl() { - BOOST_ASSERT(valid_write_lock(m_state)); + BOOST_READ_WRITE_MUTEX_TRACE("do_demote_to_read_lock_impl() enter"); + BOOST_ASSERT(valid_write_locked(m_state)); if (m_state == -1) { @@ -599,15 +936,17 @@ bool read_write_mutex_impl::do_demote_to_read_lock_impl() //If the conditions are right, release other readers - do_demote_scheduling_impl(); + do_scheduling_impl(scheduling_reason_demote); //Lock demoted - BOOST_ASSERT(valid_read_lock(m_state)); + BOOST_ASSERT(valid_read_locked(m_state)); + BOOST_READ_WRITE_MUTEX_TRACE("do_demote_to_read_lock_impl() exit 1"); return true; } else BOOST_ASSERT_ELSE(m_state >= 0) { //Lock is read-locked or unlocked can't be demoted + BOOST_READ_WRITE_MUTEX_TRACE("do_demote_to_read_lock_impl() exit 2"); throw lock_error(); return false; } @@ -617,7 +956,7 @@ template void read_write_mutex_impl::do_demote_to_read_lock() { typename Mutex::scoped_lock l(m_prot); - BOOST_ASSERT(valid_write_lock(m_state)); + BOOST_ASSERT(valid_write_locked(m_state)); do_demote_to_read_lock_impl(); } @@ -626,7 +965,7 @@ template bool read_write_mutex_impl::do_try_demote_to_read_lock() { typename Mutex::scoped_try_lock l(m_prot); - BOOST_ASSERT(valid_write_lock(m_state)); + BOOST_ASSERT(valid_write_locked(m_state)); if (!l.locked()) return false; @@ -638,7 +977,7 @@ template bool read_write_mutex_impl::do_timed_demote_to_read_lock(const boost::xtime &xt) { typename Mutex::scoped_timed_lock l(m_prot, xt); - BOOST_ASSERT(valid_write_lock(m_state)); + BOOST_ASSERT(valid_write_locked(m_state)); if (!l.locked()) return false; @@ -650,7 +989,8 @@ template void read_write_mutex_impl::do_promote_to_write_lock() { typename Mutex::scoped_lock l(m_prot); - BOOST_ASSERT(valid_read_lock(m_state)); + BOOST_READ_WRITE_MUTEX_TRACE("do_promote_to_write_lock() enter"); + BOOST_ASSERT(valid_read_locked(m_state)); if (m_state == 1) { @@ -658,7 +998,7 @@ void read_write_mutex_impl::do_promote_to_write_lock() m_state = -1; //Lock promoted - BOOST_ASSERT(valid_write_lock(m_state)); + BOOST_ASSERT(valid_write_locked(m_state)); } else if (m_state <= 0) { @@ -672,38 +1012,42 @@ void read_write_mutex_impl::do_promote_to_write_lock() } else BOOST_ASSERT_ELSE(m_state > 1 && !m_state_waiting_promotion) { - ++m_num_waiting_writers; - m_state_waiting_promotion = true; - - int loop_count = 0; + BOOST_DEFINE_LOOP_COUNT; + adjust_dual_count adjust_waking(m_num_waking_writers, m_num_max_waking_writers, false); while (m_state > 1) { - BOOST_ASSERT(++loop_count == 1); //Check for invalid loop conditions (but will also detect spurious wakeups) + BOOST_ASSERT_LOOP_COUNT(); //See note at BOOST_ASSERT_LOOP_COUNT definition above + BOOST_ASSERT(waker_exists()); //There should be someone to wake us up + adjust_count adjust_waiting(m_num_waiting_writers); + adjust_state adjust_waiting_promotion(m_state_waiting_promotion); + adjust_waking.set_adjust(true); m_waiting_promotion.wait(l); + adjust_waking.adjust_now(); } - m_state_waiting_promotion = false; - --m_num_waiting_writers; - BOOST_ASSERT(m_num_waiting_writers >= 0); - BOOST_ASSERT(m_state == 1); + BOOST_ASSERT(valid_promotable(m_state)); //Convert from read lock to write lock m_state = -1; //Lock promoted - BOOST_ASSERT(valid_write_lock(m_state)); + BOOST_ASSERT(valid_write_locked(m_state)); } + BOOST_READ_WRITE_MUTEX_TRACE("do_promote_to_write_lock() exit"); } template bool read_write_mutex_impl::do_try_promote_to_write_lock() { typename Mutex::scoped_try_lock l(m_prot); - BOOST_ASSERT(valid_read_lock(m_state)); + BOOST_READ_WRITE_MUTEX_TRACE("do_try_promote_to_write_lock() enter"); + BOOST_ASSERT(valid_read_locked(m_state)); + + bool result; if (!l.locked()) - return false; + result = false; else { if (m_state == 1) @@ -712,94 +1056,109 @@ bool read_write_mutex_impl::do_try_promote_to_write_lock() m_state = -1; //Lock promoted - BOOST_ASSERT(valid_write_lock(m_state)); - return true; + BOOST_ASSERT(valid_write_locked(m_state)); + result = true; } else if (m_state <= 0) { //Lock is write-locked or unlocked can't be promoted - throw lock_error(); + result = false; } else if (m_state_waiting_promotion) { //Someone else is already trying to promote. Avoid deadlock by returning false. - return false; + result = false; } else BOOST_ASSERT_ELSE(m_state > 1 && !m_state_waiting_promotion) { //There are other readers, so we can't promote - return false; + result = false; } } + + BOOST_READ_WRITE_MUTEX_TRACE("do_try_promote_to_write_lock() exit"); + return result; } template bool read_write_mutex_impl::do_timed_promote_to_write_lock(const boost::xtime &xt) { typename Mutex::scoped_timed_lock l(m_prot, xt); - BOOST_ASSERT(valid_read_lock(m_state)); + BOOST_READ_WRITE_MUTEX_TRACE("do_timed_promote_to_write_lock() enter"); + BOOST_ASSERT(valid_read_locked(m_state)); if (!l.locked()) - return false; - else { - if (m_state == 1) - { - //Convert from read lock to write lock - m_state = -1; - - //Lock promoted - BOOST_ASSERT(valid_write_lock(m_state)); - return true; - } - else if (m_state <= 0) - { - //Lock is not read-locked and can't be promoted - throw lock_error(); - } - else if (m_state_waiting_promotion) - { - //Someone else is already trying to promote. Avoid deadlock by returning false. - return false; - } - else BOOST_ASSERT_ELSE(m_state > 1 && !m_state_waiting_promotion) - { - ++m_num_waiting_writers; - m_state_waiting_promotion = true; + BOOST_READ_WRITE_MUTEX_TRACE("do_timed_promote_to_write_lock( exit 1"); + return false; + } - int loop_count = 0; - while (m_state > 1) + bool fail = false; + + if (m_state == 1) + { + fail = false; + } + else if (m_state <= 0) + { + //Lock is not read-locked and can't be promoted + fail = true; + } + else if (m_state_waiting_promotion) + { + //Someone else is already trying to promote. Avoid deadlock by returning false. + fail = true; + } + else BOOST_ASSERT_ELSE(m_state > 1 && !m_state_waiting_promotion) + { + BOOST_DEFINE_LOOP_COUNT; + adjust_dual_count adjust_waking(m_num_waking_writers, m_num_max_waking_writers, false); + while (m_state > 1) + { + BOOST_ASSERT_LOOP_COUNT(); //See note at BOOST_ASSERT_LOOP_COUNT definition above + BOOST_ASSERT(waker_exists()); //There should be someone to wake us up + adjust_count adjust_waiting(m_num_waiting_writers); + adjust_state adjust_waiting_promotion(m_state_waiting_promotion); + adjust_waking.set_adjust(true); + if (!m_waiting_promotion.timed_wait(l, xt)) { - BOOST_ASSERT(++loop_count == 1); //Check for invalid loop conditions (but will also detect spurious wakeups) - if (!m_waiting_promotion.timed_wait(l, xt)) - { - m_state_waiting_promotion = false; - --m_num_waiting_writers; - return false; - } + ++m_num_max_waking_writers; + fail = true; + break; } - - m_state_waiting_promotion = false; - --m_num_waiting_writers; - - BOOST_ASSERT(m_num_waiting_writers >= 0); - BOOST_ASSERT(m_state == 1); - - //Convert from read lock to write lock - m_state = -1; - - //Lock promoted - BOOST_ASSERT(valid_write_lock(m_state)); - return true; + adjust_waking.adjust_now(); } } + + if (!fail) + { + //Convert from read lock to write lock + + BOOST_ASSERT(m_num_waiting_writers >= 0); + BOOST_ASSERT(valid_promotable(m_state)); + + m_state = -1; + + //Lock promoted + + BOOST_ASSERT(valid_write_locked(m_state)); + } + else + { + //In case there is no thread with a lock that will + //call do_scheduling_impl() when it unlocks, call it ourselves + do_scheduling_impl(scheduling_reason_timeout); + } + + BOOST_READ_WRITE_MUTEX_TRACE("do_timed_promote_to_write_lock( exit 2"); + return !fail; } template bool read_write_mutex_impl::locked() { int state = m_state; - BOOST_ASSERT(valid_lock(state)); + BOOST_ASSERT(valid_lock_state(state)); return state != 0; } @@ -808,16 +1167,16 @@ template read_write_lock_state::read_write_lock_state_enum read_write_mutex_impl::state() { int state = m_state; - BOOST_ASSERT(valid_lock(state)); + BOOST_ASSERT(valid_lock_state(state)); if (state > 0) { - BOOST_ASSERT(valid_read_lock(state)); + BOOST_ASSERT(valid_read_locked(state)); return read_write_lock_state::read_locked; } else if (state == -1) { - BOOST_ASSERT(valid_write_lock(state)); + BOOST_ASSERT(valid_write_locked(state)); return read_write_lock_state::write_locked; } else BOOST_ASSERT_ELSE(state == 0) @@ -825,118 +1184,317 @@ read_write_lock_state::read_write_lock_state_enum read_write_mutex_impl:: } template -void read_write_mutex_impl::do_unlock_scheduling_impl() +void read_write_mutex_impl::do_scheduling_impl(const scheduling_reason reason) { - BOOST_ASSERT(m_state == 0); - do_scheduling_impl(); -} + switch(reason) + { + case scheduling_reason_unlock: + { + BOOST_READ_WRITE_MUTEX_TRACE("do_scheduling_impl(): scheduling_reason_unlock"); -template -void read_write_mutex_impl::do_timeout_scheduling_impl() -{ - BOOST_ASSERT(m_state == 0); - do_scheduling_impl(); -} + //A thread just unlocked, so there can be no existing write lock. + //There may still be read locks, however. -template -void read_write_mutex_impl::do_demote_scheduling_impl() -{ - BOOST_ASSERT(m_state == 1); - do_scheduling_impl(); -} + BOOST_ASSERT(valid_read_locked(m_state) || valid_unlocked(m_state)); -template -void read_write_mutex_impl::do_scheduling_impl() -{ - bool demotion = m_state > 0; //Releasing readers after lock demotion? - - BOOST_ASSERT(valid_read_lockable(m_state)); + if (m_state_waiting_promotion) + { + //If a thread is waiting for promotion, + //it must have a read lock. + + BOOST_ASSERT(valid_read_locked(m_state)); + } + } + break; + + case scheduling_reason_timeout: + { + BOOST_READ_WRITE_MUTEX_TRACE("do_scheduling_impl(): scheduling_reason_timeout"); + + //A thread waiting for a lock just timed out, so the + //lock could be in any state (read locked, write locked, unlocked). + + BOOST_ASSERT(valid_lock_state(m_state)); + + if (m_state_waiting_promotion) + { + //If a thread is waiting for promotion, + //it must have a read lock. + + BOOST_ASSERT(valid_read_locked(m_state)); + } + } + break; + + case scheduling_reason_demote: + { + BOOST_READ_WRITE_MUTEX_TRACE("do_scheduling_impl(): scheduling_reason_demote"); + + //A write lock has just converted its state to a read lock; + //since a write-locked thread has an exclusive lock, + //and no other thread has yet been allowed to obtain + //a read lock, the state should indicate that there is + //exactly one reader. + + BOOST_ASSERT(m_state == 1); + + //No thread should be waiting for promotion because to do + //so it would first have to obtain a read lock, which + //is impossible because this thread had a write lock. + + BOOST_ASSERT(!m_state_waiting_promotion); + } + break; + + default: + { + BOOST_READ_WRITE_MUTEX_TRACE("do_scheduling_impl(): scheduling_reason_none"); + + throw lock_error(); + return; //eliminate warnings on some compilers + } + break; + }; + + bool woken; if (m_num_waiting_writers > 0 && m_num_waiting_readers > 0) { //Both readers and writers waiting: use scheduling policy + if (m_sp == read_write_scheduling_policy::reader_priority) { - m_num_readers_to_wake = m_num_waiting_readers; - m_waiting_readers.notify_all(); + BOOST_READ_WRITE_MUTEX_TRACE("do_scheduling_impl() 1: writers & readers, reader_priority"); + if (woken = do_wake_all_readers()) + m_num_waking_writers = m_num_max_waking_writers = 0; //shut down any waking writers } else if (m_sp == read_write_scheduling_policy::writer_priority) { - if (!demotion) - { - if (m_state_waiting_promotion) - m_waiting_promotion.notify_one(); - else - m_waiting_writers.notify_one(); - } + BOOST_READ_WRITE_MUTEX_TRACE("do_scheduling_impl(): writers & readers, writer_priority"); + if (woken = do_wake_writer()) + m_num_waking_readers = m_num_max_waking_readers = 0; //shut down any waking readers } else if (m_sp == read_write_scheduling_policy::alternating_single_read) { - if (m_num_readers_to_wake > 0) + BOOST_READ_WRITE_MUTEX_TRACE("do_scheduling_impl() 2: writers & readers, alternating_single_read"); + if (m_readers_next) { - //Let the already woken threads work - } - else if (m_readers_next) - { - m_num_readers_to_wake = 1; - m_waiting_readers.notify_one(); + if (m_num_waking_writers == 0) + woken = do_wake_one_reader(); + else + woken = false; } else { - if (!demotion) - { - if (m_state_waiting_promotion) - m_waiting_promotion.notify_one(); - else - m_waiting_writers.notify_one(); - } + if (m_num_waking_readers == 0) + woken = do_wake_writer(); + else + woken = false; } } else BOOST_ASSERT_ELSE(m_sp == read_write_scheduling_policy::alternating_many_reads) { - if (m_num_readers_to_wake > 0) + BOOST_READ_WRITE_MUTEX_TRACE("do_scheduling_impl() 3: writers & readers, alternating_many_reads"); + if (m_readers_next) { - //Let the already woken threads work - } - else if (m_readers_next) - { - m_num_readers_to_wake = m_num_waiting_readers; - m_waiting_readers.notify_all(); + if (m_num_waking_writers == 0) + woken = do_wake_all_readers(); + else + woken = false; } else { - if (!demotion) - { - if (m_state_waiting_promotion) - m_waiting_promotion.notify_one(); - else - m_waiting_writers.notify_one(); - } + if (m_num_waking_readers == 0) + woken = do_wake_writer(); + else + woken = false; } } } else if (m_num_waiting_writers > 0) { - if (!demotion) - { - //Only writers waiting--scheduling policy doesn't matter - if (m_state_waiting_promotion) - m_waiting_promotion.notify_one(); - else - m_waiting_writers.notify_one(); - } + BOOST_READ_WRITE_MUTEX_TRACE("do_scheduling_impl() 4: writers only"); + //Only writers waiting--scheduling policy doesn't matter + woken = do_wake_writer(); } else if (m_num_waiting_readers > 0) { - //Only readers waiting--scheduling policy doesn't matter - m_num_readers_to_wake = m_num_waiting_readers; - m_waiting_readers.notify_all(); + BOOST_READ_WRITE_MUTEX_TRACE("do_scheduling_impl() 5: readers only"); + //Only readers waiting--scheduling policy doesn't matter + woken = do_wake_all_readers(); } + else + { + BOOST_READ_WRITE_MUTEX_TRACE("do_scheduling_impl() 6: no writers or readers"); + woken = false; + } + + BOOST_ASSERT( + woken + || (m_state == -1) || (m_state > (m_state_waiting_promotion ? 1 : 0)) + || (m_num_waking_writers + m_num_waking_readers > 0) + || (m_num_waiting_writers + m_num_waiting_readers == 0) + ); + //Ensure that we woke a thread, + //that another besides the current thread is already awake to wake others when it's done, + //that another besides the current thread will wake and can wake others when it's done, + //or that no other threads are waiting and so none remain to be woken +} + +template +bool read_write_mutex_impl::do_wake_one_reader(void) +{ + if (m_state == -1) + { + BOOST_READ_WRITE_MUTEX_TRACE("do_wake_one_reader() 1: don't wake, write locked"); + + //If write-locked, don't bother waking a reader + } + else if (m_num_waking_readers > 0) + { + BOOST_READ_WRITE_MUTEX_TRACE("do_wake_one_reader() 2: don't wake, nothing to wake"); + + //If a reader is already waking, + //don't bother waking another + //(since we only want one) + } + else if (m_num_waiting_readers > 0) + { + BOOST_READ_WRITE_MUTEX_TRACE("do_wake_one_reader() 3: wake"); + + //Wake a reader + BOOST_ASSERT(valid_read_lockable(m_state)); + m_num_waking_readers = m_num_max_waking_readers = 1; + m_waiting_readers.notify_one(); + return true; + } + else + { + BOOST_READ_WRITE_MUTEX_TRACE("do_wake_one_reader() 4: don't wake, nothing to wake"); + } + + return false; +} + +template +bool read_write_mutex_impl::do_wake_all_readers(void) +{ + if (m_state == -1) + { + BOOST_READ_WRITE_MUTEX_TRACE("do_wake_all_readers() 1: don't wake, write locked"); + + //If write-locked, don't bother waking readers + } + else if (m_num_waiting_readers > 0) + { + BOOST_READ_WRITE_MUTEX_TRACE("do_wake_all_readers() 2: wake"); + + //Wake readers + BOOST_ASSERT(valid_read_lockable(m_state)); + m_num_waking_readers = m_num_max_waking_readers = m_num_waiting_readers; + m_waiting_readers.notify_all(); + return true; + } + else + { + BOOST_READ_WRITE_MUTEX_TRACE("do_wake_all_readers() 3: don't wake, nothing to wake"); + } + + return false; +} + +template +bool read_write_mutex_impl::do_wake_writer(void) +{ + if (m_state_waiting_promotion) + { + //If a reader is waiting for promotion, promote it + //(it holds a read lock until it is promoted, + //so it's not possible to wake a normal writer). + + if (m_state == -1 || m_state > 1) + { + BOOST_READ_WRITE_MUTEX_TRACE("do_wake_writer() 1: waiting promotion: don't wake, still locked"); + + //If write-locked, or if read-locked by + //readers other than the thread waiting + //for promotion, don't bother waking it + } + else if (m_num_waking_writers > 0) + { + BOOST_READ_WRITE_MUTEX_TRACE("do_wake_writer() 2: waiting promotion: don't wake, writer already waking"); + + //If a writer is already waking, + //don't bother waking another + //(since only one at a time can wake anyway) + } + else if (m_num_waiting_writers > 0) + { + BOOST_READ_WRITE_MUTEX_TRACE("do_wake_writer() 3: waiting promotion: wake"); + + //Wake the thread waiting for promotion + BOOST_ASSERT(valid_promotable(m_state)); + m_num_waking_writers = m_num_max_waking_writers = 1; + m_waiting_promotion.notify_one(); + return true; + } + else + { + BOOST_READ_WRITE_MUTEX_TRACE("do_wake_writer() 4: waiting promotion: don't wake, no writers to wake"); + } + } + else + { + if (m_state != 0) + { + BOOST_READ_WRITE_MUTEX_TRACE("do_wake_writer() 5: don't wake, still locked"); + + //If locked, don't bother waking a writer + } + else if (m_num_waking_writers > 0) + { + BOOST_READ_WRITE_MUTEX_TRACE("do_wake_writer() 6: don't wake, writer already waking"); + + //If a writer is already waking, + //don't bother waking another + //(since only one at a time can wake anyway) + } + else if (m_num_waiting_writers > 0) + { + BOOST_READ_WRITE_MUTEX_TRACE("do_wake_writer() 7: wake"); + + //Wake a writer + BOOST_ASSERT(valid_write_lockable(m_state)); + m_num_waking_writers = m_num_max_waking_writers = 1; + m_waiting_writers.notify_one(); + return true; + } + else + { + BOOST_READ_WRITE_MUTEX_TRACE("do_wake_writer() 8: don't wake, no writers to wake"); + } + } + + return false; +} + +template +bool read_write_mutex_impl::waker_exists(void) +{ + //Is there a "live" thread (one that is awake or about to wake + //that will be able to wake up a thread about to go to sleep? + return valid_read_write_locked(m_state) || (m_num_waking_writers + m_num_waking_readers > 0); } } // namespace thread } // namespace detail +read_write_mutex::read_write_mutex(read_write_scheduling_policy::read_write_scheduling_policy_enum sp) + : m_impl(sp) +{} + +read_write_mutex::~read_write_mutex() +{} void read_write_mutex::do_read_lock() { @@ -978,6 +1536,13 @@ read_write_lock_state::read_write_lock_state_enum read_write_mutex::state() return m_impl.state(); } +try_read_write_mutex::try_read_write_mutex(read_write_scheduling_policy::read_write_scheduling_policy_enum sp) + : m_impl(sp) +{} + +try_read_write_mutex::~try_read_write_mutex() +{} + void try_read_write_mutex::do_read_lock() { m_impl.do_read_lock(); @@ -1039,6 +1604,13 @@ read_write_lock_state::read_write_lock_state_enum try_read_write_mutex::state() return m_impl.state(); } +timed_read_write_mutex::timed_read_write_mutex(read_write_scheduling_policy::read_write_scheduling_policy_enum sp) + : m_impl(sp) +{} + +timed_read_write_mutex::~timed_read_write_mutex() +{} + void timed_read_write_mutex::do_read_lock() { m_impl.do_read_lock();