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

Original source

Related problems