问题
Single-threaded application.
It happens not every time, only after 1.5 hours of high load.
- tcp::socket::async_connect
- tcp::socket::close (by deadline_timer)
- async_connect_handler gives success error_code (one of a million times), but socket is closed by(2). 99.999% of time it gives errno=125 (ECANCELED).
Is it possible that socket implementation or boost asio somehow do this:
- async_connect
- async success posted to io_service
- close by timer
- async success handled by me, not affected by close
Right now solved by saving state in my variables, ignoring accept success.
Linux 2.6 (fedora). Boost 1.46.0
PS: ofcouse possible bug on my part... But runs smoothly for days if not this.
回答1:
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, thenhandle_timer()
has not been invoked. A clean way to indicate tohandle_timer()
thathandle_connect()
has ran is to update the expiry time. - In
handle_timer()
, check if the expiry time has passed. If this is true, thenhandle_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.
回答2:
I accept twsansbury's answer, just want to add some more info.
About shutdown():
void async_recv_handler( boost::system::error_code ec_recv, std::size_t count )
{
if ( !m_socket.is_open() )
return; // first time don't trust to ec_recv
if ( ec_recv )
{
// oops, we have error
// log
// close
return;
}
// seems that we are just fine, no error in ec_recv, we can gracefully shutdown the connection
// but shutdown may fail! this check is working for me
boost::system::error_code ec_shutdown;
// second time don't trusting to ec_recv
m_socket.shutdown( t, ec_shutdown );
if ( !ec_shutdown )
return;
// this error code is expected
if ( ec_shutdown == boost::asio::error::not_connected )
return;
// other error codes are unexpected for me
// log << ec_shutdown.message()
throw boost::system::system_error(ec_shutdown);
}
来源:https://stackoverflow.com/questions/14586695/boost-asio-async-connect-success-after-close