add nf7::Future and nf7::Exception
This commit is contained in:
parent
b93fcb6ac7
commit
70c41b3c3b
@ -16,6 +16,7 @@ target_sources(nf7_iface
|
||||
add_executable(nf7_iface_test)
|
||||
target_sources(nf7_iface_test
|
||||
PRIVATE
|
||||
future_test.cc
|
||||
observer_test.hh
|
||||
observer_test.cc
|
||||
)
|
||||
|
@ -4,7 +4,7 @@
|
||||
#include <exception>
|
||||
#include <source_location>
|
||||
|
||||
namespace nf7::iface {
|
||||
namespace nf7 {
|
||||
|
||||
class Exception : public std::exception, std::nested_exception {
|
||||
public:
|
||||
@ -22,4 +22,4 @@ class Exception : public std::exception, std::nested_exception {
|
||||
std::source_location location_;
|
||||
};
|
||||
|
||||
} // namespace nf7::iface
|
||||
} // namespace nf7
|
||||
|
226
iface/future.hh
226
iface/future.hh
@ -4,7 +4,6 @@
|
||||
#include <cassert>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <utility>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
@ -13,141 +12,144 @@
|
||||
|
||||
namespace nf7 {
|
||||
|
||||
template <typename T>
|
||||
class InternalFuture final :
|
||||
public std::enable_shared_from_this<InternalFuture<T>> {
|
||||
public:
|
||||
using Listener =
|
||||
std::function<void(std::shared_ptr<InternalFuture<T>>&&) noexcept>;
|
||||
|
||||
InternalFuture() = default;
|
||||
InternalFuture(T&& v) noexcept : result_(std::move(v)) { }
|
||||
explicitInternalFuture(std::exception_ptr e) noexcept : result_(e) { }
|
||||
|
||||
void Complete(T&& v) noexcept {
|
||||
assert(yet());
|
||||
result_ = std::move(v);
|
||||
Finalize();
|
||||
}
|
||||
void Throw(std::exception_ptr e = std::current_exception()) noexcept {
|
||||
assert(yet());
|
||||
result_ = e;
|
||||
Finalize();
|
||||
}
|
||||
void Listen(Listener&& listener) {
|
||||
if (yet()) {
|
||||
try {
|
||||
listeners_.push_back(std::move(listener));
|
||||
} catch (const std::exception&) {
|
||||
throw Exception("memory shortage");
|
||||
}
|
||||
} else {
|
||||
listener(shared_from_this());
|
||||
}
|
||||
}
|
||||
|
||||
void Ref() noexcept {
|
||||
++refcnt_;
|
||||
}
|
||||
void Unref() noexcept {
|
||||
--refcnt_;
|
||||
if (0 == refcnt_ && yet()) {
|
||||
Throw(std::make_exception_ptr(Exception("future is forgotten")));
|
||||
}
|
||||
}
|
||||
|
||||
bool yet() const noexcept {
|
||||
return std::nullopt == result_;
|
||||
}
|
||||
bool done() const noexcept {
|
||||
return !yet() && std::holds_alternative<T>(*result_);
|
||||
}
|
||||
bool error() const noexcept {
|
||||
return !yet() && std::holds_alternative<std::exception_ptr>(*result_);
|
||||
}
|
||||
|
||||
private:
|
||||
void Finalize() noexcept {
|
||||
for (const auto& listener : listeners_) {
|
||||
listener(shared_from_this());
|
||||
}
|
||||
listeners_.clear();
|
||||
}
|
||||
|
||||
std::optional<std::variant<T, std::exception_ptr>> result_;
|
||||
|
||||
std::vector<Listener> listeners_;
|
||||
|
||||
uint32_t refcnt_ = 0;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
class Future final {
|
||||
public:
|
||||
// !! Listener MUST NOT throw any exceptions !!
|
||||
using Listener = std::function<void()>;
|
||||
|
||||
class Completer;
|
||||
class Internal final {
|
||||
public:
|
||||
Internal() = default;
|
||||
Internal(T&& v) noexcept : result_(std::move(v)) { }
|
||||
explicit Internal(std::exception_ptr e) noexcept : result_(e) { }
|
||||
|
||||
Internal(const Internal& src) = default;
|
||||
Internal(Internal&& src) = default;
|
||||
Internal& operator=(const Internal& src) = default;
|
||||
Internal& operator=(Internal&& src) = default;
|
||||
|
||||
void Complete(T&& v) noexcept {
|
||||
assert(yet());
|
||||
result_ = std::move(v);
|
||||
Finalize();
|
||||
}
|
||||
void Throw(std::exception_ptr e = std::current_exception()) noexcept {
|
||||
assert(yet());
|
||||
assert(nullptr != e);
|
||||
result_ = e;
|
||||
Finalize();
|
||||
}
|
||||
void Listen(Listener&& listener) {
|
||||
assert(!calling_listener_ &&
|
||||
"do not add listener while listener callback");
|
||||
if (yet()) {
|
||||
try {
|
||||
listeners_.push_back(std::move(listener));
|
||||
} catch (const std::exception&) {
|
||||
throw Exception("memory shortage");
|
||||
}
|
||||
} else {
|
||||
calling_listener_ = true;
|
||||
listener();
|
||||
calling_listener_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
bool yet() const noexcept { return std::holds_alternative<Yet>(result_); }
|
||||
bool done() const noexcept { return std::holds_alternative<T>(result_); }
|
||||
std::exception_ptr error() const noexcept {
|
||||
return std::holds_alternative<std::exception_ptr>(result_)
|
||||
? std::get<std::exception_ptr>(result_)
|
||||
: std::exception_ptr {};
|
||||
}
|
||||
|
||||
const T& value() const noexcept {
|
||||
assert(done());
|
||||
return std::get<T>(result_);
|
||||
}
|
||||
|
||||
private:
|
||||
void Finalize() noexcept {
|
||||
calling_listener_ = true;
|
||||
for (auto& listener : listeners_) {
|
||||
listener();
|
||||
}
|
||||
listeners_.clear();
|
||||
calling_listener_ = false;
|
||||
}
|
||||
|
||||
struct Yet {};
|
||||
std::variant<Yet, T, std::exception_ptr> result_;
|
||||
|
||||
std::vector<Listener> listeners_;
|
||||
|
||||
bool calling_listener_ = false;
|
||||
};
|
||||
|
||||
Future() = delete;
|
||||
Future(std::shared_ptr<InternalFuture>&& in) noexcept
|
||||
: internal_(std::move(in));
|
||||
Future(T&& v) : internal_(Internal(std::move(v))) {
|
||||
}
|
||||
Future(std::exception_ptr e) : internal_(Internal(e)) {
|
||||
}
|
||||
|
||||
void Listen(std::function<void(const Future<T>&)>&& listener) {
|
||||
Future(const Future&) = default;
|
||||
Future(Future&&) = default;
|
||||
Future& operator=(const Future&) = default;
|
||||
Future& operator=(Future&&) = default;
|
||||
|
||||
void Listen(Listener&& listener) {
|
||||
internal().Listen(std::move(listener));
|
||||
}
|
||||
|
||||
bool yet() const noexcept { return internal().yet(); }
|
||||
bool done() const noexcept { return internal().done(); }
|
||||
bool error() const noexcept { return internal().error(); }
|
||||
std::exception_ptr error() const noexcept { return internal().error(); }
|
||||
const T& value() { return internal().value(); }
|
||||
|
||||
private:
|
||||
InternalFuture& internal() noexcept {
|
||||
struct Visitor {
|
||||
InternalFuture& operator(InernalFuture& i) noexcept {
|
||||
return i;
|
||||
}
|
||||
InternalFuture& operator(std::shared_ptr<InternalFuture>& i) noexcept {
|
||||
return *i;
|
||||
}
|
||||
};
|
||||
return std::visit(InternalGetter(), internal_);
|
||||
Future(std::shared_ptr<Internal>&& in) noexcept
|
||||
: internal_(std::move(in)) { }
|
||||
Future(const std::shared_ptr<Internal>& in) noexcept
|
||||
: internal_(std::move(in)) { }
|
||||
|
||||
Internal& internal() noexcept {
|
||||
return std::holds_alternative<Internal>(internal_)
|
||||
? std::get<Internal>(internal_)
|
||||
: *std::get<std::shared_ptr<Internal>>(internal_);
|
||||
}
|
||||
const InternalFuture& internal() const noexcept {
|
||||
struct Visitor {
|
||||
const InternalFuture& operator(const InernalFuture& i) noexcept {
|
||||
return i;
|
||||
}
|
||||
const InternalFuture& operator(
|
||||
const std::shared_ptr<InternalFuture>& i) noexcept {
|
||||
return *i;
|
||||
}
|
||||
};
|
||||
return std::visit(InternalGetter(), internal_);
|
||||
const Internal& internal() const noexcept {
|
||||
return std::holds_alternative<Internal>(internal_)
|
||||
? std::get<Internal>(internal_)
|
||||
: *std::get<std::shared_ptr<Internal>>(internal_);
|
||||
}
|
||||
|
||||
std::variant<InternalFuture, std::shared_ptr<InternalFuture>> internal_;
|
||||
std::variant<Internal, std::shared_ptr<Internal>> internal_;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
class Future<T>::Completer final {
|
||||
public:
|
||||
Completer() : Completer(std::make_shared<InternalFuture>())
|
||||
try {
|
||||
Completer()
|
||||
try : internal_(std::make_shared<Internal>()) {
|
||||
} catch (const std::exception&) {
|
||||
throw Exception("memory shortage");
|
||||
}
|
||||
explicit Completer(std::shared_ptr<InternalFuture>&& internal) noexcept
|
||||
: internal_(std::move(internal)) {
|
||||
internal_->Ref();
|
||||
}
|
||||
~Completer() noexcept {
|
||||
if (nullptr != internal_) {
|
||||
internal_->Unref();
|
||||
}
|
||||
Finalize();
|
||||
}
|
||||
|
||||
Completer(const Completer&) = default;
|
||||
Completer(const Completer& src) = delete;
|
||||
Completer(Completer&&) = default;
|
||||
Completer& operator=(const Completer&) = default;
|
||||
Completer& operator=(Completer&&) = default;
|
||||
Completer& operator=(const Completer&) = delete;
|
||||
Completer& operator=(Completer&& src) noexcept {
|
||||
if (this != &src) {
|
||||
Finalize();
|
||||
internal_ = std::move(src.internal_);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
void Complete(T&& v) noexcept {
|
||||
internal_->Complete(std::move(v));
|
||||
@ -156,8 +158,16 @@ class Future<T>::Completer final {
|
||||
internal_->Throw(e);
|
||||
}
|
||||
|
||||
Future<T> future() const noexcept { return {internal_}; }
|
||||
|
||||
private:
|
||||
std::shared_ptr<InternalFuture> internal_;
|
||||
void Finalize() noexcept {
|
||||
if (internal_->yet()) {
|
||||
internal_->Throw(std::make_exception_ptr(Exception {"forgotten"}));
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<Internal> internal_;
|
||||
};
|
||||
|
||||
} // namespace nf7
|
||||
|
160
iface/future_test.cc
Normal file
160
iface/future_test.cc
Normal file
@ -0,0 +1,160 @@
|
||||
// No copyright
|
||||
#include "iface/future.hh"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
|
||||
#include "iface/exception.hh"
|
||||
|
||||
|
||||
TEST(Future, ImmediateValue) {
|
||||
nf7::Future<int32_t> sut {int32_t {777}};
|
||||
|
||||
EXPECT_FALSE(sut.yet());
|
||||
EXPECT_TRUE(sut.done());
|
||||
EXPECT_FALSE(sut.error());
|
||||
EXPECT_EQ(sut.value(), int32_t {777});
|
||||
}
|
||||
TEST(Future, ImmediateError) {
|
||||
nf7::Future<int32_t> sut {std::make_exception_ptr(nf7::Exception("hello"))};
|
||||
|
||||
EXPECT_FALSE(sut.yet());
|
||||
EXPECT_FALSE(sut.done());
|
||||
EXPECT_TRUE(sut.error());
|
||||
EXPECT_THROW(std::rethrow_exception(sut.error()), nf7::Exception);
|
||||
}
|
||||
|
||||
TEST(Future, LazyComplete) {
|
||||
nf7::Future<int32_t>::Completer completer;
|
||||
nf7::Future<int32_t> sut = completer.future();
|
||||
|
||||
completer.Complete(int32_t {777});
|
||||
|
||||
EXPECT_FALSE(sut.yet());
|
||||
EXPECT_TRUE(sut.done());
|
||||
EXPECT_FALSE(sut.error());
|
||||
EXPECT_EQ(sut.value(), int32_t {777});
|
||||
}
|
||||
TEST(Future, LazyThrow) {
|
||||
nf7::Future<int32_t>::Completer completer;
|
||||
nf7::Future<int32_t> sut = completer.future();
|
||||
|
||||
completer.Throw(std::make_exception_ptr(nf7::Exception {"hello"}));
|
||||
|
||||
EXPECT_FALSE(sut.yet());
|
||||
EXPECT_FALSE(sut.done());
|
||||
EXPECT_TRUE(sut.error());
|
||||
EXPECT_THROW(std::rethrow_exception(sut.error()), nf7::Exception);
|
||||
}
|
||||
TEST(Future, LazyIncomplete) {
|
||||
nf7::Future<int32_t>::Completer completer;
|
||||
nf7::Future<int32_t> sut = completer.future();
|
||||
|
||||
EXPECT_TRUE(sut.yet());
|
||||
EXPECT_FALSE(sut.done());
|
||||
EXPECT_FALSE(sut.error());
|
||||
}
|
||||
TEST(Future, LazyForgotten) {
|
||||
std::optional<nf7::Future<int32_t>> sut;
|
||||
nf7::Future<int32_t> sut2 {*sut};
|
||||
{
|
||||
nf7::Future<int32_t>::Completer completer;
|
||||
sut.emplace(completer.future());
|
||||
}
|
||||
EXPECT_FALSE(sut->yet());
|
||||
EXPECT_FALSE(sut->done());
|
||||
EXPECT_TRUE(sut->error());
|
||||
}
|
||||
|
||||
TEST(Future, ListenImmediateValue) {
|
||||
nf7::Future<int32_t> sut {int32_t {777}};
|
||||
|
||||
auto called = int32_t {0};
|
||||
sut.Listen([&]() {
|
||||
++called;
|
||||
EXPECT_EQ(sut.value(), int32_t {777});
|
||||
});
|
||||
|
||||
EXPECT_EQ(called, 1);
|
||||
}
|
||||
TEST(Future, ListenImmediateError) {
|
||||
nf7::Future<int32_t> sut {std::make_exception_ptr(nf7::Exception {"hello"})};
|
||||
|
||||
auto called = int32_t {0};
|
||||
sut.Listen([&]() {
|
||||
++called;
|
||||
EXPECT_THROW(std::rethrow_exception(sut.error()), nf7::Exception);
|
||||
});
|
||||
|
||||
EXPECT_EQ(called, 1);
|
||||
}
|
||||
TEST(Future, ListenLazyComplete) {
|
||||
nf7::Future<int32_t>::Completer completer;
|
||||
nf7::Future<int32_t> sut = completer.future();
|
||||
|
||||
auto called = int32_t {0};
|
||||
sut.Listen([&]() {
|
||||
++called;
|
||||
EXPECT_EQ(sut.value(), int32_t {777});
|
||||
});
|
||||
completer.Complete(int32_t {777});
|
||||
|
||||
EXPECT_EQ(called, 1);
|
||||
}
|
||||
TEST(Future, ListenLazyThrow) {
|
||||
nf7::Future<int32_t>::Completer completer;
|
||||
nf7::Future<int32_t> sut = completer.future();
|
||||
|
||||
auto called = int32_t {0};
|
||||
sut.Listen([&]() {
|
||||
++called;
|
||||
EXPECT_THROW(std::rethrow_exception(sut.error()), nf7::Exception);
|
||||
});
|
||||
completer.Throw(std::make_exception_ptr(nf7::Exception {"hello"}));
|
||||
|
||||
EXPECT_EQ(called, 1);
|
||||
}
|
||||
TEST(Future, ListenLazyIncomplete) {
|
||||
nf7::Future<int32_t>::Completer completer;
|
||||
nf7::Future<int32_t> sut = completer.future();
|
||||
|
||||
auto called = int32_t {0};
|
||||
sut.Listen([&]() { ++called; });
|
||||
|
||||
EXPECT_EQ(called, 0);
|
||||
}
|
||||
TEST(Future, ListenLazyForgotten) {
|
||||
auto called = int32_t {0};
|
||||
{
|
||||
nf7::Future<int32_t>::Completer completer;
|
||||
nf7::Future<int32_t> sut = completer.future();
|
||||
|
||||
sut.Listen([&, sut]() {
|
||||
++called;
|
||||
EXPECT_THROW(std::rethrow_exception(sut.error()), nf7::Exception);
|
||||
});
|
||||
}
|
||||
EXPECT_EQ(called, 1);
|
||||
}
|
||||
|
||||
#if !defined(NDEBUG)
|
||||
TEST(Future, DeathByListenInCallback) {
|
||||
nf7::Future<int32_t> sut {int32_t{777}};
|
||||
|
||||
sut.Listen([&]() {
|
||||
ASSERT_DEATH_IF_SUPPORTED(sut.Listen([](){}), "");
|
||||
});
|
||||
}
|
||||
TEST(Future, DeathByListenInLazyCallback) {
|
||||
nf7::Future<int32_t>::Completer completer;
|
||||
nf7::Future<int32_t> sut = completer.future();
|
||||
|
||||
sut.Listen([&]() {
|
||||
ASSERT_DEATH_IF_SUPPORTED(sut.Listen([](){}), "");
|
||||
});
|
||||
|
||||
completer.Complete(int32_t{777});
|
||||
}
|
||||
#endif
|
Loading…
x
Reference in New Issue
Block a user