182 lines
3.6 KiB
C++
182 lines
3.6 KiB
C++
// No copyright
|
|
#include "iface/common/mutex.hh"
|
|
|
|
#include <cstdlib>
|
|
#include <deque>
|
|
#include <thread>
|
|
#include <utility>
|
|
|
|
#include "iface/common/exception.hh"
|
|
|
|
|
|
namespace nf7 {
|
|
|
|
class Mutex::Impl final : public std::enable_shared_from_this<Impl> {
|
|
public:
|
|
enum Mode {
|
|
kExclusive,
|
|
kInclusive,
|
|
};
|
|
|
|
public:
|
|
Impl() = default;
|
|
|
|
Future<SharedToken> Lock(Mode) noexcept;
|
|
SharedToken TryLock(Mode);
|
|
void Unlock() noexcept;
|
|
|
|
void TearDown() noexcept;
|
|
|
|
private:
|
|
SharedToken MakeToken();
|
|
|
|
private:
|
|
std::weak_ptr<Token> current_;
|
|
std::deque<Future<SharedToken>::Completer> pends_;
|
|
bool last_inclusive_ = false;
|
|
|
|
# if !defined(NDEBUG)
|
|
const std::thread::id thid_ = std::this_thread::get_id();
|
|
# endif
|
|
};
|
|
|
|
class Mutex::Token final {
|
|
public:
|
|
Token() = delete;
|
|
explicit Token(const std::shared_ptr<Impl>& impl) noexcept
|
|
: impl_(impl) { }
|
|
|
|
~Token() noexcept {
|
|
if (auto impl = impl_.lock()) {
|
|
impl->Unlock();
|
|
}
|
|
}
|
|
|
|
Token(const Token&) = delete;
|
|
Token(Token&&) = delete;
|
|
Token& operator=(const Token&) = delete;
|
|
Token& operator=(Token&&) = delete;
|
|
|
|
private:
|
|
const std::weak_ptr<Impl> impl_;
|
|
};
|
|
|
|
|
|
Future<Mutex::SharedToken> Mutex::Impl::Lock(Mode mode) noexcept
|
|
try {
|
|
assert(std::this_thread::get_id() == thid_);
|
|
|
|
auto cur = current_.lock();
|
|
|
|
switch (mode) {
|
|
case kInclusive:
|
|
if (last_inclusive_) {
|
|
if (pends_.empty() && cur) {
|
|
return cur;
|
|
} else if (!pends_.empty()) {
|
|
return pends_.back().future();
|
|
}
|
|
}
|
|
last_inclusive_ = true;
|
|
break;
|
|
case kExclusive:
|
|
last_inclusive_ = false;
|
|
break;
|
|
}
|
|
if (nullptr != cur) {
|
|
pends_.emplace_back();
|
|
return pends_.back().future();
|
|
}
|
|
return MakeToken();
|
|
} catch (const std::bad_alloc&) {
|
|
return MemoryException::MakePtr("failed to queue lock request");
|
|
}
|
|
|
|
Mutex::SharedToken Mutex::Impl::TryLock(Mode mode)
|
|
try {
|
|
assert(std::this_thread::get_id() == thid_);
|
|
|
|
if (!pends_.empty()) {
|
|
return nullptr;
|
|
}
|
|
|
|
auto cur = current_.lock();
|
|
switch (mode) {
|
|
case kInclusive:
|
|
if (nullptr != cur) {
|
|
return last_inclusive_? cur: nullptr;
|
|
}
|
|
last_inclusive_ = true;
|
|
break;
|
|
case kExclusive:
|
|
if (nullptr != cur) {
|
|
return nullptr;
|
|
}
|
|
last_inclusive_ = false;
|
|
break;
|
|
}
|
|
return MakeToken();
|
|
} catch (const std::bad_alloc&) {
|
|
throw MemoryException {"failed to acquire lock"};
|
|
}
|
|
|
|
void Mutex::Impl::Unlock() noexcept {
|
|
assert(std::this_thread::get_id() == thid_);
|
|
|
|
current_ = {};
|
|
if (pends_.empty()) {
|
|
return;
|
|
}
|
|
|
|
auto comp = std::move(pends_.front());
|
|
pends_.pop_front();
|
|
|
|
try {
|
|
auto token = MakeToken();
|
|
comp.Complete(std::move(token));
|
|
} catch (const std::bad_alloc&) {
|
|
comp.Throw(MemoryException::MakePtr("failed to allocate new token"));
|
|
}
|
|
}
|
|
|
|
void Mutex::Impl::TearDown() noexcept {
|
|
assert(std::this_thread::get_id() == thid_);
|
|
pends_.clear();
|
|
}
|
|
|
|
Mutex::SharedToken Mutex::Impl::MakeToken()
|
|
try {
|
|
auto ret = std::make_shared<Token>(shared_from_this());
|
|
current_ = ret;
|
|
return ret;
|
|
} catch (const std::bad_alloc&) {
|
|
throw MemoryException {"failed to make new mutex token"};
|
|
}
|
|
|
|
|
|
Mutex::Mutex()
|
|
try : impl_(std::make_shared<Impl>()) {
|
|
} catch (const std::bad_alloc&) {
|
|
throw MemoryException {"memory shortage"};
|
|
}
|
|
|
|
Mutex::~Mutex() noexcept {
|
|
impl_->TearDown();
|
|
}
|
|
|
|
Future<Mutex::SharedToken> Mutex::Lock() noexcept {
|
|
return impl_->Lock(Impl::kInclusive);
|
|
}
|
|
Mutex::SharedToken Mutex::TryLock() {
|
|
return impl_->TryLock(Impl::kInclusive);
|
|
}
|
|
|
|
Future<Mutex::SharedToken> Mutex::LockEx() noexcept {
|
|
return impl_->Lock(Impl::kExclusive);
|
|
}
|
|
Mutex::SharedToken Mutex::TryLockEx() {
|
|
return impl_->TryLock(Impl::kExclusive);
|
|
}
|
|
|
|
} // namespace nf7
|