93.75% Lines (15/16)
100.00% Functions (2/2)
| TLA | Baseline | Branch | ||||||
|---|---|---|---|---|---|---|---|---|
| Line | Hits | Code | Line | Hits | Code | |||
| 1 | // | 1 | // | |||||
| 2 | // Copyright (c) 2026 Michael Vandeberg | 2 | // Copyright (c) 2026 Michael Vandeberg | |||||
| 3 | // | 3 | // | |||||
| 4 | // Distributed under the Boost Software License, Version 1.0. (See accompanying | 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) | 5 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) | |||||
| 6 | // | 6 | // | |||||
| 7 | // Official repository: https://github.com/cppalliance/corosio | 7 | // Official repository: https://github.com/cppalliance/corosio | |||||
| 8 | // | 8 | // | |||||
| 9 | 9 | |||||||
| 10 | #ifndef BOOST_COROSIO_NATIVE_DETAIL_CORO_OP_COMPLETE_HPP | 10 | #ifndef BOOST_COROSIO_NATIVE_DETAIL_CORO_OP_COMPLETE_HPP | |||||
| 11 | #define BOOST_COROSIO_NATIVE_DETAIL_CORO_OP_COMPLETE_HPP | 11 | #define BOOST_COROSIO_NATIVE_DETAIL_CORO_OP_COMPLETE_HPP | |||||
| 12 | 12 | |||||||
| 13 | #include <boost/corosio/detail/dispatch_coro.hpp> | 13 | #include <boost/corosio/detail/dispatch_coro.hpp> | |||||
| 14 | #include <boost/corosio/native/detail/coro_op.hpp> | 14 | #include <boost/corosio/native/detail/coro_op.hpp> | |||||
| 15 | #include <boost/capy/error.hpp> | 15 | #include <boost/capy/error.hpp> | |||||
| 16 | 16 | |||||||
| 17 | #include <cstddef> | 17 | #include <cstddef> | |||||
| 18 | #include <memory> | 18 | #include <memory> | |||||
| 19 | #include <system_error> | 19 | #include <system_error> | |||||
| 20 | 20 | |||||||
| 21 | /* | 21 | /* | |||||
| 22 | Shared completion-tail helpers for proactor ops. Every IOCP and io_uring | 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 | 23 | I/O handler ends the same way once its backend-specific result has been | |||||
| 24 | decoded into ec_out/bytes_out: | 24 | decoded into ec_out/bytes_out: | |||||
| 25 | 25 | |||||||
| 26 | 1. disarm the stop_callback, | 26 | 1. disarm the stop_callback, | |||||
| 27 | 2. on the shutdown-drain path (owner == nullptr) just break the | 27 | 2. on the shutdown-drain path (owner == nullptr) just break the | |||||
| 28 | impl_ptr keepalive cycle and return without resuming, | 28 | impl_ptr keepalive cycle and return without resuming, | |||||
| 29 | 3. otherwise resume the coroutine on its executor, dropping the | 29 | 3. otherwise resume the coroutine on its executor, dropping the | |||||
| 30 | keepalive only after the continuation has been handed off. | 30 | keepalive only after the continuation has been handed off. | |||||
| 31 | 31 | |||||||
| 32 | The *decode* step (raw DWORD/res -> {ec, bytes, eof, canceled}) stays | 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 | 33 | backend-specific because the raw encodings differ; in Phase 3 it is | |||||
| 34 | formalized as `Traits::decode_result`. These two helpers capture the | 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 | 35 | backend-agnostic prologue and resume tail so the per-op handlers shrink to | |||||
| 36 | "drain-or-decode, then resume". | 36 | "drain-or-decode, then resume". | |||||
| 37 | */ | 37 | */ | |||||
| 38 | 38 | |||||||
| 39 | namespace boost::corosio::detail { | 39 | namespace boost::corosio::detail { | |||||
| 40 | 40 | |||||||
| 41 | /** Translate a decoded I/O result into `*ec_out` using the cancelled / | 41 | /** Translate a decoded I/O result into `*ec_out` using the cancelled / | |||||
| 42 | error / EOF / success priority shared by every native backend. | 42 | error / EOF / success priority shared by every native backend. | |||||
| 43 | 43 | |||||||
| 44 | The raw error encodings differ per backend (reactor positive `errno`, | 44 | The raw error encodings differ per backend (reactor positive `errno`, | |||||
| 45 | io_uring negative `res`, IOCP `DWORD`), so the native-error -> error_code | 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 | 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 | 47 | (an empty error_code means "no error"). This helper owns only the | |||||
| 48 | priority logic, which is byte-for-byte identical everywhere: | 48 | priority logic, which is byte-for-byte identical everywhere: | |||||
| 49 | 49 | |||||||
| 50 | cancelled -> operation_canceled | 50 | cancelled -> operation_canceled | |||||
| 51 | err set -> err | 51 | err set -> err | |||||
| 52 | is_read && bytes == 0 && !empty -> end_of_file | 52 | is_read && bytes == 0 && !empty -> end_of_file | |||||
| 53 | otherwise -> success | 53 | otherwise -> success | |||||
| 54 | 54 | |||||||
| 55 | Writes nothing when @a ec_out is null. Does not touch bytes_out — callers | 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). | 56 | that report a byte count write it separately (connect/wait carry none). | |||||
| 57 | 57 | |||||||
| 58 | @param ec_out Destination (may be null). | 58 | @param ec_out Destination (may be null). | |||||
| 59 | @param cancelled The op's cancellation flag. | 59 | @param cancelled The op's cancellation flag. | |||||
| 60 | @param err Backend error already converted to error_code, or a | 60 | @param err Backend error already converted to error_code, or a | |||||
| 61 | default-constructed error_code on success. | 61 | default-constructed error_code on success. | |||||
| 62 | @param is_read True only for reads that should map a 0-byte | 62 | @param is_read True only for reads that should map a 0-byte | |||||
| 63 | completion to EOF — false for writes, connect, wait, | 63 | completion to EOF — false for writes, connect, wait, | |||||
| 64 | and datagrams (a 0-byte datagram is success, not EOF). | 64 | and datagrams (a 0-byte datagram is success, not EOF). | |||||
| 65 | @param bytes Bytes transferred (consulted only for the EOF test). | 65 | @param bytes Bytes transferred (consulted only for the EOF test). | |||||
| 66 | @param empty_buffer True when the submitted buffer was zero-length, | 66 | @param empty_buffer True when the submitted buffer was zero-length, | |||||
| 67 | which suppresses the otherwise-spurious EOF. | 67 | which suppresses the otherwise-spurious EOF. | |||||
| 68 | */ | 68 | */ | |||||
| 69 | inline void | 69 | inline void | |||||
| HITCBC | 70 | 96135 | decode_io_result( | 70 | 84191 | decode_io_result( | ||
| 71 | std::error_code* ec_out, | 71 | std::error_code* ec_out, | |||||
| 72 | bool cancelled, | 72 | bool cancelled, | |||||
| 73 | std::error_code err, | 73 | std::error_code err, | |||||
| 74 | bool is_read, | 74 | bool is_read, | |||||
| 75 | std::size_t bytes, | 75 | std::size_t bytes, | |||||
| 76 | bool empty_buffer) noexcept | 76 | bool empty_buffer) noexcept | |||||
| 77 | { | 77 | { | |||||
| HITCBC | 78 | 96135 | if (!ec_out) | 78 | 84191 | if (!ec_out) | ||
| MISUBC | 79 | ✗ | return; | 79 | ✗ | return; | ||
| HITCBC | 80 | 96135 | if (cancelled) | 80 | 84191 | if (cancelled) | ||
| HITCBC | 81 | 366 | *ec_out = capy::error::canceled; | 81 | 357 | *ec_out = capy::error::canceled; | ||
| HITCBC | 82 | 95769 | else if (err) | 82 | 83834 | else if (err) | ||
| HITCBC | 83 | 25 | *ec_out = err; | 83 | 25 | *ec_out = err; | ||
| HITCBC | 84 | 95744 | else if (is_read && bytes == 0 && !empty_buffer) | 84 | 83809 | else if (is_read && bytes == 0 && !empty_buffer) | ||
| HITCBC | 85 | 3 | *ec_out = capy::error::eof; | 85 | 3 | *ec_out = capy::error::eof; | ||
| 86 | else | 86 | else | |||||
| HITCBC | 87 | 95741 | *ec_out = {}; | 87 | 83806 | *ec_out = {}; | ||
| 88 | } | 88 | } | |||||
| 89 | 89 | |||||||
| 90 | /** Completion prologue shared by every proactor handler. | 90 | /** Completion prologue shared by every proactor handler. | |||||
| 91 | 91 | |||||||
| 92 | Disarms the stop_callback, then detects the shutdown-drain path. | 92 | Disarms the stop_callback, then detects the shutdown-drain path. | |||||
| 93 | 93 | |||||||
| 94 | @param owner The scheduler pointer (nullptr during shutdown drain). | 94 | @param owner The scheduler pointer (nullptr during shutdown drain). | |||||
| 95 | @param self The completing op. | 95 | @param self The completing op. | |||||
| 96 | @return True if this was a shutdown drain — the caller must `return` | 96 | @return True if this was a shutdown drain — the caller must `return` | |||||
| 97 | immediately without decoding or resuming. On that path the | 97 | immediately without decoding or resuming. On that path the | |||||
| 98 | impl_ptr keepalive is dropped here (which may destroy the impl, | 98 | impl_ptr keepalive is dropped here (which may destroy the impl, | |||||
| 99 | and with it the op storage). | 99 | and with it the op storage). | |||||
| 100 | */ | 100 | */ | |||||
| 101 | inline bool | 101 | inline bool | |||||
| 102 | coro_drain_if_shutdown(void* owner, coro_op* self) noexcept | 102 | coro_drain_if_shutdown(void* owner, coro_op* self) noexcept | |||||
| 103 | { | 103 | { | |||||
| 104 | self->stop_cb.reset(); | 104 | self->stop_cb.reset(); | |||||
| 105 | if (owner == nullptr) | 105 | if (owner == nullptr) | |||||
| 106 | { | 106 | { | |||||
| 107 | auto suicide = std::move(self->impl_ptr); | 107 | auto suicide = std::move(self->impl_ptr); | |||||
| 108 | return true; | 108 | return true; | |||||
| 109 | } | 109 | } | |||||
| 110 | return false; | 110 | return false; | |||||
| 111 | } | 111 | } | |||||
| 112 | 112 | |||||||
| 113 | /** Resume tail shared by every proactor handler. | 113 | /** Resume tail shared by every proactor handler. | |||||
| 114 | 114 | |||||||
| 115 | Resumes the op's coroutine on its executor and then drops the impl_ptr | 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* | 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 | 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 | 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. | 119 | scope, so nothing may touch `*self` after the resume. | |||||
| 120 | 120 | |||||||
| 121 | @pre `self->ec_out`/`bytes_out` have already been written by the | 121 | @pre `self->ec_out`/`bytes_out` have already been written by the | |||||
| 122 | backend's decode step. | 122 | backend's decode step. | |||||
| 123 | */ | 123 | */ | |||||
| 124 | inline void | 124 | inline void | |||||
| HITCBC | 125 | 96135 | coro_resume(coro_op* self) noexcept | 125 | 84191 | coro_resume(coro_op* self) noexcept | ||
| 126 | { | 126 | { | |||||
| HITCBC | 127 | 96135 | self->cont_op.cont.h = self->h; | 127 | 84191 | self->cont_op.cont.h = self->h; | ||
| HITCBC | 128 | 96135 | auto next = dispatch_coro(self->ex, self->cont_op.cont); | 128 | 84191 | auto next = dispatch_coro(self->ex, self->cont_op.cont); | ||
| HITCBC | 129 | 96135 | auto suicide = std::move(self->impl_ptr); | 129 | 84191 | auto suicide = std::move(self->impl_ptr); | ||
| HITCBC | 130 | 96135 | next.resume(); | 130 | 84191 | next.resume(); | ||
| 131 | // suicide drops here; may destroy impl + self. | 131 | // suicide drops here; may destroy impl + self. | |||||
| HITCBC | 132 | 96135 | } | 132 | 84191 | } | ||
| 133 | 133 | |||||||
| 134 | } // namespace boost::corosio::detail | 134 | } // namespace boost::corosio::detail | |||||
| 135 | 135 | |||||||
| 136 | #endif | 136 | #endif | |||||