Update io_context::executor_type to standard executor form.

This commit is contained in:
Christopher Kohlhoff 2020-05-18 20:21:08 +10:00
parent e61acbcb42
commit 4cd27fffd4
14 changed files with 1107 additions and 84 deletions

View File

@ -342,6 +342,7 @@ nobase_include_HEADERS = \
asio/impl/handler_alloc_hook.ipp \
asio/impl/io_context.hpp \
asio/impl/io_context.ipp \
asio/impl/multiple_exceptions.ipp \
asio/impl/post.hpp \
asio/impl/read_at.hpp \
asio/impl/read.hpp \
@ -415,6 +416,7 @@ nobase_include_HEADERS = \
asio/local/detail/endpoint.hpp \
asio/local/detail/impl/endpoint.ipp \
asio/local/stream_protocol.hpp \
asio/multiple_exceptions.hpp \
asio/packaged_task.hpp \
asio/placeholders.hpp \
asio/posix/basic_descriptor.hpp \

View File

@ -117,6 +117,7 @@
#include "asio/local/connect_pair.hpp"
#include "asio/local/datagram_protocol.hpp"
#include "asio/local/stream_protocol.hpp"
#include "asio/multiple_exceptions.hpp"
#include "asio/packaged_task.hpp"
#include "asio/placeholders.hpp"
#include "asio/posix/basic_descriptor.hpp"

View File

@ -1108,6 +1108,30 @@
# endif // !defined(ASIO_DISABLE_STD_INVOKE_RESULT)
#endif // !defined(ASIO_HAS_STD_INVOKE_RESULT)
// Standard library support for std::exception_ptr and std::current_exception.
#if !defined(ASIO_HAS_STD_EXCEPTION_PTR)
# if !defined(ASIO_DISABLE_STD_EXCEPTION_PTR)
# if defined(__clang__)
# if defined(ASIO_HAS_CLANG_LIBCXX)
# define ASIO_HAS_STD_EXCEPTION_PTR 1
# elif (__cplusplus >= 201103)
# define ASIO_HAS_STD_EXCEPTION_PTR 1
# endif // (__cplusplus >= 201103)
# elif defined(__GNUC__)
# if ((__GNUC__ == 4) && (__GNUC_MINOR__ >= 7)) || (__GNUC__ > 4)
# if (__cplusplus >= 201103) || defined(__GXX_EXPERIMENTAL_CXX0X__)
# define ASIO_HAS_STD_EXCEPTION_PTR 1
# endif // (__cplusplus >= 201103) || defined(__GXX_EXPERIMENTAL_CXX0X__)
# endif // ((__GNUC__ == 4) && (__GNUC_MINOR__ >= 7)) || (__GNUC__ > 4)
# endif // defined(__GNUC__)
# if defined(ASIO_MSVC)
# if (_MSC_VER >= 1800)
# define ASIO_HAS_STD_EXCEPTION_PTR 1
# endif // (_MSC_VER >= 1800)
# endif // defined(ASIO_MSVC)
# endif // !defined(ASIO_DISABLE_STD_EXCEPTION_PTR)
#endif // !defined(ASIO_HAS_STD_EXCEPTION_PTR)
// Standard library support for std::source_location.
#if !defined(ASIO_HAS_STD_SOURCE_LOCATION)
# if !defined(ASIO_DISABLE_STD_SOURCE_LOCATION)

View File

