/* * Copyright (c) Meta Platforms, Inc. and affiliates. * * 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. */ #pragma once #include #include #include #include namespace folly { namespace fibers { /** * @class TimedMutex * * Like mutex but allows timed_lock in addition to lock and try_lock. **/ class TimedMutex { public: struct Options { constexpr Options(bool stealing = true) : stealing_(stealing) {} /** * Prefer thread waiter and steal from fiber waiters if possible */ bool stealing_ = true; }; TimedMutex(Options options = Options()) noexcept : options_(std::move(options)) {} ~TimedMutex() { DCHECK(threadWaiters_.empty()); DCHECK(fiberWaiters_.empty()); DCHECK(notifiedFiber_ == nullptr); } TimedMutex(const TimedMutex& rhs) = delete; TimedMutex& operator=(const TimedMutex& rhs) = delete; TimedMutex(TimedMutex&& rhs) = delete; TimedMutex& operator=(TimedMutex&& rhs) = delete; // Lock the mutex. The thread / fiber is blocked until the mutex is free void lock(); // Lock the mutex. The thread / fiber will be blocked until a timeout elapses. // // @return true if the mutex was locked, false otherwise template bool try_lock_for(const std::chrono::duration& timeout); // Lock the mutex. The thread / fiber will be blocked until a deadline // // @return true if the mutex was locked, false otherwise template bool try_lock_until(const std::chrono::time_point& deadline); // Try to obtain lock without blocking the thread or fiber bool try_lock(); // Unlock the mutex and wake up a waiter if there is one void unlock(); private: enum class LockResult { SUCCESS, TIMEOUT, STOLEN }; template LockResult lockHelper(WaitFunc&& waitFunc); // represents a waiter waiting for the lock. The waiter waits on the // baton until it is woken up by a post or timeout expires. struct MutexWaiter { Baton baton; folly::IntrusiveListHook hook; }; using MutexWaiterList = folly::IntrusiveList; const Options options_; folly::SpinLock lock_; //< lock to protect waiter list bool locked_ = false; //< is this locked by some thread? MutexWaiterList threadWaiters_; //< list of waiters MutexWaiterList fiberWaiters_; //< list of waiters MutexWaiter* notifiedFiber_{nullptr}; //< Fiber waiter which has been notified }; /** * @class TimedRWMutexImpl * * A readers-writer lock which allows multiple readers to hold the * lock simultaneously or only one writer. * * NOTE: When ReaderPriority is set to true then the lock is a reader-preferred * 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 class TimedRWMutexImpl { public: TimedRWMutexImpl() = default; ~TimedRWMutexImpl() = default; TimedRWMutexImpl(const TimedRWMutexImpl& rhs) = delete; TimedRWMutexImpl& operator=(const TimedRWMutexImpl& rhs) = delete; TimedRWMutexImpl(TimedRWMutexImpl&& rhs) = delete; TimedRWMutexImpl& operator=(TimedRWMutexImpl&& rhs) = delete; // Lock for shared access. The thread / fiber is blocked until the lock // can be acquired. void lock_shared(); // Like lock_shared except the thread / fiber is blocked until a timeout // elapses // @return true if locked successfully, false otherwise. template bool try_lock_shared_for(const std::chrono::duration& timeout); // Like lock_shared except the thread / fiber is blocked until a deadline // @return true if locked successfully, false otherwise. template bool try_lock_shared_until( const std::chrono::time_point& deadline); // Like lock_shared but doesn't block the thread / fiber if the lock can't // be acquired. // @return true if lock was acquired, false otherwise. bool try_lock_shared(); // Release the lock. The thread / fiber will wake up a writer if there is one // and if this is the last concurrently-held read lock to be released. void unlock_shared(); // Obtain an exclusive lock. The thread / fiber is blocked until the lock // is available. void lock(); // Like lock except the thread / fiber is blocked until a timeout elapses // @return true if locked successfully, false otherwise. template bool try_lock_for(const std::chrono::duration& timeout); // Like lock except the thread / fiber is blocked until a deadline // @return true if locked successfully, false otherwise. template bool try_lock_until(const std::chrono::time_point& deadline); // Like lock but doesn't block the thread / fiber if the lock cant be // obtained. // @return true if lock was acquired, false otherwise. bool try_lock(); // Realease the lock. The thread / fiber will wake up all readers if there are // any. If there are waiting writers then only one of them will be woken up. // NOTE: readers are given priority over writers (see above comment) void unlock(); // Downgrade the lock. The thread / fiber will wake up all readers if there // are any. void unlock_and_lock_shared(); private: // invariants that must hold when the lock is not held by anyone void verify_unlocked_properties() { assert(readers_ == 0); assert(read_waiters_.empty()); assert(write_waiters_.empty()); } bool shouldReadersWait() const; void unlock_(); // Different states the lock can be in enum class State { UNLOCKED, READ_LOCKED, WRITE_LOCKED, }; typedef boost::intrusive::list_member_hook<> MutexWaiterHookType; // represents a waiter waiting for the lock. struct MutexWaiter { BatonType baton; MutexWaiterHookType hook; }; typedef boost::intrusive:: member_hook MutexWaiterHook; typedef boost::intrusive::list< MutexWaiter, MutexWaiterHook, boost::intrusive::constant_time_size> MutexWaiterList; folly::SpinLock lock_; //< lock protecting the internal state // (state_, read_waiters_, etc.) State state_ = State::UNLOCKED; uint32_t readers_ = 0; //< Number of readers who have the lock MutexWaiterList write_waiters_; //< List of thread / fibers waiting for // exclusive access MutexWaiterList read_waiters_; //< List of thread / fibers waiting for // shared access }; template using TimedRWMutexReadPriority = TimedRWMutexImpl; template using TimedRWMutexWritePriority = TimedRWMutexImpl; template using TimedRWMutex = TimedRWMutexReadPriority; } // namespace fibers } // namespace folly #include