From 739cdbf78db0b5b96a0ae0b95a70aae0b45b43b2 Mon Sep 17 00:00:00 2001 From: chris_kohlhoff Date: Thu, 9 Aug 2007 11:42:21 +0000 Subject: [PATCH] Add a workaround for strange Windows behaviour where closing a socket while there is a pending asynchronous read results in a connection reset error at the peer. --- .../asio/detail/win_iocp_socket_service.hpp | 102 ++++++++++++------ 1 file changed, 72 insertions(+), 30 deletions(-) diff --git a/asio/include/asio/detail/win_iocp_socket_service.hpp b/asio/include/asio/detail/win_iocp_socket_service.hpp index d0078978..c2e0170c 100644 --- a/asio/include/asio/detail/win_iocp_socket_service.hpp +++ b/asio/include/asio/detail/win_iocp_socket_service.hpp @@ -138,7 +138,8 @@ public: { enable_connection_aborted = 1, // User wants connection_aborted errors. user_set_linger = 2, // The user set the linger option. - user_set_non_blocking = 4 // The user wants a non-blocking socket. + user_set_hard_close = 4, // The user set the linger option for hard close. + user_set_non_blocking = 8 // The user wants a non-blocking socket. }; // Flags indicating the current state of the socket. @@ -192,7 +193,7 @@ public: while (impl) { asio::error_code ignored_ec; - close(*impl, ignored_ec); + close_for_destruction(*impl); impl = impl->next_; } } @@ -217,34 +218,7 @@ public: // Destroy a socket implementation. void destroy(implementation_type& impl) { - if (impl.socket_ != invalid_socket) - { - // Check if the reactor was created, in which case we need to close the - // socket on the reactor as well to cancel any operations that might be - // running there. - reactor_type* reactor = static_cast( - interlocked_compare_exchange_pointer( - reinterpret_cast(&reactor_), 0, 0)); - if (reactor) - reactor->close_descriptor(impl.socket_); - - if (impl.flags_ & implementation_type::user_set_linger) - { - ::linger opt; - opt.l_onoff = 0; - opt.l_linger = 0; - asio::error_code ignored_ec; - socket_ops::setsockopt(impl.socket_, - SOL_SOCKET, SO_LINGER, &opt, sizeof(opt), ignored_ec); - } - - asio::error_code ignored_ec; - socket_ops::close(impl.socket_, ignored_ec); - impl.socket_ = invalid_socket; - impl.flags_ = 0; - impl.cancel_token_.reset(); - impl.safe_cancellation_thread_id_ = 0; - } + close_for_destruction(impl); // Remove implementation from linked list of all implementations. asio::detail::mutex::scoped_lock lock(mutex_); @@ -326,6 +300,18 @@ public: if (reactor) reactor->close_descriptor(impl.socket_); + // Work around a Windows bug where if the socket is closed while there is + // a pending asynchronous read operation, the peer receives the connection + // reset error. There is no need to perform this workaround if no + // asynchronous operations have been started or if the user has set the + // linger option for a hard close. + if (impl.safe_cancellation_thread_id_ != 0 + && !(impl.flags_ & implementation_type::user_set_hard_close)) + { + asio::error_code ignored_ec; + socket_ops::shutdown(impl.socket_, shutdown_send, ignored_ec); + } + if (socket_ops::close(impl.socket_, ec) == socket_error_retval) return ec; @@ -476,6 +462,12 @@ public: && option.name(impl.protocol_) == SO_LINGER) { impl.flags_ |= implementation_type::user_set_linger; + const ::linger* linger_option = + reinterpret_cast(option.data(impl.protocol_)); + if (linger_option->l_onoff != 0 && linger_option->l_linger == 0) + impl.flags_ |= implementation_type::user_set_hard_close; + else + impl.flags_ &= ~implementation_type::user_set_hard_close; } socket_ops::setsockopt(impl.socket_, @@ -1950,6 +1942,56 @@ public: } private: + // Helper function to close a socket when the associated object is being + // destroyed. + void close_for_destruction(implementation_type& impl) + { + if (is_open(impl)) + { + // Check if the reactor was created, in which case we need to close the + // socket on the reactor as well to cancel any operations that might be + // running there. + reactor_type* reactor = static_cast( + interlocked_compare_exchange_pointer( + reinterpret_cast(&reactor_), 0, 0)); + if (reactor) + reactor->close_descriptor(impl.socket_); + + // Work around a Windows bug where if the socket is closed while there is + // a pending asynchronous read operation, the peer receives the connection + // reset error. There is no need to perform this workaround if no + // asynchronous operations have been started or if the user has set the + // linger option for a hard close. + if (impl.safe_cancellation_thread_id_ != 0 + && !(impl.flags_ & implementation_type::user_set_hard_close)) + { + asio::error_code ignored_ec; + socket_ops::shutdown(impl.socket_, shutdown_send, ignored_ec); + } + + // The socket destructor must not block. If the user has changed the + // linger option to block in the foreground, we will change it back to the + // default so that the closure is performed in the background. + if ((impl.flags_ & implementation_type::user_set_linger) + && !(impl.flags_ & implementation_type::user_set_hard_close)) + { + ::linger opt; + opt.l_onoff = 0; + opt.l_linger = 0; + asio::error_code ignored_ec; + socket_ops::setsockopt(impl.socket_, + SOL_SOCKET, SO_LINGER, &opt, sizeof(opt), ignored_ec); + } + + asio::error_code ignored_ec; + socket_ops::close(impl.socket_, ignored_ec); + impl.socket_ = invalid_socket; + impl.flags_ = 0; + impl.cancel_token_.reset(); + impl.safe_cancellation_thread_id_ = 0; + } + } + // Helper function to emulate InterlockedCompareExchangePointer functionality // for: // - very old Platform SDKs; and