@ -324,6 +324,12 @@ void scheduler::compensating_work_started()
++static_cast<thread_info*>(this_thread)->private_outstanding_work;
}
void scheduler::capture_current_exception()
{
if (thread_info_base* this_thread = thread_call_stack::contains(this))
this_thread->capture_current_exception();
}
void scheduler::post_immediate_completion(
scheduler::operation* op, bool is_continuation)
{
@ -448,6 +454,7 @@ std::size_t scheduler::do_run_one(mutex::scoped_lock& lock,
// Complete the operation. May throw an exception. Deletes the object.
o->complete(this, ec, task_result);
this_thread.rethrow_pending_exception();
return 1;
}
@ -528,6 +535,7 @@ std::size_t scheduler::do_wait_one(mutex::scoped_lock& lock,
// Complete the operation. May throw an exception. Deletes the object.
o->complete(this, ec, task_result);
this_thread.rethrow_pending_exception();
return 1;
}
@ -582,6 +590,7 @@ std::size_t scheduler::do_poll_one(mutex::scoped_lock& lock,
// Complete the operation. May throw an exception. Deletes the object.
o->complete(this, ec, task_result);
this_thread.rethrow_pending_exception();
return 1;
}

View File

@ -200,7 +200,7 @@ size_t win_iocp_io_context::run(asio::error_code& ec)
thread_call_stack::context ctx(this, this_thread);
size_t n = 0;
while (do_one(INFINITE, ec))
while (do_one(INFINITE, this_thread, ec))
if (n != (std::numeric_limits<size_t>::max)())
++n;
return n;
@ -218,7 +218,7 @@ size_t win_iocp_io_context::run_one(asio::error_code& ec)
win_iocp_thread_info this_thread;
thread_call_stack::context ctx(this, this_thread);
return do_one(INFINITE, ec);
return do_one(INFINITE, this_thread, ec);
}
size_t win_iocp_io_context::wait_one(long usec, asio::error_code& ec)
@ -233,7 +233,7 @@ size_t win_iocp_io_context::wait_one(long usec, asio::error_code& ec)
win_iocp_thread_info this_thread;
thread_call_stack::context ctx(this, this_thread);
return do_one(usec < 0 ? INFINITE : ((usec - 1) / 1000 + 1), ec);
return do_one(usec < 0 ? INFINITE : ((usec - 1) / 1000 + 1), this_thread, ec);
}
size_t win_iocp_io_context::poll(asio::error_code& ec)
@ -249,7 +249,7 @@ size_t win_iocp_io_context::poll(asio::error_code& ec)
thread_call_stack::context ctx(this, this_thread);
size_t n = 0;
while (do_one(0, ec))
while (do_one(0, this_thread, ec))
if (n != (std::numeric_limits<size_t>::max)())
++n;
return n;
@ -267,7 +267,7 @@ size_t win_iocp_io_context::poll_one(asio::error_code& ec)
win_iocp_thread_info this_thread;
thread_call_stack::context ctx(this, this_thread);
return do_one(0, ec);
return do_one(0, this_thread, ec);
}
void win_iocp_io_context::stop()
@ -287,6 +287,12 @@ void win_iocp_io_context::stop()
}
}
void win_iocp_io_context::capture_current_exception()
{
if (thread_info_base* this_thread = thread_call_stack::contains(this))
this_thread->capture_current_exception();
}
void win_iocp_io_context::post_deferred_completion(win_iocp_operation* op)
{
// Flag the operation as ready.
@ -396,7 +402,8 @@ void win_iocp_io_context::on_completion(win_iocp_operation* op,
}
}
size_t win_iocp_io_context::do_one(DWORD msec, asio::error_code& ec)
size_t win_iocp_io_context::do_one(DWORD msec,
win_iocp_thread_info& this_thread, asio::error_code& ec)
{
for (;;)
{
@ -458,6 +465,7 @@ size_t win_iocp_io_context::do_one(DWORD msec, asio::error_code& ec)
(void)on_exit;
op->complete(this, result_ec, bytes_transferred);
this_thread.rethrow_pending_exception();
ec = asio::error_code();
return 1;
}

View File

@ -104,6 +104,9 @@ public:
return thread_call_stack::contains(this) != 0;
}
/// Capture the current exception so it can be rethrown from a run function.
ASIO_DECL void capture_current_exception();
// Request invocation of the given operation and return immediately. Assumes
// that work_started() has not yet been called for the operation.
ASIO_DECL void post_immediate_completion(

View File

@ -15,10 +15,18 @@
# pragma once
#endif // defined(_MSC_VER) && (_MSC_VER >= 1200)
#include "asio/detail/config.hpp"
#include <climits>
#include <cstddef>
#include "asio/detail/noncopyable.hpp"
#if defined(ASIO_HAS_STD_EXCEPTION_PTR) \
&& !defined(ASIO_NO_EXCEPTIONS)
# include <exception>
# include "asio/multiple_exceptions.hpp"
#endif // defined(ASIO_HAS_STD_EXCEPTION_PTR)
// && !defined(ASIO_NO_EXCEPTIONS)
#include "asio/detail/push_options.hpp"
namespace asio {
@ -44,6 +52,11 @@ public:
};
thread_info_base()
#if defined(ASIO_HAS_STD_EXCEPTION_PTR) \
&& !defined(ASIO_NO_EXCEPTIONS)
: has_pending_exception_(0)
#endif // defined(ASIO_HAS_STD_EXCEPTION_PTR)
// && !defined(ASIO_NO_EXCEPTIONS)
{
for (int i = 0; i < max_mem_index; ++i)
reusable_memory_[i] = 0;
@ -111,10 +124,55 @@ public:
::operator delete(pointer);
}
void capture_current_exception()
{
#if defined(ASIO_HAS_STD_EXCEPTION_PTR) \
&& !defined(ASIO_NO_EXCEPTIONS)
switch (has_pending_exception_)
{
case 0:
has_pending_exception_ = 1;
pending_exception_ = std::current_exception();
break;
case 1:
has_pending_exception_ = 2;
pending_exception_ =
std::make_exception_ptr<multiple_exceptions>(
multiple_exceptions(pending_exception_));
default:
break;
}
#endif // defined(ASIO_HAS_STD_EXCEPTION_PTR)
// && !defined(ASIO_NO_EXCEPTIONS)
}
void rethrow_pending_exception()
{
#if defined(ASIO_HAS_STD_EXCEPTION_PTR) \
&& !defined(ASIO_NO_EXCEPTIONS)
if (has_pending_exception_ > 0)
{
has_pending_exception_ = 0;
std::exception_ptr ex(
ASIO_MOVE_CAST(std::exception_ptr)(
pending_exception_));
std::rethrow_exception(ex);
}
#endif // defined(ASIO_HAS_STD_EXCEPTION_PTR)
// && !defined(ASIO_NO_EXCEPTIONS)
}
private:
enum { chunk_size = 4 };
enum { max_mem_index = 3 };
void* reusable_memory_[max_mem_index];
#if defined(ASIO_HAS_STD_EXCEPTION_PTR) \
&& !defined(ASIO_NO_EXCEPTIONS)
int has_pending_exception_;
std::exception_ptr pending_exception_;
#endif // defined(ASIO_HAS_STD_EXCEPTION_PTR)
// && !defined(ASIO_NO_EXCEPTIONS)
};
} // namespace detail

View File

@ -114,6 +114,9 @@ public:
return thread_call_stack::contains(this) != 0;
}
/// Capture the current exception so it can be rethrown from a run function.
ASIO_DECL void capture_current_exception();
// Request invocation of the given operation and return immediately. Assumes
// that work_started() has not yet been called for the operation.
void post_immediate_completion(win_iocp_operation* op, bool)
@ -221,7 +224,8 @@ private:
// Dequeues at most one operation from the I/O completion port, and then
// executes it. Returns the number of operations that were dequeued (i.e.
// either 0 or 1).
ASIO_DECL size_t do_one(DWORD msec, asio::error_code& ec);
ASIO_DECL size_t do_one(DWORD msec,
win_iocp_thread_info& this_thread, asio::error_code& ec);
// Helper to calculate the GetQueuedCompletionStatus timeout.
ASIO_DECL static DWORD get_gqcs_timeout();

View File

