diff --git a/CMakeLists.txt b/CMakeLists.txt index 52101c0..4cff0ee 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -268,6 +268,7 @@ if(TILE_BUILD_TESTS) target_sources(${PROJECT_NAME}_test_all PRIVATE ${test_file}) endmacro() +tile_add_test(fiber_detail_scheduler_test "tile/fiber/detail/scheduler_test.cc") tile_add_test(base_internal_meta_test "tile/base/internal/meta_test.cc") # tile_add_test(net_internal_http_engine_test # "tile/net/internal/http_engine_test.cc") diff --git a/tile/base/internal/format.h b/tile/base/internal/format.h index 80a5e6d..e42b093 100644 --- a/tile/base/internal/format.h +++ b/tile/base/internal/format.h @@ -26,6 +26,14 @@ template struct has_to_string { static constexpr bool value = decltype(Test(0))::value; }; +template struct can_to_string { + template + static auto Test(int) -> decltype(ToString(std::declval()), + std::true_type()); + template static auto Test(...) -> std::false_type; + static constexpr bool value = decltype(Test(0))::value; +}; + template std::string format_as_impl(const std::chrono::duration &val, const char *suffix) { @@ -52,11 +60,21 @@ auto format_as(const T &val) template auto format_as(const T &val) -> tile::enable_if_t::value && - tile::detail::has_to_string::value, + tile::detail::has_to_string::value && + !tile::detail::can_to_string::value, std::string> { return val.ToString(); } +template +auto format_as(const T &val) + -> tile::enable_if_t::value && + !tile::detail::has_to_string::value && + tile::detail::can_to_string::value, + std::string> { + return ToString(val); +} + template auto format_as(T *ptr) -> tile::enable_if_t>::value, diff --git a/tile/fiber/detail/fiber.cc b/tile/fiber/detail/fiber.cc index b7b7589..51fe33f 100644 --- a/tile/fiber/detail/fiber.cc +++ b/tile/fiber/detail/fiber.cc @@ -1,10 +1,12 @@ #include "tile/fiber/detail/fiber.h" #include "tile/base/align.h" +#include "tile/base/internal/index_alloc.h" #include "tile/base/internal/move_on_copy.h" #include "tile/base/logging.h" #include "tile/base/make_unique.h" #include "tile/base/object_pool.h" +#include "tile/base/string.h" #include "nova/context/fcontext.h" @@ -12,6 +14,25 @@ namespace tile { namespace fiber { namespace detail { +std::string ToString(const FiberState &state) noexcept { + switch (state) { + case FiberState::Runnable: + return "Runnable"; + case FiberState::Running: + return "Running"; + case FiberState::Waiting: + return "Waiting"; + case FiberState::Terminated: + return "Terminated"; + default: + TILE_UNEXPECTED(""); + return "Unknown"; + } +} +std::ostream &operator<<(std::ostream &os, const FiberState &state) noexcept { + return os << ToString(state); +} + static thread_local Fiber *tls_current_fiber = nullptr; static thread_local Fiber *tls_master_fiber = nullptr; @@ -32,8 +53,14 @@ void FiberEntry(fcontext_transfer_t t) { try { // From Resume() t = jump_fcontext(t.fctx, nullptr); - self = reinterpret_cast(t.data); - self->caller_->ctx_ = t.fctx; + Fiber *caller = reinterpret_cast(t.data); + + self = GetCurrentFiber(); + if (caller) { + caller->ctx_ = t.fctx; + } else { + TILE_CHECK_EQ(self, GetMasterFiber()); + } (*fn)(); } catch (const std::exception &e) { @@ -41,13 +68,16 @@ void FiberEntry(fcontext_transfer_t t) { } self->state_ = FiberState::Terminated; - if (GetMasterFiber() != GetCurrentFiber()) { - GetMasterFiber()->Resume(); - } else { + if (GetMasterFiber() == self) { // master fiber end jump_fcontext(t.fctx, GetMasterFiber()); + } else { + TILE_CHECK(GetMasterFiber() != nullptr); + TILE_CHECK_NE(GetMasterFiber()->state(), FiberState::Terminated); + GetMasterFiber()->Resume(); } } + fcontext_transfer_t FiberOnTop(fcontext_transfer_t t) {} fcontext_t CreateFiber(void *stack, std::size_t stack_size, @@ -70,7 +100,8 @@ std::unique_ptr Fiber::Create(std::function proc) noexcept { } Fiber::Fiber(std::function proc) - : data_(object_pool::Get().Leak()) { + : id_(internal::IndexAlloc::For()->Next()), + data_(object_pool::Get().Leak()) { TILE_CHECK(proc); data_->proc = std::move(proc); @@ -81,15 +112,20 @@ Fiber::~Fiber() { if (data_) { object_pool::Put(data_.release()); } + internal::IndexAlloc::For()->Free(id_); } void Fiber::Resume() { auto caller = GetCurrentFiber(); - TILE_CHECK_NE(caller, this, "Calling `ResumeOn()`, on self is undefined."); + TILE_CHECK_NE(caller, this, "Calling `Resume()`, on self is undefined."); - auto t = jump_fcontext(ctx_, this); - caller = reinterpret_cast(t.data); - caller->ctx_ = t.fctx; + SetUpCurrentFiber(this); + + auto t = jump_fcontext(internal::Exchange(ctx_, nullptr), caller); + + if (auto from = reinterpret_cast(t.data)) { + from->ctx_ = t.fctx; + } SetUpCurrentFiber(caller); } @@ -97,7 +133,21 @@ void Fiber::Resume() { void Fiber::ResumeOn(std::function &&cb) noexcept { auto caller = GetCurrentFiber(); TILE_CHECK_NE(caller, this, "Calling `ResumeOn()`, on self is undefined."); - ontop_fcontext(ctx_, this, FiberOnTop); + SetUpCurrentFiber(this); + auto t = ontop_fcontext(ctx_, this, FiberOnTop); + caller = reinterpret_cast(t.data); + SetUpCurrentFiber(caller); +} + +std::string ToString(const Fiber &fiber) noexcept { return ToString(&fiber); } +std::string ToString(const std::unique_ptr &fiber) noexcept { + return ToString(fiber.get()); +} +std::string ToString(Fiber const *const fiber) noexcept { + if (TILE_UNLIKELY(fiber == nullptr)) { + return "Fiber(nullptr)"; + } + return Format("Fiber({})[{}]", fiber->id(), fmt::ptr(fiber)); } } // namespace detail diff --git a/tile/fiber/detail/fiber.h b/tile/fiber/detail/fiber.h index b671a4d..b775abb 100644 --- a/tile/fiber/detail/fiber.h +++ b/tile/fiber/detail/fiber.h @@ -22,10 +22,11 @@ class Scheduler; enum class FiberState { Runnable, Running, - Suspended, Waiting, Terminated, }; +std::string ToString(const FiberState &state) noexcept; +std::ostream &operator<<(std::ostream &os, const FiberState &state) noexcept; class Scheduler; @@ -46,6 +47,8 @@ public: void Resume(); void ResumeOn(std::function &&cb) noexcept; void Yield(); + Id id() const noexcept { return id_; } + FiberState state() const noexcept { return state_; } Scheduler *scheduler() const { return scheduler_; } @@ -61,10 +64,14 @@ private: Fiber(std::function proc = nullptr); private: + Id id_; + std::unique_ptr data_; FiberState state_{FiberState::Runnable}; + void *ctx_{nullptr}; - Fiber *caller_{nullptr}; + + // for scheduler Scheduler *scheduler_{nullptr}; }; Fiber *GetCurrentFiber() noexcept; @@ -75,6 +82,10 @@ void SetUpMasterFiber(Fiber *fiber) noexcept; inline bool IsFiberContext() noexcept { return GetCurrentFiber() != nullptr; } +std::string ToString(Fiber const *const fiber) noexcept; +std::string ToString(const std::unique_ptr &fiber) noexcept; +std::string ToString(const Fiber &fiber) noexcept; + } // namespace detail } // namespace fiber } // namespace tile diff --git a/tile/fiber/detail/scheduler.cc b/tile/fiber/detail/scheduler.cc index 5788e96..b1a8dac 100644 --- a/tile/fiber/detail/scheduler.cc +++ b/tile/fiber/detail/scheduler.cc @@ -8,7 +8,7 @@ static thread_local Scheduler *current_scheduler = nullptr; void Scheduler::SwitchTo(Fiber *self, Fiber *to) { TILE_CHECK_EQ(self, GetCurrentFiber()); - TILE_CHECK(to->state_ == FiberState::Runnable, + TILE_CHECK(to->state_ == FiberState::Terminated, "Fiber `to` is not in Runnable."); TILE_CHECK_NE(self, to, "Switch to yourself result in U.B."); diff --git a/tile/fiber/detail/scheduler_test.cc b/tile/fiber/detail/scheduler_test.cc new file mode 100644 index 0000000..ef128d5 --- /dev/null +++ b/tile/fiber/detail/scheduler_test.cc @@ -0,0 +1,55 @@ +#include "tile/fiber/detail/scheduler.h" + +#include "tile/base/deferred.h" + +#include "tile/base/random.h" +#include "gtest/gtest.h" + +namespace tile { +namespace fiber { +namespace detail { + +TEST(Fiber, Resume) { + constexpr int kFiberCount = 1000; + constexpr int kSwitchCount = 100000; + + std::unique_ptr master; + std::unique_ptr worker[kFiberCount]; + + int cnt = 0; + auto end_check = tile::Deferred([&]() { ASSERT_EQ(cnt, kSwitchCount); }); + + master = Fiber::Create([&]() { + while (cnt < kSwitchCount) { + ASSERT_EQ(GetCurrentFiber(), master.get()); + worker[Random(kFiberCount - 1)]->Resume(); + ASSERT_EQ(GetCurrentFiber(), master.get()); + } + + for (int i = 0; i != kFiberCount; ++i) { + worker[i]->Resume(); + } + }); + for (int i = 0; i != kFiberCount; ++i) { + worker[i] = Fiber::Create([&, i]() { + while (cnt < kSwitchCount) { + ++cnt; + ASSERT_EQ(GetCurrentFiber(), worker[i].get()); + master->Resume(); + ASSERT_EQ(GetCurrentFiber(), worker[i].get()); + } + }); + } + + SetUpMasterFiber(master.get()); + SetUpCurrentFiber(nullptr); + master->Resume(); + ASSERT_EQ(cnt, kSwitchCount); + for (int i = 0; i != kFiberCount; ++i) { + ASSERT_EQ(worker[i]->state(), FiberState::Terminated); + } +} + +} // namespace detail +} // namespace fiber +} // namespace tile diff --git a/tile/fiber/this_fiber.cc b/tile/fiber/this_fiber.cc index 606263a..5cd3d72 100644 --- a/tile/fiber/this_fiber.cc +++ b/tile/fiber/this_fiber.cc @@ -18,6 +18,7 @@ void SleepFor(std::chrono::nanoseconds expires_in) { Fiber::Id GetId() { auto self = fiber::detail::GetCurrentFiber(); TILE_CHECK(self, "GetId() should be called in a fiber."); + return self->id(); } } // namespace this_fiber } // namespace tile