boost coroutine doesn't resume after an async_write operation

47 views Asked by At

I have a simple boost.coroutine that occasionally is suspended? and i don't know what happens with the coroutine. It's hard to reproduce but does happen. Now, from this answer (point.2) i know that a coroutine can reach a bad state ? so how do know about it ? (so i can spawn a new one)

Sample pseudo code -

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

boost::asio::io_service io_service;
socket* socket_;


void write_to_socket(boost::asio::yield_context yield_context)
{

  while(true){
      // async write to socket
      err_code_ ec;
      socket_.async_write_some(boost::asio::buffer(buffer), yc[ec]);
      std::cout << "Done, now sleep for 10 ms" << std::endl;    
      timer.expires_from_now(boost::chrono::milliseconds(10));
      timer.async_wait(yc[ec]);
  }
}

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

The output looks like below...

Done, now sleep for 10 ms
Done, now sleep for 10 ms
.
.
.
Done, now sleep for 10 ms
Done, now sleep for 10 ms
<the program is just stuck here, nothing is printed after this>

Trying to reproduce this issue but unable to as it happens randomly. How do know if the coroutine has been destroyed so i can spawn it again ? thanks.

1

There are 1 answers

0
sehe On

You are assuming a few magical facts ("coroutine has been destroyed", "the coroutine reaches a bad state"). There is no magic here (unless your program has Undefined Behaviour).

What the other answer said that if the coroutine is suspended without a way to resume (meaning its ref count goes to zero, presumably when it returns or exits with an exception) it will be cleaned up: this is your regular reference-counted resource management. It doesn't "spontaneously" happen.

What your example lacks is error handling. Indeed, adding that:

void write_to_socket(asio::yield_context yc) {
    err_code_ ec;
    while (!ec.failed()) {
        // async write to socket
        socket_.async_write_some(asio::buffer(buffer), yc[ec]);
        std::cout << "Done, now sleep for 10 ms (" << ec.message() << ")" << std::endl;
        timer.expires_from_now(10ms);
        timer.async_wait(yc[ec]);
    }
    std::cout << "Coro done" << std::endl;
}

int main() {
    socket_.connect({{}, 8989});
    spawn(io_service, &write_to_socket);
    io_service.run();
    std::cout << "Bye" << std::endl;
}

You can see that it "fails" when the connection is broken:

enter image description here

Why don't you see Coro done and Bye? That's because you overwrite ec in the timer wait. Fix it:

    if (!ec) {
        timer.expires_from_now(10ms);
        timer.async_wait(yc[ec]);
    }

Now it will do what you expect:

enter image description here

Always Simplify

In your case the entire problem seems to be the choice for manual handling of the error codes (which you then completely forgot). Why not use exceptions?

void write_to_socket(asio::yield_context yc) try {
    while (true) {
        socket_.async_write_some(asio::buffer(buffer), yc);
        std::cout << "Done, now sleep for 10 ms" << std::endl;
        timer.expires_from_now(10ms);
        timer.async_wait(yc);
    }
} catch (boost::system::system_error const& se) {
    std::cout << "Exit: " << se.code().message() << std::endl;
}

Here's the complete test program I've used:

Live On Coliru

#include <boost/asio.hpp>
#include <boost/asio/spawn.hpp>
#include <iostream>
namespace asio = boost::asio;
using namespace std::chrono_literals;
using asio::ip::tcp;

asio::io_service   io_service;
tcp::socket        socket_{io_service};
asio::steady_timer timer{io_service};
std::string const  buffer = "SOMECONTENTNOTHINGTOSEE";

void write_to_socket(asio::yield_context yc) try {
    while (true) {
        socket_.async_write_some(asio::buffer(buffer), yc);
        std::cout << "Done, now sleep for 10 ms" << std::endl;
        timer.expires_from_now(10ms);
        timer.async_wait(yc);
    }
} catch (boost::system::system_error const& se) {
    std::cout << "Exit: " << se.code().message() << std::endl;
}

int main() {
    socket_.connect({{}, 8989});
    spawn(io_service, &write_to_socket);
    io_service.run();
    std::cout << "Bye" << std::endl;
}

Tested with

g++ -std=c++20 -O2 -Wall -pedantic -pthread main.cpp -lboost_{coroutine,context,thread}
nc -lp 8989 -w 3 > /dev/null&
sleep 1; ./a.out&
sleep 3; kill %1

Prints

Done, now sleep for 10 ms
Done, now sleep for 10 ms
Done, now sleep for 10 ms
Done, now sleep for 10 ms
Done, now sleep for 10 ms
Done, now sleep for 10 ms
...
Done, now sleep for 10 ms
Done, now sleep for 10 ms
bash: line 11: 24711 Terminated              nc -lp 8989 -w 3 > /dev/null
Done, now sleep for 10 ms
Exit: Broken pipe
Bye