add nf7::Mutex
This commit is contained in:
parent
95b8f26b77
commit
2e0f089611
@ -7,6 +7,7 @@ target_link_libraries(nf7_iface
|
||||
target_sources(nf7_iface
|
||||
PRIVATE
|
||||
common/exception.cc
|
||||
common/mutex.cc
|
||||
version.cc
|
||||
PUBLIC
|
||||
common/container.hh
|
||||
@ -14,6 +15,7 @@ target_sources(nf7_iface
|
||||
common/exception.hh
|
||||
common/future.hh
|
||||
common/leak_detector.hh
|
||||
common/mutex.hh
|
||||
common/observer.hh
|
||||
common/task.hh
|
||||
common/task_context.hh
|
||||
@ -37,6 +39,7 @@ target_sources(nf7_iface_test
|
||||
common/exception_test.cc
|
||||
common/future_test.cc
|
||||
common/leak_detector_test.cc
|
||||
common/mutex_test.cc
|
||||
common/observer_test.cc
|
||||
common/observer_test.hh
|
||||
common/task_test.cc
|
||||
|
137
iface/common/mutex.cc
Normal file
137
iface/common/mutex.cc
Normal file
@ -0,0 +1,137 @@
|
||||
// No copyright
|
||||
#include "iface/common/mutex.hh"
|
||||
|
||||
#include <cstdlib>
|
||||
#include <deque>
|
||||
#include <mutex>
|
||||
#include <utility>
|
||||
|
||||
#include "iface/common/exception.hh"
|
||||
|
||||
|
||||
namespace nf7 {
|
||||
|
||||
class Mutex::Impl final : public std::enable_shared_from_this<Impl> {
|
||||
public:
|
||||
Impl() = default;
|
||||
|
||||
Future<SharedToken> Lock() noexcept;
|
||||
SharedToken TryLock();
|
||||
void Unlock() noexcept;
|
||||
|
||||
void TearDown() noexcept;
|
||||
|
||||
private:
|
||||
SharedToken MakeToken();
|
||||
|
||||
private:
|
||||
mutable std::mutex mtx_;
|
||||
|
||||
std::weak_ptr<Token> current_;
|
||||
std::deque<Future<SharedToken>::Completer> pends_;
|
||||
};
|
||||
|
||||
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() noexcept
|
||||
try {
|
||||
std::unique_lock<std::mutex> k {mtx_};
|
||||
if (current_.lock()) {
|
||||
pends_.emplace_back();
|
||||
return pends_.back().future();
|
||||
}
|
||||
return Future<SharedToken> {MakeToken()};
|
||||
} catch (const std::exception&) {
|
||||
return Future<SharedToken> {
|
||||
Exception::MakePtr("failed to queue lock request"),
|
||||
};
|
||||
}
|
||||
|
||||
Mutex::SharedToken Mutex::Impl::TryLock()
|
||||
try {
|
||||
std::unique_lock<std::mutex> k {mtx_};
|
||||
return current_.lock()? nullptr: MakeToken();
|
||||
} catch (const std::exception&) {
|
||||
throw Exception {"failed to acquire lock"};
|
||||
}
|
||||
|
||||
void Mutex::Impl::Unlock() noexcept
|
||||
try {
|
||||
std::unique_lock<std::mutex> k {mtx_};
|
||||
current_ = {};
|
||||
if (pends_.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto comp = std::move(pends_.front());
|
||||
pends_.pop_front();
|
||||
|
||||
try {
|
||||
auto token = MakeToken();
|
||||
k.unlock();
|
||||
comp.Complete(std::move(token));
|
||||
} catch (const std::bad_alloc&) {
|
||||
k.unlock();
|
||||
comp.Throw(Exception::MakePtr("failed to acquire lock"));
|
||||
}
|
||||
} catch (const std::system_error&) {
|
||||
std::abort();
|
||||
}
|
||||
|
||||
void Mutex::Impl::TearDown() noexcept
|
||||
try {
|
||||
std::unique_lock<std::mutex> k {mtx_};
|
||||
pends_.clear();
|
||||
} catch (const std::system_error&) {
|
||||
std::abort();
|
||||
}
|
||||
|
||||
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 Exception {"failed to make new mutex token"};
|
||||
}
|
||||
|
||||
|
||||
Mutex::Mutex()
|
||||
try : impl_(std::make_shared<Impl>()) {
|
||||
} catch (const Exception&) {
|
||||
throw Exception {"memory shortage"};
|
||||
}
|
||||
|
||||
Mutex::~Mutex() noexcept {
|
||||
impl_->TearDown();
|
||||
}
|
||||
|
||||
Future<Mutex::SharedToken> Mutex::Lock() noexcept {
|
||||
return impl_->Lock();
|
||||
}
|
||||
|
||||
Mutex::SharedToken Mutex::TryLock() {
|
||||
return impl_->TryLock();
|
||||
}
|
||||
|
||||
} // namespace nf7
|
36
iface/common/mutex.hh
Normal file
36
iface/common/mutex.hh
Normal file
@ -0,0 +1,36 @@
|
||||
// No copyright
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "iface/common/future.hh"
|
||||
|
||||
|
||||
namespace nf7 {
|
||||
|
||||
class Mutex final {
|
||||
private:
|
||||
class Impl;
|
||||
|
||||
public:
|
||||
class Token;
|
||||
using SharedToken = std::shared_ptr<Token>;
|
||||
|
||||
public:
|
||||
Mutex();
|
||||
~Mutex() noexcept;
|
||||
|
||||
Mutex(const Mutex&) = delete;
|
||||
Mutex(Mutex&&) = delete;
|
||||
Mutex& operator=(const Mutex&) = delete;
|
||||
Mutex& operator=(Mutex&&) = delete;
|
||||
|
||||
public:
|
||||
Future<SharedToken> Lock() noexcept;
|
||||
SharedToken TryLock();
|
||||
|
||||
private:
|
||||
std::shared_ptr<Impl> impl_;
|
||||
};
|
||||
|
||||
} // namespace nf7
|
86
iface/common/mutex_test.cc
Normal file
86
iface/common/mutex_test.cc
Normal file
@ -0,0 +1,86 @@
|
||||
// No copyright
|
||||
#include "iface/common/mutex.hh"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
|
||||
|
||||
using namespace std::literals;
|
||||
|
||||
TEST(Mutex, TryLock) {
|
||||
nf7::Mutex mtx;
|
||||
auto k = mtx.TryLock();
|
||||
EXPECT_TRUE(k);
|
||||
}
|
||||
|
||||
TEST(Mutex, TryLockFails) {
|
||||
nf7::Mutex mtx;
|
||||
auto k1 = mtx.TryLock();
|
||||
auto k2 = mtx.TryLock();
|
||||
EXPECT_FALSE(k2);
|
||||
}
|
||||
|
||||
TEST(Mutex, Lock) {
|
||||
nf7::Mutex mtx;
|
||||
auto fu = mtx.Lock();
|
||||
EXPECT_TRUE(fu.done());
|
||||
}
|
||||
|
||||
TEST(Mutex, LockPending) {
|
||||
nf7::Mutex mtx;
|
||||
auto k = mtx.TryLock();
|
||||
auto fu = mtx.Lock();
|
||||
EXPECT_TRUE(fu.yet());
|
||||
}
|
||||
|
||||
TEST(Mutex, LockWithDelay) {
|
||||
nf7::Mutex mtx;
|
||||
auto k = mtx.TryLock();
|
||||
auto fu = mtx.Lock();
|
||||
|
||||
k = nullptr;
|
||||
EXPECT_TRUE(fu.done());
|
||||
}
|
||||
|
||||
TEST(Mutex, LockAbort) {
|
||||
std::optional<nf7::Mutex> mtx;
|
||||
mtx.emplace();
|
||||
|
||||
auto k = mtx->TryLock();
|
||||
auto fu = mtx->Lock();
|
||||
|
||||
mtx = std::nullopt;
|
||||
EXPECT_TRUE(fu.error());
|
||||
}
|
||||
|
||||
TEST(Mutex, ChaoticLock) {
|
||||
nf7::Mutex mtx;
|
||||
std::atomic<bool> flag = false;
|
||||
|
||||
std::vector<std::thread> threads;
|
||||
threads.resize(16);
|
||||
for (auto& th : threads) {
|
||||
th = std::thread {[&]() {
|
||||
mtx.Lock()
|
||||
.Then([&](auto&) {
|
||||
const auto flag_at_first = flag.exchange(true);
|
||||
EXPECT_FALSE(flag_at_first);
|
||||
|
||||
std::this_thread::sleep_for(10ms);
|
||||
|
||||
const auto flag_at_last = flag.exchange(false);
|
||||
EXPECT_TRUE(flag_at_last);
|
||||
})
|
||||
.Catch([&](const auto& e) {
|
||||
FAIL() << e;
|
||||
});
|
||||
}};
|
||||
}
|
||||
for (auto& th : threads) {
|
||||
th.join();
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user