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/corosio/detail/continuation_op.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`, `io_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 214 : void operator()() const noexcept { op->on_cancel(); }
74 : };
75 :
76 : std::coroutine_handle<> h;
77 : detail::continuation_op cont_op;
78 : capy::executor_ref ex;
79 : std::error_code* ec_out = nullptr;
80 : std::size_t* bytes_out = nullptr;
81 :
82 : /// True for receive/read ops (drives the zero-byte == EOF decision).
83 : bool is_read = false;
84 : /// True when the submitted buffer was zero-length (suppresses EOF).
85 : bool empty_buffer = false;
86 :
87 : std::atomic<bool> cancelled{false};
88 : std::optional<std::stop_callback<canceller>> stop_cb;
89 :
90 : /// Keeps the owning impl alive while the op is in flight (the kernel
91 : /// owns user buffers until completion). Dropped in the handler's resume
92 : /// tail (see coro_op_complete.hpp).
93 : std::shared_ptr<void> impl_ptr;
94 :
95 : /// Default-construct for virtual-dispatch backends (the reactors, which
96 : /// override operator()/destroy() and leave func_ null).
97 133100 : coro_op() noexcept = default;
98 :
99 : /// Construct with the completion function for func-pointer dispatch
100 : /// (io_uring / IOCP completion handlers).
101 : explicit coro_op(func_type func) noexcept : scheduler_op(func) {}
102 :
103 : /** Arm the stop-token callback. Call before the op is submitted.
104 :
105 : Resets the cancellation flag and (re)arms `stop_cb` against @a token.
106 : Derived ops that carry extra pre-submit state (e.g. io_uring's
107 : `sqe_set`) extend this.
108 : */
109 102305 : void start(std::stop_token const& token)
110 : {
111 102305 : cancelled.store(false, std::memory_order_relaxed);
112 102305 : stop_cb.reset();
113 102305 : if (token.stop_possible())
114 232 : stop_cb.emplace(token, canceller{this});
115 102305 : }
116 :
117 : /// Mark this op cancellation-requested. Shared by every backend.
118 403786 : void request_cancel() noexcept
119 : {
120 403786 : cancelled.store(true, std::memory_order_release);
121 403786 : }
122 :
123 : /** Backend cancellation hook, invoked when the stop_token fires.
124 :
125 : The default just records the request. Backends override to also
126 : drive the kernel: io_uring submits an ASYNC_CANCEL SQE; IOCP calls
127 : its stored cancel_func_ (CancelIoEx / wait-reactor deregister).
128 : */
129 MIS 0 : virtual void on_cancel() noexcept { request_cancel(); }
130 : };
131 :
132 : } // namespace boost::corosio::detail
133 :
134 : #endif
|