Search code examples
c++boost-asiofutureboost-thread

boost::asio no handler called when using post(), works when function called directly (io_context has work)


I am trying to periodically trigger a request from an application to a server by using a timer. The called function waits for completion by utilizing boost::promise (In case it is called manually and a success state is required to be displayed). When starting up I call the function directly and it completes without problem. Then a timer calls it again periodically but when initiated via deadline_timer the promise never gets fulfilled.

When calling via .post() the connection to the server is opened but on the client side the handle_connect handler is never triggered. The io_context has work assigned.

I have already tried moving the promise to the ServiceRequest-class instead of passing a refernce and also implementing it as a class member to rule out lifetime problems.

I have reduced the whole problem to a minimum example of the failing code:

(Demos on Coliru: Working (via direct call), Failing (via post))

class ServiceRequest : public boost::enable_shared_from_this<ServiceRequest>
{
    public:

        ServiceRequest(boost::asio::io_service& io_service, Client& client, boost::promise<bool>& promise)
          : io_service_(io_service),
            socket_(io_service_),
            client_(client),
            promise_(promise)
        {}

        ~ServiceRequest()
        {}

        void Start()
        {
            socket_.async_connect(
                boost::asio::ip::tcp::endpoint(boost::asio::ip::address::from_string("127.0.0.1"), 3005),
                boost::bind(&ServiceRequest::handle_connect,
                            shared_from_this(),
                            boost::asio::placeholders::error
                )
            );
        }

    private: 

        void handle_connect(const boost::system::error_code& ec)
        {
            if(!ec)
            {
                promise_.set_value(true);

                boost::asio::async_write(socket_,
                                         boost::asio::buffer("Test"),                                                
                                         boost::bind(&ServiceRequest::close_socket, 
                                                     shared_from_this())
                                        );              

            }
            else
            {           
                promise_.set_value(false);
            }           
        }

        void close_socket()
        {
            socket_.shutdown(boost::asio::ip::tcp::socket::shutdown_both);              
            socket_.close();                                                        
        }

        boost::asio::io_service&        io_service_;
        boost::asio::ip::tcp::socket    socket_;
        Client&                         client_;
        boost::promise<bool>&           promise_;       

};

class RequestHandler
{

    public:

        RequestHandler(boost::asio::io_service& io_service, Client& client)
          : io_service_(io_service),
            client_(client)
        {}

        ~RequestHandler()
        {}

        bool RequestService()
        {

            boost::promise<bool> promise;
            boost::shared_ptr<ServiceRequest> service_request = boost::make_shared<ServiceRequest>(io_service_, client_, promise);
            service_request->Start();

            bool result = promise.get_future().get();

            return result;          
        }

    private:

        boost::asio::io_service&    io_service_;
        Client&                     client_;

};

class Client {

    public:

        Client()
          : io_service_(),
            work_(io_service_),
            thread_group_(),
            timer_(io_service_),
            request_handler_(io_service_, *this)
        {
            thread_group_.create_thread(boost::bind(&boost::asio::io_service::run, &io_service_));
        }

        ~Client()
        {
            io_service_.stop();
            thread_group_.join_all();
        }

        void RequestService()
        {
            io_service_.post(boost::bind(&RequestHandler::RequestService, &request_handler_));  // <<--- deadlocks at promise.get_future().get()
            request_handler_.RequestService(); // <<--- works
            timer_.expires_from_now(boost::posix_time::seconds(10));
            timer_.async_wait(boost::bind(&Client::RequestService, this)); // <<--- deadlocks at promise.get_future().get()
        }

    private:

        boost::asio::io_service         io_service_;
        boost::asio::io_service::work   work_;
        boost::thread_group             thread_group_;
        boost::asio::deadline_timer     timer_;
        RequestHandler                  request_handler_;

};

int main()
{
    Client client;
    client.RequestService();    
    return 0;
}

When calling request_handler_.RequestService() directly everything works as expected. The handler tracking of boost::asio shows as expected:

@asio|1559149650.446538|0*1|socket@00000000007b9d40.async_connect
@asio|1559149650.456538|>1|ec=system:0
@asio|1559149650.456538|1*2|socket@00000000007b9d40.async_send
@asio|1559149650.456538|<1|
@asio|1559149650.456538|>2|ec=system:0,bytes_transferred=5
@asio|1559149650.456538|2|socket@00000000007b9d40.close
@asio|1559149650.456538|<2|

When using .post() or a deadline timer to call RequestService() the handler tracker shows:

@asio|1559149477.071693|0*1|io_context@000000000022fd90.post
@asio|1559149477.071693|>1|
@asio|1559149477.071693|1*2|socket@00000000007b9e10.async_connect

So the connection is established but the handler is not triggered and therefore no promise.set_value(bool) is called and the whole thing locks up.

What am I doing wrong here?


Solution

  • You've only got one thread that's calling io_service::run.

    post() executes the function you give it from within the io_service on one of the io_service's threads. The function that you're trying to run within the io_service's main loop (RequestHandler::RequestService) is a blocking function that is waiting for promise to be fulfilled by the work that's supposed to execute on the io_service's thread. This will never complete because you've blocked the io_service's thread.

    This is one of the primary mistakes to you need to avoid when using ASIO or any async framework. Never block in the thread that is processing your events because you can introduce subtle (or not-so-subtle) deadlocks like this.