include/boost/corosio/native/detail/coro_op.hpp

100.0% Lines (23 / 23) 100.0% Functions (6 / 6)
coro_op.hpp
f(x) Functions (6)
Line TLA Hits Source Code
1 //
2 // Copyright (c) 2026 Michael Vandeberg
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/corosio
8 //
9
10 #ifndef BOOST_COROSIO_NATIVE_DETAIL_CORO_OP_HPP
11 #define BOOST_COROSIO_NATIVE_DETAIL_CORO_OP_HPP
12
13 #include <boost/corosio/detail/config.hpp>
14 #include <boost/capy/continuation.hpp>
15 #include <boost/corosio/detail/object_ref.hpp>
16 #include <boost/corosio/detail/scheduler_op.hpp>
17 #include <boost/capy/ex/abandon.hpp>
18 #include <boost/capy/ex/executor_ref.hpp>
19
20 #include <atomic>
21 #include <coroutine>
22 #include <cstddef>
23 #include <memory>
24 #include <optional>
25 #include <stop_token>
26 #include <system_error>
27 #include <utility>
28
29 /*
30 Shared, non-template op envelope for every native backend — the readiness
31 reactors (epoll/kqueue/select), io_uring, and IOCP. It captures the part of
32 an async operation that is identical regardless of how completion is
33 reported: the coroutine to resume, the executor it dispatches on, the
34 output pointers, the stop_token wiring, the cancelled flag, and the
35 keepalive that holds the owning impl alive while the op is in flight.
36
37 What is deliberately NOT here (it differs by backend and stays in the
38 derived op layer):
39 - the result model: the reactors re-run the syscall and record
40 `errn`/`bytes_transferred` (reactor_op_base); io_uring stores the raw
41 `res`/`cqe_flags`; IOCP stores `dwError`/`bytes_transferred`. Each
42 decodes its own result.
43 - the submission + the kernel cancel action. Cancellation is unified only
44 at the call site via the virtual `on_cancel()` hook: the stop_callback
45 always targets `coro_op`, and each backend overrides `on_cancel()` —
46 the reactors route to the owning impl's cancel(), io_uring submits an
47 ASYNC_CANCEL SQE, IOCP calls the stored cancel_func_/CancelIoEx.
48
49 See tasks/proactor-dedup-decisions.md and coro-op-unification-scope.md.
50 */
51
52 namespace boost::corosio::detail {
53
54 /** Non-template op envelope shared by every native backend's operations.
55
56 `reactor_op_base`, `uring_op`, and `overlapped_op` all derive from this.
57 Derives from scheduler_op so ops queue intrusively and dispatch through the
58 function-pointer (io_uring/IOCP) or virtual (reactors) completion path —
59 hence both a default and a func_type constructor.
60
61 @note For IOCP, the concrete op multiply-inherits `OVERLAPPED` as its
62 first base (so `static_cast<OVERLAPPED*>` round-trips); `coro_op`
63 follows it.
64 */
65 struct coro_op : scheduler_op
66 {
67 /** Stop-callback handler: routes a stop_token firing to `on_cancel()`.
68
69 A single canceller type for both backends keeps `stop_cb` (and thus
70 `start()`) in this shared base; the backend-specific action lives
71 behind the `on_cancel()` virtual.
72 */
73 struct canceller
74 {
75 coro_op* op;
76 880x void operator()() const noexcept
77 {
78 880x op->on_cancel();
79 880x }
80 };
81
82 /// The awaiting coroutine's continuation. It lives in the awaitable,
83 /// which stays in the awaiting frame until resumption, so a
84 /// completion posted through an executor never shares this op's
85 /// storage, which is reused by the next operation.
86 capy::continuation* cont = nullptr;
87 capy::executor_ref ex;
88 std::error_code* ec_out = nullptr;
89 std::size_t* bytes_out = nullptr;
90
91 /// True for receive/read ops (drives the zero-byte == EOF decision).
92 bool is_read = false;
93 /// True when the submitted buffer was zero-length (suppresses EOF).
94 bool empty_buffer = false;
95
96 std::atomic<bool> cancelled{false};
97 std::optional<std::stop_callback<canceller>> stop_cb;
98
99 /// Keeps the owning impl alive while the op is in flight (the kernel
100 /// owns user buffers until completion). Released when the op resumes or
101 /// abandons its coroutine (see coro_op_complete.hpp).
102 detail::object_ref object_ref_;
103
104 /// Default-construct for virtual-dispatch backends (the reactors, which
105 /// override operator()/destroy() and leave func_ null).
106 49405x coro_op() noexcept = default;
107
108 /// Construct with the completion function for func-pointer dispatch
109 /// (io_uring / IOCP completion handlers).
110 explicit coro_op(func_type func) noexcept : scheduler_op(func) {}
111
112 /** Arm the stop-token callback. Call before the op is submitted.
113
114 Resets the cancellation flag and (re)arms `stop_cb` against @a token.
115 Derived ops that carry extra pre-submit state (e.g. io_uring's
116 `sqe_set`) extend this.
117 */
118 114494x void start(std::stop_token const& token)
119 {
120 114494x cancelled.store(false, std::memory_order_relaxed);
121 114494x stop_cb.reset();
122 114494x if (token.stop_possible())
123 3780x stop_cb.emplace(token, canceller{this});
124 114494x }
125
126 /** Give up the awaiting coroutine without resuming it.
127
128 Used when the operation is drained at teardown: the operation
129 holds the suspended coroutine, so it abandons it, which unwinds
130 its chain from the chain's owner. The keepalive is held across
131 the call because the unwind may run at once and destroy the
132 object whose impl stores this op; nothing here touches the op
133 afterwards.
134 */
135 1300x void destroy_awaiting() noexcept
136 {
137 1300x stop_cb.reset();
138 1300x auto keep = std::move(object_ref_);
139 1300x if (auto* c = std::exchange(cont, nullptr))
140 {
141 1300x if (c->h)
142 1300x capy::abandon(*c);
143 }
144 1300x }
145
146 /// Mark this op cancellation-requested. Shared by every backend.
147 559989x void request_cancel() noexcept
148 {
149 559989x cancelled.store(true, std::memory_order_release);
150 559989x }
151
152 /** Backend cancellation hook, invoked when the stop_token fires.
153
154 The default just records the request. Backends override to also
155 drive the kernel: io_uring submits an ASYNC_CANCEL SQE; IOCP calls
156 its stored cancel_func_ (CancelIoEx / wait-reactor deregister).
157 */
158 204x virtual void on_cancel() noexcept
159 {
160 204x request_cancel();
161 204x }
162 };
163
164 } // namespace boost::corosio::detail
165
166 #endif
167