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
|