0

I've recently been playing around with boost asio and some new c++11 constructs. Here is a sample section of code that causes unexpected behavior (for me at least).

void Server::startAccept()
{
    connections_.push_back(std::make_shared<Connection>(io_service_));
    acceptor_.async_accept(connections_.back()->socket(), std::bind(&Server::accept_handler, this, connections_.back(), std::placeholders::_1));
}

void Server::accept_handler(std::shared_ptr<Connection> con, const boost::system::error_code& ec)
{
    startAccept();

    if (!ec) {
        con->start();
    }
}

Before I make the call to Server::startAccept, I created an instance of io_service::work and a pool of std::thread which called io_service_.run(). After I make the call to startAccept, the main thread just waits for command line input.

I expect one of the threads in my thread pool to run Server::accept_handler on a connection initiation. This doesn't happen. Instead I have to call io_service_.run() from the main thread.

Now I played around for a while and found that I could achieve the desired behavior by doing this:

void Server::startAccept()
{
    connections_.push_back(std::make_shared<Connection>(io_service_));
    io_service_.post([this]() { acceptor_.async_accept(connections_.back()->socket(), std::bind(&Server::accept_handler, this, connections_.back(), std::placeholders::_1)); });
}

void Server::accept_handler(std::shared_ptr<Connection> con, const boost::system::error_code& ec)
{
    startAccept();

    if (!ec) {
        con->start();
    }
}

What is the difference between the .async_* operations and io_service.post?

EDIT: Defining BOOST_ASIO_ENABLE_HANDLER_TRACKING

When I compile and run my program and then connect to the server with the first block of code I included this is the output:

@asio|1350656555.431359|0*1|socket@00A2F710.async_accept

When I run the second block of code I included and connect to the server I get this output:

@asio|1350656734.789896|0*1|io_service@00ECEC78.post
@asio|1350656734.789896|>1|
@asio|1350656734.789896|1*2|socket@00D0FDE0.async_accept
@asio|1350656734.789896|<1|
@asio|1350656756.150051|>2|ec=system:0
@asio|1350656756.150051|2*3|io_service@00ECEC78.post
@asio|1350656756.150051|>3|
@asio|1350656756.150051|2*4|socket@00EE9090.async_send
@asio|1350656756.150051|3*5|socket@00D0FDE0.async_accept
@asio|1350656756.150051|2*6|socket@00EE9090.async_receive
@asio|1350656756.150051|<3|
@asio|1350656756.150051|>4|ec=system:0,bytes_transferred=54
@asio|1350656756.150051|<2|
@asio|1350656756.150051|<4|
@asio|1350656758.790803|>6|ec=system:10054,bytes_transferred=0
@asio|1350656758.790803|<6|

EDIT 2: Thread creation insight

