diff --git a/cth_coro/incl/cth/coro/awaiters/base/native_handle_awaiter_base.hpp b/cth_coro/incl/cth/coro/awaiters/base/native_handle_awaiter_base.hpp index 8992f8c..4702f0f 100644 --- a/cth_coro/incl/cth/coro/awaiters/base/native_handle_awaiter_base.hpp +++ b/cth_coro/incl/cth/coro/awaiters/base/native_handle_awaiter_base.hpp @@ -8,7 +8,7 @@ namespace cth::co { template struct native_handle_awaiter_base : dev::this_coro_awaiter_base { - explicit native_handle_awaiter_base(os::native_handle_t handle) : _handle{handle} {} + constexpr explicit native_handle_awaiter_base(os::native_handle_t handle) : _handle{handle} {} [[nodiscard]] bool await_ready() const { return os::check_ready(_handle); } void await_suspend(std::coroutine_handle<> h) const { diff --git a/cth_coro/incl/cth/coro/awaiters/wait_awaiter.hpp b/cth_coro/incl/cth/coro/awaiters/wait_awaiter.hpp index fe1d2a7..607cc2a 100644 --- a/cth_coro/incl/cth/coro/awaiters/wait_awaiter.hpp +++ b/cth_coro/incl/cth/coro/awaiters/wait_awaiter.hpp @@ -13,7 +13,7 @@ struct wait_awaiter { std::chrono::steady_clock::time_point timePoint; co::scheduler const& scheduler; - [[nodiscard]] constexpr bool await_ready() const noexcept { + [[nodiscard]] bool await_ready() const noexcept { return std::chrono::steady_clock::now() >= timePoint; } diff --git a/cth_coro/incl/cth/coro/executor.hpp b/cth_coro/incl/cth/coro/executor.hpp index 22b8015..85c7f7f 100644 --- a/cth_coro/incl/cth/coro/executor.hpp +++ b/cth_coro/incl/cth/coro/executor.hpp @@ -2,7 +2,7 @@ #include "cth/coro/awaiters/data_awaiter.hpp" #include "cth/coro/awaiters/schedule_awaiter.hpp" #include "cth/coro/func/steal.hpp" -#include "cth/coro/tasks/dev/scheduled_task.hpp" +#include "cth/coro/tasks/dev/spawn_task.hpp" #include "cth/coro/utility/concepts.hpp" @@ -33,9 +33,10 @@ class executor { * @param awaitable to steal context from * @param args extra arguments to construct the payload with (besides the auto-injected scheduler) * @return awaitable with context stolen + * @attention the awaiter does not own the task, co_await it in the same expression, do not store it */ template - [[nodiscard]] auto steal(Awaitable&& awaitable, Args&&... args) const -> awaiter_t { + [[nodiscard]] constexpr auto steal(Awaitable&& awaitable, Args&&... args) const -> awaiter_t { if constexpr(this_coro::scheduler_payload) return co::steal(std::forward(awaitable), Pyld{scheduler(), std::forward(args)...}); else @@ -60,14 +61,16 @@ class executor { } /** - * runs the coroutine on the scheduler once started, then captures execution back + * runs the coroutine on the scheduler once started + * @details the caller continues on its own scheduler afterwards, with one post if the coroutine ended elsewhere, + * a caller without this_coro support continues where the coroutine ended * @tparam Pyld payload type; the scheduler is injected automatically when it is a scheduler_payload * @param task coroutine to run on the scheduler * @param args extra arguments to construct the payload with (besides the auto-injected scheduler) - * @return cold scheduled task; starts on the scheduler when awaited + * @return cold spawn task; starts on the scheduler when awaited */ template - [[nodiscard]] auto spawn(Awaitable task, Args&&... args) const -> scheduled_task, Pyld> { + [[nodiscard]] auto spawn(Awaitable task, Args&&... args) const -> spawn_task> { if constexpr(this_coro::scheduler_payload) return spawn_impl(Pyld{scheduler(), std::forward(args)...}, *this, std::move(task)); else @@ -81,11 +84,14 @@ class executor { executor self, Awaitable task ) - -> scheduled_task, Pyld> { - co_await self.schedule(); + -> spawn_task> { + co_await self.schedule(); - // inject the payload into the child and finish back on this executor - co_return co_await co::steal(std::move(task), payload); + // the final awaiter of the spawn_task resumes the caller on its own scheduler + if constexpr(this_coro_awaitable) + co_return co_await co::steal(std::move(task), payload); // injects the payload, no hop + else + co_return co_await task; } co::scheduler const* _sched; diff --git a/cth_coro/incl/cth/coro/func/steal.hpp b/cth_coro/incl/cth/coro/func/steal.hpp index a58ab97..24a96a5 100644 --- a/cth_coro/incl/cth/coro/func/steal.hpp +++ b/cth_coro/incl/cth/coro/func/steal.hpp @@ -12,7 +12,7 @@ namespace cth::co { template -[[nodiscard]] auto steal(Awaitable awaitable, Pyld const& payload) -> capture_task> { +[[nodiscard]] auto steal(Awaitable awaitable, Pyld payload) -> capture_task> { static_assert(has_scheduler, "foreign awaitables can only be stolen with a scheduler in the payload"); using result_t = awaited_t; @@ -29,6 +29,10 @@ template } +/** + * injects the payload into the awaiter of the awaitable + * @attention the awaiter does not own the task, co_await it in the same expression, do not store it + */ template constexpr auto steal(Awaitable&& awaitable, Pyld const& payload) -> awaiter_t { static_assert( @@ -43,6 +47,10 @@ constexpr auto steal(Awaitable&& awaitable, Pyld const& payload) -> awaiter_t constexpr auto steal(Awaitable&& awaitable, Args&&... args) -> awaiter_t { return co::steal(std::forward(awaitable), Pyld{std::forward(args)...}); @@ -63,6 +71,10 @@ template return co::steal(std::move(awaitable), this_coro::default_payload{scheduler}); } +/** + * injects the default payload of the scheduler into the awaiter of the awaitable + * @attention the awaiter does not own the task, co_await it in the same expression, do not store it + */ template constexpr auto steal(Awaitable&& awaitable, scheduler const& scheduler) -> awaiter_t { return co::steal(std::forward(awaitable), this_coro::default_payload{scheduler}); diff --git a/cth_coro/incl/cth/coro/tasks/awaiters/dev/basic_final_promise_awaiter.hpp b/cth_coro/incl/cth/coro/tasks/awaiters/dev/basic_final_promise_awaiter.hpp index 0b84a01..81c95b0 100644 --- a/cth_coro/incl/cth/coro/tasks/awaiters/dev/basic_final_promise_awaiter.hpp +++ b/cth_coro/incl/cth/coro/tasks/awaiters/dev/basic_final_promise_awaiter.hpp @@ -6,7 +6,7 @@ struct basic_final_promise_awaiter { constexpr bool await_ready() noexcept { return false; } template - [[nodiscard]] constexpr auto await_suspend(std::coroutine_handle h) noexcept + [[nodiscard]] auto await_suspend(std::coroutine_handle h) noexcept -> std::coroutine_handle<> { if(auto continuation = h.promise().continuation) return continuation; diff --git a/cth_coro/incl/cth/coro/tasks/awaiters/dev/capture_promise_awaiter.hpp b/cth_coro/incl/cth/coro/tasks/awaiters/dev/capture_promise_awaiter.hpp index 9df29f1..82782d1 100644 --- a/cth_coro/incl/cth/coro/tasks/awaiters/dev/capture_promise_awaiter.hpp +++ b/cth_coro/incl/cth/coro/tasks/awaiters/dev/capture_promise_awaiter.hpp @@ -1,13 +1,14 @@ #pragma once #include "basic_promise_awaiter.hpp" -#include "capture_promise_awaiter_base.hpp" + +#include "cth/coro/utility/fwd.hpp" namespace cth::co::dev { template struct capture_promise_awaiter : basic_promise_awaiter - , capture_promise_awaiter_base { + , capture_awaiter_base { using dev::basic_promise_awaiter::basic_promise_awaiter; }; } diff --git a/cth_coro/incl/cth/coro/tasks/awaiters/dev/capture_promise_awaiter_base.hpp b/cth_coro/incl/cth/coro/tasks/awaiters/dev/capture_promise_awaiter_base.hpp deleted file mode 100644 index d3e1b13..0000000 --- a/cth_coro/incl/cth/coro/tasks/awaiters/dev/capture_promise_awaiter_base.hpp +++ /dev/null @@ -1,5 +0,0 @@ -#pragma once - -namespace cth::co::dev { -struct capture_promise_awaiter_base {}; -} diff --git a/cth_coro/incl/cth/coro/tasks/awaiters/dev/spawn_final_awaiter.hpp b/cth_coro/incl/cth/coro/tasks/awaiters/dev/spawn_final_awaiter.hpp new file mode 100644 index 0000000..9e91889 --- /dev/null +++ b/cth_coro/incl/cth/coro/tasks/awaiters/dev/spawn_final_awaiter.hpp @@ -0,0 +1,34 @@ +#pragma once +#include "cth/coro/scheduler.hpp" + +#include + + +namespace cth::co::dev { +/** + * final awaiter of @ref spawn_promise, resumes the awaiting coroutine on its own scheduler + * @details transfers directly if the scheduler of the caller owns the thread, else posts the resume onto it, + * a caller without a known scheduler continues where the spawned coroutine ended + * @attention terminates if the post throws, the final awaiter is noexcept + */ +struct spawn_final_awaiter { + [[nodiscard]] constexpr bool await_ready() const noexcept { return false; } + + template + [[nodiscard]] auto await_suspend(std::coroutine_handle h) const noexcept -> std::coroutine_handle<> { + auto const continuation = h.promise().continuation; + if(!continuation) + return std::noop_coroutine(); + + auto const* caller = h.promise().caller; + if(caller == nullptr || caller->owns_thread()) + return continuation; + + // the caller may destroy this coroutine as soon as it resumes, nothing touches the frame after the post + caller->post([continuation] { continuation.resume(); }); + return std::noop_coroutine(); + } + + constexpr void await_resume() const noexcept {} +}; +} diff --git a/cth_coro/incl/cth/coro/tasks/awaiters/dev/spawn_promise_awaiter.hpp b/cth_coro/incl/cth/coro/tasks/awaiters/dev/spawn_promise_awaiter.hpp new file mode 100644 index 0000000..a35f816 --- /dev/null +++ b/cth_coro/incl/cth/coro/tasks/awaiters/dev/spawn_promise_awaiter.hpp @@ -0,0 +1,47 @@ +#pragma once +#include "basic_promise_awaiter.hpp" + +#include "cth/coro/this_coro/payload.hpp" +#include "cth/coro/this_coro/this_coro_base.hpp" +#include "cth/coro/utility/fwd.hpp" + +#include + +#include + + +namespace cth::co::dev { +/** + * awaiter of @ref spawn_task, takes the scheduler of the caller from the injected payload + * @details the payload of the caller is not stored and not rebound, + * a caller without this_coro support injects nothing + * @tparam Promise of the spawned coroutine, needs a `caller` member + */ +template +struct spawn_promise_awaiter : this_coro_base, private basic_promise_awaiter { +private: + using base_t = basic_promise_awaiter; + +public: + using promise_type = base_t::promise_type; + + using base_t::await_ready; + using base_t::await_resume; + using base_t::base_t; + + /** + * stores the scheduler of the caller + * @param payload of the caller + */ + template + constexpr void inject(Pyld const& payload) noexcept { _caller = &payload.scheduler(); } + + [[nodiscard]] auto await_suspend(std::coroutine_handle<> caller) noexcept -> std::coroutine_handle { + base_t::handle.promise().caller = _caller; + return base_t::await_suspend(caller); + } + +private: + co::scheduler const* _caller = nullptr; +}; +} diff --git a/cth_coro/incl/cth/coro/tasks/awaiters/dev/this_coro_awaiter_base.hpp b/cth_coro/incl/cth/coro/tasks/awaiters/dev/this_coro_awaiter_base.hpp index 07f6aaf..c87bb4b 100644 --- a/cth_coro/incl/cth/coro/tasks/awaiters/dev/this_coro_awaiter_base.hpp +++ b/cth_coro/incl/cth/coro/tasks/awaiters/dev/this_coro_awaiter_base.hpp @@ -44,8 +44,8 @@ struct this_coro_awaiter_base : this_coro_base { } protected: - Pyld& payload() { return *_payload; } - Pyld const& payload() const { return *_payload; } + constexpr Pyld& payload() { return *_payload; } + constexpr Pyld const& payload() const { return *_payload; } private: cth::dt::optional _payload{}; diff --git a/cth_coro/incl/cth/coro/tasks/dev/scheduled_task.hpp b/cth_coro/incl/cth/coro/tasks/dev/scheduled_task.hpp deleted file mode 100644 index 6df67e0..0000000 --- a/cth_coro/incl/cth/coro/tasks/dev/scheduled_task.hpp +++ /dev/null @@ -1,48 +0,0 @@ -#pragma once -#include "cth/coro/tasks/task_base.hpp" - -#include "cth/coro/tasks/awaiters/dev/capture_promise_awaiter.hpp" -#include "cth/coro/tasks/promises/basic_promise.hpp" -#include "cth/coro/tasks/promises/this_coro_promise_base.hpp" -#include "cth/coro/utility/fwd.hpp" - - -namespace cth::co::dev { - -template -struct scheduled_promise : basic_promise, this_coro_promise_base { - explicit scheduled_promise(Pyld const& payload) : this_coro_promise_base{payload} {} - - template - explicit scheduled_promise(Pyld const& payload, Args&&...) : scheduled_promise{payload} {} - - - scheduled_task get_return_object() noexcept; -}; - -} - -namespace cth::co { - -template -class [[nodiscard]] scheduled_task - : public task_base, dev::capture_promise_awaiter> { - using base_t = task_base, dev::capture_promise_awaiter>; - - using base_t::base_t; - friend base_t::promise_type; - -public: - scheduled_task(scheduled_task const& other) = delete; - scheduled_task& operator=(scheduled_task const& other) = delete; - - scheduled_task(scheduled_task&& other) noexcept = default; - scheduled_task& operator=(scheduled_task&& other) noexcept = default; -}; - -template -scheduled_task dev::scheduled_promise::get_return_object() noexcept { - return scheduled_task{std::coroutine_handle::from_promise(*this)}; -} - -} diff --git a/cth_coro/incl/cth/coro/tasks/dev/spawn_task.hpp b/cth_coro/incl/cth/coro/tasks/dev/spawn_task.hpp new file mode 100644 index 0000000..d11de8a --- /dev/null +++ b/cth_coro/incl/cth/coro/tasks/dev/spawn_task.hpp @@ -0,0 +1,54 @@ +#pragma once +#include "cth/coro/tasks/task_base.hpp" + +#include "cth/coro/tasks/awaiters/dev/spawn_final_awaiter.hpp" +#include "cth/coro/tasks/awaiters/dev/spawn_promise_awaiter.hpp" +#include "cth/coro/tasks/promises/basic_promise.hpp" +#include "cth/coro/utility/fwd.hpp" + +#include + + +namespace cth::co::dev { + +template +struct spawn_promise : basic_promise { + /** + * scheduler of the awaiting coroutine, nullptr -> unknown + */ + co::scheduler const* caller = nullptr; + + spawn_task get_return_object() noexcept; +}; + +} + +namespace cth::co { + +/** + * task of @ref executor::spawn() + * @details this_coro awaitable: the caller continues on its own scheduler after the spawned coroutine ended, + * a caller without this_coro support continues where the spawned coroutine ended + * @tparam T result type + */ +template +class [[nodiscard]] spawn_task : public task_base, dev::spawn_promise_awaiter> { + using base_t = task_base, dev::spawn_promise_awaiter>; + + using base_t::base_t; + friend base_t::promise_type; + +public: + spawn_task(spawn_task const& other) = delete; + spawn_task& operator=(spawn_task const& other) = delete; + + spawn_task(spawn_task&& other) noexcept = default; + spawn_task& operator=(spawn_task&& other) noexcept = default; +}; + +template +spawn_task dev::spawn_promise::get_return_object() noexcept { + return spawn_task{std::coroutine_handle::from_promise(*this)}; +} + +} diff --git a/cth_coro/incl/cth/coro/tasks/promises/raw_promise.hpp b/cth_coro/incl/cth/coro/tasks/promises/raw_promise.hpp index 1300d61..4eda45d 100644 --- a/cth_coro/incl/cth/coro/tasks/promises/raw_promise.hpp +++ b/cth_coro/incl/cth/coro/tasks/promises/raw_promise.hpp @@ -15,10 +15,10 @@ struct raw_promise : dev::raw_promise_base { std::exception_ptr exception; std::coroutine_handle<> continuation = nullptr; - void unhandled_exception() { exception = std::current_exception(); } + constexpr void unhandled_exception() { exception = std::current_exception(); } template - void return_value(U&& value) noexcept { + constexpr void return_value(U&& value) noexcept { result.emplace(std::forward(value)); } }; @@ -31,8 +31,8 @@ struct raw_promise : dev::raw_promise_base { std::exception_ptr exception; std::coroutine_handle<> continuation = nullptr; - void unhandled_exception() { exception = std::current_exception(); } + constexpr void unhandled_exception() { exception = std::current_exception(); } - void return_void() noexcept { result.emplace(); } + constexpr void return_void() noexcept { result.emplace(); } }; } diff --git a/cth_coro/incl/cth/coro/tasks/promises/this_coro_promise_base.hpp b/cth_coro/incl/cth/coro/tasks/promises/this_coro_promise_base.hpp index 0f19ef4..5ca2b35 100644 --- a/cth_coro/incl/cth/coro/tasks/promises/this_coro_promise_base.hpp +++ b/cth_coro/incl/cth/coro/tasks/promises/this_coro_promise_base.hpp @@ -57,7 +57,7 @@ struct this_coro_promise_base : this_coro_base { [[nodiscard]] constexpr auto await_transform(Awaitable&& captured) { return cth::co::extract_awaiter(captured); } template requires (this_coro::scheduler_payload) - [[nodiscard]] auto await_transform(Waitable const& waitable) { + [[nodiscard]] constexpr auto await_transform(Waitable const& waitable) { return raw_native_handle_awaiter{_payload->scheduler(), waitable.native_handle()}; } @@ -85,8 +85,8 @@ struct this_coro_promise_base : this_coro_base { _payload->rebind_scheduler(scheduler); } - this_coro_promise_base(Pyld const& payload) noexcept : _payload{payload} {} - this_coro_promise_base() noexcept = default; + constexpr this_coro_promise_base(Pyld const& payload) noexcept : _payload{payload} {} + constexpr this_coro_promise_base() noexcept = default; private: cth::dt::optional _payload{}; diff --git a/cth_coro/incl/cth/coro/tasks/task_base.hpp b/cth_coro/incl/cth/coro/tasks/task_base.hpp index 8bc811d..72e32b5 100644 --- a/cth_coro/incl/cth/coro/tasks/task_base.hpp +++ b/cth_coro/incl/cth/coro/tasks/task_base.hpp @@ -24,13 +24,14 @@ class [[nodiscard]] task_base { public: [[nodiscard]] constexpr auto handle(this auto&& self) { return self._handle.get(); } + /** + * awaits the task, the task stays the owner of the coroutine + * @details a temporary task lives until the end of the full expression, after await_resume() + */ [[nodiscard]] constexpr auto operator co_await() const& noexcept { return awaiter_type{handle()}; } - [[nodiscard]] constexpr auto operator co_await() && noexcept { - return awaiter_type{this->_handle.extract()}; - } task_base(task_base const&) = delete; - task_base operator=(task_base const&) = delete; + task_base& operator=(task_base const&) = delete; constexpr task_base(task_base&&) noexcept = default; constexpr task_base& operator=(task_base&&) noexcept = default; }; diff --git a/cth_coro/incl/cth/coro/this_coro/wait_tag.hpp b/cth_coro/incl/cth/coro/this_coro/wait_tag.hpp index a682c60..1f213f3 100644 --- a/cth_coro/incl/cth/coro/this_coro/wait_tag.hpp +++ b/cth_coro/incl/cth/coro/this_coro/wait_tag.hpp @@ -28,7 +28,7 @@ constexpr auto wait_until(chrono::time_point_t time_point) { return wait_tag{tim * @return wait tag */ template -constexpr auto wait(std::chrono::duration const& duration) { +auto wait(std::chrono::duration const& duration) { return wait_tag{chrono::clock_t::now() + duration}; } diff --git a/cth_coro/incl/cth/coro/utility/fwd.hpp b/cth_coro/incl/cth/coro/utility/fwd.hpp index ef2921a..cf4d4a7 100644 --- a/cth_coro/incl/cth/coro/utility/fwd.hpp +++ b/cth_coro/incl/cth/coro/utility/fwd.hpp @@ -100,8 +100,8 @@ class sync_task; template class executor_task; -template -class scheduled_task; +template +class spawn_task; template class sync_executor_task; @@ -123,7 +123,12 @@ struct this_coro_promise_awaiter_base; template struct this_coro_promise_base; -struct capture_awaiter_base; +/** + * opt-in tag for awaiters that return to the right context themselves + * @details @ref this_coro_promise_base awaits them as they are, without a steal back to its payload context + */ +struct capture_awaiter_base {}; + struct sync_promise_base; } diff --git a/cth_coro/tests/src/executor_test.cpp b/cth_coro/tests/src/executor_test.cpp index 34d209d..1fc7eec 100644 --- a/cth_coro/tests/src/executor_test.cpp +++ b/cth_coro/tests/src/executor_test.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include @@ -251,6 +252,172 @@ CORO_TEST(executor, context_scheduler_reflects_schedule_hop) { EXPECT_EQ(actual, &other); } +// spawn() runs the child on its scheduler, the caller continues on its own scheduler afterwards +CORO_TEST(executor, interop_spawn_caller_continues_on_own_scheduler) { + scheduler home{autostart, 1}; + scheduler other{autostart, 1}; + executor homeExec{home}; + + auto child = [](scheduler const& target) -> executor_task { co_return target.owns_thread(); }; + + auto task = [](scheduler const& h, scheduler const& o, auto c) + -> executor_task> { + bool const childOnOther = co_await executor{o}.spawn(c(o)); + scheduler const& current = co_await this_coro::scheduler; + co_return std::tuple{childOnOther, h.owns_thread(), ¤t}; + }; + + auto const [childOnOther, onHome, current] = sync_wait(homeExec, task(home, other, child)); + EXPECT_TRUE(childOnOther); + EXPECT_TRUE(onHome); + EXPECT_EQ(current, &home); +} + +CORO_TEST(executor, interop_spawn_on_own_scheduler) { + scheduler home{autostart, 1}; + executor homeExec{home}; + + auto child = [](scheduler const& target) -> executor_task { co_return target.owns_thread(); }; + + auto task = [](executor e, scheduler const& h, auto c) -> executor_task> { + bool const childOnHome = co_await e.spawn(c(h)); + co_return std::pair{childOnHome, h.owns_thread()}; + }; + + auto const [childOnHome, onHome] = sync_wait(homeExec, task(homeExec, home, child)); + EXPECT_TRUE(childOnHome); + EXPECT_TRUE(onHome); +} + +CORO_TEST(executor, interop_spawn_void_caller_continues_on_own_scheduler) { + scheduler home{autostart, 1}; + scheduler other{autostart, 1}; + executor homeExec{home}; + std::atomic childOnOther{false}; + + auto child = [](scheduler const& target, std::atomic& flag) -> executor_task { + flag = target.owns_thread(); + co_return; + }; + + auto task = [](scheduler const& h, scheduler const& o, std::atomic& flag, auto c) -> executor_task { + co_await executor{o}.spawn(c(o, flag)); + co_return h.owns_thread(); + }; + + EXPECT_TRUE(sync_wait(homeExec, task(home, other, childOnOther, child))); + EXPECT_TRUE(childOnOther); +} + +// the child may end on another scheduler than the one it was spawned on +CORO_TEST(executor, interop_spawn_child_ends_elsewhere) { + scheduler home{autostart, 1}; + scheduler other{autostart, 1}; + scheduler third{autostart, 1}; + executor homeExec{home}; + + auto child = [](executor end) -> executor_task { co_await end.schedule(); }; + + auto task = [](scheduler const& h, executor o, executor t, auto c) -> executor_task> { + co_await o.spawn(c(t)); + bool const afterThird = h.owns_thread(); + + co_await o.spawn(c(executor{h})); + co_return std::pair{afterThird, h.owns_thread()}; + }; + + auto const [afterThird, afterHome] = sync_wait(homeExec, task(home, executor{other}, executor{third}, child)); + EXPECT_TRUE(afterThird); + EXPECT_TRUE(afterHome); +} + +CORO_TEST(executor, interop_spawn_exception_reaches_caller_on_own_scheduler) { + scheduler home{autostart, 1}; + scheduler other{autostart, 1}; + executor homeExec{home}; + + auto child = []() -> executor_task { + throw std::runtime_error("spawned boom"); + co_return; + }; + + auto task = [](scheduler const& h, executor o, auto c) -> executor_task> { + bool caught = false; + try { + co_await o.spawn(c()); + } catch(std::runtime_error const&) { + caught = true; + } + co_return std::pair{caught, h.owns_thread()}; + }; + + auto const [caught, onHome] = sync_wait(homeExec, task(home, executor{other}, child)); + EXPECT_TRUE(caught); + EXPECT_TRUE(onHome); +} + +CORO_TEST(executor, interop_spawn_nested_each_level_on_own_scheduler) { + scheduler home{autostart, 1}; + scheduler middle{autostart, 1}; + scheduler inner{autostart, 1}; + executor homeExec{home}; + + auto grandchild = [](scheduler const& i) -> executor_task { co_return i.owns_thread(); }; + + auto child = [](scheduler const& m, scheduler const& i, auto g) -> executor_task> { + bool const grandchildOnInner = co_await executor{i}.spawn(g(i)); + co_return std::pair{grandchildOnInner, m.owns_thread()}; + }; + + auto task = [](scheduler const& h, scheduler const& m, scheduler const& i, auto c, auto g) + -> executor_task> { + auto const [grandchildOnInner, childOnMiddle] = co_await executor{m}.spawn(c(m, i, g)); + co_return std::tuple{grandchildOnInner, childOnMiddle, h.owns_thread()}; + }; + + auto const [grandchildOnInner, childOnMiddle, onHome] = + sync_wait(homeExec, task(home, middle, inner, child, grandchild)); + EXPECT_TRUE(grandchildOnInner); + EXPECT_TRUE(childOnMiddle); + EXPECT_TRUE(onHome); +} + +// a caller without this_coro support has no scheduler to return to: it continues where the child ended +CORO_TEST(executor, interop_spawn_caller_without_payload_continues_where_child_ended) { + scheduler other{autostart, 1}; + + auto child = []() -> executor_task { co_return; }; + + auto task = [](scheduler const& o, auto c) -> sync_task { + co_await executor{o}.spawn(c()); + co_return o.owns_thread(); + }; + + auto waiter = task(other, child); + waiter.start(); + EXPECT_TRUE(waiter.await()); +} + +// the capture_task of steal() owns its payload: the payload of steal(foreign, scheduler) is a temporary +CORO_TEST(executor, interop_captured_steal_continues_on_steal_context) { + scheduler home{autostart, 1}; + scheduler other{autostart, 1}; + executor homeExec{home}; + + auto task = [](scheduler const& o) -> executor_task> { + co_await co::steal(foreign_task{}, o); + bool const voidOnOther = o.owns_thread(); + + int const value = co_await co::steal(foreign_value_task{7}, o); + co_return std::tuple{voidOnOther, value, o.owns_thread()}; + }; + + auto const [voidOnOther, value, valueOnOther] = sync_wait(homeExec, task(other)); + EXPECT_TRUE(voidOnOther); + EXPECT_EQ(value, 7); + EXPECT_TRUE(valueOnOther); +} + CORO_TEST(executor, errors_exception_in_spawned_task) { scheduler sched{autostart, 1}; executor exec{sched}; @@ -304,7 +471,7 @@ CORO_TEST(executor, stress_concurrent_spawns) { executor exec{sched}; constexpr int count = 100; - std::vector> tasks; + std::vector> tasks; tasks.reserve(count); std::atomic completed{0}; @@ -317,7 +484,7 @@ CORO_TEST(executor, stress_concurrent_spawns) { co_return; }; - auto spawner = [&](std::vector>& t) -> executor_task { + auto spawner = [&](std::vector>& t) -> executor_task { // ref capture fine bc no co_await for(int i = 0; i < count; ++i) { auto h = exec.spawn(increment_task(completed)); diff --git a/cth_coro/tests/src/utility/concepts_test.cpp b/cth_coro/tests/src/utility/concepts_test.cpp new file mode 100644 index 0000000..6cb229e --- /dev/null +++ b/cth_coro/tests/src/utility/concepts_test.cpp @@ -0,0 +1,33 @@ +#include "cth/coro/utility/concepts.hpp" + +#include "cth/coro/tasks/dev/capture_task.hpp" +#include "cth/coro/tasks/dev/spawn_task.hpp" +#include "cth/coro/tasks/executor_task.hpp" +#include "cth/coro/tasks/sync_task.hpp" + + +namespace cth::co { +// the result of steal() returns to the payload context of the steal itself +static_assert(captured_awaitable>); +static_assert(captured_awaitable>); +static_assert(!foreign_awaitable>); +static_assert(!this_coro_awaitable>); + +// the result of spawn() takes the scheduler of the caller from its payload +static_assert(this_coro_awaitable>); +static_assert(this_coro_awaitable>); +static_assert(!captured_awaitable>); +static_assert(!foreign_awaitable>); + +static_assert(this_coro_awaitable>); +static_assert(!captured_awaitable>); +static_assert(!foreign_awaitable>); + +static_assert(this_coro_awaitable>); +static_assert(!captured_awaitable>); +static_assert(!foreign_awaitable>); + +static_assert(foreign_awaitable>); +static_assert(!captured_awaitable>); +static_assert(!this_coro_awaitable>); +}