Files
scylladb/core/shared_mutex.hh
Avi Kivity 9679075b30 shared_mutex: reinstate FIFO ordering
Noticed by Nadav.
2015-06-21 20:54:53 +03:00

164 lines
5.2 KiB
C++

/*
* This file is open source software, licensed to you under the terms
* of the Apache License, Version 2.0 (the "License"). See the NOTICE file
* distributed with this work for additional information regarding copyright
* ownership. 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.
*/
/*
* Copyright (C) 2015 Cloudius Systems, Ltd.
*/
#pragma once
#include "future.hh"
#include "circular_buffer.hh"
namespace seastar {
/// \addtogroup fiber-module
/// @{
/// \brief Shared/exclusive mutual exclusion.
///
/// Similar to \c std::shared_mutex, this class provides protection
/// for a shared resource, with two levels of access protection: shared
/// and exclusive. Shared access allows multiple tasks to access the
/// shared resource concurrently, while exclusive access allows just
/// one task to access the resource at a time.
///
/// Note that many seastar tasks do not require protection at all,
/// since the seastar scheduler is not preemptive; however tasks that do
/// (by waiting on a future) may require explicit locking.
///
/// The \ref with_shared(shared_mutex&, Func&&) and
/// \ref with_lock(shared_mutex&, Func&&) provide exception-safe
/// wrappers for use with \c shared_mutex.
///
/// \see semaphore simpler mutual exclusion
class shared_mutex {
unsigned _readers = 0;
bool _writer = false;
struct waiter {
waiter(promise<>&& pr, bool for_write) : pr(std::move(pr)), for_write(for_write) {}
promise<> pr;
bool for_write;
};
circular_buffer<waiter> _waiters;
public:
shared_mutex() = default;
shared_mutex(shared_mutex&&) = default;
shared_mutex& operator=(shared_mutex&&) = default;
shared_mutex(const shared_mutex&) = delete;
void operator=(const shared_mutex&) = delete;
/// Lock the \c shared_mutex for shared access
///
/// \return a future that becomes ready when no exclusive access
/// is granted to anyone.
future<> lock_shared() {
if (!_writer && _waiters.empty()) {
++_readers;
return make_ready_future<>();
}
_waiters.emplace_back(promise<>(), false);
return _waiters.back().pr.get_future();
}
/// Unlocks a \c shared_mutex after a previous call to \ref lock_shared().
void unlock_shared() {
--_readers;
wake();
}
/// Lock the \c shared_mutex for exclusive access
///
/// \return a future that becomes ready when no access, shared or exclusive
/// is granted to anyone.
future<> lock() {
if (!_readers && !_writer) {
_writer = true;
return make_ready_future<>();
}
_waiters.emplace_back(promise<>(), true);
return _waiters.back().pr.get_future();
}
/// Unlocks a \c shared_mutex after a previous call to \ref lock().
void unlock() {
_writer = false;
wake();
}
private:
void wake() {
while (!_waiters.empty()) {
auto& w = _waiters.front();
// note: _writer == false in wake()
if (w.for_write) {
if (!_readers) {
_writer = true;
w.pr.set_value();
_waiters.pop_front();
}
break;
} else { // for read
++_readers;
w.pr.set_value();
_waiters.pop_front();
}
}
}
};
/// Executes a function while holding shared access to a resource.
///
/// Executes a function while holding shared access to a resource. When
/// the function returns, the mutex is automatically unlocked.
///
/// \param sm a \ref shared_mutex guarding access to the shared resource
/// \param func callable object to invoke while the mutex is held for shared access
/// \return whatever \c func returns, as a future
///
/// \relates shared_mutex
template <typename Func>
inline
futurize_t<std::result_of_t<Func ()>>
with_shared(shared_mutex& sm, Func&& func) {
return sm.lock_shared().then([func = std::forward<Func>(func)] () mutable {
return func();
}).finally([&sm] {
sm.unlock_shared();
});
}
/// Executes a function while holding exclusive access to a resource.
///
/// Executes a function while holding exclusive access to a resource. When
/// the function returns, the mutex is automatically unlocked.
///
/// \param sm a \ref shared_mutex guarding access to the shared resource
/// \param func callable object to invoke while the mutex is held for shared access
/// \return whatever \c func returns, as a future
///
/// \relates shared_mutex
template <typename Func>
inline
futurize_t<std::result_of_t<Func ()>>
with_lock(shared_mutex& sm, Func&& func) {
return sm.lock().then([func = std::forward<Func>(func)] () mutable {
return func();
}).finally([&sm] {
sm.unlock();
});
}
/// @}
}