include/boost/corosio/native/detail/coro_op_complete.hpp

93.8% Lines (15/16) 100.0% List of functions (2/2)
coro_op_complete.hpp
f(x) Functions (2)
Line TLA Hits 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` 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
70 102301x 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 {
78 102301x if (!ec_out)
79 return;
80 102301x if (cancelled)
81 370x *ec_out = capy::error::canceled;
82 101931x else if (err)
83 25x *ec_out = err;
84 101906x else if (is_read && bytes == 0 && !empty_buffer)
85 3x *ec_out = capy::error::eof;
86 else
87 101903x *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
125 102301x coro_resume(coro_op* self) noexcept
126 {
127 102301x self->cont_op.cont.h = self->h;
128 102301x auto next = dispatch_coro(self->ex, self->cont_op.cont);
129 102301x auto suicide = std::move(self->impl_ptr);
130 102301x next.resume();
131 // suicide drops here; may destroy impl + self.
132 102301x }
133
134 } // namespace boost::corosio::detail
135
136 #endif
137