include/boost/capy/ex/io_env.hpp

100.0% Lines (5/5) 100.0% Functions (2/2)
Line TLA Hits Source Code
1 //
2 // Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com)
3 //
4 // Distributed under the Boost Software License, Version 1.0. (See accompanying
5 // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6 //
7 // Official repository: https://github.com/cppalliance/capy
8 //
9
10 #ifndef BOOST_CAPY_IO_ENV_HPP
11 #define BOOST_CAPY_IO_ENV_HPP
12
13 #include <boost/capy/detail/config.hpp>
14 #include <boost/capy/ex/executor_ref.hpp>
15
16 #include <coroutine>
17 #include <memory_resource>
18 #include <stop_token>
19
20 namespace boost {
21 namespace capy {
22
23 /** Callable that posts a coroutine handle to an executor.
24
25 Use this as the callback type for `std::stop_callback` instead
26 of a raw `std::coroutine_handle<>`. Raw handles resume the
27 coroutine inline on whatever thread calls `request_stop()`,
28 which bypasses the executor and corrupts the thread-local
29 frame allocator.
30
31 Prefer @ref io_env::post_resume and the @ref stop_resume_callback
32 alias to construct these—see examples there.
33
34 @see io_env::post_resume, stop_resume_callback
35 */
36 struct resume_via_post
37 {
38 executor_ref ex;
39 std::coroutine_handle<> h;
40
41 // post() must not throw; stop_callback requires a
42 // non-throwing invocable.
43 254x void operator()() const noexcept
44 {
45 254x ex.post(h);
46 254x }
47 };
48
49 /** Execution environment for IoAwaitables.
50
51 This struct bundles the execution context passed through
52 coroutine chains via the IoAwaitable protocol. It contains
53 the executor for resumption, a stop token for cancellation,
54 and an optional frame allocator for coroutine frame allocation.
55
56 @par Lifetime
57
58 Launch functions (@ref run_async, @ref run) own the `io_env` and
59 guarantee it outlives all tasks and awaitables in the launched
60 chain. Awaitables receive `io_env const*` in `await_suspend`
61 and should store it directly, never copy the pointed-to object.
62
63 @par Stop Callback Contract
64
65 Awaitables that register a `std::stop_callback` **must not**
66 resume the coroutine handle directly. The callback fires
67 synchronously on the thread that calls `request_stop()`, which
68 may not be an executor-managed thread. Resuming inline poisons
69 that thread's TLS frame allocator with the pool's allocator,
70 causing use-after-free on the next coroutine allocation.
71
72 Use @ref io_env::post_resume and @ref stop_resume_callback:
73 @code
74 std::optional<stop_resume_callback> stop_cb_;
75 // In await_suspend:
76 stop_cb_.emplace(env->stop_token, env->post_resume(h));
77 @endcode
78
79 @par Thread Safety
80 The referenced executor and allocator must remain valid
81 for the lifetime of any coroutine using this environment.
82
83 @see IoAwaitable, IoRunnable, resume_via_post
84 */
85 struct io_env
86 {
87 /** The executor for coroutine resumption. */
88 executor_ref executor;
89
90 /** The stop token for cancellation propagation. */
91 std::stop_token stop_token;
92
93 /** The frame allocator for coroutine frame allocation.
94
95 When null, the default allocator is used.
96 */
97 std::pmr::memory_resource* frame_allocator = nullptr;
98
99 /** Create a resume_via_post callable for this environment.
100
101 Convenience method for registering @ref stop_resume_callback
102 instances. Equivalent to `resume_via_post{executor, h}`.
103
104 @par Example
105 @code
106 stop_cb_.emplace(env->stop_token, env->post_resume(h));
107 @endcode
108
109 @param h The coroutine handle to post on cancellation.
110
111 @return A @ref resume_via_post callable that holds a
112 non-owning @ref executor_ref and the coroutine handle.
113 The callable must not outlive the executor it references.
114
115 @see resume_via_post, stop_resume_callback
116 */
117 resume_via_post
118 254x post_resume(std::coroutine_handle<> h) const noexcept
119 {
120 254x return resume_via_post{executor, h};
121 }
122 };
123
124 /** Type alias for a stop callback that posts through the executor.
125
126 Use this to declare the stop callback member in your awaitable:
127 @code
128 std::optional<stop_resume_callback> stop_cb_;
129 @endcode
130
131 @see resume_via_post, io_env::post_resume
132 */
133 using stop_resume_callback = std::stop_callback<resume_via_post>;
134
135 } // capy
136 } // boost
137
138 #endif
139