95.00% Lines (19/20) 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   impl_ptr keepalive cycle and return without resuming, 28   impl_ptr 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   99491 decode_io_result( 83   96547 decode_io_result(
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   99491 if (bytes_out) 92   96547 if (bytes_out)
HITCBC 93   90219 *bytes_out = bytes; 93   87246 *bytes_out = bytes;
HITCBC 94   99491 if (!ec_out) 94   96547 if (!ec_out)
MISUBC 95   ✗ return; 95   ✗ return;
HITCBC 96   99491 if (bytes > 0) 96   96547 if (bytes > 0)
HITCBC 97   89552 *ec_out = err; 97   86589 *ec_out = err;
HITCBC 98   9939 else if (cancelled) 98   9958 else if (cancelled)
HITCBC 99   686 *ec_out = capy::error::canceled; 99   676 *ec_out = capy::error::canceled;
HITCBC 100   9253 else if (err) 100   9282 else if (err)
HITCBC 101   165 *ec_out = err; 101   164 *ec_out = err;
HITCBC 102   9088 else if (is_read && !empty_buffer) 102   9118 else if (is_read && !empty_buffer)
HITCBC 103   31 *ec_out = capy::error::eof; 103   31 *ec_out = capy::error::eof;
104   else 104   else
HITCBC 105   9057 *ec_out = {}; 105   9087 *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   impl_ptr keepalive is dropped here (which may destroy the impl, 116   impl_ptr keepalive is dropped here (which may destroy the impl,
117   and with it the op storage). 117   and with it the op storage).
118   */ 118   */
119   inline bool 119   inline bool
120   coro_drain_if_shutdown(void* owner, coro_op* self) noexcept 120   coro_drain_if_shutdown(void* owner, coro_op* self) noexcept
121   { 121   {
122   self->stop_cb.reset(); 122   self->stop_cb.reset();
123   if (owner == nullptr) 123   if (owner == nullptr)
124   { 124   {
125   auto suicide = std::move(self->impl_ptr); 125   auto suicide = std::move(self->impl_ptr);
126   return true; 126   return true;
127   } 127   }
128   return false; 128   return false;
129   } 129   }
130   130  
131   /** Resume tail shared by every proactor handler. 131   /** Resume tail shared by every proactor handler.
132   132  
133   Resumes the op's coroutine on its executor and then drops the impl_ptr 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* 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 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 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. 137   scope, so nothing may touch `*self` after the resume.
138   138  
139   @pre `self->ec_out`/`bytes_out` have already been written by the 139   @pre `self->ec_out`/`bytes_out` have already been written by the
140   backend's decode step. 140   backend's decode step.
141   */ 141   */
142   inline void 142   inline void
HITCBC 143   98854 coro_resume(coro_op* self) noexcept 143   95916 coro_resume(coro_op* self) noexcept
144   { 144   {
HITCBC 145   98854 self->cont.h = self->h; 145   95916 self->cont.h = self->h;
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   98854 auto suicide = std::move(self->impl_ptr); 149   95916 auto suicide = std::move(self->impl_ptr);
HITCBC 150   98854 auto next = dispatch_coro(self->ex, self->cont); 150   95916 auto next = dispatch_coro(self->ex, self->cont);
HITCBC 151   98854 next.resume(); 151   95916 next.resume();
152   // suicide drops here; may destroy impl + self. 152   // suicide drops here; may destroy impl + self.
HITCBC 153   98854 } 153   95916 }
154   154  
155   } // namespace boost::corosio::detail 155   } // namespace boost::corosio::detail
156   156  
157   #endif 157   #endif