Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What does boost::asio::spawn do?

I am unable to form a mental picture of how the control flow happens with spawn.

  1. When I call spawn(io_service, my_coroutine), does it add a new handler to the io_service queue that wraps a call to the my_coroutine?

  2. When inside the coroutine I call an async function passing it my yield_context, does it suspend the coroutine until the async operation completes?

    void my_coroutine(yield_context yield)
    {
      ...
      async_foo(params ..., yield);
      ...   // control comes here only once the async_foo operation completes
    }

What I don't understand is how we avoid waits. Say if my_coroutine serves a TCP connection, how are other instances of my_coroutine invoked while on particular instance is suspended, waiting for async_foo to complete?

like image 948
CppNoob Avatar asked May 31 '15 12:05

CppNoob


1 Answers

In short:

  1. When spawn() is invoked, Boost.Asio performs some setup work and then will use a strand to dispatch() an internal handler that creates a coroutine using the user provided function as an entry point. Under certain conditions, the internal handler can be will be invoked within the call to spawn(), and other times it will be posted to the io_service for deferred invocation.
  2. The coroutine is suspended until either the operation completes and the completion handler is invoked, the io_service is destroyed, or Boost.Asio detects that the coroutine has been suspended with no way to resume it, at which point Boost.Asio will destroy the coroutine.

As mentioned above, when spawn() is invoked, Boost.Asio performs some setup work and then will use a strand to dispatch() an internal handler that creates 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. The previously mentioned strand is owned by the coroutine is used to guarantee the yield occurs before resume. Lets consider a simple example demonstrating spawn():

#include <iostream>
#include <boost/asio.hpp>
#include <boost/asio/spawn.hpp>

boost::asio::io_service io_service;

void other_work()
{
  std::cout << "Other work" << std::endl;
}

void my_work(boost::asio::yield_context yield_context)
{
  // Add more work to the io_service.
  io_service.post(&other_work);

  // Wait on a timer within the coroutine.
  boost::asio::deadline_timer timer(io_service);
  timer.expires_from_now(boost::posix_time::seconds(1));
  std::cout << "Start wait" << std::endl;
  timer.async_wait(yield_context);
  std::cout << "Woke up" << std::endl;    
}

int main ()
{
  boost::asio::spawn(io_service, &my_work);
  io_service.run();
}

The above example outputs:

Start wait
Other work
Woke up

Here is an attempt to illustrate the execution of the example. Paths in | indicate the active stack, : indicates the suspended stack, and arrows are used to indicate transfer of control:

boost::asio::io_service io_service;
boost::asio::spawn(io_service, &my_work);
`-- dispatch a coroutine creator
    into the io_service.
io_service.run();
|-- invoke the coroutine creator
|   handler.
|   |-- create and jump into
|   |   into coroutine         ----> my_work()
:   :                                |-- post &other_work onto
:   :                                |   the io_service
:   :                                |-- create timer
:   :                                |-- set timer expiration
:   :                                |-- cout << "Start wait" << endl;
:   :                                |-- 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         :   :
|   &other_work and async_wait)      :   :
|-- invoke other_work()              :   :
|   `-- cout << "Other work"         :   :
|       << endl;                     :   :
|-- io_service still 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 << "Woke up." << endl;
:   :                                |-- exiting my_work block, timer is 
:   :                                |   destroyed.
|   `-- return                 <---- `-- coroutine done, yielding
`-- no outstanding work in 
    io_service, return.
like image 103
Tanner Sansbury Avatar answered Oct 03 '22 19:10

Tanner Sansbury