add nf7::Future and nf7::Exception

This commit is contained in:
falsycat 2023-07-15 10:59:30 +09:00
parent b93fcb6ac7
commit 70c41b3c3b
4 changed files with 281 additions and 110 deletions

View File

@ -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
)

View File

@ -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

View File

@ -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
View 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