/* * Copyright (c) Facebook, Inc. and its 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 #include #include namespace folly { // upgrade_lock // // A lock-holder type which holds upgrade locks, usable with any shared mutex // type which supports the upgrade state. Similar to std::unique_lock vis-a-vis // all mutex types or to std::shared_lock vis-a-vis all shared mutex types. // // As long as the associated mutex type exposes members in the style: // - lock_upgrade void() // - try_lock_upgrade bool() // - try_lock_upgrade_for bool(std::chrono::duration<...> const&) // - try_lock_upgrade_until bool(std::chrono::time_point<...> const&) // - unlock_upgrade // // Upgrade locks are not useful by themselves; they are primarily useful since // upgrade locks may be transitioned atomically to exclusive locks. This lock- // holder type works with the transition_to_... functions below to facilitate // atomic transition from ugprade lock to exclusive lock. template class upgrade_lock { public: using mutex_type = Mutex; upgrade_lock() noexcept = default; upgrade_lock(upgrade_lock&& that) noexcept : mutex_{std::exchange(that.mutex_, nullptr)}, owns_{std::exchange(that.owns_, false)} {} upgrade_lock(mutex_type& mutex) : mutex_{&mutex}, owns_{true} { mutex.lock_upgrade(); } upgrade_lock(mutex_type& mutex, std::defer_lock_t) noexcept : mutex_{&mutex} {} upgrade_lock(mutex_type& mutex, std::try_to_lock_t) : mutex_{&mutex}, owns_{mutex.try_lock_upgrade()} {} upgrade_lock(mutex_type& mutex, std::adopt_lock_t) : mutex_{&mutex}, owns_{true} {} template upgrade_lock( mutex_type& mutex, std::chrono::duration const& timeout) : mutex_{&mutex}, owns_{mutex.try_lock_upgrade_for(timeout)} {} template upgrade_lock( mutex_type& mutex, std::chrono::time_point const& deadline) : mutex_{&mutex}, owns_{mutex.try_lock_upgrade_until(deadline)} {} ~upgrade_lock() { if (owns_) { mutex_->unlock_upgrade(); } } upgrade_lock& operator=(upgrade_lock&& that) noexcept { if (owns_lock()) { unlock(); } mutex_ = std::exchange(that.mutex_, nullptr); owns_ = std::exchange(that.owns_, false); return *this; } void lock() { check(); mutex_->lock_upgrade(); owns_ = true; } bool try_lock() { check(); return owns_ = mutex_->try_lock_upgrade(); } template bool try_lock_for(std::chrono::duration const& timeout) { check(); return owns_ = mutex_->try_lock_upgrade_for(timeout); } template bool try_lock_until( std::chrono::time_point const& deadline) { check(); return owns_ = mutex_->try_lock_upgrade_until(deadline); } void unlock() { check(); mutex_->unlock_upgrade(); owns_ = false; } void swap(upgrade_lock& that) noexcept { std::swap(mutex_, that.mutex_); std::swap(owns_, that.owns_); } friend void swap(upgrade_lock& a, upgrade_lock& b) noexcept { a.swap(b); } mutex_type* release() noexcept { owns_ = false; return std::exchange(mutex_, nullptr); } mutex_type* mutex() const noexcept { return mutex_; } bool owns_lock() const noexcept { return owns_; } explicit operator bool() const noexcept { return owns_; } private: template void check() { if (!mutex_ || owns_ != Owns) { check_(); } } template [[noreturn]] FOLLY_NOINLINE void check_() { throw_exception(std::make_error_code( !mutex_ || !owns_ ? std::errc::operation_not_permitted : std::errc::resource_deadlock_would_occur)); } mutex_type* mutex_{nullptr}; bool owns_{false}; }; namespace detail { template < template class To, template class From, typename Mutex, typename Transition> To try_transition_lock_(From& lock, Transition transition) { auto owns = lock.owns_lock(); if (!lock.mutex()) { return To{}; } if (!owns) { return To{*lock.release(), std::defer_lock}; } if (!transition(*lock.mutex())) { return To{}; } return To{*lock.release(), std::adopt_lock}; } template < template class To, template class From, typename Mutex, typename Transition> To transition_lock_(From& lock, Transition transition) { return try_transition_lock_(lock, [&](auto& l) { // return transition(l), true; }); } } // namespace detail // transition_to_shared_lock(unique_lock) // // Wraps mutex member function unlock_and_lock_shared. // // Represents an immediate atomic downgrade transition from exclusive lock to // to shared lock. template std::shared_lock transition_to_shared_lock( std::unique_lock& lock) { return detail::transition_lock_(lock, [](auto& _) { // _.unlock_and_lock_shared(); }); } // transition_to_shared_lock(upgrade_lock) // // Wraps mutex member function unlock_upgrade_and_lock_shared. // // Represents an immediate atomic downgrade transition from upgrade lock to // shared lock. template std::shared_lock transition_to_shared_lock( // upgrade_lock& lock) { return detail::transition_lock_(lock, [](auto& _) { // _.unlock_upgrade_and_lock_shared(); }); } // transition_to_upgrade_lock(unique_lock) // // Wraps mutex member function unlock_and_lock_upgrade. // // Represents an immediate atomic downgrade transition from unique lock to // upgrade lock. template upgrade_lock transition_to_upgrade_lock( // std::unique_lock& lock) { return detail::transition_lock_(lock, [](auto& _) { // _.unlock_and_lock_upgrade(); }); } // transition_to_unique_lock(upgrade_lock) // // Wraps mutex member function unlock_upgrade_and_lock. // // Represents an eventual atomic upgrade transition from upgrade lock to unique // lock. template std::unique_lock transition_to_unique_lock( // upgrade_lock& lock) { return detail::transition_lock_(lock, [](auto& _) { // _.unlock_upgrade_and_lock(); }); } // try_transition_to_unique_lock(upgrade_lock) // // Wraps mutex member function try_unlock_upgrade_and_lock. // // Represents an immediate attempted atomic upgrade transition from upgrade // lock to unique lock. template std::unique_lock try_transition_to_unique_lock( upgrade_lock& lock) { return detail::try_transition_lock_(lock, [](auto& _) { // return _.try_unlock_upgrade_and_lock(); }); } // try_transition_to_unique_lock_for(upgrade_lock) // // Wraps mutex member function try_unlock_upgrade_and_lock_for. // // Represents an eventual attempted atomic upgrade transition from upgrade // lock to unique lock. template std::unique_lock try_transition_to_unique_lock_for( upgrade_lock& lock, std::chrono::duration const& timeout) { return detail::try_transition_lock_(lock, [&](auto& _) { return _.try_unlock_upgrade_and_lock_for(timeout); }); } // try_transition_to_unique_lock_until(upgrade_lock) // // Wraps mutex member function try_unlock_upgrade_and_lock_until. // // Represents an eventual attemped atomic upgrade transition from upgrade // lock to unique lock. template std::unique_lock try_transition_to_unique_lock_until( upgrade_lock& lock, std::chrono::time_point const& deadline) { return detail::try_transition_lock_(lock, [&](auto& _) { return _.try_unlock_upgrade_and_lock_until(deadline); }); } } // namespace folly