Can I use a stackful coroutine as the wait handler of a steady_timer which is defined inside the very stackful coroutine?
boost, boost-asio, c++
Solution
Yes, it is safe to pass `boost::asio::yield_context` to an object that has automatic storage duration within the same coroutine.
Boost.Coroutine uses Boost.Context to perform context switching. Boost.Context provides a means to suspend the current execution path, preserving the stack (including local variables such as `Work()`'s `timer`), and transfer execution control, allowing the same thread to run with a different stack. Hence, with the `boost::asio::steady_timer` object having automatic storage duration, its lifespan will end when either:
- control exits the block specified by `Work()` via either a `return`, reaching end of function, or an exception unwinding the stack.
- The associated `io_service` is destroyed. Internal handlers maintain shared ownership of the coroutine, and when the `io_service` is destroyed, all associated handlers are also destroyed. This destruction will cause Boost.Coroutine to force each coroutine's stack to unwind.
When `boost::asio::spawn()` is invoked, Boost.Asio performs some setup work and then will dispatch() an internal handler that will create a coroutine using the user provided function as an entry point. When the `yield_context` object is passed as a handler to asynchronous operations, Boost.Asio will yield immediately after initiating the asynchronous operation with a completion handler that will copy results and resume the coroutine. A `strand` owned by the coroutine is used to guarantee the yield occurs before resume. Here is an attempt to illustrate the execution of the example code:
boost::asio::io_service io_service;
boost::asio::spawn(io_service, &Work);
`-- dispatch a coroutine creator
into the io_service.
io_service.run();
|-- invoke the coroutine creator
| handler.
| |-- create and jump into
| | into coroutine ----> Work()
: : |-- timer created
: : |-- setting timer expiration
: : |-- timer.async_wait(yield)
: : | |-- create error_code on stack
: : | |-- initiate async_wait operation,
: : | | passing in completion handler that
: : | | will resume the coroutine
| `-- return <---- | |-- yield
|-- io_service has work (the : :
| async_wait operation) : :
| ...async wait completes... : :
|-- invoke completion handler : :
| |-- copies error_code : :
| | provided by service : :
| | into the one on the : :
| | coroutine stack : :
| |-- resume ----> | `-- return error code
: : |-- cout << "Waked up." << endl;
: : |-- exiting Work() block, timer is
: : | destroyed.
| `-- return <---- `-- coroutine done, yielding
`-- no outstanding work in
io_service, return.
Problem
Can I use stackful coroutine and `boost::asio::steady_timer::async_wait` in the following way? The point is that (my understanding, not sure) during waiting, local variable `timer` is not on the stack and thus inaccessible. So can the callback proceed normally? (FYI, it works fine on my Mac using clang++5.0 .) ``` boost::asio::io_service io; void Work(boost::asio::yield_context yield) { boost::asio::steady_timer timer(io); timer.expires_from_now(std::chrono::seconds(5)); timer.async_wait(yield); cout << "Woke up." << endl; } int main() { boost::asio::spawn(io, Work); io.run(); return 0; } ``` I think it is worth a comparison between this question: boost asio deadline_timer