@ -228,32 +228,133 @@ io_context::wrap(Handler handler)
#endif // !defined(ASIO_NO_DEPRECATED)
inline io_context&
io_context::executor_type::context() const ASIO_NOEXCEPT
template <typename Allocator, unsigned int Bits>
io_context::basic_executor_type<Allocator, Bits>&
io_context::basic_executor_type<Allocator, Bits>::operator=(
const basic_executor_type& other) ASIO_NOEXCEPT
{
return io_context_;
if (this != &other)
{
io_context* old_io_context = io_context_;
io_context_ = other.io_context_;
allocator_ = other.allocator_;
bits_ = other.bits_;
if (Bits & outstanding_work_tracked)
{
if (io_context_)
io_context_->impl_.work_started();
if (old_io_context)
old_io_context->impl_.work_finished();
}
}
return *this;
}
inline void
io_context::executor_type::on_work_started() const ASIO_NOEXCEPT
#if defined(ASIO_HAS_MOVE)
template <typename Allocator, unsigned int Bits>
io_context::basic_executor_type<Allocator, Bits>&
io_context::basic_executor_type<Allocator, Bits>::operator=(
basic_executor_type&& other) ASIO_NOEXCEPT
{
io_context_.impl_.work_started();
if (this != &other)
{
io_context_ = other.io_context_;
allocator_ = std::move(other.allocator_);
bits_ = other.bits_;
if (Bits & outstanding_work_tracked)
other.io_context_ = 0;
}
return *this;
}
#endif // defined(ASIO_HAS_MOVE)
template <typename Allocator, unsigned int Bits>
inline bool io_context::basic_executor_type<Allocator,
Bits>::running_in_this_thread() const ASIO_NOEXCEPT
{
return io_context_->impl_.can_dispatch();
}
inline void
io_context::executor_type::on_work_finished() const ASIO_NOEXCEPT
template <typename Allocator, unsigned int Bits>
template <typename Function>
void io_context::basic_executor_type<Allocator, Bits>::execute(
ASIO_MOVE_ARG(Function) f) const
{
io_context_.impl_.work_finished();
typedef typename decay<Function>::type function_type;
// Invoke immediately if the blocking.possibly property is enabled and we are
// already inside the thread pool.
if ((bits_ & blocking_never) == 0 && io_context_->impl_.can_dispatch())
{
// Make a local, non-const copy of the function.
function_type tmp(ASIO_MOVE_CAST(Function)(f));
#if defined(ASIO_HAS_STD_EXCEPTION_PTR) \
&& !defined(ASIO_NO_EXCEPTIONS)
try
{
#endif // defined(ASIO_HAS_STD_EXCEPTION_PTR)
// && !defined(ASIO_NO_EXCEPTIONS)
detail::fenced_block b(detail::fenced_block::full);
asio_handler_invoke_helpers::invoke(tmp, tmp);
return;
#if defined(ASIO_HAS_STD_EXCEPTION_PTR) \
&& !defined(ASIO_NO_EXCEPTIONS)
}
catch (...)
{
io_context_->impl_.capture_current_exception();
return;
}
#endif // defined(ASIO_HAS_STD_EXCEPTION_PTR)
// && !defined(ASIO_NO_EXCEPTIONS)
}
// Allocate and construct an operation to wrap the function.
typedef detail::executor_op<function_type, Allocator, detail::operation> op;
typename op::ptr p = { detail::addressof(allocator_),
op::ptr::allocate(allocator_), 0 };
p.p = new (p.v) op(ASIO_MOVE_CAST(Function)(f), allocator_);
ASIO_HANDLER_CREATION((*io_context_, *p.p,
"io_context", io_context_, 0, "execute"));
io_context_->impl_.post_immediate_completion(p.p,
(bits_ & relationship_continuation) != 0);
p.v = p.p = 0;
}
template <typename Function, typename Allocator>
void io_context::executor_type::dispatch(
ASIO_MOVE_ARG(Function) f, const Allocator& a) const
#if !defined(ASIO_NO_TS_EXECUTORS)
template <typename Allocator, unsigned int Bits>
inline io_context& io_context::basic_executor_type<
Allocator, Bits>::context() const ASIO_NOEXCEPT
{
return *io_context_;
}
template <typename Allocator, unsigned int Bits>
inline void io_context::basic_executor_type<Allocator,
Bits>::on_work_started() const ASIO_NOEXCEPT
{
io_context_->impl_.work_started();
}
template <typename Allocator, unsigned int Bits>
inline void io_context::basic_executor_type<Allocator,
Bits>::on_work_finished() const ASIO_NOEXCEPT
{
io_context_->impl_.work_finished();
}
template <typename Allocator, unsigned int Bits>
template <typename Function, typename OtherAllocator>
void io_context::basic_executor_type<Allocator, Bits>::dispatch(
ASIO_MOVE_ARG(Function) f, const OtherAllocator& a) const
{
typedef typename decay<Function>::type function_type;
// Invoke immediately if we are already inside the thread pool.
if (io_context_.impl_.can_dispatch())
if (io_context_->impl_.can_dispatch())
{
// Make a local, non-const copy of the function.
function_type tmp(ASIO_MOVE_CAST(Function)(f));
@ -264,58 +365,58 @@ void io_context::executor_type::dispatch(
}
// Allocate and construct an operation to wrap the function.
typedef detail::executor_op<function_type, Allocator, detail::operation> op;
typedef detail::executor_op<function_type,
OtherAllocator, detail::operation> op;
typename op::ptr p = { detail::addressof(a), op::ptr::allocate(a), 0 };
p.p = new (p.v) op(ASIO_MOVE_CAST(Function)(f), a);
ASIO_HANDLER_CREATION((this->context(), *p.p,
"io_context", &this->context(), 0, "dispatch"));
ASIO_HANDLER_CREATION((*io_context_, *p.p,
"io_context", io_context_, 0, "dispatch"));
io_context_.impl_.post_immediate_completion(p.p, false);
io_context_->impl_.post_immediate_completion(p.p, false);
p.v = p.p = 0;
}
template <typename Function, typename Allocator>
void io_context::executor_type::post(
ASIO_MOVE_ARG(Function) f, const Allocator& a) const
template <typename Allocator, unsigned int Bits>
template <typename Function, typename OtherAllocator>
void io_context::basic_executor_type<Allocator, Bits>::post(
ASIO_MOVE_ARG(Function) f, const OtherAllocator& a) const
{
typedef typename decay<Function>::type function_type;
// Allocate and construct an operation to wrap the function.
typedef detail::executor_op<function_type, Allocator, detail::operation> op;
typedef detail::executor_op<function_type,
OtherAllocator, detail::operation> op;
typename op::ptr p = { detail::addressof(a), op::ptr::allocate(a), 0 };
p.p = new (p.v) op(ASIO_MOVE_CAST(Function)(f), a);
ASIO_HANDLER_CREATION((this->context(), *p.p,
"io_context", &this->context(), 0, "post"));
ASIO_HANDLER_CREATION((*io_context_, *p.p,
"io_context", io_context_, 0, "post"));
io_context_.impl_.post_immediate_completion(p.p, false);
io_context_->impl_.post_immediate_completion(p.p, false);
p.v = p.p = 0;
}
template <typename Function, typename Allocator>
void io_context::executor_type::defer(
ASIO_MOVE_ARG(Function) f, const Allocator& a) const
template <typename Allocator, unsigned int Bits>
template <typename Function, typename OtherAllocator>
void io_context::basic_executor_type<Allocator, Bits>::defer(
ASIO_MOVE_ARG(Function) f, const OtherAllocator& a) const
{
typedef typename decay<Function>::type function_type;
// Allocate and construct an operation to wrap the function.
typedef detail::executor_op<function_type, Allocator, detail::operation> op;
typedef detail::executor_op<function_type,
OtherAllocator, detail::operation> op;
typename op::ptr p = { detail::addressof(a), op::ptr::allocate(a), 0 };
p.p = new (p.v) op(ASIO_MOVE_CAST(Function)(f), a);
ASIO_HANDLER_CREATION((this->context(), *p.p,
"io_context", &this->context(), 0, "defer"));
ASIO_HANDLER_CREATION((*io_context_, *p.p,
"io_context", io_context_, 0, "defer"));
io_context_.impl_.post_immediate_completion(p.p, true);
io_context_->impl_.post_immediate_completion(p.p, true);
p.v = p.p = 0;
}
inline bool
io_context::executor_type::running_in_this_thread() const ASIO_NOEXCEPT
{
return io_context_.impl_.can_dispatch();
}
#endif // !defined(ASIO_NO_TS_EXECUTORS)
#if !defined(ASIO_NO_DEPRECATED)
inline io_context::work::work(asio::io_context& io_context)

View File

@ -0,0 +1,49 @@
//
// impl/multiple_exceptions.ipp
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
// Copyright (c) 2003-2020 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 ASIO_IMPL_MULTIPLE_EXCEPTIONS_IPP
#define ASIO_IMPL_MULTIPLE_EXCEPTIONS_IPP
#if defined(_MSC_VER) && (_MSC_VER >= 1200)
# pragma once
#endif // defined(_MSC_VER) && (_MSC_VER >= 1200)
#include "asio/detail/config.hpp"
#include "asio/multiple_exceptions.hpp"
#include "asio/detail/push_options.hpp"
namespace asio {
#if defined(ASIO_HAS_STD_EXCEPTION_PTR)
multiple_exceptions::multiple_exceptions(
std::exception_ptr first) ASIO_NOEXCEPT
: first_(ASIO_MOVE_CAST(std::exception_ptr)(first))
{
}
const char* multiple_exceptions::what() const ASIO_NOEXCEPT_OR_NOTHROW
{
return "multiple exceptions";
}
std::exception_ptr multiple_exceptions::first_exception() const
{
return first_;
}
#endif // defined(ASIO_HAS_STD_EXCEPTION_PTR)
} // namespace asio
#include "asio/detail/pop_options.hpp"
#endif // ASIO_IMPL_MULTIPLE_EXCEPTIONS_IPP

View File

@ -25,6 +25,7 @@
#include "asio/impl/executor.ipp"
#include "asio/impl/handler_alloc_hook.ipp"
#include "asio/impl/io_context.ipp"
#include "asio/impl/multiple_exceptions.ipp"
#include "asio/impl/serial_port_base.ipp"
#include "asio/impl/system_context.ipp"
#include "asio/impl/thread_pool.ipp"

View File

@ -22,6 +22,7 @@
#include "asio/async_result.hpp"
#include "asio/detail/wrapped_handler.hpp"
#include "asio/error_code.hpp"
#include "asio/execution.hpp"
#include "asio/execution_context.hpp"
#if defined(ASIO_HAS_CHRONO)
@ -46,6 +47,13 @@ namespace detail {
#else
typedef class scheduler io_context_impl;
#endif
struct io_context_bits
{
ASIO_STATIC_CONSTEXPR(unsigned int, blocking_never = 1);
ASIO_STATIC_CONSTEXPR(unsigned int, relationship_continuation = 2);
ASIO_STATIC_CONSTEXPR(unsigned int, outstanding_work_tracked = 4);
};
} // namespace detail
/// Provides core I/O functionality.
@ -184,8 +192,14 @@ private:
#endif
public:
class executor_type;
friend class executor_type;
template <typename Allocator, unsigned int Bits>
class basic_executor_type;
template <typename Allocator, unsigned int Bits>
friend class basic_executor_type;
/// Executor used to submit functions to an io_context.
typedef basic_executor_type<std::allocator<void>, 0> executor_type;
#if !defined(ASIO_NO_DEPRECATED)
class work;
@ -620,10 +634,215 @@ private:
impl_type& impl_;
};
/// Executor used to submit functions to an io_context.
class io_context::executor_type
namespace detail {
} // namespace detail
/// Executor implementation type used to submit functions to an io_context.
template <typename Allocator, unsigned int Bits>
class io_context::basic_executor_type : detail::io_context_bits
{
public:
/// Copy construtor.
basic_executor_type(
const basic_executor_type& other) ASIO_NOEXCEPT
: io_context_(other.io_context_),
allocator_(other.allocator_),
bits_(other.bits_)
{
if (Bits & outstanding_work_tracked)
if (io_context_)
io_context_->impl_.work_started();
}
#if defined(ASIO_HAS_MOVE) || defined(GENERATING_DOCUMENTATION)
/// Move construtor.
basic_executor_type(basic_executor_type&& other) ASIO_NOEXCEPT
: io_context_(other.io_context_),
allocator_(ASIO_MOVE_CAST(Allocator)(other.allocator_)),
bits_(other.bits_)
{
if (Bits & outstanding_work_tracked)
other.io_context_ = 0;
}
#endif // defined(ASIO_HAS_MOVE) || defined(GENERATING_DOCUMENTATION)
/// Destructor.
~basic_executor_type()
{
if (Bits & outstanding_work_tracked)
if (io_context_)
io_context_->impl_.work_finished();
}
/// Assignment operator.
basic_executor_type& operator=(
const basic_executor_type& other) ASIO_NOEXCEPT;
#if defined(ASIO_HAS_MOVE) || defined(GENERATING_DOCUMENTATION)
/// Move assignment operator.
basic_executor_type& operator=(
basic_executor_type&& other) ASIO_NOEXCEPT;
#endif // defined(ASIO_HAS_MOVE) || defined(GENERATING_DOCUMENTATION)
/// Obtain an executor with the @c blocking.possibly property.
ASIO_CONSTEXPR basic_executor_type require(
execution::blocking_t::possibly_t) const
{
return basic_executor_type(io_context_,
allocator_, bits_ & ~blocking_never);
}
/// Obtain an executor with the @c blocking.never property.
ASIO_CONSTEXPR basic_executor_type require(
execution::blocking_t::never_t) const
{
return basic_executor_type(io_context_,
allocator_, bits_ | blocking_never);
}
/// Obtain an executor with the @c relationship.fork property.
ASIO_CONSTEXPR basic_executor_type require(
execution::relationship_t::fork_t) const
{
return basic_executor_type(io_context_,
allocator_, bits_ & ~relationship_continuation);
}
/// Obtain an executor with the @c relationship.continuation property.
ASIO_CONSTEXPR basic_executor_type require(
execution::relationship_t::continuation_t) const
{
return basic_executor_type(io_context_,
allocator_, bits_ | relationship_continuation);
}
/// Obtain an executor with the @c outstanding_work.tracked property.
ASIO_CONSTEXPR basic_executor_type<Allocator,
ASIO_UNSPECIFIED(Bits | outstanding_work_tracked)>
require(execution::outstanding_work_t::tracked_t) const
{
return basic_executor_type<Allocator, Bits | outstanding_work_tracked>(
io_context_, allocator_, bits_);
}
/// Obtain an executor with the @c outstanding_work.untracked property.
ASIO_CONSTEXPR basic_executor_type<Allocator,
ASIO_UNSPECIFIED(Bits & ~outstanding_work_tracked)>
require(execution::outstanding_work_t::untracked_t) const
{
return basic_executor_type<Allocator, Bits & ~outstanding_work_tracked>(
io_context_, allocator_, bits_);
}
/// Obtain an executor with the specified @c allocator property.
template <typename OtherAllocator>
ASIO_CONSTEXPR basic_executor_type<OtherAllocator, Bits>
require(execution::allocator_t<OtherAllocator> a) const
{
return basic_executor_type<OtherAllocator, Bits>(
io_context_, a.value(), bits_);
}
/// Obtain an executor with the default @c allocator property.
ASIO_CONSTEXPR basic_executor_type<std::allocator<void>, Bits>
require(execution::allocator_t<void>) const
{
return basic_executor_type<std::allocator<void>, Bits>(
io_context_, std::allocator<void>(), bits_);
}
/// Query the current value of the @c mapping property.
static ASIO_CONSTEXPR execution::mapping_t query(
execution::mapping_t) ASIO_NOEXCEPT
{
return execution::mapping.thread;
}
/// Query the current value of the @c context property.
io_context& query(execution::context_t) const ASIO_NOEXCEPT
{
return *io_context_;
}
/// Query the current value of the @c blocking property.
ASIO_CONSTEXPR execution::blocking_t query(
execution::blocking_t) const ASIO_NOEXCEPT
{
return (bits_ & blocking_never)
? execution::blocking_t(execution::blocking.never)
: execution::blocking_t(execution::blocking.possibly);
}
/// Query the current value of the @c relationship property.
ASIO_CONSTEXPR execution::relationship_t query(
execution::relationship_t) const ASIO_NOEXCEPT
{
return (bits_ & relationship_continuation)
? execution::relationship_t(execution::relationship.continuation)
: execution::relationship_t(execution::relationship.fork);
}
/// Query the current value of the @c outstanding_work property.
static ASIO_CONSTEXPR execution::outstanding_work_t query(
execution::outstanding_work_t) ASIO_NOEXCEPT
{
return (Bits & outstanding_work_tracked)
? execution::outstanding_work_t(execution::outstanding_work.tracked)
: execution::outstanding_work_t(execution::outstanding_work.untracked);
}
/// Query the current value of the @c allocator property.
template <typename OtherAllocator>
ASIO_CONSTEXPR Allocator query(
execution::allocator_t<OtherAllocator>) const ASIO_NOEXCEPT
{
return allocator_;
}
/// Query the current value of the @c allocator property.
ASIO_CONSTEXPR Allocator query(
execution::allocator_t<void>) const ASIO_NOEXCEPT
{
return allocator_;
}
/// Determine whether the io_context is running in the current thread.
/**
* @return @c true if the current thread is running the io_context. Otherwise
* returns @c false.
*/
bool running_in_this_thread() const ASIO_NOEXCEPT;
/// Compare two executors for equality.
/**
* Two executors are equal if they refer to the same underlying io_context.
*/
friend bool operator==(const basic_executor_type& a,
const basic_executor_type& b) ASIO_NOEXCEPT
{
return a.io_context_ == b.io_context_
&& a.allocator_ == b.allocator_
&& a.bits_ == b.bits_;
}
/// Compare two executors for inequality.
/**
* Two executors are equal if they refer to the same underlying io_context.
*/
friend bool operator!=(const basic_executor_type& a,
const basic_executor_type& b) ASIO_NOEXCEPT
{
return a.io_context_ != b.io_context_
|| a.allocator_ != b.allocator_
|| a.bits_ != b.bits_;
}
/// Execution function.
template <typename Function>
void execute(ASIO_MOVE_ARG(Function) f) const;
#if !defined(ASIO_NO_TS_EXECUTORS)
/// Obtain the underlying execution context.
io_context& context() const ASIO_NOEXCEPT;
@ -657,8 +876,9 @@ public:
* @param a An allocator that may be used by the executor to allocate the
* internal storage needed for function invocation.
*/
template <typename Function, typename Allocator>
void dispatch(ASIO_MOVE_ARG(Function) f, const Allocator& a) const;
template <typename Function, typename OtherAllocator>
void dispatch(ASIO_MOVE_ARG(Function) f,
const OtherAllocator& a) const;
/// Request the io_context to invoke the given function object.
/**
@ -673,8 +893,9 @@ public:
* @param a An allocator that may be used by the executor to allocate the
* internal storage needed for function invocation.
*/
template <typename Function, typename Allocator>
void post(ASIO_MOVE_ARG(Function) f, const Allocator& a) const;
template <typename Function, typename OtherAllocator>
void post(ASIO_MOVE_ARG(Function) f,
const OtherAllocator& a) const;
/// Request the io_context to invoke the given function object.
/**
@ -693,44 +914,45 @@ public:
* @param a An allocator that may be used by the executor to allocate the
* internal storage needed for function invocation.
*/
template <typename Function, typename Allocator>
void defer(ASIO_MOVE_ARG(Function) f, const Allocator& a) const;
/// Determine whether the io_context is running in the current thread.
/**
* @return @c true if the current thread is running the io_context. Otherwise
* returns @c false.
*/
bool running_in_this_thread() const ASIO_NOEXCEPT;
/// Compare two executors for equality.
/**
* Two executors are equal if they refer to the same underlying io_context.
*/
friend bool operator==(const executor_type& a,
const executor_type& b) ASIO_NOEXCEPT
{
return &a.io_context_ == &b.io_context_;
}
/// Compare two executors for inequality.
/**
* Two executors are equal if they refer to the same underlying io_context.
*/
friend bool operator!=(const executor_type& a,
const executor_type& b) ASIO_NOEXCEPT
{
return &a.io_context_ != &b.io_context_;
}
template <typename Function, typename OtherAllocator>
void defer(ASIO_MOVE_ARG(Function) f,
const OtherAllocator& a) const;
#endif // !defined(ASIO_NO_TS_EXECUTORS)
private:
friend class io_context;
template <typename, unsigned int> friend class basic_executor_type;
// Constructor.
explicit executor_type(io_context& i) : io_context_(i) {}
// Constructor used by io_context::get_executor().
explicit basic_executor_type(io_context& i) ASIO_NOEXCEPT
: io_context_(&i),
allocator_(),
bits_(0)
{
if (Bits & outstanding_work_tracked)
io_context_->impl_.work_started();
}
// Constructor used by require().
basic_executor_type(io_context* i,
const Allocator& a, unsigned int bits) ASIO_NOEXCEPT
: io_context_(i),
allocator_(a),
bits_(bits)
{
if (Bits & outstanding_work_tracked)
if (io_context_)
io_context_->impl_.work_started();
}
// The underlying io_context.
io_context& io_context_;
io_context* io_context_;
// The allocator used for execution functions.
Allocator allocator_;
// The runtime-switched properties of the io_context executor.
unsigned int bits_;
};
#if !defined(ASIO_NO_DEPRECATED)
@ -854,6 +1076,266 @@ template <typename Type>
asio::detail::service_id<Type> service_base<Type>::id;
} // namespace detail
#if !defined(GENERATING_DOCUMENTATION)
namespace execution {
#if !defined(ASIO_HAS_DEDUCED_EXECUTION_IS_EXECUTOR_TRAIT)
template <typename Allocator, unsigned int Bits>
struct is_executor<
asio::io_context::basic_executor_type<Allocator, Bits>
> : true_type
{
};
#endif // !defined(ASIO_HAS_DEDUCED_EXECUTION_IS_EXECUTOR_TRAIT)
} // namespace execution
namespace traits {
#if !defined(ASIO_HAS_DEDUCED_EXECUTE_MEMBER_TRAIT)
template <typename Allocator, unsigned int Bits, typename Function>
struct execute_member<
asio::io_context::basic_executor_type<Allocator, Bits>,
Function
>
{
ASIO_STATIC_CONSTEXPR(bool, is_valid = true);
ASIO_STATIC_CONSTEXPR(bool, is_noexcept = false);
typedef void result_type;
};
#endif // !defined(ASIO_HAS_DEDUCED_EXECUTE_MEMBER_TRAIT)
#if !defined(ASIO_HAS_DEDUCED_REQUIRE_MEMBER_TRAIT)
template <typename Allocator, unsigned int Bits>
struct require_member<
asio::io_context::basic_executor_type<Allocator, Bits>,
asio::execution::blocking_t::possibly_t
>
{
ASIO_STATIC_CONSTEXPR(bool, is_valid = true);
ASIO_STATIC_CONSTEXPR(bool, is_noexcept = false);
typedef asio::io_context::basic_executor_type<
Allocator, Bits> result_type;
};
template <typename Allocator, unsigned int Bits>
struct require_member<
asio::io_context::basic_executor_type<Allocator, Bits>,
asio::execution::blocking_t::never_t
>
{
ASIO_STATIC_CONSTEXPR(bool, is_valid = true);
ASIO_STATIC_CONSTEXPR(bool, is_noexcept = false);
typedef asio::io_context::basic_executor_type<
Allocator, Bits> result_type;
};
template <typename Allocator, unsigned int Bits>
struct require_member<
asio::io_context::basic_executor_type<Allocator, Bits>,
asio::execution::relationship_t::fork_t
>
{
ASIO_STATIC_CONSTEXPR(bool, is_valid = true);
ASIO_STATIC_CONSTEXPR(bool, is_noexcept = false);
typedef asio::io_context::basic_executor_type<
Allocator, Bits> result_type;
};
template <typename Allocator, unsigned int Bits>
struct require_member<
asio::io_context::basic_executor_type<Allocator, Bits>,
asio::execution::relationship_t::continuation_t
>
{
ASIO_STATIC_CONSTEXPR(bool, is_valid = true);
ASIO_STATIC_CONSTEXPR(bool, is_noexcept = false);
typedef asio::io_context::basic_executor_type<
Allocator, Bits> result_type;
};
template <typename Allocator, unsigned int Bits>
struct require_member<
asio::io_context::basic_executor_type<Allocator, Bits>,
asio::execution::outstanding_work_t::tracked_t
> : asio::detail::io_context_bits
{
ASIO_STATIC_CONSTEXPR(bool, is_valid = true);
ASIO_STATIC_CONSTEXPR(bool, is_noexcept = false);
typedef asio::io_context::basic_executor_type<
Allocator, Bits | outstanding_work_tracked> result_type;
};
template <typename Allocator, unsigned int Bits>
struct require_member<
asio::io_context::basic_executor_type<Allocator, Bits>,
asio::execution::outstanding_work_t::untracked_t
> : asio::detail::io_context_bits
{
ASIO_STATIC_CONSTEXPR(bool, is_valid = true);
ASIO_STATIC_CONSTEXPR(bool, is_noexcept = false);
typedef asio::io_context::basic_executor_type<
Allocator, Bits & ~outstanding_work_tracked> result_type;
};
template <typename Allocator, unsigned int Bits>
struct require_member<
asio::io_context::basic_executor_type<Allocator, Bits>,
asio::execution::allocator_t<void>
>
{
ASIO_STATIC_CONSTEXPR(bool, is_valid = true);
ASIO_STATIC_CONSTEXPR(bool, is_noexcept = false);
typedef asio::io_context::basic_executor_type<
std::allocator<void>, Bits> result_type;
};
template <unsigned int Bits,
typename Allocator, typename OtherAllocator>
struct require_member<
asio::io_context::basic_executor_type<Allocator, Bits>,
asio::execution::allocator_t<OtherAllocator>
>
{
ASIO_STATIC_CONSTEXPR(bool, is_valid = true);
ASIO_STATIC_CONSTEXPR(bool, is_noexcept = false);
typedef asio::io_context::basic_executor_type<
OtherAllocator, Bits> result_type;
};
#endif // !defined(ASIO_HAS_DEDUCED_REQUIRE_MEMBER_TRAIT)
#if !defined(ASIO_HAS_DEDUCED_QUERY_STATIC_CONSTEXPR_MEMBER_TRAIT)
template <typename Allocator, unsigned int Bits, typename Property>
struct query_static_constexpr_member<
asio::io_context::basic_executor_type<Allocator, Bits>,
Property,
typename asio::enable_if<
asio::is_convertible<
Property,
asio::execution::outstanding_work_t
>::value
>::type
> : asio::detail::io_context_bits
{
ASIO_STATIC_CONSTEXPR(bool, is_valid = true);
ASIO_STATIC_CONSTEXPR(bool, is_noexcept = true);
typedef asio::execution::outstanding_work_t result_type;
static ASIO_CONSTEXPR result_type value() ASIO_NOEXCEPT
{
return (Bits & outstanding_work_tracked)
? execution::outstanding_work_t(execution::outstanding_work.tracked)
: execution::outstanding_work_t(execution::outstanding_work.untracked);
}
};
template <typename Allocator, unsigned int Bits, typename Property>
struct query_static_constexpr_member<
asio::io_context::basic_executor_type<Allocator, Bits>,
Property,
typename asio::enable_if<
asio::is_convertible<
Property,
asio::execution::mapping_t
>::value
>::type
>
{
ASIO_STATIC_CONSTEXPR(bool, is_valid = true);
ASIO_STATIC_CONSTEXPR(bool, is_noexcept = true);
typedef asio::execution::mapping_t::thread_t result_type;
static ASIO_CONSTEXPR result_type value() ASIO_NOEXCEPT
{
return result_type();
}
};
#endif // !defined(ASIO_HAS_DEDUCED_QUERY_STATIC_CONSTEXPR_MEMBER_TRAIT)
#if !defined(ASIO_HAS_DEDUCED_QUERY_MEMBER_TRAIT)
template <typename Allocator, unsigned int Bits, typename Property>
struct query_member<
asio::io_context::basic_executor_type<Allocator, Bits>,
Property,
typename asio::enable_if<
asio::is_convertible<
Property,
asio::execution::blocking_t
>::value
>::type
>
{
ASIO_STATIC_CONSTEXPR(bool, is_valid = true);
ASIO_STATIC_CONSTEXPR(bool, is_noexcept = true);
typedef asio::execution::blocking_t result_type;
};
template <typename Allocator, unsigned int Bits, typename Property>
struct query_member<
asio::io_context::basic_executor_type<Allocator, Bits>,
Property,
typename asio::enable_if<
asio::is_convertible<
Property,
asio::execution::relationship_t
>::value
>::type
>
{
ASIO_STATIC_CONSTEXPR(bool, is_valid = true);
ASIO_STATIC_CONSTEXPR(bool, is_noexcept = true);
typedef asio::execution::relationship_t result_type;
};
template <typename Allocator, unsigned int Bits>
struct query_member<
asio::io_context::basic_executor_type<Allocator, Bits>,
asio::execution::context_t
>
{
ASIO_STATIC_CONSTEXPR(bool, is_valid = true);
ASIO_STATIC_CONSTEXPR(bool, is_noexcept = true);
typedef asio::io_context& result_type;
};
template <typename Allocator, unsigned int Bits>
struct query_member<
asio::io_context::basic_executor_type<Allocator, Bits>,
asio::execution::allocator_t<void>
>
{
ASIO_STATIC_CONSTEXPR(bool, is_valid = true);
ASIO_STATIC_CONSTEXPR(bool, is_noexcept = true);
typedef Allocator result_type;
};
template <typename Allocator, unsigned int Bits, typename OtherAllocator>
struct query_member<
asio::io_context::basic_executor_type<Allocator, Bits>,
asio::execution::allocator_t<OtherAllocator>
>
{
ASIO_STATIC_CONSTEXPR(bool, is_valid = true);
ASIO_STATIC_CONSTEXPR(bool, is_noexcept = true);
typedef Allocator result_type;
};
#endif // !defined(ASIO_HAS_DEDUCED_QUERY_MEMBER_TRAIT)
} // namespace traits
#endif // !defined(GENERATING_DOCUMENTATION)
} // namespace asio
#include "asio/detail/pop_options.hpp"

