// A complete, bounded coroutine experiment using standalone Asio 1.38.2. // One io_context runner, one operation at a time, no detached work or sleeps. #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace { enum class Mode { succeed, cancel, fail }; enum class Event { started, other_handler, resumed, completed }; struct Control { asio::cancellation_signal cancel; std::array events{}; std::size_t count{}; void record(Event event) { if (count==events.size()) throw std::logic_error{"trace exceeded its bound"}; events[count++]=event; } }; struct Payload { std::string label; }; struct Outcome { bool cancelled{}; std::string label; }; // Named coroutine + value parameters: the frame owns these shared owners. // The payload has no owner outside the suspended operation after caller.reset(). asio::awaitable wait_owned(std::shared_ptr payload, std::shared_ptr control, Mode mode) { const auto executor=co_await asio::this_coro::executor; asio::steady_timer timer{executor}; timer.expires_at(mode==Mode::cancel ? std::chrono::steady_clock::time_point::max() : std::chrono::steady_clock::now()); control->record(Event::started); if (mode==Mode::cancel) { // post never invokes this handler inline. There is only one run() thread: // the coroutine reaches its pending wait before this handler can execute. asio::post(executor,[control] { control->record(Event::other_handler); control->cancel.emit(asio::cancellation_type::terminal); }); } asio::error_code error; co_await timer.async_wait(asio::redirect_error(asio::use_awaitable,error)); control->record(Event::resumed); if (error==asio::error::operation_aborted) co_return Outcome{true,payload->label}; if (error) throw asio::system_error{error}; if (mode==Mode::fail) throw std::runtime_error{"injected preparation failure"}; co_return Outcome{false,payload->label}; } void check(bool condition,const char* message) { if (!condition) throw std::runtime_error{message}; } void run_case(Mode mode) { asio::io_context context; auto control=std::make_shared(); auto caller=std::make_shared(Payload{"owned study"}); const std::weak_ptr probe=caller; std::exception_ptr failure; Outcome result; std::size_t completions{}; // This is an ordinary completion handler, NOT a capturing coroutine lambda. // References stay valid because run() drains the operation before return. asio::co_spawn(context,wait_owned(caller,control,mode), asio::bind_cancellation_slot(control->cancel.slot(), [&](std::exception_ptr error,Outcome value) { ++completions; failure=error; result=std::move(value); control->record(Event::completed); })); caller.reset(); check(!probe.expired(),"suspended operation must own its payload"); context.run(); // Graceful drain: do not use stop() to skip cleanup completions. check(completions==1,"one completion must be observed"); check(probe.expired(),"frame/payload must be released after completion"); check(control->events[0]==Event::started,"coroutine entry recorded"); check(control->events[control->count-1]==Event::completed,"completion recorded last"); if (mode==Mode::fail) { check(static_cast(failure),"coroutine exception must reach completion"); try { std::rethrow_exception(failure); } catch (const std::runtime_error& error) { check(std::string{error.what()}=="injected preparation failure","expected injected exception"); } std::cout << "failure: exception observed; payload released\n"; } else { check(!failure,"unexpected coroutine exception"); check(result.label=="owned study","owned input survives suspension"); check(result.cancelled==(mode==Mode::cancel),"expected terminal outcome"); if (mode==Mode::cancel) { check(control->count==4 && control->events[1]==Event::other_handler && control->events[2]==Event::resumed,"wait -> other handler -> cancelled resume -> completion"); std::cout << "cancel: started -> other handler -> aborted wait -> completion; payload released\n"; } else std::cout << "success: expired wait -> completion; payload released\n"; } } } int main() { try { run_case(Mode::succeed); run_case(Mode::cancel); run_case(Mode::fail); std::cout << "3 bounded coroutine scenarios passed\n"; return 0; } catch (const std::exception& error) { std::cerr << "coroutine experiment: " << error.what() << '\n'; return 1; } }