for (int i = 0; i < NUM_WORKERS; i++) {
    thread_pool.push_back(std::shared_ptr<std::thread>(new std::thread([this]() { io_service_.run(); })));
}
flumpb
  • 1,707
  • 3
  • 16
  • 33
  • 1
    Note that it does not have to be the _main_ thread, it could be any thread. In fact it could be _several_ threads. – K-ballo Oct 19 '12 at 05:37
  • @K-ballo I have already called io_service.run from another thread that is not the main thread. Server::accept_handler will only execute if io_service.run is called from the main thread in the first block of code I provided. – flumpb Oct 19 '12 at 12:04
  • 1
    Initial code and description looks fine, and that type of behavior would normally result from the [work](http://www.boost.org/doc/libs/1_51_0/doc/html/boost_asio/reference/io_service__work.html) object going out of scope. However, it does not explain the latter behavior. Maybe enabling [BOOST_ASIO_ENABLE_HANDLER_TRACKING](http://www.boost.org/doc/libs/1_51_0/doc/html/boost_asio/overview/core/handler_tracking.html) would provide some insight. – Tanner Sansbury Oct 19 '12 at 14:01
  • @twsansbury I'll do that and then report back. Also the work object never is not out of scope. It's a member of Server – flumpb Oct 19 '12 at 14:04
  • post a complete example demonstrating the problem, snippets are not useful in this scenario. – Sam Miller Oct 19 '12 at 15:04
  • 1
    I tried to run similar example and it is working properly. I'm using boost 1.52.0 beta1 and Visual Studio 2012. See code here [http://pastebin.com/96FRDyLa](http://pastebin.com/96FRDyLa) – pogorskiy Oct 19 '12 at 19:53
  • Ah, this is working in boost 1.52.0. Sorry for the late response. – flumpb Nov 25 '12 at 02:14

2 Answers2

0

The io_service.run function is the actual event loop, and what it basically does is to call io_service.post in a loop.

Edit: From the io_service.post documentation:

Request the io_service to invoke the given handler and return immediately.

And

The io_service guarantees that the handler will only be called in a thread in which the run(), run_one(), poll() or poll_one() member functions is currently being invoked.

What you should do is either implement your own event loop, calling io_service.run_one, or call io_service.run to let Boost handle the event loop. And it doesn't matter which thread you run the event loop from, all event handlers will be invoked from the thread you run the event loop in.

Some programmer dude
  • 400,186
  • 35
  • 402
  • 621
  • But why must I run the second block of code I provided in order to have accept_handler execute by a thread that is NOT the main thread? – flumpb Oct 19 '12 at 12:06
  • @JoachimPileborg As I understand it, the problem is that async_accept does not execute in io_service::run thread in the first case – pogorskiy Oct 19 '12 at 13:04
  • @JoachimPileborg "And it doesn't matter which thread you run the event loop from, all event handlers will be invoked from the thread you run the event loop in." This is simply not true in my case. – flumpb Oct 19 '12 at 14:04
0

If you don't forgot to call io_service::run for each thread in pool and you used io_service::work to avoid exit from io_service::run loop, your code in the first case is absolutely correct.

Here is working example (I ignore the connection handling and correct shutdown of the program)

class Connection
{
public:
    Connection(boost::asio::io_service & io_serivce) : socket_(io_serivce) {}
    boost::asio::ip::tcp::socket & socket() { return socket_; }
private:
    boost::asio::ip::tcp::socket socket_;
};

class Server
{
public:
    Server(boost::asio::io_service & io_serivce, const std::string & addr, const std::string & port) : io_service_(io_serivce), acceptor_(io_service_) {
        boost::asio::ip::tcp::resolver resolver(io_service_);
        boost::asio::ip::tcp::resolver::query query(addr, port);
        boost::asio::ip::tcp::endpoint endpoint = *resolver.resolve(query);
        acceptor_.open(endpoint.protocol());
        acceptor_.set_option(boost::asio::ip::tcp::acceptor::reuse_address(true));
        acceptor_.bind(endpoint);
        acceptor_.listen();
        startAccept();
    }

    void startAccept(){
        connections_.push_back(boost::make_shared<Connection>(  boost::ref(io_service_) ));    
        acceptor_.async_accept(connections_.back()->socket(), boost::bind(&Server::accept_handler, this, connections_.back(), _1) );
    }

    void Server::accept_handler(boost::shared_ptr<Connection> con, const boost::system::error_code& ec)
    {
        std::cout << "start connection" << std::endl;
        startAccept();
    }

private:
    boost::asio::io_service & io_service_;
    boost::asio::ip::tcp::acceptor acceptor_; 
    std::vector< boost::shared_ptr<Connection> > connections_;
};

int main(int argc, char * argv[])
{
    // thread pool
    boost::thread_group threads_;   
    boost::asio::io_service io_service_;
    boost::asio::io_service::work work_(io_service_);

    const size_t kThreadsCount = 3;
    for (std::size_t i = 0; i < kThreadsCount; ++i) {
        threads_.create_thread(boost::bind(&boost::asio::io_service::run, &io_service_));
    }  

    Server s(io_service_, "127.0.0.1", "8089");

    char ch;
    std::cin >> ch;
    return 0;
}
pogorskiy
  • 4,705
  • 1
  • 22
  • 21