LCOV - code coverage report
Current view: top level - corosio/native/detail - coro_op_complete.hpp (source / functions) Coverage Total Hit Missed
Test: coverage_remapped.info Lines: 95.0 % 20 19 1
Test Date: 2026-09-25 22:49:29 Functions: 100.0 % 2 2

           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_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`/`*bytes_out` using the
      42                 :     error / transfer / cancelled / EOF priority shared by every native
      43                 :     backend.
      44                 : 
      45                 :     The raw error encodings differ per backend (reactor positive `errno`,
      46                 :     io_uring negative `res`, IOCP `DWORD`), so the native-error -> error_code
      47                 :     step stays backend-local: the caller passes @a err already converted
      48                 :     (an empty error_code means "no error"). This helper owns only the
      49                 :     priority logic, which is byte-for-byte identical everywhere:
      50                 : 
      51                 :         bytes > 0                          -> err if set, else success
      52                 :         cancelled                          -> operation_canceled
      53                 :         err set                            -> err
      54                 :         is_read && !empty                  -> end_of_file
      55                 :         otherwise                          -> success
      56                 : 
      57                 :     A transfer outranks the cancellation flag: the stream contracts
      58                 :     require a completed transfer to be reported verbatim — a stop
      59                 :     request that lost the race changes nothing, and the next operation
      60                 :     on the still-stopped token reports `canceled`. With nothing
      61                 :     transferred, the flag outranks the raw completion error: a
      62                 :     cancellation request is what tears pending ops down locally (close,
      63                 :     stop), and the flag normalizes whichever error that teardown
      64                 :     surfaced (and it outranks the EOF mapping for the same reason: an
      65                 :     aborted read is `canceled`, not `eof`).
      66                 : 
      67                 :     The byte count is always stored — never zeroed by cancellation.
      68                 : 
      69                 :     @param ec_out        Error destination (may be null).
      70                 :     @param bytes_out     Byte-count destination (null for connect/wait/
      71                 :                          accept, which report no count).
      72                 :     @param cancelled     The op's cancellation flag.
      73                 :     @param err           Backend error already converted to error_code, or a
      74                 :                          default-constructed error_code on success.
      75                 :     @param is_read       True only for reads that should map a 0-byte
      76                 :                          completion to EOF — false for writes, connect, wait,
      77                 :                          and datagrams (a 0-byte datagram is success, not EOF).
      78                 :     @param bytes         Bytes transferred.
      79                 :     @param empty_buffer  True when the submitted buffer was zero-length,
      80                 :                          which suppresses the otherwise-spurious EOF.
      81                 : */
      82                 : inline void
      83 HIT       98874 : decode_io_result(
      84                 :     std::error_code* ec_out,
      85                 :     std::size_t* bytes_out,
      86                 :     bool cancelled,
      87                 :     std::error_code err,
      88                 :     bool is_read,
      89                 :     std::size_t bytes,
      90                 :     bool empty_buffer) noexcept
      91                 : {
      92           98874 :     if (bytes_out)
      93           89883 :         *bytes_out = bytes;
      94           98874 :     if (!ec_out)
      95 MIS           0 :         return;
      96 HIT       98874 :     if (bytes > 0)
      97           89196 :         *ec_out = err;
      98            9678 :     else if (cancelled)
      99             706 :         *ec_out = capy::error::canceled;
     100            8972 :     else if (err)
     101             164 :         *ec_out = err;
     102            8808 :     else if (is_read && !empty_buffer)
     103              31 :         *ec_out = capy::error::eof;
     104                 :     else
     105            8777 :         *ec_out = {};
     106                 : }
     107                 : 
     108                 : /** Completion prologue shared by every proactor handler.
     109                 : 
     110                 :     Disarms the stop_callback, then detects the shutdown-drain path.
     111                 : 
     112                 :     @param owner The scheduler pointer (nullptr during shutdown drain).
     113                 :     @param self  The completing op.
     114                 :     @return True if this was a shutdown drain — the caller must `return`
     115                 :             immediately without decoding or resuming. On that path the
     116                 :             impl_ptr keepalive is dropped here (which may destroy the impl,
     117                 :             and with it the op storage).
     118                 : */
     119                 : inline bool
     120                 : coro_drain_if_shutdown(void* owner, coro_op* self) noexcept
     121                 : {
     122                 :     self->stop_cb.reset();
     123                 :     if (owner == nullptr)
     124                 :     {
     125                 :         auto suicide = std::move(self->impl_ptr);
     126                 :         return true;
     127                 :     }
     128                 :     return false;
     129                 : }
     130                 : 
     131                 : /** Resume tail shared by every proactor handler.
     132                 : 
     133                 :     Resumes the op's coroutine on its executor and then drops the impl_ptr
     134                 :     keepalive. The keepalive is moved into a local that is released *after*
     135                 :     `resume()` returns, matching the existing io_uring ordering: the impl (and
     136                 :     therefore this op's storage) may be destroyed as the local goes out of
     137                 :     scope, so nothing may touch `*self` after the resume.
     138                 : 
     139                 :     @pre `self->ec_out`/`bytes_out` have already been written by the
     140                 :          backend's decode step.
     141                 : */
     142                 : inline void
     143           98213 : coro_resume(coro_op* self) noexcept
     144                 : {
     145           98213 :     self->cont.h = self->h;
     146                 :     // Clear the keepalive before publishing the continuation: a strand
     147                 :     // drained on another thread can reuse this op via reset() the instant
     148                 :     // it runs, so this write must be ordered before the publish, not after.
     149           98213 :     auto suicide = std::move(self->impl_ptr);
     150           98213 :     auto next    = dispatch_coro(self->ex, self->cont);
     151           98213 :     next.resume();
     152                 :     // suicide drops here; may destroy impl + self.
     153           98213 : }
     154                 : 
     155                 : } // namespace boost::corosio::detail
     156                 : 
     157                 : #endif
        

Generated by: LCOV version 2.3