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