1//
2// detail/impl/win_iocp_handle_service.ipp
3// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4//
5// Copyright (c) 2003-2021 Christopher M. Kohlhoff (chris at kohlhoff dot com)
6// Copyright (c) 2008 Rep Invariant Systems, Inc. ([email protected])
7//
8// Distributed under the Boost Software License, Version 1.0. (See accompanying
9// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
10//
11
12#ifndef BOOST_ASIO_DETAIL_IMPL_WIN_IOCP_HANDLE_SERVICE_IPP
13#define BOOST_ASIO_DETAIL_IMPL_WIN_IOCP_HANDLE_SERVICE_IPP
14
15#if defined(_MSC_VER) && (_MSC_VER >= 1200)
16# pragma once
17#endif // defined(_MSC_VER) && (_MSC_VER >= 1200)
18
19#include <boost/asio/detail/config.hpp>
20
21#if defined(BOOST_ASIO_HAS_IOCP)
22
23#include <boost/asio/detail/win_iocp_handle_service.hpp>
24
25#include <boost/asio/detail/push_options.hpp>
26
27namespace boost {
28namespace asio {
29namespace detail {
30
31class win_iocp_handle_service::overlapped_wrapper
32  : public OVERLAPPED
33{
34public:
35  explicit overlapped_wrapper(boost::system::error_code& ec)
36  {
37    Internal = 0;
38    InternalHigh = 0;
39    Offset = 0;
40    OffsetHigh = 0;
41
42    // Create a non-signalled manual-reset event, for GetOverlappedResult.
43    hEvent = ::CreateEventW(0, TRUE, FALSE, 0);
44    if (hEvent)
45    {
46      // As documented in GetQueuedCompletionStatus, setting the low order
47      // bit of this event prevents our synchronous writes from being treated
48      // as completion port events.
49      DWORD_PTR tmp = reinterpret_cast<DWORD_PTR>(hEvent);
50      hEvent = reinterpret_cast<HANDLE>(tmp | 1);
51    }
52    else
53    {
54      DWORD last_error = ::GetLastError();
55      ec = boost::system::error_code(last_error,
56          boost::asio::error::get_system_category());
57    }
58  }
59
60  ~overlapped_wrapper()
61  {
62    if (hEvent)
63    {
64      ::CloseHandle(hEvent);
65    }
66  }
67};
68
69win_iocp_handle_service::win_iocp_handle_service(execution_context& context)
70  : execution_context_service_base<win_iocp_handle_service>(context),
71    iocp_service_(boost::asio::use_service<win_iocp_io_context>(context)),
72    mutex_(),
73    impl_list_(0)
74{
75}
76
77void win_iocp_handle_service::shutdown()
78{
79  // Close all implementations, causing all operations to complete.
80  boost::asio::detail::mutex::scoped_lock lock(mutex_);
81  implementation_type* impl = impl_list_;
82  while (impl)
83  {
84    close_for_destruction(*impl);
85    impl = impl->next_;
86  }
87}
88
89void win_iocp_handle_service::construct(
90    win_iocp_handle_service::implementation_type& impl)
91{
92  impl.handle_ = INVALID_HANDLE_VALUE;
93  impl.safe_cancellation_thread_id_ = 0;
94
95  // Insert implementation into linked list of all implementations.
96  boost::asio::detail::mutex::scoped_lock lock(mutex_);
97  impl.next_ = impl_list_;
98  impl.prev_ = 0;
99  if (impl_list_)
100    impl_list_->prev_ = &impl;
101  impl_list_ = &impl;
102}
103
104void win_iocp_handle_service::move_construct(
105    win_iocp_handle_service::implementation_type& impl,
106    win_iocp_handle_service::implementation_type& other_impl)
107{
108  impl.handle_ = other_impl.handle_;
109  other_impl.handle_ = INVALID_HANDLE_VALUE;
110
111  impl.safe_cancellation_thread_id_ = other_impl.safe_cancellation_thread_id_;
112  other_impl.safe_cancellation_thread_id_ = 0;
113
114  // Insert implementation into linked list of all implementations.
115  boost::asio::detail::mutex::scoped_lock lock(mutex_);
116  impl.next_ = impl_list_;
117  impl.prev_ = 0;
118  if (impl_list_)
119    impl_list_->prev_ = &impl;
120  impl_list_ = &impl;
121}
122
123void win_iocp_handle_service::move_assign(
124    win_iocp_handle_service::implementation_type& impl,
125    win_iocp_handle_service& other_service,
126    win_iocp_handle_service::implementation_type& other_impl)
127{
128  close_for_destruction(impl);
129
130  if (this != &other_service)
131  {
132    // Remove implementation from linked list of all implementations.
133    boost::asio::detail::mutex::scoped_lock lock(mutex_);
134    if (impl_list_ == &impl)
135      impl_list_ = impl.next_;
136    if (impl.prev_)
137      impl.prev_->next_ = impl.next_;
138    if (impl.next_)
139      impl.next_->prev_= impl.prev_;
140    impl.next_ = 0;
141    impl.prev_ = 0;
142  }
143
144  impl.handle_ = other_impl.handle_;
145  other_impl.handle_ = INVALID_HANDLE_VALUE;
146
147  impl.safe_cancellation_thread_id_ = other_impl.safe_cancellation_thread_id_;
148  other_impl.safe_cancellation_thread_id_ = 0;
149
150  if (this != &other_service)
151  {
152    // Insert implementation into linked list of all implementations.
153    boost::asio::detail::mutex::scoped_lock lock(other_service.mutex_);
154    impl.next_ = other_service.impl_list_;
155    impl.prev_ = 0;
156    if (other_service.impl_list_)
157      other_service.impl_list_->prev_ = &impl;
158    other_service.impl_list_ = &impl;
159  }
160}
161
162void win_iocp_handle_service::destroy(
163    win_iocp_handle_service::implementation_type& impl)
164{
165  close_for_destruction(impl);
166
167  // Remove implementation from linked list of all implementations.
168  boost::asio::detail::mutex::scoped_lock lock(mutex_);
169  if (impl_list_ == &impl)
170    impl_list_ = impl.next_;
171  if (impl.prev_)
172    impl.prev_->next_ = impl.next_;
173  if (impl.next_)
174    impl.next_->prev_= impl.prev_;
175  impl.next_ = 0;
176  impl.prev_ = 0;
177}
178
179boost::system::error_code win_iocp_handle_service::assign(
180    win_iocp_handle_service::implementation_type& impl,
181    const native_handle_type& handle, boost::system::error_code& ec)
182{
183  if (is_open(impl))
184  {
185    ec = boost::asio::error::already_open;
186    return ec;
187  }
188
189  if (iocp_service_.register_handle(handle, ec))
190    return ec;
191
192  impl.handle_ = handle;
193  ec = boost::system::error_code();
194  return ec;
195}
196
197boost::system::error_code win_iocp_handle_service::close(
198    win_iocp_handle_service::implementation_type& impl,
199    boost::system::error_code& ec)
200{
201  if (is_open(impl))
202  {
203    BOOST_ASIO_HANDLER_OPERATION((iocp_service_.context(), "handle",
204          &impl, reinterpret_cast<uintmax_t>(impl.handle_), "close"));
205
206    if (!::CloseHandle(impl.handle_))
207    {
208      DWORD last_error = ::GetLastError();
209      ec = boost::system::error_code(last_error,
210          boost::asio::error::get_system_category());
211    }
212    else
213    {
214      ec = boost::system::error_code();
215    }
216
217    impl.handle_ = INVALID_HANDLE_VALUE;
218    impl.safe_cancellation_thread_id_ = 0;
219  }
220  else
221  {
222    ec = boost::system::error_code();
223  }
224
225  return ec;
226}
227
228boost::system::error_code win_iocp_handle_service::cancel(
229    win_iocp_handle_service::implementation_type& impl,
230    boost::system::error_code& ec)
231{
232  if (!is_open(impl))
233  {
234    ec = boost::asio::error::bad_descriptor;
235    return ec;
236  }
237
238  BOOST_ASIO_HANDLER_OPERATION((iocp_service_.context(), "handle",
239        &impl, reinterpret_cast<uintmax_t>(impl.handle_), "cancel"));
240
241  if (FARPROC cancel_io_ex_ptr = ::GetProcAddress(
242        ::GetModuleHandleA("KERNEL32"), "CancelIoEx"))
243  {
244    // The version of Windows supports cancellation from any thread.
245    typedef BOOL (WINAPI* cancel_io_ex_t)(HANDLE, LPOVERLAPPED);
246    cancel_io_ex_t cancel_io_ex = reinterpret_cast<cancel_io_ex_t>(
247        reinterpret_cast<void*>(cancel_io_ex_ptr));
248    if (!cancel_io_ex(impl.handle_, 0))
249    {
250      DWORD last_error = ::GetLastError();
251      if (last_error == ERROR_NOT_FOUND)
252      {
253        // ERROR_NOT_FOUND means that there were no operations to be
254        // cancelled. We swallow this error to match the behaviour on other
255        // platforms.
256        ec = boost::system::error_code();
257      }
258      else
259      {
260        ec = boost::system::error_code(last_error,
261            boost::asio::error::get_system_category());
262      }
263    }
264    else
265    {
266      ec = boost::system::error_code();
267    }
268  }
269  else if (impl.safe_cancellation_thread_id_ == 0)
270  {
271    // No operations have been started, so there's nothing to cancel.
272    ec = boost::system::error_code();
273  }
274  else if (impl.safe_cancellation_thread_id_ == ::GetCurrentThreadId())
275  {
276    // Asynchronous operations have been started from the current thread only,
277    // so it is safe to try to cancel them using CancelIo.
278    if (!::CancelIo(impl.handle_))
279    {
280      DWORD last_error = ::GetLastError();
281      ec = boost::system::error_code(last_error,
282          boost::asio::error::get_system_category());
283    }
284    else
285    {
286      ec = boost::system::error_code();
287    }
288  }
289  else
290  {
291    // Asynchronous operations have been started from more than one thread,
292    // so cancellation is not safe.
293    ec = boost::asio::error::operation_not_supported;
294  }
295
296  return ec;
297}
298
299size_t win_iocp_handle_service::do_write(
300    win_iocp_handle_service::implementation_type& impl, uint64_t offset,
301    const boost::asio::const_buffer& buffer, boost::system::error_code& ec)
302{
303  if (!is_open(impl))
304  {
305    ec = boost::asio::error::bad_descriptor;
306    return 0;
307  }
308
309  // A request to write 0 bytes on a handle is a no-op.
310  if (buffer.size() == 0)
311  {
312    ec = boost::system::error_code();
313    return 0;
314  }
315
316  overlapped_wrapper overlapped(ec);
317  if (ec)
318  {
319    return 0;
320  }
321
322  // Write the data.
323  overlapped.Offset = offset & 0xFFFFFFFF;
324  overlapped.OffsetHigh = (offset >> 32) & 0xFFFFFFFF;
325  BOOL ok = ::WriteFile(impl.handle_, buffer.data(),
326      static_cast<DWORD>(buffer.size()), 0, &overlapped);
327  if (!ok)
328  {
329    DWORD last_error = ::GetLastError();
330    if (last_error != ERROR_IO_PENDING)
331    {
332      ec = boost::system::error_code(last_error,
333          boost::asio::error::get_system_category());
334      return 0;
335    }
336  }
337
338  // Wait for the operation to complete.
339  DWORD bytes_transferred = 0;
340  ok = ::GetOverlappedResult(impl.handle_,
341      &overlapped, &bytes_transferred, TRUE);
342  if (!ok)
343  {
344    DWORD last_error = ::GetLastError();
345    ec = boost::system::error_code(last_error,
346        boost::asio::error::get_system_category());
347    return 0;
348  }
349
350  ec = boost::system::error_code();
351  return bytes_transferred;
352}
353
354void win_iocp_handle_service::start_write_op(
355    win_iocp_handle_service::implementation_type& impl, uint64_t offset,
356    const boost::asio::const_buffer& buffer, operation* op)
357{
358  update_cancellation_thread_id(impl);
359  iocp_service_.work_started();
360
361  if (!is_open(impl))
362  {
363    iocp_service_.on_completion(op, boost::asio::error::bad_descriptor);
364  }
365  else if (buffer.size() == 0)
366  {
367    // A request to write 0 bytes on a handle is a no-op.
368    iocp_service_.on_completion(op);
369  }
370  else
371  {
372    DWORD bytes_transferred = 0;
373    op->Offset = offset & 0xFFFFFFFF;
374    op->OffsetHigh = (offset >> 32) & 0xFFFFFFFF;
375    BOOL ok = ::WriteFile(impl.handle_, buffer.data(),
376        static_cast<DWORD>(buffer.size()),
377        &bytes_transferred, op);
378    DWORD last_error = ::GetLastError();
379    if (!ok && last_error != ERROR_IO_PENDING
380        && last_error != ERROR_MORE_DATA)
381    {
382      iocp_service_.on_completion(op, last_error, bytes_transferred);
383    }
384    else
385    {
386      iocp_service_.on_pending(op);
387    }
388  }
389}
390
391size_t win_iocp_handle_service::do_read(
392    win_iocp_handle_service::implementation_type& impl, uint64_t offset,
393    const boost::asio::mutable_buffer& buffer, boost::system::error_code& ec)
394{
395  if (!is_open(impl))
396  {
397    ec = boost::asio::error::bad_descriptor;
398    return 0;
399  }
400
401  // A request to read 0 bytes on a stream handle is a no-op.
402  if (buffer.size() == 0)
403  {
404    ec = boost::system::error_code();
405    return 0;
406  }
407
408  overlapped_wrapper overlapped(ec);
409  if (ec)
410  {
411    return 0;
412  }
413
414  // Read some data.
415  overlapped.Offset = offset & 0xFFFFFFFF;
416  overlapped.OffsetHigh = (offset >> 32) & 0xFFFFFFFF;
417  BOOL ok = ::ReadFile(impl.handle_, buffer.data(),
418      static_cast<DWORD>(buffer.size()), 0, &overlapped);
419  if (!ok)
420  {
421    DWORD last_error = ::GetLastError();
422    if (last_error != ERROR_IO_PENDING && last_error != ERROR_MORE_DATA)
423    {
424      if (last_error == ERROR_HANDLE_EOF)
425      {
426        ec = boost::asio::error::eof;
427      }
428      else
429      {
430        ec = boost::system::error_code(last_error,
431            boost::asio::error::get_system_category());
432      }
433      return 0;
434    }
435  }
436
437  // Wait for the operation to complete.
438  DWORD bytes_transferred = 0;
439  ok = ::GetOverlappedResult(impl.handle_,
440      &overlapped, &bytes_transferred, TRUE);
441  if (!ok)
442  {
443    DWORD last_error = ::GetLastError();
444    if (last_error == ERROR_HANDLE_EOF)
445    {
446      ec = boost::asio::error::eof;
447    }
448    else
449    {
450      ec = boost::system::error_code(last_error,
451          boost::asio::error::get_system_category());
452    }
453    return (last_error == ERROR_MORE_DATA) ? bytes_transferred : 0;
454  }
455
456  ec = boost::system::error_code();
457  return bytes_transferred;
458}
459
460void win_iocp_handle_service::start_read_op(
461    win_iocp_handle_service::implementation_type& impl, uint64_t offset,
462    const boost::asio::mutable_buffer& buffer, operation* op)
463{
464  update_cancellation_thread_id(impl);
465  iocp_service_.work_started();
466
467  if (!is_open(impl))
468  {
469    iocp_service_.on_completion(op, boost::asio::error::bad_descriptor);
470  }
471  else if (buffer.size() == 0)
472  {
473    // A request to read 0 bytes on a handle is a no-op.
474    iocp_service_.on_completion(op);
475  }
476  else
477  {
478    DWORD bytes_transferred = 0;
479    op->Offset = offset & 0xFFFFFFFF;
480    op->OffsetHigh = (offset >> 32) & 0xFFFFFFFF;
481    BOOL ok = ::ReadFile(impl.handle_, buffer.data(),
482        static_cast<DWORD>(buffer.size()),
483        &bytes_transferred, op);
484    DWORD last_error = ::GetLastError();
485    if (!ok && last_error != ERROR_IO_PENDING
486        && last_error != ERROR_MORE_DATA)
487    {
488      iocp_service_.on_completion(op, last_error, bytes_transferred);
489    }
490    else
491    {
492      iocp_service_.on_pending(op);
493    }
494  }
495}
496
497void win_iocp_handle_service::update_cancellation_thread_id(
498    win_iocp_handle_service::implementation_type& impl)
499{
500  if (impl.safe_cancellation_thread_id_ == 0)
501    impl.safe_cancellation_thread_id_ = ::GetCurrentThreadId();
502  else if (impl.safe_cancellation_thread_id_ != ::GetCurrentThreadId())
503    impl.safe_cancellation_thread_id_ = ~DWORD(0);
504}
505
506void win_iocp_handle_service::close_for_destruction(implementation_type& impl)
507{
508  if (is_open(impl))
509  {
510    BOOST_ASIO_HANDLER_OPERATION((iocp_service_.context(), "handle",
511          &impl, reinterpret_cast<uintmax_t>(impl.handle_), "close"));
512
513    ::CloseHandle(impl.handle_);
514    impl.handle_ = INVALID_HANDLE_VALUE;
515    impl.safe_cancellation_thread_id_ = 0;
516  }
517}
518
519} // namespace detail
520} // namespace asio
521} // namespace boost
522
523#include <boost/asio/detail/pop_options.hpp>
524
525#endif // defined(BOOST_ASIO_HAS_IOCP)
526
527#endif // BOOST_ASIO_DETAIL_IMPL_WIN_IOCP_HANDLE_SERVICE_IPP
528