Fix deadlock that can occur on Windows when shutting down a pool of io_service threads due to running out of work.
diff --git a/asio/include/asio/detail/impl/win_iocp_io_service.ipp b/asio/include/asio/detail/impl/win_iocp_io_service.ipp index 2580ee2..8f5722c 100644 --- a/asio/include/asio/detail/impl/win_iocp_io_service.ipp +++ b/asio/include/asio/detail/impl/win_iocp_io_service.ipp
@@ -67,6 +67,7 @@ iocp_(), outstanding_work_(0), stopped_(0), + stop_event_posted_(0), shutdown_(0), dispatch_required_(0) { @@ -147,7 +148,7 @@ { if (::InterlockedExchangeAdd(&outstanding_work_, 0) == 0) { - InterlockedExchange(&stopped_, 1); + stop(); ec = asio::error_code(); return 0; } @@ -165,7 +166,7 @@ { if (::InterlockedExchangeAdd(&outstanding_work_, 0) == 0) { - InterlockedExchange(&stopped_, 1); + stop(); ec = asio::error_code(); return 0; } @@ -179,7 +180,7 @@ { if (::InterlockedExchangeAdd(&outstanding_work_, 0) == 0) { - InterlockedExchange(&stopped_, 1); + stop(); ec = asio::error_code(); return 0; } @@ -197,7 +198,7 @@ { if (::InterlockedExchangeAdd(&outstanding_work_, 0) == 0) { - InterlockedExchange(&stopped_, 1); + stop(); ec = asio::error_code(); return 0; } @@ -211,12 +212,15 @@ { if (::InterlockedExchange(&stopped_, 1) == 0) { - if (!::PostQueuedCompletionStatus(iocp_.handle, 0, 0, 0)) + if (::InterlockedExchange(&stop_event_posted_, 1) == 0) { - DWORD last_error = ::GetLastError(); - asio::error_code ec(last_error, - asio::error::get_system_category()); - asio::detail::throw_error(ec, "pqcs"); + if (!::PostQueuedCompletionStatus(iocp_.handle, 0, 0, 0)) + { + DWORD last_error = ::GetLastError(); + asio::error_code ec(last_error, + asio::error::get_system_category()); + asio::detail::throw_error(ec, "pqcs"); + } } } } @@ -420,17 +424,23 @@ } else { + // Indicate that there is no longer an in-flight stop event. + ::InterlockedExchange(&stop_event_posted_, 0); + // The stopped_ flag is always checked to ensure that any leftover - // interrupts from a previous run invocation are ignored. + // stop events from a previous run invocation are ignored. if (::InterlockedExchangeAdd(&stopped_, 0) != 0) { // Wake up next thread that is blocked on GetQueuedCompletionStatus. - if (!::PostQueuedCompletionStatus(iocp_.handle, 0, 0, 0)) + if (::InterlockedExchange(&stop_event_posted_, 1) == 0) { - last_error = ::GetLastError(); - ec = asio::error_code(last_error, - asio::error::get_system_category()); - return 0; + if (!::PostQueuedCompletionStatus(iocp_.handle, 0, 0, 0)) + { + last_error = ::GetLastError(); + ec = asio::error_code(last_error, + asio::error::get_system_category()); + return 0; + } } ec = asio::error_code();
diff --git a/asio/include/asio/detail/win_iocp_io_service.hpp b/asio/include/asio/detail/win_iocp_io_service.hpp index 20bf28f..181df84 100644 --- a/asio/include/asio/detail/win_iocp_io_service.hpp +++ b/asio/include/asio/detail/win_iocp_io_service.hpp
@@ -236,6 +236,11 @@ // Flag to indicate whether the event loop has been stopped. mutable long stopped_; + // Flag to indicate whether there is an in-flight stop event. Every event + // posted using PostQueuedCompletionStatus consumes non-paged pool, so to + // avoid exhausting this resouce we limit the number of outstanding events. + long stop_event_posted_; + // Flag to indicate whether the service has been shut down. long shutdown_;