#include #include #include #include #include #include namespace capy = boost::capy; //-------------------------------------------------------------------- // A minimal *eager* custom coroutine whose promise participates in the // IoAwaitable environment protocol (it inherits io_awaitable_promise_base). // // The only thing that makes it dangerous is the eager initial_suspend. //-------------------------------------------------------------------- template struct demo_task { struct promise_type : capy::io_awaitable_promise_base { demo_task get_return_object() { return demo_task{ std::coroutine_handle::from_promise(*this)}; } // The hazard lives here: eager start means the body runs during the // call, before this coroutine is awaited and before set_environment(). std::suspend_never initial_suspend() noexcept { return {}; } std::suspend_always final_suspend() noexcept { return {}; } void return_void() noexcept {} void unhandled_exception() noexcept { std::terminate(); } }; explicit demo_task(std::coroutine_handle h) noexcept : h_(h) { } demo_task(demo_task&& other) noexcept : h_(std::exchange(other.h_, {})) { } ~demo_task() { if(h_) h_.destroy(); } std::coroutine_handle h_; }; //-------------------------------------------------------------------- // SAFE: a lazy capy::task reads the stop token from its body. By the time the // body runs, run_async has installed the environment, so env_ is non-null. //-------------------------------------------------------------------- capy::task<> lazy_reads_stop_token() { auto token = co_await capy::this_coro::stop_token; std::cout << "[lazy] stop_requested() = " << std::boolalpha << token.stop_requested() << "\n"; co_return; } //-------------------------------------------------------------------- // HAZARD: an eager demo_task tries to read the stop token as its first action. // The eager body executes during the call below -- before the coroutine is ever // awaited -- so env_ is still nullptr. //-------------------------------------------------------------------- demo_task<> eager_reads_stop_token() { // env_ == nullptr here. In io_awaitable_promise_base::await_transform this // hits BOOST_CAPY_ASSERT(env_) and then env_->stop_token. auto token = co_await capy::this_coro::stop_token; std::cout << "[eager] stop_requested() = " << std::boolalpha << token.stop_requested() << "\n"; co_return; } int main() { // 1) The correct pattern: works, prints "false". { capy::thread_pool pool; capy::run_async(pool.get_executor())(lazy_reads_stop_token()); pool.join(); } // 2) The hazard. The eager body runs *inside* this call. std::cout << "[eager] calling eager coroutine " "(its body runs during this call)...\n"; std::cout.flush(); auto t = eager_reads_stop_token(); // <-- assert fires / UB happens here // Reached only if assertions are disabled AND the null read happened to // not crash -- do not rely on this. std::cout << "[eager] returned without crashing " "(undefined behavior already occurred)\n"; (void)t; return 0; }