diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt index 5d0c53e..c15e81d 100644 --- a/core/CMakeLists.txt +++ b/core/CMakeLists.txt @@ -17,6 +17,7 @@ target_sources(nf7_core luajit/context.hh luajit/lambda.hh luajit/thread.hh + uv/context.hh clock.hh logger.hh version.hh @@ -30,6 +31,7 @@ target_sources(nf7_core_test luajit/lambda_test.cc luajit/thread_test.cc luajit/thread_test.hh + uv/context_test.hh clock_test.cc ) target_link_libraries(nf7_core_test diff --git a/core/uv/context.hh b/core/uv/context.hh new file mode 100644 index 0000000..b8b5eb3 --- /dev/null +++ b/core/uv/context.hh @@ -0,0 +1,102 @@ +// No copyright +#pragma once + +#include +#include + +#include + +#include "iface/common/exception.hh" +#include "iface/subsys/interface.hh" +#include "iface/subsys/logger.hh" +#include "iface/env.hh" + +#include "core/logger.hh" + +namespace nf7::core::uv { + +class Context : public subsys::Interface { + private: + static std::shared_ptr MakeLoop() { + auto ptr = uvw::loop::create(); + if (nullptr == ptr) { + throw Exception {"failed to create loop"}; + } + return ptr; + } + + protected: + Context(const char* name, Env& env) + : subsys::Interface(name), + logger_(env.GetOr(NullLogger::instance())), + loop_(MakeLoop()), + stop_(Make()) { + stop_->unreference(); + stop_->on([loop = loop_, logger = logger_](auto&, auto&) { + loop->stop(); + logger->Trace("stopped loop iteration"); + }); + } + + public: + ~Context() noexcept override { + if (0 != loop_->close()) { + logger_->Warn("failed to close uv loop"); + } + } + + public: + template + std::shared_ptr Make() const + try { + auto ptr = loop_->resource(); + if (nullptr == ptr) { + throw Exception {"failed to init libuv resource"}; + } + return ptr; + } catch (const std::bad_alloc&) { + throw Exception {"failed to allocate libuv resource"}; + } + + // THREAD-SAFE + void Exit() noexcept { + if (0 == stop_->send()) { + logger_->Info("requested to exit uv loop"); + } else { + logger_->Error("a request to exit is dismissed"); + } + } + + const std::shared_ptr& loop() const noexcept { return loop_; } + + protected: + void Run() noexcept { + loop_->run(uvw::loop::run_mode::DEFAULT); + } + void RunOnce() noexcept { + loop_->run(uvw::loop::run_mode::ONCE); + } + void RunAndClose() noexcept { + Run(); + loop_->walk([](auto&& h) { h.close(); }); + Run(); + } + + private: + const std::shared_ptr logger_; + + const std::shared_ptr loop_; + const std::shared_ptr stop_; +}; + +class MainContext : public Context { + public: + explicit MainContext(Env& env) + : Context("nf7::core::uv::MainContext", env) { } + + using Context::Run; + using Context::RunOnce; + using Context::RunAndClose; +}; + +} // namespace nf7::core::uv diff --git a/core/uv/context_test.hh b/core/uv/context_test.hh new file mode 100644 index 0000000..5101177 --- /dev/null +++ b/core/uv/context_test.hh @@ -0,0 +1,39 @@ +// No copyright +#pragma once + +#include "core/uv/context.hh" + +#include + +#include +#include + +#include "iface/subsys/clock.hh" +#include "iface/env.hh" + +#include "core/uv/clock.hh" + + +namespace nf7::core::uv::test { + +class ContextFixture : public ::testing::Test { + protected: + void SetUp() override { + env_.emplace(SimpleEnv::FactoryMap { + SimpleEnv::MakePair(), + SimpleEnv::MakePair(), + }); + ctx_ = std::dynamic_pointer_cast(env_->Get()); + } + void TearDown() override { + ctx_->RunAndClose(); + env_ = std::nullopt; + ctx_ = nullptr; + } + + protected: + std::optional env_; + std::shared_ptr ctx_; +}; + +} // namespace nf7::core:uv::test