include/boost/corosio/native/detail/coro_op_complete.hpp
90.0% Lines (9/10)
100.0% List of functions (1/1)
91.7% Branches (11/12)
Functions (1)
Function
Calls
Lines
Branches
Blocks
boost::corosio::detail::decode_io_result(std::error_code*, bool, std::error_code, bool, unsigned long long, bool)
:70
589461x
90.0%
91.7%
93.8%
| Line | Branch | 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 | 589461x | 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 |
1/2✗ Branch 2 → 3 not taken.
✓ Branch 2 → 4 taken 589461 times.
|
589461x | if (!ec_out) | |
| 79 | ✗ | return; | ||
| 80 |
2/2✓ Branch 4 → 5 taken 2032 times.
✓ Branch 4 → 7 taken 587429 times.
|
589461x | if (cancelled) | |
| 81 | 2032x | *ec_out = capy::error::canceled; | ||
| 82 |
2/2✓ Branch 8 → 9 taken 27 times.
✓ Branch 8 → 10 taken 587402 times.
|
587429x | else if (err) | |
| 83 | 27x | *ec_out = err; | ||
| 84 |
6/6✓ Branch 10 → 11 taken 292814 times.
✓ Branch 10 → 15 taken 294588 times.
✓ Branch 11 → 12 taken 18 times.
✓ Branch 11 → 15 taken 292796 times.
✓ Branch 12 → 13 taken 16 times.
✓ Branch 12 → 15 taken 2 times.
|
587402x | else if (is_read && bytes == 0 && !empty_buffer) | |
| 85 | 16x | *ec_out = capy::error::eof; | ||
| 86 | else | |||
| 87 | 587386x | *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 | coro_resume(coro_op* self) noexcept | |||
| 126 | { | |||
| 127 | self->cont_op.cont.h = self->h; | |||
| 128 | auto next = dispatch_coro(self->ex, self->cont_op.cont); | |||
| 129 | auto suicide = std::move(self->impl_ptr); | |||
| 130 | next.resume(); | |||
| 131 | // suicide drops here; may destroy impl + self. | |||
| 132 | } | |||
| 133 | ||||
| 134 | } // namespace boost::corosio::detail | |||
| 135 | ||||
| 136 | #endif | |||
| 137 |