93.75% Lines (15/16) 100.00% Functions (2/2)
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_COMPLETE_HPP
  11 + #define BOOST_COROSIO_NATIVE_DETAIL_CORO_OP_COMPLETE_HPP
  12 +
  13 + #include <boost/corosio/detail/dispatch_coro.hpp>
  14 + #include <boost/corosio/native/detail/coro_op.hpp>
  15 + #include <boost/capy/error.hpp>
  16 +
  17 + #include <cstddef>
  18 + #include <memory>
  19 + #include <system_error>
  20 +
  21 + /*
  22 + Shared completion-tail helpers for proactor ops. Every IOCP and io_uring
  23 + I/O handler ends the same way once its backend-specific result has been
  24 + decoded into ec_out/bytes_out:
  25 +
  26 + 1. disarm the stop_callback,
  27 + 2. on the shutdown-drain path (owner == nullptr) just break the
  28 + impl_ptr keepalive cycle and return without resuming,
  29 + 3. otherwise resume the coroutine on its executor, dropping the
  30 + keepalive only after the continuation has been handed off.
  31 +
  32 + The *decode* step (raw DWORD/res -> {ec, bytes, eof, canceled}) stays
  33 + backend-specific because the raw encodings differ; in Phase 3 it is
  34 + formalized as `Traits::decode_result`. These two helpers capture the
  35 + backend-agnostic prologue and resume tail so the per-op handlers shrink to
  36 + "drain-or-decode, then resume".
  37 + */
  38 +
  39 + namespace boost::corosio::detail {
  40 +
  41 + /** Translate a decoded I/O result into `*ec_out` using the cancelled /
  42 + error / EOF / success priority shared by every native backend.
  43 +
  44 + The raw error encodings differ per backend (reactor positive `errno`,
  45 + io_uring negative `res`, IOCP `DWORD`), so the native-error -> error_code
  46 + step stays backend-local: the caller passes @a err already converted
  47 + (an empty error_code means "no error"). This helper owns only the
  48 + priority logic, which is byte-for-byte identical everywhere:
  49 +
  50 + cancelled -> operation_canceled
  51 + err set -> err
  52 + is_read && bytes == 0 && !empty -> end_of_file
  53 + otherwise -> success
  54 +
  55 + Writes nothing when @a ec_out is null. Does not touch bytes_out — callers
  56 + that report a byte count write it separately (connect/wait carry none).
  57 +
  58 + @param ec_out Destination (may be null).
  59 + @param cancelled The op's cancellation flag.
  60 + @param err Backend error already converted to error_code, or a
  61 + default-constructed error_code on success.
  62 + @param is_read True only for reads that should map a 0-byte
  63 + completion to EOF — false for writes, connect, wait,
  64 + and datagrams (a 0-byte datagram is success, not EOF).
  65 + @param bytes Bytes transferred (consulted only for the EOF test).
  66 + @param empty_buffer True when the submitted buffer was zero-length,
  67 + which suppresses the otherwise-spurious EOF.
  68 + */
  69 + inline void
HITGNC   70 + 102301 decode_io_result(
  71 + std::error_code* ec_out,
  72 + bool cancelled,
  73 + std::error_code err,
  74 + bool is_read,
  75 + std::size_t bytes,
  76 + bool empty_buffer) noexcept
  77 + {
HITGNC   78 + 102301 if (!ec_out)
MISUNC   79 + return;
HITGNC   80 + 102301 if (cancelled)
HITGNC   81 + 370 *ec_out = capy::error::canceled;
HITGNC   82 + 101931 else if (err)
HITGNC   83 + 25 *ec_out = err;
HITGNC   84 + 101906 else if (is_read && bytes == 0 && !empty_buffer)
HITGNC   85 + 3 *ec_out = capy::error::eof;
  86 + else
HITGNC   87 + 101903 *ec_out = {};
  88 + }
  89 +
  90 + /** Completion prologue shared by every proactor handler.
  91 +
  92 + Disarms the stop_callback, then detects the shutdown-drain path.
  93 +
  94 + @param owner The scheduler pointer (nullptr during shutdown drain).
  95 + @param self The completing op.
  96 + @return True if this was a shutdown drain — the caller must `return`
  97 + immediately without decoding or resuming. On that path the
  98 + impl_ptr keepalive is dropped here (which may destroy the impl,
  99 + and with it the op storage).
  100 + */
  101 + inline bool
  102 + coro_drain_if_shutdown(void* owner, coro_op* self) noexcept
  103 + {
  104 + self->stop_cb.reset();
  105 + if (owner == nullptr)
  106 + {
  107 + auto suicide = std::move(self->impl_ptr);
  108 + return true;
  109 + }
  110 + return false;
  111 + }
  112 +
  113 + /** Resume tail shared by every proactor handler.
  114 +
  115 + Resumes the op's coroutine on its executor and then drops the impl_ptr
  116 + keepalive. The keepalive is moved into a local that is released *after*
  117 + `resume()` returns, matching the existing io_uring ordering: the impl (and
  118 + therefore this op's storage) may be destroyed as the local goes out of
  119 + scope, so nothing may touch `*self` after the resume.
  120 +
  121 + @pre `self->ec_out`/`bytes_out` have already been written by the
  122 + backend's decode step.
  123 + */
  124 + inline void
HITGNC   125 + 102301 coro_resume(coro_op* self) noexcept
  126 + {
HITGNC   127 + 102301 self->cont_op.cont.h = self->h;
HITGNC   128 + 102301 auto next = dispatch_coro(self->ex, self->cont_op.cont);
HITGNC   129 + 102301 auto suicide = std::move(self->impl_ptr);
HITGNC   130 + 102301 next.resume();
  131 + // suicide drops here; may destroy impl + self.
HITGNC   132 + 102301 }
  133 +
  134 + } // namespace boost::corosio::detail
  135 +
  136 + #endif