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