diff --git a/CMakeLists.txt b/CMakeLists.txt index a1aec6a..352a309 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -82,6 +82,28 @@ if(BUILD_TBB) endif() endif() +### capy options + +option(BUILD_CAPY "Build the capy sources included in CoroutineTests" TRUE) + +if (USE_SYSTEM_LIBS) + set(USE_SYSTEM_CAPY_DEFAULT ON) +else() + set(USE_SYSTEM_CAPY_DEFAULT OFF) +endif() +option(USE_SYSTEM_CAPY + "Pick up an existing installation of capy from the build environment" + ${USE_SYSTEM_CAPY_DEFAULT}) +unset(USE_SYSTEM_CAPY_DEFAULT) + +if(BUILD_CAPY) + if(USE_SYSTEM_CAPY) + find_package(capy REQUIRED) + else() + add_subdirectory(extern/capy) + endif() +endif() + ### CUDA options option(BUILD_CUDA "Build with CUDA support" OFF) diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 788a397..a349754 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -56,6 +56,16 @@ if(BUILD_BEMAN_TASK) endif() endif() + +if(BUILD_CAPY) + add_executable(capy_task capy_task.cpp) + target_link_libraries(capy_task PRIVATE CoroutineTests Boost::capy) + if (BUILD_TBB) + add_executable(capy_tbb capy_tbb.cpp) + target_link_libraries(capy_tbb PRIVATE CoroutineTests Boost::capy TBB::tbb) + endif() +endif() + if(BUILD_CUDA AND BUILD_TBB) add_executable(alien_reco alien_reco.cpp) target_link_libraries(alien_reco PRIVATE CoroutineTests CUDA::cudart TBB::tbb) diff --git a/examples/README.md b/examples/README.md index b39b05c..a3886e5 100644 --- a/examples/README.md +++ b/examples/README.md @@ -102,7 +102,7 @@ This example shows usage of `task` coroutine return type from future C++26 stand Link: [exec_tbb.cpp](exec_tbb.cpp) -This is a variant of the "Exec task" example, but using a custom C++ senders/receivers scheduler to execute task coroutines on Intel TBB task arena. +This is a variant of the ["Exec task" example](#exec-task), but using a custom C++ senders/receivers scheduler to execute task coroutines on Intel TBB task arena. ## Alien @@ -144,6 +144,18 @@ Link: [alien_counting_scope.cpp](alien_counting_scope.cpp) This example demonstrates dynamic work submitting with `counting_scope` compatible with coroutine semantic as in ["Alien" example](#alien). `spawn` schedules execution of a coroutine that returns `void`. `join()` blocks the current thread until all submitted coroutines are finished (and rethrows the first exception captured from submitted work, if any). +## Capy task + +Link: [capy_task.cpp](capy_task.cpp) + +This is a variant of ["Exec task" example](#exec-task) using Boost.Capy and IoAwaitables protocol ([p4003](https://www.open-std.org/JTC1/SC22/WG21/docs/papers/2026/p4003r0.pdf)) instead of C++26 execution. + +## Capy TBB + +Link: [capy_tbb.cpp](capy_tbb.cpp) + +This is a variant of ["Exec tbb" example](#exec-tbb) using Boost.Capy and IoAwaitables protocol ([p4003](https://www.open-std.org/JTC1/SC22/WG21/docs/papers/2026/p4003r0.pdf)) instead of C++26 execution. This example shows implementation and usage of custom executor scheduling tasks on TBB task arena. + ## Reconstruction Link: [alien_reco.cpp](alien_reco.cpp) and [exec_reco.cpp](exec_reco.cpp) diff --git a/examples/capy_task.cpp b/examples/capy_task.cpp new file mode 100644 index 0000000..7466cdc --- /dev/null +++ b/examples/capy_task.cpp @@ -0,0 +1,163 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "capy_timer.hpp" // TimerIoAwaitable +#include "logging_utils.hpp" // log, format_name +#include "statuscode.hpp" // StatusCodeImpl + +namespace tools { +struct Tag { + static constexpr const char* name = "tools"; +}; +using StatusCode = StatusCodeImpl; +} // namespace tools + +namespace algs { +struct Tag { + static constexpr const char* name = "algs"; +}; +using StatusCode = StatusCodeImpl; +} // namespace algs + +template +class VerboseExecutor { + + public: + VerboseExecutor(Ex& ex) : m_executor(&ex) { + static_assert(boost::capy::Executor>, + "VerboseExecutor should be a valid capy Executor"); + } + + auto post(std::coroutine_handle<> h) const { + log() << "executor posting new work item" << std::endl; + m_executor->post(h); + } + auto dispatch(std::coroutine_handle<> h) const { + log() << "executor dispatching new work item" << std::endl; + return m_executor->dispatch(h); + } + auto& context() const noexcept { return m_executor->context(); } + auto on_work_started() const noexcept { + log() << "executor work started" << std::endl; + m_executor->on_work_started(); + } + auto on_work_finished() const noexcept { + log() << "executor work finished" << std::endl; + m_executor->on_work_finished(); + } + bool operator==(const VerboseExecutor& other) const noexcept { + return (*m_executor) == (*other.m_executor); + } + + private: + Ex* m_executor; +}; + +boost::capy::task tool1_execute(std::string_view parent) { + const auto self = format_name(parent, "tool1"); + + log(self) << "Calling async API in tool1" << std::endl; + auto status = co_await TimerIoAwaitable{std::chrono::milliseconds(100), + timer::StatusCode::SUCCESS, self}; + log(self) << "Result from async API in tool1: " << status << std::endl; + + log(self) << "Finishing tool1" << std::endl; + co_return tools::StatusCode::FAILURE; +} + +boost::capy::task tool2_execute(std::string_view parent) { + const auto self = format_name(parent, "tool2"); + + log(self) << "Calling async API in tool2" << std::endl; + auto status1 = co_await TimerIoAwaitable{std::chrono::milliseconds(10), + timer::StatusCode::SUCCESS, self}; + log(self) << "Result from async API in tool2: " << status1 << std::endl; + + log(self) << "Launching tool1" << std::endl; + auto code = co_await tool1_execute(self); + log(self) << "Result from tool1: " << code << std::endl; + + log(self) << "Calling async API in tool2" << std::endl; + auto status2 = co_await TimerIoAwaitable{std::chrono::milliseconds(10), + timer::StatusCode::FAILURE, self}; + log(self) << "Result from async API in tool2: " << status2 << std::endl; + + log(self) << "Finishing tool2" << std::endl; + co_return tools::StatusCode::SUCCESS; +} + +boost::capy::task tool3_execute(std::string_view parent) { + const auto self = format_name(parent, "tool3"); + log(self) << "Finishing tool3" << std::endl; + co_return tools::StatusCode::FAILURE; +} + +boost::capy::task algorithm_execute(std::string_view parent) { + const auto self = format_name(parent, "algorithm"); + + log(self) << "Calling async API in algorithm" << std::endl; + auto status1 = co_await TimerIoAwaitable{std::chrono::milliseconds(42), + timer::StatusCode::SUCCESS, self}; + log(self) << "Result from async API in algorithm: " << status1 << std::endl; + + log(self) << "Launching tool1" << std::endl; + auto code1 = co_await tool1_execute(self); + log(self) << "Result from tool1: " << code1 << std::endl; + + log(self) << "Calling async API in algorithm" << std::endl; + auto status2 = co_await TimerIoAwaitable{std::chrono::milliseconds(17), + timer::StatusCode::FAILURE, self}; + log(self) << "Result from async API in algorithm: " << status2 << std::endl; + + log(self) << "Launching tool2" << std::endl; + auto code2 = co_await tool2_execute(self); + log(self) << "Result from tool2: " << code2 << std::endl; + + log(self) << "Launching tool3" << std::endl; + auto code3 = co_await tool3_execute(self); + log(self) << "Result from tool3: " << code3 << std::endl; + + log(self) << "Finishing algorithm" << std::endl; + co_return algs::StatusCode::SUCCESS; +} + +int main() { + log() << "main Starting" << std::endl; + + auto pool = boost::capy::thread_pool(4); + auto pool_executor = pool.get_executor(); + auto verbose_executor = VerboseExecutor(pool_executor); + + auto condition = std::condition_variable(); + auto mutex = std::mutex(); + auto final_result = algs::StatusCode{}; + auto result_handler = [&condition, &mutex, + &final_result](algs::StatusCode code) { + { + std::lock_guard lock(mutex); + final_result = code; + } + condition.notify_one(); + }; + + log() << "main launching algorithm" << std::endl; + boost::capy::run_async(verbose_executor, + result_handler)(algorithm_execute("main")); + + { + log() << "main waiting for algorithm to finish..." << std::endl; + auto lock = std::unique_lock(mutex); + condition.wait(lock, [&final_result]() { + return final_result != algs::StatusCode::UNDEFINED; + }); + } + log() << "Final status of algorithm " << final_result << std::endl; + + log() << "main Done" << std::endl; + return EXIT_SUCCESS; +} diff --git a/examples/capy_task_arena_executor.hpp b/examples/capy_task_arena_executor.hpp new file mode 100644 index 0000000..fd875dd --- /dev/null +++ b/examples/capy_task_arena_executor.hpp @@ -0,0 +1,46 @@ +#include + +#include +#include + +class TaskArenaContext : public boost::capy::execution_context { + public: + TaskArenaContext(tbb::task_arena& arena) : m_arena(&arena) {} + + void schedule(std::coroutine_handle<> h) const { + m_arena->enqueue([h]() { h.resume(); }); + } + bool operator==(const TaskArenaContext& other) const noexcept { + return m_arena == other.m_arena; + } + + private: + tbb::task_arena* m_arena; +}; + +class TaskArenaExecutor { + + public: + TaskArenaExecutor(TaskArenaContext& context) noexcept + : m_context(&context) { + static_assert(boost::capy::Executor, + "TaskArenaExecutor should be a valid capy Executor"); + } + + TaskArenaExecutor(TaskArenaExecutor const&) noexcept = default; + + std::coroutine_handle<> dispatch(std::coroutine_handle<> h) const { + m_context->schedule(h); + return std::noop_coroutine(); + } + void post(std::coroutine_handle<> h) const { m_context->schedule(h); } + TaskArenaContext& context() const noexcept { return *m_context; } + void on_work_started() const noexcept {} + void on_work_finished() const noexcept {} + bool operator==(const TaskArenaExecutor& other) const noexcept { + return m_context == other.m_context; + } + + private: + TaskArenaContext* m_context; +}; diff --git a/examples/capy_tbb.cpp b/examples/capy_tbb.cpp new file mode 100644 index 0000000..97e2d8a --- /dev/null +++ b/examples/capy_tbb.cpp @@ -0,0 +1,132 @@ +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "capy_task_arena_executor.hpp" // TaskArenaExecutor +#include "capy_timer.hpp" // TimerIoAwaitable +#include "logging_utils.hpp" // log, format_name +#include "statuscode.hpp" // StatusCodeImpl + +namespace tools { +struct Tag { + static constexpr const char* name = "tools"; +}; +using StatusCode = StatusCodeImpl; +} // namespace tools + +namespace algs { +struct Tag { + static constexpr const char* name = "algs"; +}; +using StatusCode = StatusCodeImpl; +} // namespace algs + +boost::capy::task tool1_execute(std::string_view parent) { + const auto self = format_name(parent, "tool1"); + + log(self) << "Calling async API in tool1" << std::endl; + auto status = co_await TimerIoAwaitable{std::chrono::milliseconds(100), + timer::StatusCode::SUCCESS, self}; + log(self) << "Result from async API in tool1: " << status << std::endl; + + log(self) << "Finishing tool1" << std::endl; + co_return tools::StatusCode::FAILURE; +} + +boost::capy::task tool2_execute(std::string_view parent) { + const auto self = format_name(parent, "tool2"); + + log(self) << "Calling async API in tool2" << std::endl; + auto status1 = co_await TimerIoAwaitable{std::chrono::milliseconds(10), + timer::StatusCode::SUCCESS, self}; + log(self) << "Result from async API in tool2: " << status1 << std::endl; + + log(self) << "Launching tool1" << std::endl; + auto code = co_await tool1_execute(self); + log(self) << "Result from tool1: " << code << std::endl; + + log(self) << "Calling async API in tool2" << std::endl; + auto status2 = co_await TimerIoAwaitable{std::chrono::milliseconds(10), + timer::StatusCode::FAILURE, self}; + log(self) << "Result from async API in tool2: " << status2 << std::endl; + + log(self) << "Finishing tool2" << std::endl; + co_return tools::StatusCode::SUCCESS; +} + +boost::capy::task tool3_execute(std::string_view parent) { + const auto self = format_name(parent, "tool3"); + log(self) << "Finishing tool3" << std::endl; + co_return tools::StatusCode::FAILURE; +} + +boost::capy::task algorithm_execute(std::string_view parent) { + const auto self = format_name(parent, "algorithm"); + + log(self) << "Calling async API in algorithm" << std::endl; + auto status1 = co_await TimerIoAwaitable{std::chrono::milliseconds(42), + timer::StatusCode::SUCCESS, self}; + log(self) << "Result from async API in algorithm: " << status1 << std::endl; + + log(self) << "Launching tool1" << std::endl; + auto code1 = co_await tool1_execute(self); + log(self) << "Result from tool1: " << code1 << std::endl; + + log(self) << "Calling async API in algorithm" << std::endl; + auto status2 = co_await TimerIoAwaitable{std::chrono::milliseconds(17), + timer::StatusCode::FAILURE, self}; + log(self) << "Result from async API in algorithm: " << status2 << std::endl; + + log(self) << "Launching tool2" << std::endl; + auto code2 = co_await tool2_execute(self); + log(self) << "Result from tool2: " << code2 << std::endl; + + log(self) << "Launching tool3" << std::endl; + auto code3 = co_await tool3_execute(self); + log(self) << "Result from tool3: " << code3 << std::endl; + + log(self) << "Finishing algorithm" << std::endl; + co_return algs::StatusCode::SUCCESS; +} + +int main() { + log() << "main Starting" << std::endl; + + auto arena = tbb::task_arena(2); + auto context = TaskArenaContext(arena); + auto executor = TaskArenaExecutor(context); + + auto condition = std::condition_variable(); + auto mutex = std::mutex(); + auto final_result = algs::StatusCode{}; + auto result_handler = [&condition, &mutex, + &final_result](algs::StatusCode code) { + { + std::lock_guard lock(mutex); + final_result = code; + } + condition.notify_one(); + }; + + boost::capy::run_async(executor, result_handler)(algorithm_execute("main")); + + { + log() << "main waiting for algorithm to finish..." << std::endl; + auto lock = std::unique_lock(mutex); + condition.wait(lock, [&final_result]() { + return final_result != algs::StatusCode::UNDEFINED; + }); + } + + log() << "Final status of algorithm " << final_result << std::endl; + // Block until all work items in the scope are done + log() << "main Done" << std::endl; + return EXIT_SUCCESS; +} diff --git a/examples/capy_timer.hpp b/examples/capy_timer.hpp new file mode 100644 index 0000000..d8a591a --- /dev/null +++ b/examples/capy_timer.hpp @@ -0,0 +1,37 @@ +#pragma once + +#include +#include +#include +#include + +#include "logging_utils.hpp" // log, format_name +#include "statuscode.hpp" // StatusCodeImpl + +namespace timer { +struct Tag { + static constexpr const char* name = "timer"; +}; +using StatusCode = StatusCodeImpl; +} // namespace timer + +struct TimerIoAwaitable { + std::chrono::milliseconds delay; + timer::StatusCode status_code; + std::string_view parent; + + bool await_ready() const noexcept { return false; } + + void await_suspend(std::coroutine_handle<> handle, + boost::capy::io_env const* env) noexcept { + std::thread([this, handle, env]() { + const auto self = format_name(parent, "TimerIoAwaitable"); + log(self) << "Async operation started, will take " << delay.count() + << " ms" << std::endl; + std::this_thread::sleep_for(delay); + log(self) << "Async operation finished" << std::endl; + env->executor.post(handle); + }).detach(); + } + timer::StatusCode await_resume() const noexcept { return status_code; } +}; diff --git a/examples/exec_reco.cpp b/examples/exec_reco.cpp index 9219ae1..e1847eb 100644 --- a/examples/exec_reco.cpp +++ b/examples/exec_reco.cpp @@ -7,10 +7,10 @@ #include #include -#include "exec_statuscode.hpp" // StatusCodeImpl #include "exec_stream_await_sender.hpp" // stream_await_sender #include "exec_task_arena_scheduler.hpp" // TaskArenaScheduler #include "logging_utils.hpp" // log, format_name +#include "statuscode.hpp" // StatusCodeImpl namespace tools { struct Tag { diff --git a/examples/exec_task.cpp b/examples/exec_task.cpp index 7951cd2..40e4761 100644 --- a/examples/exec_task.cpp +++ b/examples/exec_task.cpp @@ -4,10 +4,10 @@ #include #include -#include "exec_backend.hpp" // std exec backend selection -#include "exec_statuscode.hpp" // exec StatusCodeImpl -#include "exec_timer.hpp" // TimerSender -#include "logging_utils.hpp" // log, format_name +#include "exec_backend.hpp" // std exec backend selection +#include "exec_timer.hpp" // TimerSender +#include "logging_utils.hpp" // log, format_name +#include "statuscode.hpp" // StatusCodeImpl namespace tools { struct Tag { diff --git a/examples/exec_tbb.cpp b/examples/exec_tbb.cpp index 191ec4e..0d2b760 100644 --- a/examples/exec_tbb.cpp +++ b/examples/exec_tbb.cpp @@ -7,10 +7,10 @@ #include #include "exec_backend.hpp" // std exec backend selection -#include "exec_statuscode.hpp" // exec StatusCodeImpl #include "exec_task_arena_scheduler.hpp" // TaskArenaScheduler/// TaskArenaSchduler #include "exec_timer.hpp" // TimerSender #include "logging_utils.hpp" // log, format_name +#include "statuscode.hpp" // StatusCodeImpl namespace tools { struct Tag { diff --git a/examples/exec_statuscode.hpp b/examples/statuscode.hpp similarity index 84% rename from examples/exec_statuscode.hpp rename to examples/statuscode.hpp index e144c0f..aa4c70f 100644 --- a/examples/exec_statuscode.hpp +++ b/examples/statuscode.hpp @@ -7,8 +7,12 @@ class StatusCodeImpl { enum class Status { SUCCESS = 0, FAILURE = 1, UNDEFINED = 2 }; inline static const StatusCodeImpl SUCCESS{Status::SUCCESS}; inline static const StatusCodeImpl FAILURE{Status::FAILURE}; + inline static const StatusCodeImpl UNDEFINED{Status::UNDEFINED}; StatusCodeImpl(Status status = Status::UNDEFINED) : m_status(status) {} + bool operator==(const StatusCodeImpl& other) const { + return m_status == other.m_status; + } Status status() const { return m_status; } private: diff --git a/extern/capy/CMakeLists.txt b/extern/capy/CMakeLists.txt new file mode 100644 index 0000000..7761a5a --- /dev/null +++ b/extern/capy/CMakeLists.txt @@ -0,0 +1,22 @@ +cmake_minimum_required(VERSION 3.25) +include(FetchContent) + +# Tell the user what's happening. +message(STATUS "Building capy as part of the CoroutineTests project") + +# Declare where to get stdexec from. +set(COROUTINETESTS_CAPY_SOURCE + "URL;https://github.com/cppalliance/capy/archive/df92ffbb0670c06ebf0f201da73f06d1385d5d9b.zip;URL_MD5;d6b6e1e25684986d17ae92c13f992cfc" + CACHE STRING "Source for capy, when built as part of this project") + +mark_as_advanced(COROUTINETESTS_CAPY_SOURCE) +FetchContent_Declare(capy SYSTEM ${COROUTINETESTS_CAPY_SOURCE}) + +# Turn off build tests, examples, and docs. +set(BOOST_CAPY_BUILD_TESTS FALSE) +set(BOOST_CAPY_BUILD_EXAMPLES FALSE) +set(BOOST_CAPY_BUILD_BENCH FALSE) +set(BOOST_CAPY_MRDOCS_BUILD FALSE) + +# Get it into the current directory. +FetchContent_MakeAvailable(capy)