TLA Line data 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/scheduler_op.hpp>
16 : #include <boost/capy/ex/executor_ref.hpp>
17 :
18 : #include <atomic>
19 : #include <coroutine>
20 : #include <cstddef>
21 : #include <memory>
22 : #include <optional>
23 : #include <stop_token>
24 : #include <system_error>
25 :
26 : /*
27 : Shared, non-template op envelope for every native backend — the readiness
28 : reactors (epoll/kqueue/select), io_uring, and IOCP. It captures the part of
29 : an async operation that is identical regardless of how completion is
30 : reported: the coroutine to resume, the executor it dispatches on, the
31 : output pointers, the stop_token wiring, the cancelled flag, and the
32 : keepalive that holds the owning impl alive while the op is in flight.
33 :
34 : What is deliberately NOT here (it differs by backend and stays in the
35 : derived op layer):
36 : - the result model: the reactors re-run the syscall and record
37 : `errn`/`bytes_transferred` (reactor_op_base); io_uring stores the raw
38 : `res`/`cqe_flags`; IOCP stores `dwError`/`bytes_transferred`. Each
39 : decodes its own result.
40 : - the submission + the kernel cancel action. Cancellation is unified only
41 : at the call site via the virtual `on_cancel()` hook: the stop_callback
42 : always targets `coro_op`, and each backend overrides `on_cancel()` —
43 : the reactors route to the owning impl's cancel(), io_uring submits an
44 : ASYNC_CANCEL SQE, IOCP calls the stored cancel_func_/CancelIoEx.
45 :
46 : See tasks/proactor-dedup-decisions.md and coro-op-unification-scope.md.
47 : */
48 :
49 : namespace boost::corosio::detail {
50 :
51 : /** Non-template op envelope shared by every native backend's operations.
52 :
53 : `reactor_op_base`, `uring_op`, and `overlapped_op` all derive from this.
54 : Derives from scheduler_op so ops queue intrusively and dispatch through the
55 : function-pointer (io_uring/IOCP) or virtual (reactors) completion path —
56 : hence both a default and a func_type constructor.
57 :
58 : @note For IOCP, the concrete op multiply-inherits `OVERLAPPED` as its
59 : first base (so `static_cast<OVERLAPPED*>` round-trips); `coro_op`
60 : follows it.
61 : */
62 : struct coro_op : scheduler_op
63 : {
64 : /** Stop-callback handler: routes a stop_token firing to `on_cancel()`.
65 :
66 : A single canceller type for both backends keeps `stop_cb` (and thus
67 : `start()`) in this shared base; the backend-specific action lives
68 : behind the `on_cancel()` virtual.
69 : */
70 : struct canceller
71 : {
72 : coro_op* op;
73 HIT 407 : void operator()() const noexcept
74 : {
75 407 : op->on_cancel();
76 407 : }
77 : };
78 :
79 : std::coroutine_handle<> h;
80 : capy::continuation cont;
81 : capy::executor_ref ex;
82 : std::error_code* ec_out = nullptr;
83 : std::size_t* bytes_out = nullptr;
84 :
85 : /// True for receive/read ops (drives the zero-byte == EOF decision).
86 : bool is_read = false;
87 : /// True when the submitted buffer was zero-length (suppresses EOF).
88 : bool empty_buffer = false;
89 :
90 : std::atomic<bool> cancelled{false};
91 : std::optional<std::stop_callback<canceller>> stop_cb;
92 :
93 : /// Keeps the owning impl alive while the op is in flight (the kernel
94 : /// owns user buffers until completion). Dropped in the handler's resume
95 : /// tail (see coro_op_complete.hpp).
96 : std::shared_ptr<void> impl_ptr;
97 :
98 : /// Default-construct for virtual-dispatch backends (the reactors, which
99 : /// override operator()/destroy() and leave func_ null).
100 95654 : coro_op() noexcept = default;
101 :
102 : /// Construct with the completion function for func-pointer dispatch
103 : /// (io_uring / IOCP completion handlers).
104 : explicit coro_op(func_type func) noexcept : scheduler_op(func) {}
105 :
106 : /** Arm the stop-token callback. Call before the op is submitted.
107 :
108 : Resets the cancellation flag and (re)arms `stop_cb` against @a token.
109 : Derived ops that carry extra pre-submit state (e.g. io_uring's
110 : `sqe_set`) extend this.
111 : */
112 92193 : void start(std::stop_token const& token)
113 : {
114 92193 : cancelled.store(false, std::memory_order_relaxed);
115 92193 : stop_cb.reset();
116 92193 : if (token.stop_possible())
117 517 : stop_cb.emplace(token, canceller{this});
118 92193 : }
119 :
120 : /// Mark this op cancellation-requested. Shared by every backend.
121 288223 : void request_cancel() noexcept
122 : {
123 288223 : cancelled.store(true, std::memory_order_release);
124 288223 : }
125 :
126 : /** Backend cancellation hook, invoked when the stop_token fires.
127 :
128 : The default just records the request. Backends override to also
129 : drive the kernel: io_uring submits an ASYNC_CANCEL SQE; IOCP calls
130 : its stored cancel_func_ (CancelIoEx / wait-reactor deregister).
131 : */
132 20 : virtual void on_cancel() noexcept
133 : {
134 20 : request_cancel();
135 20 : }
136 : };
137 :
138 : } // namespace boost::corosio::detail
139 :
140 : #endif
|