View File

@ -0,0 +1,58 @@
//
// multiple_exceptions.hpp
// ~~~~~~~~~~~~~~~~~~~~~~~
//
// Copyright (c) 2003-2020 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 ASIO_MULTIPLE_EXCEPTIONS_HPP
#define ASIO_MULTIPLE_EXCEPTIONS_HPP
#if defined(_MSC_VER) && (_MSC_VER >= 1200)
# pragma once
#endif // defined(_MSC_VER) && (_MSC_VER >= 1200)
#include "asio/detail/config.hpp"
#include <exception>
#include "asio/detail/push_options.hpp"
namespace asio {
#if defined(ASIO_HAS_STD_EXCEPTION_PTR) \
|| defined(GENERATING_DOCUMENTATION)
/// Exception thrown when there are multiple pending exceptions to rethrow.
class multiple_exceptions
: public std::exception
{
public:
/// Constructor.
ASIO_DECL multiple_exceptions(
std::exception_ptr first) ASIO_NOEXCEPT;
/// Obtain message associated with exception.
ASIO_DECL virtual const char* what() const
ASIO_NOEXCEPT_OR_NOTHROW;
/// Obtain a pointer to the first exception.
ASIO_DECL std::exception_ptr first_exception() const;
private:
std::exception_ptr first_;
};
#endif // defined(ASIO_HAS_STD_EXCEPTION_PTR)
// || defined(GENERATING_DOCUMENTATION)
} // namespace asio
#include "asio/detail/pop_options.hpp"
#if defined(ASIO_HEADER_ONLY)
# include "asio/impl/multiple_exceptions.ipp"
#endif // defined(ASIO_HEADER_ONLY)
#endif // ASIO_MULTIPLE_EXCEPTIONS_HPP

