Commit 78c14b68 authored by Cornel Rat's avatar Cornel Rat Committed by Facebook Github Bot

Add writer priority mode in TimedRWMutex

Summary:
- The current TimedRwMutex behavior is biased heavily towards readers. This can lead to writer starvation under heavy read load which can result in writers unable to acquire the lock for several minutes
- The current change adds a writer priority mode to TimedRWMutex which can be specified at creation time. The default behavior will still be reader priority and since the logic is handled through templates it shouldn't introduce any side effects for the reader priority behavior

Reviewed By: matheweis

Differential Revision: D15204620

fbshipit-source-id: e940868aa9cec8f4a0a6b59c695ed3beca11c11a
parent 14010eb6
...@@ -149,13 +149,19 @@ inline void TimedMutex::unlock() { ...@@ -149,13 +149,19 @@ inline void TimedMutex::unlock() {
} }
// //
// TimedRWMutex implementation // TimedRWMutexImpl implementation
// //
template <typename BatonType> template <bool ReaderPriority, typename BatonType>
void TimedRWMutex<BatonType>::read_lock() { bool TimedRWMutexImpl<ReaderPriority, BatonType>::shouldReadersWait() const {
return state_ == State::WRITE_LOCKED ||
(!ReaderPriority && !write_waiters_.empty());
}
template <bool ReaderPriority, typename BatonType>
void TimedRWMutexImpl<ReaderPriority, BatonType>::read_lock() {
std::unique_lock<folly::SpinLock> ulock{lock_}; std::unique_lock<folly::SpinLock> ulock{lock_};
if (state_ == State::WRITE_LOCKED) { if (shouldReadersWait()) {
MutexWaiter waiter; MutexWaiter waiter;
read_waiters_.push_back(waiter); read_waiters_.push_back(waiter);
ulock.unlock(); ulock.unlock();
...@@ -171,12 +177,12 @@ void TimedRWMutex<BatonType>::read_lock() { ...@@ -171,12 +177,12 @@ void TimedRWMutex<BatonType>::read_lock() {
readers_ += 1; readers_ += 1;
} }
template <typename BatonType> template <bool ReaderPriority, typename BatonType>
template <typename Rep, typename Period> template <typename Rep, typename Period>
bool TimedRWMutex<BatonType>::timed_read_lock( bool TimedRWMutexImpl<ReaderPriority, BatonType>::timed_read_lock(
const std::chrono::duration<Rep, Period>& duration) { const std::chrono::duration<Rep, Period>& duration) {
std::unique_lock<folly::SpinLock> ulock{lock_}; std::unique_lock<folly::SpinLock> ulock{lock_};
if (state_ == State::WRITE_LOCKED) { if (shouldReadersWait()) {
MutexWaiter waiter; MutexWaiter waiter;
read_waiters_.push_back(waiter); read_waiters_.push_back(waiter);
ulock.unlock(); ulock.unlock();
...@@ -204,10 +210,10 @@ bool TimedRWMutex<BatonType>::timed_read_lock( ...@@ -204,10 +210,10 @@ bool TimedRWMutex<BatonType>::timed_read_lock(
return true; return true;
} }
template <typename BatonType> template <bool ReaderPriority, typename BatonType>
bool TimedRWMutex<BatonType>::try_read_lock() { bool TimedRWMutexImpl<ReaderPriority, BatonType>::try_read_lock() {
std::lock_guard<SpinLock> guard{lock_}; std::lock_guard<SpinLock> guard{lock_};
if (state_ != State::WRITE_LOCKED) { if (!shouldReadersWait()) {
assert( assert(
(state_ == State::UNLOCKED && readers_ == 0) || (state_ == State::UNLOCKED && readers_ == 0) ||
(state_ == State::READ_LOCKED && readers_ > 0)); (state_ == State::READ_LOCKED && readers_ > 0));
...@@ -219,8 +225,8 @@ bool TimedRWMutex<BatonType>::try_read_lock() { ...@@ -219,8 +225,8 @@ bool TimedRWMutex<BatonType>::try_read_lock() {
return false; return false;
} }
template <typename BatonType> template <bool ReaderPriority, typename BatonType>
void TimedRWMutex<BatonType>::write_lock() { void TimedRWMutexImpl<ReaderPriority, BatonType>::write_lock() {
std::unique_lock<folly::SpinLock> ulock{lock_}; std::unique_lock<folly::SpinLock> ulock{lock_};
if (state_ == State::UNLOCKED) { if (state_ == State::UNLOCKED) {
verify_unlocked_properties(); verify_unlocked_properties();
...@@ -233,9 +239,9 @@ void TimedRWMutex<BatonType>::write_lock() { ...@@ -233,9 +239,9 @@ void TimedRWMutex<BatonType>::write_lock() {
waiter.baton.wait(); waiter.baton.wait();
} }
template <typename BatonType> template <bool ReaderPriority, typename BatonType>
template <typename Rep, typename Period> template <typename Rep, typename Period>
bool TimedRWMutex<BatonType>::timed_write_lock( bool TimedRWMutexImpl<ReaderPriority, BatonType>::timed_write_lock(
const std::chrono::duration<Rep, Period>& duration) { const std::chrono::duration<Rep, Period>& duration) {
std::unique_lock<folly::SpinLock> ulock{lock_}; std::unique_lock<folly::SpinLock> ulock{lock_};
if (state_ == State::UNLOCKED) { if (state_ == State::UNLOCKED) {
...@@ -263,8 +269,8 @@ bool TimedRWMutex<BatonType>::timed_write_lock( ...@@ -263,8 +269,8 @@ bool TimedRWMutex<BatonType>::timed_write_lock(
return true; return true;
} }
template <typename BatonType> template <bool ReaderPriority, typename BatonType>
bool TimedRWMutex<BatonType>::try_write_lock() { bool TimedRWMutexImpl<ReaderPriority, BatonType>::try_write_lock() {
std::lock_guard<SpinLock> guard{lock_}; std::lock_guard<SpinLock> guard{lock_};
if (state_ == State::UNLOCKED) { if (state_ == State::UNLOCKED) {
verify_unlocked_properties(); verify_unlocked_properties();
...@@ -274,8 +280,8 @@ bool TimedRWMutex<BatonType>::try_write_lock() { ...@@ -274,8 +280,8 @@ bool TimedRWMutex<BatonType>::try_write_lock() {
return false; return false;
} }
template <typename BatonType> template <bool ReaderPriority, typename BatonType>
void TimedRWMutex<BatonType>::unlock() { void TimedRWMutexImpl<ReaderPriority, BatonType>::unlock() {
std::lock_guard<SpinLock> guard{lock_}; std::lock_guard<SpinLock> guard{lock_};
assert(state_ != State::UNLOCKED); assert(state_ != State::UNLOCKED);
assert( assert(
...@@ -285,7 +291,7 @@ void TimedRWMutex<BatonType>::unlock() { ...@@ -285,7 +291,7 @@ void TimedRWMutex<BatonType>::unlock() {
readers_ -= 1; readers_ -= 1;
} }
if (!read_waiters_.empty()) { if (!read_waiters_.empty() && (ReaderPriority || write_waiters_.empty())) {
assert( assert(
state_ == State::WRITE_LOCKED && readers_ == 0 && state_ == State::WRITE_LOCKED && readers_ == 0 &&
"read waiters can only accumulate while write locked"); "read waiters can only accumulate while write locked");
...@@ -299,7 +305,7 @@ void TimedRWMutex<BatonType>::unlock() { ...@@ -299,7 +305,7 @@ void TimedRWMutex<BatonType>::unlock() {
} }
} else if (readers_ == 0) { } else if (readers_ == 0) {
if (!write_waiters_.empty()) { if (!write_waiters_.empty()) {
assert(read_waiters_.empty()); assert(read_waiters_.empty() || !ReaderPriority);
state_ = State::WRITE_LOCKED; state_ = State::WRITE_LOCKED;
// Wake a single writer (after releasing the spin lock) // Wake a single writer (after releasing the spin lock)
...@@ -315,8 +321,8 @@ void TimedRWMutex<BatonType>::unlock() { ...@@ -315,8 +321,8 @@ void TimedRWMutex<BatonType>::unlock() {
} }
} }
template <typename BatonType> template <bool ReaderPriority, typename BatonType>
void TimedRWMutex<BatonType>::downgrade() { void TimedRWMutexImpl<ReaderPriority, BatonType>::downgrade() {
std::lock_guard<SpinLock> guard{lock_}; std::lock_guard<SpinLock> guard{lock_};
assert(state_ == State::WRITE_LOCKED && readers_ == 0); assert(state_ == State::WRITE_LOCKED && readers_ == 0);
state_ = State::READ_LOCKED; state_ = State::READ_LOCKED;
......
...@@ -80,24 +80,32 @@ class TimedMutex { ...@@ -80,24 +80,32 @@ class TimedMutex {
}; };
/** /**
* @class TimedRWMutex * @class TimedRWMutexImpl
* *
* A readers-writer lock which allows multiple readers to hold the * A readers-writer lock which allows multiple readers to hold the
* lock simultaneously or only one writer. * lock simultaneously or only one writer.
* *
* NOTE: This is a reader-preferred RWLock i.e. readers are give priority * NOTE: When ReaderPriority is set to true then the lock is a reader-preferred
* when there are both readers and writers waiting to get the lock. * RWLock i.e. readers are given priority when there are both readers and
* writers waiting to get the lock.
*
* When ReaderPriority is set to false then the lock is a writer-preferred
* RWLock i.e. writers are given priority when there are both readers and
* writers waiting to get the lock. Note that when the lock is in
* writer-preferred mode, the readers are not re-entrant (e.g. if a caller owns
* a read lock, it can't attempt to acquire the read lock again as it can
* deadlock.)
**/ **/
template <typename BatonType> template <bool ReaderPriority, typename BatonType>
class TimedRWMutex { class TimedRWMutexImpl {
public: public:
TimedRWMutex() = default; TimedRWMutexImpl() = default;
~TimedRWMutex() = default; ~TimedRWMutexImpl() = default;
TimedRWMutex(const TimedRWMutex& rhs) = delete; TimedRWMutexImpl(const TimedRWMutexImpl& rhs) = delete;
TimedRWMutex& operator=(const TimedRWMutex& rhs) = delete; TimedRWMutexImpl& operator=(const TimedRWMutexImpl& rhs) = delete;
TimedRWMutex(TimedRWMutex&& rhs) = delete; TimedRWMutexImpl(TimedRWMutexImpl&& rhs) = delete;
TimedRWMutex& operator=(TimedRWMutex&& rhs) = delete; TimedRWMutexImpl& operator=(TimedRWMutexImpl&& rhs) = delete;
// Lock for shared access. The thread / fiber is blocked until the lock // Lock for shared access. The thread / fiber is blocked until the lock
// can be acquired. // can be acquired.
...@@ -143,7 +151,7 @@ class TimedRWMutex { ...@@ -143,7 +151,7 @@ class TimedRWMutex {
class ReadHolder { class ReadHolder {
public: public:
explicit ReadHolder(TimedRWMutex& lock) : lock_(&lock) { explicit ReadHolder(TimedRWMutexImpl& lock) : lock_(&lock) {
lock_->read_lock(); lock_->read_lock();
} }
...@@ -159,12 +167,12 @@ class TimedRWMutex { ...@@ -159,12 +167,12 @@ class TimedRWMutex {
ReadHolder& operator=(ReadHolder&& rhs) = delete; ReadHolder& operator=(ReadHolder&& rhs) = delete;
private: private:
TimedRWMutex* lock_; TimedRWMutexImpl* lock_;
}; };
class WriteHolder { class WriteHolder {
public: public:
explicit WriteHolder(TimedRWMutex& lock) : lock_(&lock) { explicit WriteHolder(TimedRWMutexImpl& lock) : lock_(&lock) {
lock_->write_lock(); lock_->write_lock();
} }
...@@ -180,7 +188,7 @@ class TimedRWMutex { ...@@ -180,7 +188,7 @@ class TimedRWMutex {
WriteHolder& operator=(WriteHolder&& rhs) = delete; WriteHolder& operator=(WriteHolder&& rhs) = delete;
private: private:
TimedRWMutex* lock_; TimedRWMutexImpl* lock_;
}; };
private: private:
...@@ -191,6 +199,8 @@ class TimedRWMutex { ...@@ -191,6 +199,8 @@ class TimedRWMutex {
assert(write_waiters_.empty()); assert(write_waiters_.empty());
} }
bool shouldReadersWait() const;
// Different states the lock can be in // Different states the lock can be in
enum class State { enum class State {
UNLOCKED, UNLOCKED,
...@@ -228,6 +238,16 @@ class TimedRWMutex { ...@@ -228,6 +238,16 @@ class TimedRWMutex {
MutexWaiterList read_waiters_; //< List of thread / fibers waiting for MutexWaiterList read_waiters_; //< List of thread / fibers waiting for
// shared access // shared access
}; };
template <typename BatonType>
using TimedRWMutexReadPriority = TimedRWMutexImpl<true, BatonType>;
template <typename BatonType>
using TimedRWMutexWritePriority = TimedRWMutexImpl<false, BatonType>;
template <typename BatonType>
using TimedRWMutex = TimedRWMutexReadPriority<BatonType>;
} // namespace fibers } // namespace fibers
} // namespace folly } // namespace folly
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment