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` 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 HIT 102301 : 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 102301 : if (!ec_out)
79 MIS 0 : return;
80 HIT 102301 : if (cancelled)
81 370 : *ec_out = capy::error::canceled;
82 101931 : else if (err)
83 25 : *ec_out = err;
84 101906 : else if (is_read && bytes == 0 && !empty_buffer)
85 3 : *ec_out = capy::error::eof;
86 : else
87 101903 : *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 102301 : coro_resume(coro_op* self) noexcept
126 : {
127 102301 : self->cont_op.cont.h = self->h;
128 102301 : auto next = dispatch_coro(self->ex, self->cont_op.cont);
129 102301 : auto suicide = std::move(self->impl_ptr);
130 102301 : next.resume();
131 : // suicide drops here; may destroy impl + self.
132 102301 : }
133 :
134 : } // namespace boost::corosio::detail
135 :
136 : #endif
|