boost asio async_connect success after close

前端 未结 2 1924
天涯浪人
天涯浪人 2021-02-11 08:35

Single-threaded application.

It happens not every time, only after 1.5 hours of high load.

  1. tcp::socket::async_connect
  2. tcp::socket::close (by deadl
2条回答
  •  梦毁少年i
    2021-02-11 09:03

    As Igor mentions in the comments, the completion handler is already queued.

    This scenario is the result of a separation in time between when an operation executes and when a handler is invoked. The documentation for io_service::run(), io_service::run_one(), io_service::poll(), and io_service::poll_one() is specific to mention handlers, and not operations. In the scenario, the socket::async_connect() operation and deadline_timer::async_wait() operation complete in the same event loop iteration. This results in both handlers being added to the io_service for deferred invocation, in an unspecified order.

    Consider the following snippet that accentuates the scenario:

    void handle_wait(const boost::system::error_code& error)
    {
      if (error) return;
      socket_.close();
    }
    
    timer_.expires_from_now(boost::posix_time::seconds(30));
    timer_.async_wait(&handle_wait);
    socket_.async_connect(endpoint_, handle_connect);
    boost::this_thread::sleep(boost::posix_time::seconds(60));
    io_service_.run_one();
    

    When io_service_.run_one() is invoked, both socket::async_connect() and deadline_timer::async_wait() operations may have completed, causing handle_wait and handle_connect to be ready for invocation from within the io_service in an unspecified order. To properly handle this unspecified order, additional logic need to occur from within handle_wait() and handle_connect() to query the current state, and determine if the other handler has been invoked, rather than depending solely on the status (error_code) of the operation.

    The easiest way to determine if the other handler has invoked is:

    • In handle_connect(), check if the socket is still open via is_open(). If the socket is still open, then handle_timer() has not been invoked. A clean way to indicate to handle_timer() that handle_connect() has ran is to update the expiry time.
    • In handle_timer(), check if the expiry time has passed. If this is true, then handle_connect() has not ran, so close the socket.

    The resulting handlers could look like the following:

    void handle_wait(const boost::system::error_code& error)
    {
      // On error, return early.
      if (error) return;
    
      // If the timer expires in the future, then connect handler must have
      // first.
      if (timer_.expires_at() > deadline_timer::traits_type::now()) return;
    
      // Timeout has occurred, so close the socket.
      socket_.close();
    }
    
    void handle_connect(const boost::system::error_code& error)
    {
      // The async_connect() function automatically opens the socket at the start
      // of the asynchronous operation. If the socket is closed at this time then
      // the timeout handler must have run first.
      if (!socket_.is_open()) return;
    
      // On error, return early.
      if (error) return;
    
      // Otherwise, a connection has been established.  Update the timer state
      // so that the timeout handler does not close the socket.
      timer_.expires_at(boost::posix_time::pos_infin);
    }
    

    Boost.Asio provides some examples for handling timeouts.

提交回复
热议问题