View File

@ -354,9 +354,232 @@ void io_context_service_test()
ASIO_CHECK(!asio::has_service<test_service>(ioc3));
}
void io_context_executor_query_test()
{
io_context ioc;
ASIO_CHECK(
&asio::query(ioc.get_executor(),
asio::execution::context)
== &ioc);
ASIO_CHECK(
asio::query(ioc.get_executor(),
asio::execution::blocking)
== asio::execution::blocking.possibly);
ASIO_CHECK(
asio::query(ioc.get_executor(),
asio::execution::blocking.possibly)
== asio::execution::blocking.possibly);
ASIO_CHECK(
asio::query(ioc.get_executor(),
asio::execution::outstanding_work)
== asio::execution::outstanding_work.untracked);
ASIO_CHECK(
asio::query(ioc.get_executor(),
asio::execution::outstanding_work.untracked)
== asio::execution::outstanding_work.untracked);
ASIO_CHECK(
asio::query(ioc.get_executor(),
asio::execution::relationship)
== asio::execution::relationship.fork);
ASIO_CHECK(
asio::query(ioc.get_executor(),
asio::execution::relationship.fork)
== asio::execution::relationship.fork);
ASIO_CHECK(
asio::query(ioc.get_executor(),
asio::execution::mapping)
== asio::execution::mapping.thread);
ASIO_CHECK(
asio::query(ioc.get_executor(),
asio::execution::allocator)
== std::allocator<void>());
}
void io_context_executor_execute_test()
{
io_context ioc;
int count = 0;
asio::execution::execute(ioc.get_executor(),
bindns::bind(increment, &count));
// No handlers can be called until run() is called.
ASIO_CHECK(!ioc.stopped());
ASIO_CHECK(count == 0);
ioc.run();
// The run() call will not return until all work has finished.
ASIO_CHECK(ioc.stopped());
ASIO_CHECK(count == 1);
count = 0;
ioc.restart();
asio::execution::execute(
asio::require(ioc.get_executor(),
asio::execution::blocking.possibly),
bindns::bind(increment, &count));
// No handlers can be called until run() is called.
ASIO_CHECK(!ioc.stopped());
ASIO_CHECK(count == 0);
ioc.run();
// The run() call will not return until all work has finished.
ASIO_CHECK(ioc.stopped());
ASIO_CHECK(count == 1);
count = 0;
ioc.restart();
asio::execution::execute(
asio::require(ioc.get_executor(),
asio::execution::blocking.never),
bindns::bind(increment, &count));
// No handlers can be called until run() is called.
ASIO_CHECK(!ioc.stopped());
ASIO_CHECK(count == 0);
ioc.run();
// The run() call will not return until all work has finished.
ASIO_CHECK(ioc.stopped());
ASIO_CHECK(count == 1);
count = 0;
ioc.restart();
ASIO_CHECK(!ioc.stopped());
asio::execution::execute(
asio::require(ioc.get_executor(),
asio::execution::blocking.never,
asio::execution::outstanding_work.tracked),
bindns::bind(increment, &count));
// No handlers can be called until run() is called.
ASIO_CHECK(!ioc.stopped());
ASIO_CHECK(count == 0);
ioc.run();
// The run() call will not return until all work has finished.
ASIO_CHECK(ioc.stopped());
ASIO_CHECK(count == 1);
count = 0;
ioc.restart();
asio::execution::execute(
asio::require(ioc.get_executor(),
asio::execution::blocking.never,
asio::execution::outstanding_work.untracked),
bindns::bind(increment, &count));
// No handlers can be called until run() is called.
ASIO_CHECK(!ioc.stopped());
ASIO_CHECK(count == 0);
ioc.run();
// The run() call will not return until all work has finished.
ASIO_CHECK(ioc.stopped());
ASIO_CHECK(count == 1);
count = 0;
ioc.restart();
asio::execution::execute(
asio::require(ioc.get_executor(),
asio::execution::blocking.never,
asio::execution::outstanding_work.untracked,
asio::execution::relationship.fork),
bindns::bind(increment, &count));
// No handlers can be called until run() is called.
ASIO_CHECK(!ioc.stopped());
ASIO_CHECK(count == 0);
ioc.run();
// The run() call will not return until all work has finished.
ASIO_CHECK(ioc.stopped());
ASIO_CHECK(count == 1);
count = 0;
ioc.restart();
asio::execution::execute(
asio::require(ioc.get_executor(),
asio::execution::blocking.never,
asio::execution::outstanding_work.untracked,
asio::execution::relationship.continuation),
bindns::bind(increment, &count));
// No handlers can be called until run() is called.
ASIO_CHECK(!ioc.stopped());
ASIO_CHECK(count == 0);
ioc.run();
// The run() call will not return until all work has finished.
ASIO_CHECK(ioc.stopped());
ASIO_CHECK(count == 1);
count = 0;
ioc.restart();
asio::execution::execute(
asio::prefer(
asio::require(ioc.get_executor(),
asio::execution::blocking.never,
asio::execution::outstanding_work.untracked,
asio::execution::relationship.continuation),
asio::execution::allocator(std::allocator<void>())),
bindns::bind(increment, &count));
// No handlers can be called until run() is called.
ASIO_CHECK(!ioc.stopped());
ASIO_CHECK(count == 0);
ioc.run();
// The run() call will not return until all work has finished.
ASIO_CHECK(ioc.stopped());
ASIO_CHECK(count == 1);
count = 0;
ioc.restart();
asio::execution::execute(
asio::prefer(
asio::require(ioc.get_executor(),
asio::execution::blocking.never,
asio::execution::outstanding_work.untracked,
asio::execution::relationship.continuation),
asio::execution::allocator),
bindns::bind(increment, &count));
// No handlers can be called until run() is called.
ASIO_CHECK(!ioc.stopped());
ASIO_CHECK(count == 0);
ioc.run();
// The run() call will not return until all work has finished.
ASIO_CHECK(ioc.stopped());
ASIO_CHECK(count == 1);
}
ASIO_TEST_SUITE
(
"io_context",
ASIO_TEST_CASE(io_context_test)
ASIO_TEST_CASE(io_context_service_test)
ASIO_TEST_CASE(io_context_executor_query_test)
ASIO_TEST_CASE(io_context_executor_execute_test)
)