94.74% Lines (18/19) 100.00% Functions (2/2)
TLA Baseline Branch
Line Hits Code Line Hits Code
1   // 1   //
2   // Copyright (c) 2026 Michael Vandeberg 2   // Copyright (c) 2026 Michael Vandeberg
3   // 3   //
4   // Distributed under the Boost Software License, Version 1.0. (See accompanying 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) 5   // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6   // 6   //
7   // Official repository: https://github.com/cppalliance/corosio 7   // Official repository: https://github.com/cppalliance/corosio
8   // 8   //
9   9  
10   #ifndef BOOST_COROSIO_NATIVE_DETAIL_CORO_OP_COMPLETE_HPP 10   #ifndef BOOST_COROSIO_NATIVE_DETAIL_CORO_OP_COMPLETE_HPP
11   #define BOOST_COROSIO_NATIVE_DETAIL_CORO_OP_COMPLETE_HPP 11   #define BOOST_COROSIO_NATIVE_DETAIL_CORO_OP_COMPLETE_HPP
12   12  
13   #include <boost/corosio/detail/dispatch_coro.hpp> 13   #include <boost/corosio/detail/dispatch_coro.hpp>
14   #include <boost/corosio/native/detail/coro_op.hpp> 14   #include <boost/corosio/native/detail/coro_op.hpp>
15   #include <boost/capy/error.hpp> 15   #include <boost/capy/error.hpp>
16   16  
17   #include <cstddef> 17   #include <cstddef>
18   #include <memory> 18   #include <memory>
19   #include <system_error> 19   #include <system_error>
20   20  
21   /* 21   /*
22   Shared completion-tail helpers for proactor ops. Every IOCP and io_uring 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 23   I/O handler ends the same way once its backend-specific result has been
24   decoded into ec_out/bytes_out: 24   decoded into ec_out/bytes_out:
25   25  
26   1. disarm the stop_callback, 26   1. disarm the stop_callback,
27   2. on the shutdown-drain path (owner == nullptr) just break the 27   2. on the shutdown-drain path (owner == nullptr) just break the
28   object_ref_ keepalive cycle and return without resuming, 28   object_ref_ keepalive cycle and return without resuming,
29   3. otherwise resume the coroutine on its executor, dropping the 29   3. otherwise resume the coroutine on its executor, dropping the
30   keepalive only after the continuation has been handed off. 30   keepalive only after the continuation has been handed off.
31   31  
32   The *decode* step (raw DWORD/res -> {ec, bytes, eof, canceled}) stays 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 33   backend-specific because the raw encodings differ; in Phase 3 it is
34   formalized as `Traits::decode_result`. These two helpers capture the 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 35   backend-agnostic prologue and resume tail so the per-op handlers shrink to
36   "drain-or-decode, then resume". 36   "drain-or-decode, then resume".
37   */ 37   */
38   38  
39   namespace boost::corosio::detail { 39   namespace boost::corosio::detail {
40   40  
41   /** Translate a decoded I/O result into `*ec_out`/`*bytes_out` using the 41   /** Translate a decoded I/O result into `*ec_out`/`*bytes_out` using the
42   error / transfer / cancelled / EOF priority shared by every native 42   error / transfer / cancelled / EOF priority shared by every native
43   backend. 43   backend.
44   44  
45   The raw error encodings differ per backend (reactor positive `errno`, 45   The raw error encodings differ per backend (reactor positive `errno`,
46   io_uring negative `res`, IOCP `DWORD`), so the native-error -> error_code 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 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 48   (an empty error_code means "no error"). This helper owns only the
49   priority logic, which is byte-for-byte identical everywhere: 49   priority logic, which is byte-for-byte identical everywhere:
50   50  
51   bytes > 0 -> err if set, else success 51   bytes > 0 -> err if set, else success
52   cancelled -> operation_canceled 52   cancelled -> operation_canceled
53   err set -> err 53   err set -> err
54   is_read && !empty -> end_of_file 54   is_read && !empty -> end_of_file
55   otherwise -> success 55   otherwise -> success
56   56  
57   A transfer outranks the cancellation flag: the stream contracts 57   A transfer outranks the cancellation flag: the stream contracts
58   require a completed transfer to be reported verbatim — a stop 58   require a completed transfer to be reported verbatim — a stop
59   request that lost the race changes nothing, and the next operation 59   request that lost the race changes nothing, and the next operation
60   on the still-stopped token reports `canceled`. With nothing 60   on the still-stopped token reports `canceled`. With nothing
61   transferred, the flag outranks the raw completion error: a 61   transferred, the flag outranks the raw completion error: a
62   cancellation request is what tears pending ops down locally (close, 62   cancellation request is what tears pending ops down locally (close,
63   stop), and the flag normalizes whichever error that teardown 63   stop), and the flag normalizes whichever error that teardown
64   surfaced (and it outranks the EOF mapping for the same reason: an 64   surfaced (and it outranks the EOF mapping for the same reason: an
65   aborted read is `canceled`, not `eof`). 65   aborted read is `canceled`, not `eof`).
66   66  
67   The byte count is always stored — never zeroed by cancellation. 67   The byte count is always stored — never zeroed by cancellation.
68   68  
69   @param ec_out Error destination (may be null). 69   @param ec_out Error destination (may be null).
70   @param bytes_out Byte-count destination (null for connect/wait/ 70   @param bytes_out Byte-count destination (null for connect/wait/
71   accept, which report no count). 71   accept, which report no count).
72   @param cancelled The op's cancellation flag. 72   @param cancelled The op's cancellation flag.
73   @param err Backend error already converted to error_code, or a 73   @param err Backend error already converted to error_code, or a
74   default-constructed error_code on success. 74   default-constructed error_code on success.
75   @param is_read True only for reads that should map a 0-byte 75   @param is_read True only for reads that should map a 0-byte
76   completion to EOF — false for writes, connect, wait, 76   completion to EOF — false for writes, connect, wait,
77   and datagrams (a 0-byte datagram is success, not EOF). 77   and datagrams (a 0-byte datagram is success, not EOF).
78   @param bytes Bytes transferred. 78   @param bytes Bytes transferred.
79   @param empty_buffer True when the submitted buffer was zero-length, 79   @param empty_buffer True when the submitted buffer was zero-length,
80   which suppresses the otherwise-spurious EOF. 80   which suppresses the otherwise-spurious EOF.
81   */ 81   */
82   inline void 82   inline void
HITCBC 83   108037 decode_results( 83   112534 decode_results(
84   std::error_code* ec_out, 84   std::error_code* ec_out,
85   std::size_t* bytes_out, 85   std::size_t* bytes_out,
86   bool cancelled, 86   bool cancelled,
87   std::error_code err, 87   std::error_code err,
88   bool is_read, 88   bool is_read,
89   std::size_t bytes, 89   std::size_t bytes,
90   bool empty_buffer) noexcept 90   bool empty_buffer) noexcept
91   { 91   {
HITCBC 92   108037 if (bytes_out) 92   112534 if (bytes_out)
HITCBC 93   93264 *bytes_out = bytes; 93   98032 *bytes_out = bytes;
HITCBC 94   108037 if (!ec_out) 94   112534 if (!ec_out)
MISUBC 95   ✗ return; 95   ✗ return;
HITCBC 96   108037 if (bytes > 0) 96   112534 if (bytes > 0)
HITCBC 97   91440 *ec_out = err; 97   96075 *ec_out = err;
HITCBC 98   16597 else if (cancelled) 98   16459 else if (cancelled)
HITCBC 99   1271 *ec_out = capy::error::canceled; 99   1269 *ec_out = capy::error::canceled;
HITCBC 100   15326 else if (err) 100   15190 else if (err)
HITCBC 101   417 *ec_out = err; 101   426 *ec_out = err;
HITCBC 102   14909 else if (is_read && !empty_buffer) 102   14764 else if (is_read && !empty_buffer)
HITCBC 103   39 *ec_out = capy::error::eof; 103   39 *ec_out = capy::error::eof;
104   else 104   else
HITCBC 105   14870 *ec_out = {}; 105   14725 *ec_out = {};
106   } 106   }
107   107  
108   /** Completion prologue shared by every proactor handler. 108   /** Completion prologue shared by every proactor handler.
109   109  
110   Disarms the stop_callback, then detects the shutdown-drain path. 110   Disarms the stop_callback, then detects the shutdown-drain path.
111   111  
112   @param owner The scheduler pointer (nullptr during shutdown drain). 112   @param owner The scheduler pointer (nullptr during shutdown drain).
113   @param self The completing op. 113   @param self The completing op.
114   @return True if this was a shutdown drain — the caller must `return` 114   @return True if this was a shutdown drain — the caller must `return`
115   immediately without decoding or resuming. On that path the 115   immediately without decoding or resuming. On that path the
116   awaiting coroutine is abandoned (see `coro_op::destroy_awaiting`) 116   awaiting coroutine is abandoned (see `coro_op::destroy_awaiting`)
117   and the object_ref_ keepalive dropped, which may destroy the impl 117   and the object_ref_ keepalive dropped, which may destroy the impl
118   and with it the op storage. 118   and with it the op storage.
119   */ 119   */
120   inline bool 120   inline bool
121   coro_drain_if_shutdown(void* owner, coro_op* self) noexcept 121   coro_drain_if_shutdown(void* owner, coro_op* self) noexcept
122   { 122   {
123   if (owner == nullptr) 123   if (owner == nullptr)
124   { 124   {
125   self->destroy_awaiting(); 125   self->destroy_awaiting();
126   return true; 126   return true;
127   } 127   }
128   self->stop_cb.reset(); 128   self->stop_cb.reset();
129   return false; 129   return false;
130   } 130   }
131   131  
132   /** Resume tail shared by every proactor handler. 132   /** Resume tail shared by every proactor handler.
133   133  
134   Resumes the op's coroutine on its executor and then drops the object_ref_ 134   Resumes the op's coroutine on its executor and then drops the object_ref_
135   keepalive. The keepalive is moved into a local that is released *after* 135   keepalive. The keepalive is moved into a local that is released *after*
136   `resume()` returns, matching the existing io_uring ordering: the impl (and 136   `resume()` returns, matching the existing io_uring ordering: the impl (and
137   therefore this op's storage) may be destroyed as the local goes out of 137   therefore this op's storage) may be destroyed as the local goes out of
138   scope, so nothing may touch `*self` after the resume. 138   scope, so nothing may touch `*self` after the resume.
139   139  
140   @pre `self->ec_out`/`bytes_out` have already been written by the 140   @pre `self->ec_out`/`bytes_out` have already been written by the
141   backend's decode step. 141   backend's decode step.
142   */ 142   */
143   inline void 143   inline void
HITCBC 144   106861 coro_resume(coro_op* self) noexcept 144   111370 coro_resume(coro_op* self) noexcept
145   { 145   {
146   // Clear the keepalive before publishing the continuation: a strand 146   // Clear the keepalive before publishing the continuation: a strand
147   // drained on another thread can reuse this op via reset() the instant 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. 148   // it runs, so this write must be ordered before the publish, not after.
HITCBC 149   106861 auto suicide = std::move(self->object_ref_); 149   111370 auto suicide = std::move(self->object_ref_);
HITCBC 150   106861 auto next = dispatch_coro(self->ex, *self->cont); 150   111370 auto next = dispatch_coro(self->ex, *self->cont);
HITCBC 151   106861 next.resume(); 151   111370 next.resume();
152   // suicide drops here; may destroy impl + self. 152   // suicide drops here; may destroy impl + self.
HITCBC 153   106861 } 153   111370 }
154   154  
155   } // namespace boost::corosio::detail 155   } // namespace boost::corosio::detail
156   156  
157   #endif 157   #endif