91.67% Lines (11/12) 80.00% Functions (4/5)
TLA Baseline Branch
Line Hits Code Line Hits 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;
HITGNC   73 + 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).
HITGNC   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 + */
HITGNC   109 + 102305 void start(std::stop_token const& token)
  110 + {
HITGNC   111 + 102305 cancelled.store(false, std::memory_order_relaxed);
HITGNC   112 + 102305 stop_cb.reset();
HITGNC   113 + 102305 if (token.stop_possible())
HITGNC   114 + 232 stop_cb.emplace(token, canceller{this});
HITGNC   115 + 102305 }
  116 +
  117 + /// Mark this op cancellation-requested. Shared by every backend.
HITGNC   118 + 403786 void request_cancel() noexcept
  119 + {
HITGNC   120 + 403786 cancelled.store(true, std::memory_order_release);
HITGNC   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 + */
MISUNC   129 + virtual void on_cancel() noexcept { request_cancel(); }
  130 + };
  131 +
  132 + } // namespace boost::corosio::detail
  133 +
  134 + #endif