Fix off by one in pending i/o count on HTTPClient

This commit is contained in:
Vinnie Falco
2013-09-11 21:38:30 -07:00
parent 057344e1af
commit 8c522aa758

View File

@@ -92,7 +92,6 @@ public:
boost::asio::io_service io_service; boost::asio::io_service io_service;
async_get (io_service, nullptr, url); async_get (io_service, nullptr, url);
io_service.run (); io_service.run ();
cancel ();
return result (); return result ();
} }
@@ -224,6 +223,7 @@ private:
, m_context (boost::asio::ssl::context::sslv23) , m_context (boost::asio::ssl::context::sslv23)
, m_buffer (bufferSize) , m_buffer (bufferSize)
, m_parser (HTTPParser::typeResponse) , m_parser (HTTPParser::typeResponse)
, m_timer_set (false)
, m_timer_canceled (false) , m_timer_canceled (false)
, m_timer_expired (false) , m_timer_expired (false)
, m_messageLimitBytes (messageLimitBytes) , m_messageLimitBytes (messageLimitBytes)
@@ -241,6 +241,7 @@ private:
m_timer.expires_from_now ( m_timer.expires_from_now (
boost::posix_time::milliseconds ( boost::posix_time::milliseconds (
long (timeoutSeconds * 1000))); long (timeoutSeconds * 1000)));
m_timer_set = true;
++m_io_pending; ++m_io_pending;
m_timer.async_wait (TimerHandler (this)); m_timer.async_wait (TimerHandler (this));
@@ -261,7 +262,11 @@ private:
// //
void cancel () void cancel ()
{ {
cancel_all (); cancel_timer ();
m_resolver.cancel ();
error_code ec;
m_socket.close (ec);
m_done.wait (); m_done.wait ();
} }
@@ -270,7 +275,7 @@ private:
// Counts a pending i/o as canceled // Counts a pending i/o as canceled
// //
void cancel_io () void io_canceled ()
{ {
bassert (m_io_pending.get () > 0); bassert (m_io_pending.get () > 0);
if (--m_io_pending == 0) if (--m_io_pending == 0)
@@ -280,23 +285,42 @@ private:
// Cancels the deadline timer. // Cancels the deadline timer.
// //
void cancel_timer () void cancel_timer ()
{
// Make sure the timer was set (versus infinite timeout)
if (m_timer_set)
{
// See if it was already canceled.
if (! m_timer_canceled)
{ {
m_timer_canceled = true; m_timer_canceled = true;
error_code ec; error_code ec;
m_timer.cancel (ec); m_timer.cancel (ec);
// At this point, there will either be a pending completion
// or a pending abort for the handler. If its a completion,
// they will see that the timer was canceled (since we're on
// a strand, everything is serialized). If its an abort it
// counts as a cancellation anyway. Either way, we will deduct
// one i/o from the pending i/o count.
}
}
} }
// Called to notify the original handler the operation is complete. // Called to notify the original handler the operation is complete.
// //
void complete (error_code const& ec) void complete (error_code const& ec)
{ {
// Set the error code in the result.
m_owner.m_result.error = ec; m_owner.m_result.error = ec;
// Cancel the deadline timer. This ensures that
// we will not return 'timeout' to the caller later.
//
cancel_timer (); cancel_timer ();
bassert (m_io_pending.get () > 0); bassert (m_io_pending.get () > 0);
cancel_io (); io_canceled ();
// We call the handler directly since we know // We call the handler directly since we know
// we are already in the right context, and // we are already in the right context, and
@@ -314,62 +338,66 @@ private:
if (m_timer_expired || if (m_timer_expired ||
ec == boost::asio::error::operation_aborted) ec == boost::asio::error::operation_aborted)
{ {
cancel_io (); // Timer expired, or the operation was aborted due to
// cancel, so we deduct one i/o and return immediately.
//
io_canceled ();
return true; return true;
} }
else if (ec != 0 && ec != boost::asio::error::eof)
if (ec != 0 && ec != boost::asio::error::eof)
{ {
// A real error happened, and the timer didn't expire, so
// notify the original handler that the operation is complete.
//
complete (ec); complete (ec);
return true; return true;
} }
return false; // Process the completion as usual. If the caller does not
} // call another initiating function, it is their responsibility
// to call io_canceled() to deduce one pending i/o.
// Cancels/closes all i/o objects.
// //
void cancel_all () return false;
{
cancel_timer ();
m_resolver.cancel ();
error_code ec;
m_socket.close (ec);
} }
// Called when the deadline timer expires or is canceled. // Called when the deadline timer expires or is canceled.
// //
void timerCompletion (error_code ec) void timerCompletion (error_code ec)
{ {
if (ec == boost::asio::error::operation_aborted) bassert (m_timer_set);
if (m_timer_canceled || ec == boost::asio::error::operation_aborted)
{ {
bassert (m_timer_canceled); // If the cancel flag is set or the operation was aborted it
return cancel_io (); // means we canceled the timer so deduct one i/o and return.
//
io_canceled ();
return;
} }
check_invariant (ec == 0); bassert (ec == 0);
// Handle the case where the timer completion has already // The timer expired, so this is a real timeout scenario.
// been queued for dispatch but we have finished the operation // We want to set the error code, notify the handler, and cancel
// and queued the completion handler for dispatch. // all other pending i/o.
// //
if (! m_timer_canceled)
{
m_timer_expired = true; m_timer_expired = true;
ec = error_code (boost::asio::error::timed_out, ec = error_code (boost::asio::error::timed_out,
boost::asio::error::get_system_category ()); boost::asio::error::get_system_category ());
complete (ec); // Cancel pending name resolution
io_complete (ec);
m_resolver.cancel (); m_resolver.cancel ();
// Close the socket. This will cancel up to 2 pending i/o
m_socket.close (ec); m_socket.close (ec);
}
else // Notify the original handler of a timeout error.
{ // The call to complete() consumes one pending i/o, which
cancel_io (); // we need since this function counts as one completion.
} //
complete (ec);
} }
//---------------------------------------------------------------------- //----------------------------------------------------------------------
@@ -594,7 +622,7 @@ private:
} }
// deduct one i/o since we aren't issuing any new one // deduct one i/o since we aren't issuing any new one
cancel_io (); io_canceled ();
} }
void read_complete (error_code ec, std::size_t bytes_transferred) void read_complete (error_code ec, std::size_t bytes_transferred)
@@ -671,6 +699,7 @@ private:
State m_state; State m_state;
HTTPParser m_parser; HTTPParser m_parser;
String m_get_string; String m_get_string;
bool m_timer_set;
bool m_timer_canceled; bool m_timer_canceled;
bool m_timer_expired; bool m_timer_expired;
std::size_t m_messageLimitBytes; std::size_t m_messageLimitBytes;