409 lines
16 KiB
Plaintext
409 lines
16 KiB
Plaintext
[/
|
||
/ Copyright (c) 2003-2023 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)
|
||
/]
|
||
|
||
[section:asynchronous_operations Requirements on asynchronous operations]
|
||
|
||
This section uses the names `Alloc1`, `Alloc2`, `alloc1`, `alloc2`, `Args`,
|
||
`CompletionHandler`, `completion_handler`, `Executor1`, `Executor2`, `ex1`,
|
||
`ex2`, `f`, [^['i]], [^['N]], `Signatures`, `token`, `initiation`, `Initiation`,
|
||
[^T[sub ['i]]], [^t[sub ['i]]], `work1`, and `work2` as placeholders for
|
||
specifying the requirements below.
|
||
|
||
[heading General asynchronous operation concepts]
|
||
|
||
An ['initiating function] is a function which may be called to start an
|
||
asynchronous operation. A ['completion handler] is a function object that will
|
||
be invoked, at most once, with the result of the asynchronous operation.
|
||
|
||
The lifecycle of an asynchronous operation is comprised of the following events
|
||
and phases:
|
||
|
||
[mdash] Event 1: The asynchronous operation is started by a call to the
|
||
initiating function.
|
||
|
||
[mdash] Phase 1: The asynchronous operation is now ['outstanding].
|
||
|
||
[mdash] Event 2: The externally observable side effects of the asynchronous
|
||
operation, if any, are fully established. The completion handler is submitted
|
||
to an executor.
|
||
|
||
[mdash] Phase 2: The asynchronous operation is now ['completed].
|
||
|
||
[mdash] Event 3: The completion handler is called with the result of the
|
||
asynchronous operation.
|
||
|
||
In this library, all functions with the prefix `async_` are initiating
|
||
functions.
|
||
|
||
[heading Completion tokens and handlers]
|
||
|
||
Initiating functions:
|
||
|
||
[mdash] are function templates with template parameter `CompletionToken`;
|
||
|
||
[mdash] accept, as the final parameter, a ['completion token] object `token`
|
||
of type `CompletionToken`;
|
||
|
||
[mdash] specify one or more ['completion signatures], referred to below as a
|
||
variadic pack of call signatures (C++Std [func.def]) `Signatures`, that
|
||
determine the possible argument sets to the completion handler.
|
||
|
||
In this library, initiating functions specify a ['Completion signature] element
|
||
that defines the call signatures `Signatures`. The ['Completion signature]
|
||
elements in this library may have named parameters, and the results of an
|
||
asynchronous operation may be specified in terms of these names.
|
||
|
||
Completion token behaviour is determined through specialisation of the
|
||
`async_result` trait. These specialisations must have the form:
|
||
|
||
template <class CompletionToken, class... Signatures>
|
||
struct async_result
|
||
{
|
||
template<
|
||
class Initiation,
|
||
class RawCompletionToken,
|
||
class... Args
|
||
>
|
||
static initiating-fn-return-type initiate(
|
||
Initiation&& initiation,
|
||
RawCompletionToken&& token,
|
||
Args&&... args
|
||
);
|
||
};
|
||
|
||
An `async_result` specialisation’s implementation of the `initiate()` static
|
||
member function must:
|
||
|
||
* Transform the completion token into an object `completion_handler` of type
|
||
`CompletionHandler`. The type `CompletionHandler` must satisfy the
|
||
requirements of `Destructible` (C++Std [destructible]) and `MoveConstructible`
|
||
(C++Std [moveconstructible]), and be callable with the specified call
|
||
signatures.
|
||
|
||
* Cause the invocation of the function object `initiation` as if by calling:
|
||
``
|
||
std::forward<Initiation>(initiation)(
|
||
std::move(completion_handler),
|
||
std::forward<Args>(args)...);
|
||
``
|
||
|
||
This invocation of `initiation` may be immediate, or it may be deferred (e.g.
|
||
to support lazy evaluation). If `initiation` is deferred, the `initiation` and
|
||
`args...` objects must be decay-copied and moved as required.
|
||
|
||
The `async_result` trait must be specialised for the decayed type of a
|
||
`CompletionToken`. A helper function template `async_initiate` is provided to
|
||
simplify correct invocation of `async_result<>::initiate` for the appropriate
|
||
specialisation:
|
||
|
||
template<
|
||
class CompletionToken,
|
||
completion_signature... Signatures,
|
||
class Initiation,
|
||
class... Args
|
||
>
|
||
``['DEDUCED]`` async_initiate(
|
||
Initiation&& initiation,
|
||
CompletionToken& token,
|
||
Args&&... args
|
||
);
|
||
|
||
[inline_note No other requirements are placed on the type `CompletionToken`.]
|
||
|
||
[heading Automatic deduction of initiating function return type]
|
||
|
||
An initiating function returns [^async_initiate<CompletionToken,
|
||
Signatures...>(initiation, token, ['unspecified-args...])], where `initiation`
|
||
is a function object of unspecified type `Initiation`, which is defined as:
|
||
|
||
class Initiation
|
||
{
|
||
public:
|
||
using executor_type = Executor; // optional
|
||
executor_type get_executor() const noexcept; // optional
|
||
|
||
template <class CompletionHandler, ``['unspecified-args...]``>
|
||
void operator()(CompletionHandler&& completion_handler, ``['unspecified-args...]``) const;
|
||
};
|
||
|
||
For the sake of exposition, this library sometimes annotates functions with a
|
||
return type ['[^DEDUCED]]. For every function declaration that returns
|
||
['[^DEDUCED]], the meaning is equivalent to an automatically deduced return
|
||
type, having the type of the expression [^async_initiate<CompletionToken,
|
||
Signatures...>(initiation, token, ['unspecified-args...])].
|
||
|
||
\[['Example:] Given an asynchronous operation with a single ['Completion
|
||
signature] `void(R1 r1, R2 r2)`, an initiating function meeting these
|
||
requirements may be implemented as follows:
|
||
|
||
template<class CompletionToken>
|
||
auto async_xyz(T1 t1, T2 t2, CompletionToken&& token)
|
||
{
|
||
return async_initiate<CompletionToken, void(R1, R2)>(
|
||
[](auto completion_handler, T1 t1, T2 t2)
|
||
{
|
||
// initiate the operation and cause completion_handler to be invoked
|
||
// with the result
|
||
}, token, std::move(t1), std::move(t2));
|
||
}
|
||
|
||
The concepts `completion_token_for` and `completion_handler_for` may also be
|
||
used to improve compile-time diagnostics:
|
||
|
||
template<completion_token_for<void(R1, R2)> CompletionToken>
|
||
auto async_xyz(T1 t1, T2 t2, CompletionToken&& token)
|
||
{
|
||
return async_initiate<CompletionToken, void(R1, R2)>(
|
||
[](completion_handler_for<void(R1, R2)> auto completion_handler, T1 t1, T2 t2)
|
||
{
|
||
// initiate the operation and cause completion_handler to be invoked
|
||
// with the result
|
||
}, token, std::move(t1), std::move(t2));
|
||
}
|
||
|
||
Initiation functions may also be implemented using the `async_result` trait
|
||
directly:
|
||
|
||
template<class CompletionToken>
|
||
auto async_xyz(T1 t1, T2 t2, CompletionToken&& token)
|
||
{
|
||
return async_result<decay_t<CompletionToken>, void(R1, R2)>::initiate(
|
||
[](auto completion_handler, T1 t1, T2 t2)
|
||
{
|
||
// initiate the operation and cause completion_handler to be invoked
|
||
// with the result
|
||
}, forward<CompletionToken>(token), std::move(t1), std::move(t2));
|
||
}
|
||
|
||
Note the use of `decay_t` and `forward` applied to the `CompletionToken` type.
|
||
However, the first form is preferred as it preserves compatibility with legacy
|
||
completion token requirements.
|
||
|
||
'''—'''['end example]\]
|
||
|
||
[heading Lifetime of initiating function arguments]
|
||
|
||
Unless otherwise specified, the lifetime of arguments to initiating functions
|
||
shall be treated as follows:
|
||
|
||
[mdash] If the parameter has a pointer type or has a type of lvalue reference
|
||
to non-const, the implementation may assume the validity of the pointee or
|
||
referent, respectively, until the completion handler is invoked. [inline_note
|
||
In other words, the program must guarantee the validity of the argument until
|
||
the completion handler is invoked.]
|
||
|
||
[mdash] Otherwise, the implementation must not assume the validity of the
|
||
argument after the initiating function completes. [inline_note In other words,
|
||
the program is not required to guarantee the validity of the argument after the
|
||
initiating function completes.] The implementation may make copies of the
|
||
argument, and all copies shall be destroyed no later than immediately after
|
||
invocation of the completion handler.
|
||
|
||
[heading Non-blocking requirements on initiating functions]
|
||
|
||
An initiating function shall not block (C++Std [defns.block]) the calling
|
||
thread pending completion of the outstanding operation.
|
||
|
||
[inline_note Initiating functions may still block the calling thread for other
|
||
reasons. For example, an initiating function may lock a mutex in order to
|
||
synchronize access to shared data.]
|
||
|
||
[heading Associated executor]
|
||
|
||
Certain objects that participate in asynchronous operations have an
|
||
['associated executor]. These are obtained as specified in the sections below.
|
||
|
||
[heading Associated I/O executor]
|
||
|
||
An asynchronous operation has an associated executor satisfying the [link
|
||
boost_asio.reference.Executor1 `Executor`] requirements. If not otherwise specified by
|
||
the asynchronous operation, this associated executor is an object of type
|
||
`system_executor`.
|
||
|
||
All asynchronous operations in this library have an associated I/O executor
|
||
object that is determined as follows:
|
||
|
||
[mdash] If the initiating function is a member function, the associated
|
||
executor is that returned by the `get_executor` member function on the same
|
||
object.
|
||
|
||
[mdash] If the initiating function is not a member function, the associated
|
||
executor is that returned by the `get_executor` member function of the first
|
||
argument to the initiating function.
|
||
|
||
The operation's associated I/O executor may be exposed via the `Initiation`
|
||
function object's `executor_type` type alias and `get_executor()` member
|
||
function.
|
||
|
||
Let `Executor1` be the type of the associated executor. Let `ex1` be a value of
|
||
type `Executor1`, representing the associated executor object obtained as
|
||
described above.
|
||
|
||
[heading Associated completion handler executor]
|
||
|
||
A completion handler object of type `CompletionHandler` has an associated
|
||
executor of type `Executor2` satisfying the [link boost_asio.reference.Executor1
|
||
Executor requirements]. The type `Executor2` is
|
||
`associated_executor_t<CompletionHandler, Executor1>`. Let `ex2` be a value of
|
||
type `Executor2` obtained by performing
|
||
`get_associated_executor(completion_handler, ex1)`.
|
||
|
||
[heading Associated immediate completion executor]
|
||
|
||
A completion handler object of type `CompletionHandler` has an associated
|
||
immediate executor of type `Executor3` satisfying the
|
||
[link boost_asio.reference.Executor1 Executor requirements]. The type `Executor3` is
|
||
`associated_immediate_executor_t<CompletionHandler, Executor1>`. Let `ex3` be a
|
||
value of type `Executor3` obtained by performing
|
||
`get_associated_immediate_executor(completion_handler, ex1)`.
|
||
|
||
[heading Outstanding work]
|
||
|
||
If the operation does not complete immediately (that is, the operation does not
|
||
complete within the thread of execution calling the initiating function, before
|
||
the initiating function returns) then, until the asynchronous operation has
|
||
completed, the asynchronous operation shall maintain:
|
||
|
||
[mdash] an object `work1` of type `executor_work_guard<Executor1>`, initialized
|
||
as `work1(ex1)`, and where `work1.owns_work() == true`; and
|
||
|
||
[mdash] an object `work2` of type `executor_work_guard<Executor2>`, initialized
|
||
as `work2(ex2)`, and where `work2.owns_work() == true`.
|
||
|
||
[heading Allocation of intermediate storage]
|
||
|
||
Asynchronous operations may allocate memory. [inline_note Such as a data
|
||
structure to store copies of the `completion_handler` object and the initiating
|
||
function's arguments.]
|
||
|
||
Let `Alloc1` be a type, satisfying the [link boost_asio.reference.ProtoAllocator
|
||
`ProtoAllocator`] requirements, that represents the asynchronous operation's
|
||
default allocation strategy. [inline_note Typically `std::allocator<void>`.]
|
||
Let `alloc1` be a value of type `Alloc1`.
|
||
|
||
A completion handler object of type `CompletionHandler` has an associated
|
||
allocator object `alloc2` of type `Alloc2` satisfying the [link
|
||
boost_asio.reference.ProtoAllocator `ProtoAllocator`] requirements. The type `Alloc2`
|
||
is `associated_allocator_t<CompletionHandler, Alloc1>`. Let `alloc2` be a value
|
||
of type `Alloc2` obtained by performing
|
||
`get_associated_allocator(completion_handler, alloc1)`.
|
||
|
||
The asynchronous operations defined in this library:
|
||
|
||
[mdash] If required, allocate memory using only the completion handler's
|
||
associated allocator.
|
||
|
||
[mdash] Prior to completion handler execution, deallocate any memory allocated
|
||
using the completion handler's associated allocator.
|
||
|
||
[inline_note The implementation may perform operating system or underlying API
|
||
calls that perform memory allocations not using the associated allocator.
|
||
Invocations of the allocator functions may not introduce data races (See C++Std
|
||
\[res.on.data.races\]).]
|
||
|
||
[heading Execution of completion handler on completion of asynchronous operation]
|
||
|
||
Let `Args...` be the argument types of a completion signature in
|
||
`Signatures...` and let [^['N]] be `sizeof...(Args)`. Let [^['i]] be in the
|
||
range [half_open_range `0`,[^['N]]]. Let [^T[sub ['i]]] be the [^['i]]th type
|
||
in `Args...` and let [^t[sub ['i]]] be the [^['i]]th completion handler
|
||
argument associated with [^T[sub ['i]]].
|
||
|
||
Let `f` be a function object that:
|
||
|
||
[mdash] is callable as `f()`, and when so called invokes `completion_handler`
|
||
as if by [^std::move(completion_handler)(forward<T[sub ['0]]>(t[sub ['0]]), ...,
|
||
forward<T[sub ['N-1]]>(t[sub ['N-1]]))];
|
||
|
||
[mdash] has an associated executor such that `get_associated_executor(f, ex1)`
|
||
returns an executor that is equal to `ex2`; and
|
||
|
||
[mdash] has an associated allocator such that `get_associated_allocator(f,
|
||
alloc1)` returns an allocator that is equal to `alloc2`.
|
||
|
||
[inline_note These associated executor and allocator requirements on `f` are
|
||
typically implemented by specialising the associator traits for `f` so that they
|
||
forward to the associator traits for `completion_handler`.]
|
||
|
||
If an asynchonous operation completes immediately (that is, the operation
|
||
completes within the thread of execution calling the initiating function, and
|
||
before the initiating function returns), the completion handler shall be
|
||
submitted for execution as if by performing `post(ex1, std::move(f))`.
|
||
|
||
Otherwise, when the operation completes, the completion handler shall be
|
||
submitted for execution as if by performing `dispatch(ex2, std::move(f))`.
|
||
|
||
[heading Optimisation of immediate completion]
|
||
|
||
If an asynchronous operation completes immediately then, as an optimisation,
|
||
the operation may either:
|
||
|
||
[mdash] obtain the associated immediate completion executor `ex3` by performing
|
||
`get_associated_immediate_executor(completion_handler, ex1)`, and then
|
||
submit the completion handler for execution as if by performing `dispatch(ex3,
|
||
std::move(f))`; or
|
||
|
||
[mdash] submit the completion handler for execution by performing the expression
|
||
`post(ex2, std::move(f))`, if that expression is well-formed.
|
||
|
||
[inline_note If `completion_handler` does not customise the associated immediate
|
||
executor, the behaviour of the first optimisation is equivalent to `post(ex1,
|
||
std::move(f))`.]
|
||
|
||
[heading Completion handlers and exceptions]
|
||
|
||
Completion handlers are permitted to throw exceptions. The effect of any
|
||
exception propagated from the execution of a completion handler is determined
|
||
by the executor which is executing the completion handler.
|
||
|
||
[heading Default completion tokens]
|
||
|
||
Every I/O executor type has an associated default completion token type. This is
|
||
specified via the `default_completion_token` trait. This trait may be used in
|
||
asynchronous operation declarations as follows:
|
||
|
||
template <
|
||
typename IoObject,
|
||
typename CompletionToken =
|
||
typename default_completion_token<
|
||
typename IoObject::executor_type
|
||
>::type
|
||
>
|
||
auto async_xyz(
|
||
IoObject& io_object,
|
||
CompletionToken&& token =
|
||
typename default_completion_token<
|
||
typename IoObject::executor_type
|
||
>::type{}
|
||
);
|
||
|
||
If not specialised, this trait type is `void`, meaning no default completion
|
||
token type is available for the given I/O executor.
|
||
|
||
\[['Example:] The `default_completion_token` trait is specialised for the
|
||
`use_awaitable` completion token so that it may be used as shown in the
|
||
following example:
|
||
|
||
auto socket = use_awaitable.as_default_on(tcp::socket(my_context));
|
||
// ...
|
||
co_await socket.async_connect(my_endpoint); // Defaults to use_awaitable.
|
||
|
||
In this example, the type of the `socket` object is transformed from
|
||
`tcp::socket` to have an I/O executor with the default completion token set to
|
||
`use_awaitable`.
|
||
|
||
Alternatively, the socket type may be computed directly:
|
||
|
||
using tcp_socket = use_awaitable_t<>::as_default_on_t<tcp::socket>;
|
||
tcp_socket socket(my_context);
|
||
// ...
|
||
co_await socket.async_connect(my_endpoint); // Defaults to use_awaitable.
|
||
|
||
'''—'''['end example]\]
|
||
|
||
[endsect]
|