diff --git a/doc/modules/ROOT/pages/2.cpp20-coroutines/2b.syntax.adoc b/doc/modules/ROOT/pages/2.cpp20-coroutines/2b.syntax.adoc index e98629b68..78cfda5ba 100644 --- a/doc/modules/ROOT/pages/2.cpp20-coroutines/2b.syntax.adoc +++ b/doc/modules/ROOT/pages/2.cpp20-coroutines/2b.syntax.adoc @@ -50,13 +50,24 @@ The `promise_type` nested structure provides the minimum scaffolding the compile The presence of `co_return` transforms what looks like a regular function into a coroutine. +[[awaitables_and_awaiters]] == Awaitables and Awaiters -When you write `co_await expr`, the expression `expr` must be an *awaitable*—something that knows how to suspend and resume a coroutine. The awaitable produces an *awaiter* object that implements three methods: +When you see expression `co_await aw`, the sub-expression `aw` is an *awaitable*. +An awaitable conveys state and the type, but on its own it doesn't do much. +When expression `co_await aw` is evaluated, as the first thing, awaitable `aw` +is used to produce an *awaiter*. This is done by first trying to call member function +`await_transform` of the coroutine's promise object, if present, and then calling member +function `operator co_await` -- if present -- on the result. The result is the awaiter. +It is next used to control the coroutine suspension and resumption via its interface +in the form of three member functions: -* `await_ready()` — Returns `true` if the result is immediately available and no suspension is needed -* `await_suspend(handle)` — Called when the coroutine suspends; receives a handle to the coroutine for later resumption -* `await_resume()` — Called when the coroutine resumes; its return value becomes the value of the `co_await` expression +* `await_ready()` — Returns `true` if the result is immediately available and no suspension is needed. +* `await_suspend(handle)` — Called when the coroutine suspends; receives a handle to the coroutine for later resumption. +* `await_resume()` — Called when the coroutine resumes; its return value becomes the value of the `co_await` expression. + +NOTE: In special cases an awaiter and an awaitable can be the same object. + In other special cases, an awaitable can be as simple a type as an `int`. === Example: Understanding the Awaiter Protocol @@ -91,6 +102,11 @@ Study this execution flow: The variable `i` inside `counter` maintains its value across all these suspension and resumption cycles. + +NOTE: While only a coroutine, by definition, can evaluate a `co_await` expression, + the awaiter — that is, the thing being awaited on — need not be a coroutine, or invoke one. + + === Standard Awaiters The {cpp} standard library provides two predefined awaiters: diff --git a/doc/modules/ROOT/pages/2.cpp20-coroutines/2c.machinery.adoc b/doc/modules/ROOT/pages/2.cpp20-coroutines/2c.machinery.adoc index 4a9ac57fc..d11e13981 100644 --- a/doc/modules/ROOT/pages/2.cpp20-coroutines/2c.machinery.adoc +++ b/doc/modules/ROOT/pages/2.cpp20-coroutines/2c.machinery.adoc @@ -30,7 +30,7 @@ Called if an exception escapes the coroutine body. Typically you either rethrow The compiler transforms your coroutine body into something resembling this pseudocode: -NOTE: The `co_await` keywords below are intentional. This mirrors the {cpp} standard's own description ({cpp}20 [dcl.fct.def.coroutine]/5), which uses `co_await` to express the logical suspension points. The compiler expands each `co_await` into the full awaiter protocol (`await_ready`, `await_suspend`, `await_resume`) as described in xref:2.cpp20-coroutines/2b.syntax.adoc#_awaitables_and_awaiters[Awaitables and Awaiters]. +NOTE: The `co_await` keywords below are intentional. This mirrors the {cpp} standard's own description ({cpp}20 [dcl.fct.def.coroutine]/5), which uses `co_await` to express the logical suspension points. The compiler expands each `co_await` into the full awaiter protocol (`await_ready`, `await_suspend`, `await_resume`) as described in xref:2.cpp20-coroutines/2b.syntax.adoc#awaitables_and_awaiters[Awaitables and Awaiters]. .What the compiler generates [source,cpp,role=pseudocode] diff --git a/doc/modules/ROOT/pages/4.coroutines/4d.io-awaitable.adoc b/doc/modules/ROOT/pages/4.coroutines/4d.io-awaitable.adoc index 0114c7ea1..1cbf3fc81 100644 --- a/doc/modules/ROOT/pages/4.coroutines/4d.io-awaitable.adoc +++ b/doc/modules/ROOT/pages/4.coroutines/4d.io-awaitable.adoc @@ -33,7 +33,12 @@ Capy uses *forward propagation*: the caller passes context to the awaitable thro == The Two-Argument await_suspend -The IoAwaitable protocol extends `await_suspend` to receive context: +In order to make sure — and statically enforce — that every awaitable ``co_await``ed in Capy-coroutines +footnote:[The only awaitables excluded from this constraint are "tag objects" from namespace cpp:this_coro[]: cpp:this_coro::environment[environment], cpp:this_coro::executor[executor], cpp:this_coro::frame_allocator[frame_allocator] and cpp:this_coro::stop_token[stop_token].] +participates in the execution environment propagation, the awaitables +are required to provide member function `await_suspend`, similar +to the one present in C++ awaiters, which takes the second parameter representing +the execution environment: [source,cpp] ---- @@ -48,18 +53,23 @@ This signature receives: ** cpp:io_env::stop_token[env->stop_token] — A stop token for cooperative cancellation ** cpp:io_env::frame_allocator[env->frame_allocator] — An optional frame allocator -Many IoAwaitables return `std::coroutine_handle<>` to enable symmetric transfer. +Following the convention of awaiters in C++, the return type of the function has to be one of: -== IoAwaitable Concept +* `void`, +* `bool`, +* `std::coroutine_handle
` for any type `P`.
-An awaitable satisfies cpp:IoAwaitable[] if `a.await_suspend(h, env)` is a valid expression:
+Many types modelling cpp:IoAwaitable[] return `std::coroutine_handle<>` to enable
+xref:2.cpp20-coroutines/2d.advanced.adoc#_symmetric_transfer[symmetric transfer].
-[source,cpp]
-----
-include::example$snippets/4d_io_awaitable.cpp[tag=io_awaitable_concept]
-----
+== IoAwaitable Concept
+
+Concept cpp:IoAwaitable[] specifies all requirements on a type that can be ``co_await``ed
+in Capy-coroutines. Apart from the two-argument `await_suspend`, the type also needs to
+provide the two member functions specific to awaiters: `await_ready` and `await_resume`.
-The concept constrains only the two-argument `await_suspend` that receives the `io_env`. It does not require `await_ready` or `await_resume`, nor does it constrain the return type of `await_suspend`. A complete awaitable still provides `await_ready` and `await_resume` so it can be `co_await`-ed.
+In order to model cpp:IoAwaitable[] a type need not be associated with any coroutine. In fact,
+this is the case for a number of cpp:IoAwaitable[]s in Corosio.
== IoRunnable Concept
@@ -82,9 +92,8 @@ Capy's cpp:task[task ` for any type `P`.
+
+ If the return type is `void`, instructs the coroutine machinery that the control shall be
+ returned to the resumer of the coroutine that invoked the `co_await` expression.
+ Takes the ownership for scheduling the
+ resumption of the coroutine represented by `h` via either `env->executor.post` or `env->executor.dispatch`.
+
+ If the return type is `bool`:
+
+ @li value @c true indicates the behavior equivalent to that of the @c void return type;
+
+ @li value @c false instructs the coroutine machinery to resume the coroutine represented by @c h and to immediately invoke @c await_resume .
+
+ If the return type is `std::coroutine_handle `, instructs the compiler to resume
+ the coroutine represented by the returned handle, in a tail call manner (not consuming the stack).
+
+ _Note_:
+
+ @li Returning @c h is equivalent to returning @c false in the @c bool return type signature.
+ @li Returning @c std::noop_coroutine() is equivalent to using the @c void return type, returning @c true in the @c bool return type signature.
+
+
+ _Lifetime_: The object of type @ref io_env pointed to by `env` remains valid
+ for the duration of the async operation represented by `A`. This is guarantee is maintained
+ by @ref run, @ref run_async and the other functions that "start a task".
+
+
+ @par `await_resume`
+
+ In the context of processing a `co_await` expression, when the suspended coroutine is being resumed or
+ upon immediate resumption, returns a value — if any — that shall be returned from the `co_await` expression.
+
+ If it throws an exception, the exception is propagated out of the `co_await` expression into the awaiting coroutine's
+ scope.
+
+ _Returns_: value intended to be returned from the `co_await` expression. The return type determines the type of
+ the enclosing `co_await` expression and can be `void`.
+
+
- T await_resume();
- };
- @endcode
@par Example
+ The example demonstrates a "leaf" awaitable: one that is associated directly with
+ a system's I/O operation but no coroutine.
+
@code
- struct my_io_op
+ class my_awaitable
{
- io_env const* env_ = nullptr;
- continuation cont_;
-
- auto await_suspend(
- std::coroutine_handle<> h,
- io_env const* env )
+ capy::io_env const* env_ = nullptr;
+ capy::continuation cont_;
+ std::error_code ec_ {};
+
+ public:
+ bool await_ready() const noexcept { return false; }
+
+ std::coroutine_handle<>
+ await_suspend(std::coroutine_handle<> h, capy::io_env const* env) noexcept
{
- env_ = env;
- cont_ = continuation{h};
- // Pass members by value; capturing this
- // risks use-after-free in async callbacks.
- // When the async operation completes, resume
- // via executor.post(cont_) or executor.dispatch(cont_)
- // rather than calling h.resume() directly.
- start_async(
- env_->stop_token,
- env_->executor,
- cont_ );
- return std::noop_coroutine();
+ env_ = env; // store the pointer, never a copy
+ cont_ = capy::continuation{h};
+
+ auto completion = [this](std::error_code ec) noexcept
+ {
+ ec_ = ec; // publish result; touch *this
+ env_->executor.post(cont_); // only before post, never after
+ };
+
+ start_my_io_op(env_->stop_token, completion);
+
+ return std::noop_coroutine(); // go back to scheduler
}
-
- bool await_ready() const noexcept { return false; }
- void await_resume() {}
+
+ capy::io_result<> await_resume() const noexcept { return {ec_}; }
};
@endcode
- @see IoRunnable
+ @par Models
+
+ General-purpose class templates that model `IoAwaitable`: @ref task, @ref quitter, @ref immediate.
+
+
+ @see @ref IoRunnable, @ref io_env, @ref executor_ref
*/
template next;
std::coroutine_handle<>
await_suspend(std::coroutine_handle<>, io_env const*)
{
@@ -59,22 +60,36 @@ class await_suspend_helper_test
{
auto const h = std::noop_coroutine();
- // void -> noop_coroutine, and the awaitable was invoked.
- void_awaitable va;
- BOOST_TEST(call_await_suspend(&va, h, nullptr) == h);
- BOOST_TEST(va.suspended);
+ {
+ // void -> noop_coroutine, and the awaitable was invoked.
+ void_awaitable va;
+ BOOST_TEST(call_await_suspend(&va, h, nullptr) == h);
+ BOOST_TEST(va.suspended);
+ }
- // bool true -> noop_coroutine (stay suspended).
- bool_awaitable bt{true};
- BOOST_TEST(call_await_suspend(&bt, h, nullptr) == h);
+ {
+ // bool true -> noop_coroutine (stay suspended).
+ bool_awaitable bt{true};
+ BOOST_TEST(call_await_suspend(&bt, h, nullptr) == h);
+ }
- // bool false -> the original handle (resume).
- bool_awaitable bf{false};
- BOOST_TEST(call_await_suspend(&bf, h, nullptr) == h);
+ {
+ // bool false -> the original handle (resume).
+ bool_awaitable bf{false};
+ BOOST_TEST(call_await_suspend(&bf, h, nullptr) == h);
+ }
- // handle -> the returned handle.
- handle_awaitable ha{h};
- BOOST_TEST(call_await_suspend(&ha, h, nullptr) == h);
+ {
+ // handle -> the returned handle.
+ handle_awaitable