// | |
// detail/impl/win_iocp_socket_service_base.ipp | |
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
// | |
// Copyright (c) 2003-2011 Christopher M. Kohlhoff (chris at kohlhoff dot com) | |
// | |
// Distributed under the Boost Software License, Version 1.0. (See accompanying | |
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) | |
// | |
#ifndef BOOST_ASIO_DETAIL_IMPL_WIN_IOCP_SOCKET_SERVICE_BASE_IPP | |
#define BOOST_ASIO_DETAIL_IMPL_WIN_IOCP_SOCKET_SERVICE_BASE_IPP | |
#if defined(_MSC_VER) && (_MSC_VER >= 1200) | |
# pragma once | |
#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) | |
#include <boost/asio/detail/config.hpp> | |
#if defined(BOOST_ASIO_HAS_IOCP) | |
#include <boost/asio/detail/win_iocp_socket_service_base.hpp> | |
#include <boost/asio/detail/push_options.hpp> | |
namespace boost { | |
namespace asio { | |
namespace detail { | |
win_iocp_socket_service_base::win_iocp_socket_service_base( | |
boost::asio::io_service& io_service) | |
: io_service_(io_service), | |
iocp_service_(use_service<win_iocp_io_service>(io_service)), | |
reactor_(0), | |
mutex_(), | |
impl_list_(0) | |
{ | |
} | |
void win_iocp_socket_service_base::shutdown_service() | |
{ | |
// Close all implementations, causing all operations to complete. | |
boost::asio::detail::mutex::scoped_lock lock(mutex_); | |
base_implementation_type* impl = impl_list_; | |
while (impl) | |
{ | |
boost::system::error_code ignored_ec; | |
close_for_destruction(*impl); | |
impl = impl->next_; | |
} | |
} | |
void win_iocp_socket_service_base::construct( | |
win_iocp_socket_service_base::base_implementation_type& impl) | |
{ | |
impl.socket_ = invalid_socket; | |
impl.state_ = 0; | |
impl.cancel_token_.reset(); | |
#if defined(BOOST_ASIO_ENABLE_CANCELIO) | |
impl.safe_cancellation_thread_id_ = 0; | |
#endif // defined(BOOST_ASIO_ENABLE_CANCELIO) | |
// Insert implementation into linked list of all implementations. | |
boost::asio::detail::mutex::scoped_lock lock(mutex_); | |
impl.next_ = impl_list_; | |
impl.prev_ = 0; | |
if (impl_list_) | |
impl_list_->prev_ = &impl; | |
impl_list_ = &impl; | |
} | |
void win_iocp_socket_service_base::destroy( | |
win_iocp_socket_service_base::base_implementation_type& impl) | |
{ | |
close_for_destruction(impl); | |
// Remove implementation from linked list of all implementations. | |
boost::asio::detail::mutex::scoped_lock lock(mutex_); | |
if (impl_list_ == &impl) | |
impl_list_ = impl.next_; | |
if (impl.prev_) | |
impl.prev_->next_ = impl.next_; | |
if (impl.next_) | |
impl.next_->prev_= impl.prev_; | |
impl.next_ = 0; | |
impl.prev_ = 0; | |
} | |
boost::system::error_code win_iocp_socket_service_base::close( | |
win_iocp_socket_service_base::base_implementation_type& impl, | |
boost::system::error_code& ec) | |
{ | |
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* r = static_cast<reactor*>( | |
interlocked_compare_exchange_pointer( | |
reinterpret_cast<void**>(&reactor_), 0, 0)); | |
if (r) | |
r->close_descriptor(impl.socket_, impl.reactor_data_); | |
} | |
if (socket_ops::close(impl.socket_, impl.state_, false, ec) == 0) | |
{ | |
impl.socket_ = invalid_socket; | |
impl.state_ = 0; | |
impl.cancel_token_.reset(); | |
#if defined(BOOST_ASIO_ENABLE_CANCELIO) | |
impl.safe_cancellation_thread_id_ = 0; | |
#endif // defined(BOOST_ASIO_ENABLE_CANCELIO) | |
} | |
return ec; | |
} | |
boost::system::error_code win_iocp_socket_service_base::cancel( | |
win_iocp_socket_service_base::base_implementation_type& impl, | |
boost::system::error_code& ec) | |
{ | |
if (!is_open(impl)) | |
{ | |
ec = boost::asio::error::bad_descriptor; | |
return ec; | |
} | |
else if (FARPROC cancel_io_ex_ptr = ::GetProcAddress( | |
::GetModuleHandleA("KERNEL32"), "CancelIoEx")) | |
{ | |
// The version of Windows supports cancellation from any thread. | |
typedef BOOL (WINAPI* cancel_io_ex_t)(HANDLE, LPOVERLAPPED); | |
cancel_io_ex_t cancel_io_ex = (cancel_io_ex_t)cancel_io_ex_ptr; | |
socket_type sock = impl.socket_; | |
HANDLE sock_as_handle = reinterpret_cast<HANDLE>(sock); | |
if (!cancel_io_ex(sock_as_handle, 0)) | |
{ | |
DWORD last_error = ::GetLastError(); | |
if (last_error == ERROR_NOT_FOUND) | |
{ | |
// ERROR_NOT_FOUND means that there were no operations to be | |
// cancelled. We swallow this error to match the behaviour on other | |
// platforms. | |
ec = boost::system::error_code(); | |
} | |
else | |
{ | |
ec = boost::system::error_code(last_error, | |
boost::asio::error::get_system_category()); | |
} | |
} | |
else | |
{ | |
ec = boost::system::error_code(); | |
} | |
} | |
#if defined(BOOST_ASIO_ENABLE_CANCELIO) | |
else if (impl.safe_cancellation_thread_id_ == 0) | |
{ | |
// No operations have been started, so there's nothing to cancel. | |
ec = boost::system::error_code(); | |
} | |
else if (impl.safe_cancellation_thread_id_ == ::GetCurrentThreadId()) | |
{ | |
// Asynchronous operations have been started from the current thread only, | |
// so it is safe to try to cancel them using CancelIo. | |
socket_type sock = impl.socket_; | |
HANDLE sock_as_handle = reinterpret_cast<HANDLE>(sock); | |
if (!::CancelIo(sock_as_handle)) | |
{ | |
DWORD last_error = ::GetLastError(); | |
ec = boost::system::error_code(last_error, | |
boost::asio::error::get_system_category()); | |
} | |
else | |
{ | |
ec = boost::system::error_code(); | |
} | |
} | |
else | |
{ | |
// Asynchronous operations have been started from more than one thread, | |
// so cancellation is not safe. | |
ec = boost::asio::error::operation_not_supported; | |
} | |
#else // defined(BOOST_ASIO_ENABLE_CANCELIO) | |
else | |
{ | |
// Cancellation is not supported as CancelIo may not be used. | |
ec = boost::asio::error::operation_not_supported; | |
} | |
#endif // defined(BOOST_ASIO_ENABLE_CANCELIO) | |
// Cancel any operations started via the reactor. | |
if (!ec) | |
{ | |
reactor* r = static_cast<reactor*>( | |
interlocked_compare_exchange_pointer( | |
reinterpret_cast<void**>(&reactor_), 0, 0)); | |
if (r) | |
r->cancel_ops(impl.socket_, impl.reactor_data_); | |
} | |
return ec; | |
} | |
boost::system::error_code win_iocp_socket_service_base::do_open( | |
win_iocp_socket_service_base::base_implementation_type& impl, | |
int family, int type, int protocol, boost::system::error_code& ec) | |
{ | |
if (is_open(impl)) | |
{ | |
ec = boost::asio::error::already_open; | |
return ec; | |
} | |
socket_holder sock(socket_ops::socket(family, type, protocol, ec)); | |
if (sock.get() == invalid_socket) | |
return ec; | |
HANDLE sock_as_handle = reinterpret_cast<HANDLE>(sock.get()); | |
if (iocp_service_.register_handle(sock_as_handle, ec)) | |
return ec; | |
impl.socket_ = sock.release(); | |
switch (type) | |
{ | |
case SOCK_STREAM: impl.state_ = socket_ops::stream_oriented; break; | |
case SOCK_DGRAM: impl.state_ = socket_ops::datagram_oriented; break; | |
default: impl.state_ = 0; break; | |
} | |
impl.cancel_token_.reset(static_cast<void*>(0), socket_ops::noop_deleter()); | |
ec = boost::system::error_code(); | |
return ec; | |
} | |
boost::system::error_code win_iocp_socket_service_base::do_assign( | |
win_iocp_socket_service_base::base_implementation_type& impl, | |
int type, socket_type native_socket, boost::system::error_code& ec) | |
{ | |
if (is_open(impl)) | |
{ | |
ec = boost::asio::error::already_open; | |
return ec; | |
} | |
HANDLE sock_as_handle = reinterpret_cast<HANDLE>(native_socket); | |
if (iocp_service_.register_handle(sock_as_handle, ec)) | |
return ec; | |
impl.socket_ = native_socket; | |
switch (type) | |
{ | |
case SOCK_STREAM: impl.state_ = socket_ops::stream_oriented; break; | |
case SOCK_DGRAM: impl.state_ = socket_ops::datagram_oriented; break; | |
default: impl.state_ = 0; break; | |
} | |
impl.cancel_token_.reset(static_cast<void*>(0), socket_ops::noop_deleter()); | |
ec = boost::system::error_code(); | |
return ec; | |
} | |
void win_iocp_socket_service_base::start_send_op( | |
win_iocp_socket_service_base::base_implementation_type& impl, | |
WSABUF* buffers, std::size_t buffer_count, | |
socket_base::message_flags flags, bool noop, operation* op) | |
{ | |
update_cancellation_thread_id(impl); | |
iocp_service_.work_started(); | |
if (noop) | |
iocp_service_.on_completion(op); | |
else if (!is_open(impl)) | |
iocp_service_.on_completion(op, boost::asio::error::bad_descriptor); | |
else | |
{ | |
DWORD bytes_transferred = 0; | |
int result = ::WSASend(impl.socket_, buffers, | |
static_cast<DWORD>(buffer_count), &bytes_transferred, flags, op, 0); | |
DWORD last_error = ::WSAGetLastError(); | |
if (last_error == ERROR_PORT_UNREACHABLE) | |
last_error = WSAECONNREFUSED; | |
if (result != 0 && last_error != WSA_IO_PENDING) | |
iocp_service_.on_completion(op, last_error, bytes_transferred); | |
else | |
iocp_service_.on_pending(op); | |
} | |
} | |
void win_iocp_socket_service_base::start_send_to_op( | |
win_iocp_socket_service_base::base_implementation_type& impl, | |
WSABUF* buffers, std::size_t buffer_count, | |
const socket_addr_type* addr, int addrlen, | |
socket_base::message_flags flags, operation* op) | |
{ | |
update_cancellation_thread_id(impl); | |
iocp_service_.work_started(); | |
if (!is_open(impl)) | |
iocp_service_.on_completion(op, boost::asio::error::bad_descriptor); | |
else | |
{ | |
DWORD bytes_transferred = 0; | |
int result = ::WSASendTo(impl.socket_, buffers, | |
static_cast<DWORD>(buffer_count), | |
&bytes_transferred, flags, addr, addrlen, op, 0); | |
DWORD last_error = ::WSAGetLastError(); | |
if (last_error == ERROR_PORT_UNREACHABLE) | |
last_error = WSAECONNREFUSED; | |
if (result != 0 && last_error != WSA_IO_PENDING) | |
iocp_service_.on_completion(op, last_error, bytes_transferred); | |
else | |
iocp_service_.on_pending(op); | |
} | |
} | |
void win_iocp_socket_service_base::start_receive_op( | |
win_iocp_socket_service_base::base_implementation_type& impl, | |
WSABUF* buffers, std::size_t buffer_count, | |
socket_base::message_flags flags, bool noop, operation* op) | |
{ | |
update_cancellation_thread_id(impl); | |
iocp_service_.work_started(); | |
if (noop) | |
iocp_service_.on_completion(op); | |
else if (!is_open(impl)) | |
iocp_service_.on_completion(op, boost::asio::error::bad_descriptor); | |
else | |
{ | |
DWORD bytes_transferred = 0; | |
DWORD recv_flags = flags; | |
int result = ::WSARecv(impl.socket_, buffers, | |
static_cast<DWORD>(buffer_count), | |
&bytes_transferred, &recv_flags, op, 0); | |
DWORD last_error = ::WSAGetLastError(); | |
if (last_error == ERROR_NETNAME_DELETED) | |
last_error = WSAECONNRESET; | |
else if (last_error == ERROR_PORT_UNREACHABLE) | |
last_error = WSAECONNREFUSED; | |
if (result != 0 && last_error != WSA_IO_PENDING) | |
iocp_service_.on_completion(op, last_error, bytes_transferred); | |
else | |
iocp_service_.on_pending(op); | |
} | |
} | |
void win_iocp_socket_service_base::start_null_buffers_receive_op( | |
win_iocp_socket_service_base::base_implementation_type& impl, | |
socket_base::message_flags flags, reactor_op* op) | |
{ | |
if ((impl.state_ & socket_ops::stream_oriented) != 0) | |
{ | |
// For stream sockets on Windows, we may issue a 0-byte overlapped | |
// WSARecv to wait until there is data available on the socket. | |
::WSABUF buf = { 0, 0 }; | |
start_receive_op(impl, &buf, 1, flags, false, op); | |
} | |
else | |
{ | |
start_reactor_op(impl, | |
(flags & socket_base::message_out_of_band) | |
? reactor::except_op : reactor::read_op, | |
op); | |
} | |
} | |
void win_iocp_socket_service_base::start_receive_from_op( | |
win_iocp_socket_service_base::base_implementation_type& impl, | |
WSABUF* buffers, std::size_t buffer_count, socket_addr_type* addr, | |
socket_base::message_flags flags, int* addrlen, operation* op) | |
{ | |
update_cancellation_thread_id(impl); | |
iocp_service_.work_started(); | |
if (!is_open(impl)) | |
iocp_service_.on_completion(op, boost::asio::error::bad_descriptor); | |
else | |
{ | |
DWORD bytes_transferred = 0; | |
DWORD recv_flags = flags; | |
int result = ::WSARecvFrom(impl.socket_, buffers, | |
static_cast<DWORD>(buffer_count), | |
&bytes_transferred, &recv_flags, addr, addrlen, op, 0); | |
DWORD last_error = ::WSAGetLastError(); | |
if (last_error == ERROR_PORT_UNREACHABLE) | |
last_error = WSAECONNREFUSED; | |
if (result != 0 && last_error != WSA_IO_PENDING) | |
iocp_service_.on_completion(op, last_error, bytes_transferred); | |
else | |
iocp_service_.on_pending(op); | |
} | |
} | |
void win_iocp_socket_service_base::start_accept_op( | |
win_iocp_socket_service_base::base_implementation_type& impl, | |
bool peer_is_open, socket_holder& new_socket, int family, int type, | |
int protocol, void* output_buffer, DWORD address_length, operation* op) | |
{ | |
update_cancellation_thread_id(impl); | |
iocp_service_.work_started(); | |
if (!is_open(impl)) | |
iocp_service_.on_completion(op, boost::asio::error::bad_descriptor); | |
else if (peer_is_open) | |
iocp_service_.on_completion(op, boost::asio::error::already_open); | |
else | |
{ | |
boost::system::error_code ec; | |
new_socket.reset(socket_ops::socket(family, type, protocol, ec)); | |
if (new_socket.get() == invalid_socket) | |
iocp_service_.on_completion(op, ec); | |
else | |
{ | |
DWORD bytes_read = 0; | |
BOOL result = ::AcceptEx(impl.socket_, new_socket.get(), output_buffer, | |
0, address_length, address_length, &bytes_read, op); | |
DWORD last_error = ::WSAGetLastError(); | |
if (!result && last_error != WSA_IO_PENDING) | |
iocp_service_.on_completion(op, last_error); | |
else | |
iocp_service_.on_pending(op); | |
} | |
} | |
} | |
void win_iocp_socket_service_base::restart_accept_op( | |
socket_type s, socket_holder& new_socket, int family, int type, | |
int protocol, void* output_buffer, DWORD address_length, operation* op) | |
{ | |
new_socket.reset(); | |
iocp_service_.work_started(); | |
boost::system::error_code ec; | |
new_socket.reset(socket_ops::socket(family, type, protocol, ec)); | |
if (new_socket.get() == invalid_socket) | |
iocp_service_.on_completion(op, ec); | |
else | |
{ | |
DWORD bytes_read = 0; | |
BOOL result = ::AcceptEx(s, new_socket.get(), output_buffer, | |
0, address_length, address_length, &bytes_read, op); | |
DWORD last_error = ::WSAGetLastError(); | |
if (!result && last_error != WSA_IO_PENDING) | |
iocp_service_.on_completion(op, last_error); | |
else | |
iocp_service_.on_pending(op); | |
} | |
} | |
void win_iocp_socket_service_base::start_reactor_op( | |
win_iocp_socket_service_base::base_implementation_type& impl, | |
int op_type, reactor_op* op) | |
{ | |
reactor& r = get_reactor(); | |
update_cancellation_thread_id(impl); | |
if (is_open(impl)) | |
{ | |
r.start_op(op_type, impl.socket_, impl.reactor_data_, op, false); | |
return; | |
} | |
else | |
op->ec_ = boost::asio::error::bad_descriptor; | |
iocp_service_.post_immediate_completion(op); | |
} | |
void win_iocp_socket_service_base::start_connect_op( | |
win_iocp_socket_service_base::base_implementation_type& impl, | |
reactor_op* op, const socket_addr_type* addr, std::size_t addrlen) | |
{ | |
reactor& r = get_reactor(); | |
update_cancellation_thread_id(impl); | |
if ((impl.state_ & socket_ops::non_blocking) != 0 | |
|| socket_ops::set_internal_non_blocking( | |
impl.socket_, impl.state_, op->ec_)) | |
{ | |
if (socket_ops::connect(impl.socket_, addr, addrlen, op->ec_) != 0) | |
{ | |
if (op->ec_ == boost::asio::error::in_progress | |
|| op->ec_ == boost::asio::error::would_block) | |
{ | |
op->ec_ = boost::system::error_code(); | |
r.start_op(reactor::connect_op, impl.socket_, | |
impl.reactor_data_, op, false); | |
return; | |
} | |
} | |
} | |
r.post_immediate_completion(op); | |
} | |
void win_iocp_socket_service_base::close_for_destruction( | |
win_iocp_socket_service_base::base_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* r = static_cast<reactor*>( | |
interlocked_compare_exchange_pointer( | |
reinterpret_cast<void**>(&reactor_), 0, 0)); | |
if (r) | |
r->close_descriptor(impl.socket_, impl.reactor_data_); | |
} | |
boost::system::error_code ignored_ec; | |
socket_ops::close(impl.socket_, impl.state_, true, ignored_ec); | |
impl.socket_ = invalid_socket; | |
impl.state_ = 0; | |
impl.cancel_token_.reset(); | |
#if defined(BOOST_ASIO_ENABLE_CANCELIO) | |
impl.safe_cancellation_thread_id_ = 0; | |
#endif // defined(BOOST_ASIO_ENABLE_CANCELIO) | |
} | |
void win_iocp_socket_service_base::update_cancellation_thread_id( | |
win_iocp_socket_service_base::base_implementation_type& impl) | |
{ | |
#if defined(BOOST_ASIO_ENABLE_CANCELIO) | |
if (impl.safe_cancellation_thread_id_ == 0) | |
impl.safe_cancellation_thread_id_ = ::GetCurrentThreadId(); | |
else if (impl.safe_cancellation_thread_id_ != ::GetCurrentThreadId()) | |
impl.safe_cancellation_thread_id_ = ~DWORD(0); | |
#else // defined(BOOST_ASIO_ENABLE_CANCELIO) | |
(void)impl; | |
#endif // defined(BOOST_ASIO_ENABLE_CANCELIO) | |
} | |
reactor& win_iocp_socket_service_base::get_reactor() | |
{ | |
reactor* r = static_cast<reactor*>( | |
interlocked_compare_exchange_pointer( | |
reinterpret_cast<void**>(&reactor_), 0, 0)); | |
if (!r) | |
{ | |
r = &(use_service<reactor>(io_service_)); | |
interlocked_exchange_pointer(reinterpret_cast<void**>(&reactor_), r); | |
} | |
return *r; | |
} | |
void* win_iocp_socket_service_base::interlocked_compare_exchange_pointer( | |
void** dest, void* exch, void* cmp) | |
{ | |
#if defined(_M_IX86) | |
return reinterpret_cast<void*>(InterlockedCompareExchange( | |
reinterpret_cast<PLONG>(dest), reinterpret_cast<LONG>(exch), | |
reinterpret_cast<LONG>(cmp))); | |
#else | |
return InterlockedCompareExchangePointer(dest, exch, cmp); | |
#endif | |
} | |
void* win_iocp_socket_service_base::interlocked_exchange_pointer( | |
void** dest, void* val) | |
{ | |
#if defined(_M_IX86) | |
return reinterpret_cast<void*>(InterlockedExchange( | |
reinterpret_cast<PLONG>(dest), reinterpret_cast<LONG>(val))); | |
#else | |
return InterlockedExchangePointer(dest, val); | |
#endif | |
} | |
} // namespace detail | |
} // namespace asio | |
} // namespace boost | |
#include <boost/asio/detail/pop_options.hpp> | |
#endif // defined(BOOST_ASIO_HAS_IOCP) | |
#endif // BOOST_ASIO_DETAIL_IMPL_WIN_IOCP_SOCKET_SERVICE_BASE_IPP |