100.00% Lines (101/101)
100.00% Functions (21/21)
| TLA | Baseline | Branch | ||||||
|---|---|---|---|---|---|---|---|---|
| Line | Hits | Code | Line | Hits | Code | |||
| 1 | // | 1 | // | |||||
| 2 | // Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com) | 2 | // Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com) | |||||
| 3 | + | // Copyright (c) 2026 Michael Vandeberg | ||||||
| 3 | // | 4 | // | |||||
| 4 | // Distributed under the Boost Software License, Version 1.0. (See accompanying | 5 | // 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 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) | |||||
| 6 | // | 7 | // | |||||
| 7 | // Official repository: https://github.com/cppalliance/capy | 8 | // Official repository: https://github.com/cppalliance/capy | |||||
| 8 | // | 9 | // | |||||
| 9 | 10 | |||||||
| 10 | #ifndef BOOST_CAPY_IO_ANY_WRITE_STREAM_HPP | 11 | #ifndef BOOST_CAPY_IO_ANY_WRITE_STREAM_HPP | |||||
| 11 | #define BOOST_CAPY_IO_ANY_WRITE_STREAM_HPP | 12 | #define BOOST_CAPY_IO_ANY_WRITE_STREAM_HPP | |||||
| 12 | 13 | |||||||
| 13 | #include <boost/capy/detail/config.hpp> | 14 | #include <boost/capy/detail/config.hpp> | |||||
| 14 | #include <boost/capy/detail/await_suspend_helper.hpp> | 15 | #include <boost/capy/detail/await_suspend_helper.hpp> | |||||
| 15 | #include <boost/capy/buffers.hpp> | 16 | #include <boost/capy/buffers.hpp> | |||||
| 16 | #include <boost/capy/detail/buffer_array.hpp> | 17 | #include <boost/capy/detail/buffer_array.hpp> | |||||
| 17 | #include <boost/capy/concept/io_awaitable.hpp> | 18 | #include <boost/capy/concept/io_awaitable.hpp> | |||||
| 18 | #include <boost/capy/concept/write_stream.hpp> | 19 | #include <boost/capy/concept/write_stream.hpp> | |||||
| 19 | #include <coroutine> | 20 | #include <coroutine> | |||||
| 20 | #include <boost/capy/ex/io_env.hpp> | 21 | #include <boost/capy/ex/io_env.hpp> | |||||
| 21 | #include <boost/capy/io_result.hpp> | 22 | #include <boost/capy/io_result.hpp> | |||||
| 22 | 23 | |||||||
| 23 | #include <concepts> | 24 | #include <concepts> | |||||
| 24 | #include <coroutine> | 25 | #include <coroutine> | |||||
| 25 | #include <cstddef> | 26 | #include <cstddef> | |||||
| 26 | #include <exception> | 27 | #include <exception> | |||||
| 27 | #include <new> | 28 | #include <new> | |||||
| 28 | #include <span> | 29 | #include <span> | |||||
| 29 | #include <stop_token> | 30 | #include <stop_token> | |||||
| 30 | #include <system_error> | 31 | #include <system_error> | |||||
| 31 | #include <utility> | 32 | #include <utility> | |||||
| 32 | 33 | |||||||
| 33 | namespace boost { | 34 | namespace boost { | |||||
| 34 | namespace capy { | 35 | namespace capy { | |||||
| 35 | 36 | |||||||
| 36 | - | /** Type-erased wrapper for any WriteStream. | 37 | + | /** Dispatches `write_some` through a type-erased vtable, using preallocated awaitable storage. | |||
| 37 | 38 | |||||||
| 38 | This class provides type erasure for any type satisfying the | 39 | This class provides type erasure for any type satisfying the | |||||
| 39 | @ref WriteStream concept, enabling runtime polymorphism for | 40 | @ref WriteStream concept, enabling runtime polymorphism for | |||||
| 40 | write operations. It uses cached awaitable storage to achieve | 41 | write operations. It uses cached awaitable storage to achieve | |||||
| 41 | zero steady-state allocation after construction. | 42 | zero steady-state allocation after construction. | |||||
| 42 | 43 | |||||||
| 43 | The wrapper supports two construction modes: | 44 | The wrapper supports two construction modes: | |||||
| 44 | - **Owning**: Pass by value to transfer ownership. The wrapper | 45 | - **Owning**: Pass by value to transfer ownership. The wrapper | |||||
| 45 | allocates storage and owns the stream. | 46 | allocates storage and owns the stream. | |||||
| 46 | - **Reference**: Pass a pointer to wrap without ownership. The | 47 | - **Reference**: Pass a pointer to wrap without ownership. The | |||||
| 47 | pointed-to stream must outlive this wrapper. | 48 | pointed-to stream must outlive this wrapper. | |||||
| 48 | 49 | |||||||
| 49 | @par Awaitable Preallocation | 50 | @par Awaitable Preallocation | |||||
| 50 | The constructor preallocates storage for the type-erased awaitable. | 51 | The constructor preallocates storage for the type-erased awaitable. | |||||
| 51 | This reserves all virtual address space at server startup | 52 | This reserves all virtual address space at server startup | |||||
| 52 | so memory usage can be measured up front, rather than | 53 | so memory usage can be measured up front, rather than | |||||
| 53 | allocating piecemeal as traffic arrives. | 54 | allocating piecemeal as traffic arrives. | |||||
| 54 | 55 | |||||||
| 55 | @par Immediate Completion | 56 | @par Immediate Completion | |||||
| 56 | Operations complete immediately without suspending when the | 57 | Operations complete immediately without suspending when the | |||||
| 57 | buffer sequence is empty, or when the underlying stream's | 58 | buffer sequence is empty, or when the underlying stream's | |||||
| 58 | awaitable reports readiness via `await_ready`. | 59 | awaitable reports readiness via `await_ready`. | |||||
| 59 | 60 | |||||||
| 60 | @par Thread Safety | 61 | @par Thread Safety | |||||
| 61 | Not thread-safe. Concurrent operations on the same wrapper | 62 | Not thread-safe. Concurrent operations on the same wrapper | |||||
| 62 | are undefined behavior. | 63 | are undefined behavior. | |||||
| 63 | 64 | |||||||
| 64 | @par Example | 65 | @par Example | |||||
| 65 | @code | 66 | @code | |||||
| 66 | // Owning - takes ownership of the stream | 67 | // Owning - takes ownership of the stream | |||||
| 67 | - | any_write_stream stream(socket{ioc}); | 68 | + | any_write_stream owning_stream(socket{ioc}); | |||
| 68 | 69 | |||||||
| 69 | // Reference - wraps without ownership | 70 | // Reference - wraps without ownership | |||||
| 70 | socket sock(ioc); | 71 | socket sock(ioc); | |||||
| 71 | - | any_write_stream stream(&sock); | 72 | + | any_write_stream ref_stream(&sock); | |||
| 72 | 73 | |||||||
| 73 | - | const_buffer buf(data, size); | 74 | + | char data[] = "hello"; | |||
| 74 | - | auto [ec, n] = co_await stream.write_some(std::span(&buf, 1)); | 75 | + | const_buffer buf(data, sizeof(data)); | |||
| 76 | + | auto [ec, n] = co_await owning_stream.write_some(std::span(&buf, 1)); | ||||||
| 75 | @endcode | 77 | @endcode | |||||
| 76 | 78 | |||||||
| 77 | @see any_read_stream, any_stream, WriteStream | 79 | @see any_read_stream, any_stream, WriteStream | |||||
| 78 | */ | 80 | */ | |||||
| 79 | class any_write_stream | 81 | class any_write_stream | |||||
| 80 | { | 82 | { | |||||
| 81 | struct vtable; | 83 | struct vtable; | |||||
| 82 | 84 | |||||||
| 83 | template<WriteStream S> | 85 | template<WriteStream S> | |||||
| 84 | struct vtable_for_impl; | 86 | struct vtable_for_impl; | |||||
| 85 | 87 | |||||||
| 86 | // ordered for cache line coherence | 88 | // ordered for cache line coherence | |||||
| 87 | void* stream_ = nullptr; | 89 | void* stream_ = nullptr; | |||||
| 88 | vtable const* vt_ = nullptr; | 90 | vtable const* vt_ = nullptr; | |||||
| 89 | void* cached_awaitable_ = nullptr; | 91 | void* cached_awaitable_ = nullptr; | |||||
| 90 | void* storage_ = nullptr; | 92 | void* storage_ = nullptr; | |||||
| 91 | bool awaitable_active_ = false; | 93 | bool awaitable_active_ = false; | |||||
| 92 | 94 | |||||||
| 93 | public: | 95 | public: | |||||
| 94 | /** Destructor. | 96 | /** Destructor. | |||||
| 95 | 97 | |||||||
| 96 | Destroys the owned stream (if any) and releases the cached | 98 | Destroys the owned stream (if any) and releases the cached | |||||
| 97 | awaitable storage. | 99 | awaitable storage. | |||||
| 98 | */ | 100 | */ | |||||
| 99 | ~any_write_stream(); | 101 | ~any_write_stream(); | |||||
| 100 | 102 | |||||||
| 101 | /** Construct a default instance. | 103 | /** Construct a default instance. | |||||
| 102 | 104 | |||||||
| 103 | - | Constructs an empty wrapper. Operations on a default-constructed | 105 | + | Constructs an empty wrapper. @ref has_value and `operator bool` | |||
| 104 | - | wrapper result in undefined behavior. | 106 | + | report the empty state; calling @ref write_some before the | |||
| 107 | + | wrapper holds a stream is undefined behavior. | ||||||
| 105 | */ | 108 | */ | |||||
| HITCBC | 106 | 4 | any_write_stream() = default; | 109 | 4 | any_write_stream() = default; | ||
| 107 | 110 | |||||||
| 108 | /** Non-copyable. | 111 | /** Non-copyable. | |||||
| 109 | 112 | |||||||
| 110 | The awaitable cache is per-instance and cannot be shared. | 113 | The awaitable cache is per-instance and cannot be shared. | |||||
| 114 | + | |||||||
| 115 | + | @param other The wrapper that would be copied. | ||||||
| 111 | */ | 116 | */ | |||||
| 112 | - | any_write_stream(any_write_stream const&) = delete; | 117 | + | any_write_stream(any_write_stream const& other) = delete; | |||
| 113 | - | any_write_stream& operator=(any_write_stream const&) = delete; | 118 | + | ||||
| 119 | + | /** Copy assignment is disabled. | ||||||
| 120 | + | |||||||
| 121 | + | The awaitable cache is per-instance and cannot be shared. | ||||||
| 122 | + | |||||||
| 123 | + | @param other The wrapper that would be assigned from. | ||||||
| 124 | + | |||||||
| 125 | + | @return A reference to `*this`. | ||||||
| 126 | + | */ | ||||||
| 127 | + | any_write_stream& operator=(any_write_stream const& other) = delete; | ||||||
| 114 | 128 | |||||||
| 115 | /** Construct by moving. | 129 | /** Construct by moving. | |||||
| 116 | 130 | |||||||
| 117 | Transfers ownership of the wrapped stream (if owned) and | 131 | Transfers ownership of the wrapped stream (if owned) and | |||||
| 118 | cached awaitable storage from `other`. After the move, `other` is | 132 | cached awaitable storage from `other`. After the move, `other` is | |||||
| 119 | in a default-constructed state. | 133 | in a default-constructed state. | |||||
| 120 | 134 | |||||||
| 121 | @param other The wrapper to move from. | 135 | @param other The wrapper to move from. | |||||
| 122 | */ | 136 | */ | |||||
| HITCBC | 123 | 2 | any_write_stream(any_write_stream&& other) noexcept | 137 | 2 | any_write_stream(any_write_stream&& other) noexcept | ||
| HITCBC | 124 | 2 | : stream_(std::exchange(other.stream_, nullptr)) | 138 | 2 | : stream_(std::exchange(other.stream_, nullptr)) | ||
| HITCBC | 125 | 2 | , vt_(std::exchange(other.vt_, nullptr)) | 139 | 2 | , vt_(std::exchange(other.vt_, nullptr)) | ||
| HITCBC | 126 | 2 | , cached_awaitable_(std::exchange(other.cached_awaitable_, nullptr)) | 140 | 2 | , cached_awaitable_(std::exchange(other.cached_awaitable_, nullptr)) | ||
| HITCBC | 127 | 2 | , storage_(std::exchange(other.storage_, nullptr)) | 141 | 2 | , storage_(std::exchange(other.storage_, nullptr)) | ||
| HITCBC | 128 | 2 | , awaitable_active_(std::exchange(other.awaitable_active_, false)) | 142 | 2 | , awaitable_active_(std::exchange(other.awaitable_active_, false)) | ||
| 129 | { | 143 | { | |||||
| HITCBC | 130 | 2 | } | 144 | 2 | } | ||
| 131 | 145 | |||||||
| 132 | /** Assign by moving. | 146 | /** Assign by moving. | |||||
| 133 | 147 | |||||||
| 134 | Destroys any owned stream and releases existing resources, | 148 | Destroys any owned stream and releases existing resources, | |||||
| 135 | then transfers ownership from `other`. | 149 | then transfers ownership from `other`. | |||||
| 136 | 150 | |||||||
| 137 | @param other The wrapper to move from. | 151 | @param other The wrapper to move from. | |||||
| 138 | @return Reference to this wrapper. | 152 | @return Reference to this wrapper. | |||||
| 139 | */ | 153 | */ | |||||
| 140 | any_write_stream& | 154 | any_write_stream& | |||||
| 141 | operator=(any_write_stream&& other) noexcept; | 155 | operator=(any_write_stream&& other) noexcept; | |||||
| 142 | 156 | |||||||
| 143 | /** Construct by taking ownership of a WriteStream. | 157 | /** Construct by taking ownership of a WriteStream. | |||||
| 144 | 158 | |||||||
| 145 | Allocates storage and moves the stream into this wrapper. | 159 | Allocates storage and moves the stream into this wrapper. | |||||
| 146 | - | The wrapper owns the stream and will destroy it. | 160 | + | The wrapper owns the stream and destroys it. | |||
| 147 | 161 | |||||||
| 148 | @param s The stream to take ownership of. | 162 | @param s The stream to take ownership of. | |||||
| 149 | */ | 163 | */ | |||||
| 150 | template<WriteStream S> | 164 | template<WriteStream S> | |||||
| 151 | requires (!std::same_as<std::decay_t<S>, any_write_stream>) | 165 | requires (!std::same_as<std::decay_t<S>, any_write_stream>) | |||||
| 152 | any_write_stream(S s); | 166 | any_write_stream(S s); | |||||
| 153 | 167 | |||||||
| 154 | /** Construct by wrapping a WriteStream without ownership. | 168 | /** Construct by wrapping a WriteStream without ownership. | |||||
| 155 | 169 | |||||||
| 156 | Wraps the given stream by pointer. The stream must remain | 170 | Wraps the given stream by pointer. The stream must remain | |||||
| 157 | valid for the lifetime of this wrapper. | 171 | valid for the lifetime of this wrapper. | |||||
| 158 | 172 | |||||||
| 159 | @param s Pointer to the stream to wrap. | 173 | @param s Pointer to the stream to wrap. | |||||
| 160 | */ | 174 | */ | |||||
| 161 | template<WriteStream S> | 175 | template<WriteStream S> | |||||
| 162 | any_write_stream(S* s); | 176 | any_write_stream(S* s); | |||||
| 163 | 177 | |||||||
| 164 | /** Check if the wrapper contains a valid stream. | 178 | /** Check if the wrapper contains a valid stream. | |||||
| 165 | 179 | |||||||
| 166 | @return `true` if wrapping a stream, `false` if default-constructed | 180 | @return `true` if wrapping a stream, `false` if default-constructed | |||||
| 167 | or moved-from. | 181 | or moved-from. | |||||
| 168 | */ | 182 | */ | |||||
| 169 | bool | 183 | bool | |||||
| HITCBC | 170 | 26 | has_value() const noexcept | 184 | 26 | has_value() const noexcept | ||
| 171 | { | 185 | { | |||||
| HITCBC | 172 | 26 | return stream_ != nullptr; | 186 | 26 | return stream_ != nullptr; | ||
| 173 | } | 187 | } | |||||
| 174 | 188 | |||||||
| 175 | /** Check if the wrapper contains a valid stream. | 189 | /** Check if the wrapper contains a valid stream. | |||||
| 176 | 190 | |||||||
| 177 | @return `true` if wrapping a stream, `false` if default-constructed | 191 | @return `true` if wrapping a stream, `false` if default-constructed | |||||
| 178 | or moved-from. | 192 | or moved-from. | |||||
| 179 | */ | 193 | */ | |||||
| 180 | explicit | 194 | explicit | |||||
| HITCBC | 181 | 3 | operator bool() const noexcept | 195 | 3 | operator bool() const noexcept | ||
| 182 | { | 196 | { | |||||
| HITCBC | 183 | 3 | return has_value(); | 197 | 3 | return has_value(); | ||
| 184 | } | 198 | } | |||||
| 185 | 199 | |||||||
| 186 | /** Initiate an asynchronous write operation. | 200 | /** Initiate an asynchronous write operation. | |||||
| 187 | 201 | |||||||
| 188 | Writes data from the provided buffer sequence. The operation | 202 | Writes data from the provided buffer sequence. The operation | |||||
| 189 | - | completes when at least one byte has been written, or an error | 203 | + | completes when at least one byte is written, or an error | |||
| 190 | occurs. | 204 | occurs. | |||||
| 191 | 205 | |||||||
| 192 | @param buffers The buffer sequence containing data to write. | 206 | @param buffers The buffer sequence containing data to write. | |||||
| 193 | Passed by value to ensure the sequence lives in the | 207 | Passed by value to ensure the sequence lives in the | |||||
| 194 | coroutine frame across suspension points. | 208 | coroutine frame across suspension points. | |||||
| 195 | 209 | |||||||
| 196 | @return An awaitable that await-returns `(error_code,std::size_t)`. | 210 | @return An awaitable that await-returns `(error_code,std::size_t)`. | |||||
| 197 | 211 | |||||||
| 198 | @par Immediate Completion | 212 | @par Immediate Completion | |||||
| 199 | The operation completes immediately without suspending | 213 | The operation completes immediately without suspending | |||||
| 200 | the calling coroutine when: | 214 | the calling coroutine when: | |||||
| 201 | @li The buffer sequence is empty, returning `{error_code{}, 0}`. | 215 | @li The buffer sequence is empty, returning `{error_code{}, 0}`. | |||||
| 202 | @li The underlying stream's awaitable reports immediate | 216 | @li The underlying stream's awaitable reports immediate | |||||
| 203 | readiness via `await_ready`. | 217 | readiness via `await_ready`. | |||||
| 204 | 218 | |||||||
| 205 | @note This is a partial operation and may not process the | 219 | @note This is a partial operation and may not process the | |||||
| 206 | entire buffer sequence. Use the composed @ref write algorithm | 220 | entire buffer sequence. Use the composed @ref write algorithm | |||||
| 207 | for guaranteed complete transfer. | 221 | for guaranteed complete transfer. | |||||
| 208 | 222 | |||||||
| 209 | @par Preconditions | 223 | @par Preconditions | |||||
| 210 | The wrapper must contain a valid stream (`has_value() == true`). | 224 | The wrapper must contain a valid stream (`has_value() == true`). | |||||
| 225 | + | |||||||
| 226 | + | @par After an Error | ||||||
| 227 | + | A subsequent call is permitted. The wrapper forwards directly | ||||||
| 228 | + | to the underlying stream, imposing no stricter rule than | ||||||
| 229 | + | @ref WriteStream. | ||||||
| 211 | */ | 230 | */ | |||||
| 212 | template<ConstBufferSequence CB> | 231 | template<ConstBufferSequence CB> | |||||
| 213 | auto | 232 | auto | |||||
| 214 | write_some(CB buffers); | 233 | write_some(CB buffers); | |||||
| 215 | 234 | |||||||
| 216 | protected: | 235 | protected: | |||||
| 217 | /** Rebind to a new stream after move. | 236 | /** Rebind to a new stream after move. | |||||
| 218 | 237 | |||||||
| 219 | Updates the internal pointer to reference a new stream object. | 238 | Updates the internal pointer to reference a new stream object. | |||||
| 220 | Used by owning wrappers after move assignment when the owned | 239 | Used by owning wrappers after move assignment when the owned | |||||
| 221 | object has moved to a new location. | 240 | object has moved to a new location. | |||||
| 222 | 241 | |||||||
| 223 | @param new_stream The new stream to bind to. Must be the same | 242 | @param new_stream The new stream to bind to. Must be the same | |||||
| 224 | type as the original stream. | 243 | type as the original stream. | |||||
| 225 | 244 | |||||||
| 226 | @note Terminates if called with a stream of different type | 245 | @note Terminates if called with a stream of different type | |||||
| 227 | than the original. | 246 | than the original. | |||||
| 228 | */ | 247 | */ | |||||
| 229 | template<WriteStream S> | 248 | template<WriteStream S> | |||||
| 230 | void | 249 | void | |||||
| 231 | rebind(S& new_stream) noexcept | 250 | rebind(S& new_stream) noexcept | |||||
| 232 | { | 251 | { | |||||
| 233 | if(vt_ != &vtable_for_impl<S>::value) | 252 | if(vt_ != &vtable_for_impl<S>::value) | |||||
| 234 | std::terminate(); | 253 | std::terminate(); | |||||
| 235 | stream_ = &new_stream; | 254 | stream_ = &new_stream; | |||||
| 236 | } | 255 | } | |||||
| 237 | }; | 256 | }; | |||||
| 238 | 257 | |||||||
| 239 | struct any_write_stream::vtable | 258 | struct any_write_stream::vtable | |||||
| 240 | { | 259 | { | |||||
| 241 | // ordered by call frequency for cache line coherence | 260 | // ordered by call frequency for cache line coherence | |||||
| 242 | void (*construct_awaitable)( | 261 | void (*construct_awaitable)( | |||||
| 243 | void* stream, | 262 | void* stream, | |||||
| 244 | void* storage, | 263 | void* storage, | |||||
| 245 | std::span<const_buffer const> buffers); | 264 | std::span<const_buffer const> buffers); | |||||
| 246 | bool (*await_ready)(void*); | 265 | bool (*await_ready)(void*); | |||||
| 247 | std::coroutine_handle<> (*await_suspend)(void*, std::coroutine_handle<>, io_env const*); | 266 | std::coroutine_handle<> (*await_suspend)(void*, std::coroutine_handle<>, io_env const*); | |||||
| 248 | io_result<std::size_t> (*await_resume)(void*); | 267 | io_result<std::size_t> (*await_resume)(void*); | |||||
| 249 | void (*destroy_awaitable)(void*) noexcept; | 268 | void (*destroy_awaitable)(void*) noexcept; | |||||
| 250 | std::size_t awaitable_size; | 269 | std::size_t awaitable_size; | |||||
| 251 | std::size_t awaitable_align; | 270 | std::size_t awaitable_align; | |||||
| 252 | void (*destroy)(void*) noexcept; | 271 | void (*destroy)(void*) noexcept; | |||||
| 253 | }; | 272 | }; | |||||
| 254 | 273 | |||||||
| 255 | template<WriteStream S> | 274 | template<WriteStream S> | |||||
| 256 | struct any_write_stream::vtable_for_impl | 275 | struct any_write_stream::vtable_for_impl | |||||
| 257 | { | 276 | { | |||||
| 258 | using Awaitable = decltype(std::declval<S&>().write_some( | 277 | using Awaitable = decltype(std::declval<S&>().write_some( | |||||
| 259 | std::span<const_buffer const>{})); | 278 | std::span<const_buffer const>{})); | |||||
| 260 | 279 | |||||||
| 261 | static void | 280 | static void | |||||
| HITCBC | 262 | 3 | do_destroy_impl(void* stream) noexcept | 281 | 3 | do_destroy_impl(void* stream) noexcept | ||
| 263 | { | 282 | { | |||||
| HITCBC | 264 | 3 | static_cast<S*>(stream)->~S(); | 283 | 3 | static_cast<S*>(stream)->~S(); | ||
| HITCBC | 265 | 3 | } | 284 | 3 | } | ||
| 266 | 285 | |||||||
| 267 | static void | 286 | static void | |||||
| HITCBC | 268 | 89 | construct_awaitable_impl( | 287 | 89 | construct_awaitable_impl( | ||
| 269 | void* stream, | 288 | void* stream, | |||||
| 270 | void* storage, | 289 | void* storage, | |||||
| 271 | std::span<const_buffer const> buffers) | 290 | std::span<const_buffer const> buffers) | |||||
| 272 | { | 291 | { | |||||
| HITCBC | 273 | 89 | auto& s = *static_cast<S*>(stream); | 292 | 89 | auto& s = *static_cast<S*>(stream); | ||
| HITCBC | 274 | 89 | ::new(storage) Awaitable(s.write_some(buffers)); | 293 | 89 | ::new(storage) Awaitable(s.write_some(buffers)); | ||
| HITCBC | 275 | 89 | } | 294 | 89 | } | ||
| 276 | 295 | |||||||
| 277 | static constexpr vtable value = { | 296 | static constexpr vtable value = { | |||||
| 278 | &construct_awaitable_impl, | 297 | &construct_awaitable_impl, | |||||
| HITCBC | 279 | 89 | +[](void* p) { | 298 | 89 | +[](void* p) { | ||
| HITCBC | 280 | 89 | return static_cast<Awaitable*>(p)->await_ready(); | 299 | 89 | return static_cast<Awaitable*>(p)->await_ready(); | ||
| 281 | }, | 300 | }, | |||||
| HITCBC | 282 | 79 | +[](void* p, std::coroutine_handle<> h, io_env const* env) { | 301 | 79 | +[](void* p, std::coroutine_handle<> h, io_env const* env) { | ||
| HITCBC | 283 | 79 | return detail::call_await_suspend( | 302 | 79 | return detail::call_await_suspend( | ||
| HITCBC | 284 | 79 | static_cast<Awaitable*>(p), h, env); | 303 | 79 | static_cast<Awaitable*>(p), h, env); | ||
| 285 | }, | 304 | }, | |||||
| HITCBC | 286 | 87 | +[](void* p) { | 305 | 87 | +[](void* p) { | ||
| HITCBC | 287 | 87 | return static_cast<Awaitable*>(p)->await_resume(); | 306 | 87 | return static_cast<Awaitable*>(p)->await_resume(); | ||
| 288 | }, | 307 | }, | |||||
| HITCBC | 289 | 91 | +[](void* p) noexcept { | 308 | 91 | +[](void* p) noexcept { | ||
| HITCBC | 290 | 26 | static_cast<Awaitable*>(p)->~Awaitable(); | 309 | 26 | static_cast<Awaitable*>(p)->~Awaitable(); | ||
| 291 | }, | 310 | }, | |||||
| 292 | sizeof(Awaitable), | 311 | sizeof(Awaitable), | |||||
| 293 | alignof(Awaitable), | 312 | alignof(Awaitable), | |||||
| 294 | &do_destroy_impl | 313 | &do_destroy_impl | |||||
| 295 | }; | 314 | }; | |||||
| 296 | }; | 315 | }; | |||||
| 297 | 316 | |||||||
| 298 | inline | 317 | inline | |||||
| HITCBC | 299 | 117 | any_write_stream::~any_write_stream() | 318 | 117 | any_write_stream::~any_write_stream() | ||
| 300 | { | 319 | { | |||||
| HITCBC | 301 | 117 | if(storage_) | 320 | 117 | if(storage_) | ||
| 302 | { | 321 | { | |||||
| HITCBC | 303 | 2 | vt_->destroy(stream_); | 322 | 2 | vt_->destroy(stream_); | ||
| HITCBC | 304 | 2 | ::operator delete(storage_); | 323 | 2 | ::operator delete(storage_); | ||
| 305 | } | 324 | } | |||||
| HITCBC | 306 | 117 | if(cached_awaitable_) | 325 | 117 | if(cached_awaitable_) | ||
| 307 | { | 326 | { | |||||
| HITCBC | 308 | 102 | if(awaitable_active_) | 327 | 102 | if(awaitable_active_) | ||
| HITCBC | 309 | 1 | vt_->destroy_awaitable(cached_awaitable_); | 328 | 1 | vt_->destroy_awaitable(cached_awaitable_); | ||
| HITCBC | 310 | 102 | ::operator delete(cached_awaitable_); | 329 | 102 | ::operator delete(cached_awaitable_); | ||
| 311 | } | 330 | } | |||||
| HITCBC | 312 | 117 | } | 331 | 117 | } | ||
| 313 | 332 | |||||||
| 314 | inline any_write_stream& | 333 | inline any_write_stream& | |||||
| HITCBC | 315 | 10 | any_write_stream::operator=(any_write_stream&& other) noexcept | 334 | 10 | any_write_stream::operator=(any_write_stream&& other) noexcept | ||
| 316 | { | 335 | { | |||||
| HITCBC | 317 | 10 | if(this != &other) | 336 | 10 | if(this != &other) | ||
| 318 | { | 337 | { | |||||
| HITCBC | 319 | 10 | if(storage_) | 338 | 10 | if(storage_) | ||
| 320 | { | 339 | { | |||||
| HITCBC | 321 | 1 | vt_->destroy(stream_); | 340 | 1 | vt_->destroy(stream_); | ||
| HITCBC | 322 | 1 | ::operator delete(storage_); | 341 | 1 | ::operator delete(storage_); | ||
| 323 | } | 342 | } | |||||
| HITCBC | 324 | 10 | if(cached_awaitable_) | 343 | 10 | if(cached_awaitable_) | ||
| 325 | { | 344 | { | |||||
| HITCBC | 326 | 4 | if(awaitable_active_) | 345 | 4 | if(awaitable_active_) | ||
| HITCBC | 327 | 1 | vt_->destroy_awaitable(cached_awaitable_); | 346 | 1 | vt_->destroy_awaitable(cached_awaitable_); | ||
| HITCBC | 328 | 4 | ::operator delete(cached_awaitable_); | 347 | 4 | ::operator delete(cached_awaitable_); | ||
| 329 | } | 348 | } | |||||
| HITCBC | 330 | 10 | stream_ = std::exchange(other.stream_, nullptr); | 349 | 10 | stream_ = std::exchange(other.stream_, nullptr); | ||
| HITCBC | 331 | 10 | vt_ = std::exchange(other.vt_, nullptr); | 350 | 10 | vt_ = std::exchange(other.vt_, nullptr); | ||
| HITCBC | 332 | 10 | cached_awaitable_ = std::exchange(other.cached_awaitable_, nullptr); | 351 | 10 | cached_awaitable_ = std::exchange(other.cached_awaitable_, nullptr); | ||
| HITCBC | 333 | 10 | storage_ = std::exchange(other.storage_, nullptr); | 352 | 10 | storage_ = std::exchange(other.storage_, nullptr); | ||
| HITCBC | 334 | 10 | awaitable_active_ = std::exchange(other.awaitable_active_, false); | 353 | 10 | awaitable_active_ = std::exchange(other.awaitable_active_, false); | ||
| 335 | } | 354 | } | |||||
| HITCBC | 336 | 10 | return *this; | 355 | 10 | return *this; | ||
| 337 | } | 356 | } | |||||
| 338 | 357 | |||||||
| 339 | template<WriteStream S> | 358 | template<WriteStream S> | |||||
| 340 | requires (!std::same_as<std::decay_t<S>, any_write_stream>) | 359 | requires (!std::same_as<std::decay_t<S>, any_write_stream>) | |||||
| HITCBC | 341 | 4 | any_write_stream::any_write_stream(S s) | 360 | 4 | any_write_stream::any_write_stream(S s) | ||
| HITCBC | 342 | 4 | : vt_(&vtable_for_impl<S>::value) | 361 | 4 | : vt_(&vtable_for_impl<S>::value) | ||
| 343 | { | 362 | { | |||||
| 344 | struct guard { | 363 | struct guard { | |||||
| 345 | any_write_stream* self; | 364 | any_write_stream* self; | |||||
| 346 | bool committed = false; | 365 | bool committed = false; | |||||
| HITCBC | 347 | 4 | ~guard() { | 366 | 4 | ~guard() { | ||
| HITCBC | 348 | 4 | if(!committed && self->storage_) { | 367 | 4 | if(!committed && self->storage_) { | ||
| HITCBC | 349 | 1 | if(self->stream_) | 368 | 1 | if(self->stream_) | ||
| 350 | self->vt_->destroy(self->stream_); // LCOV_EXCL_LINE OOM rollback: only when the cached-awaitable allocation throws | 369 | self->vt_->destroy(self->stream_); // LCOV_EXCL_LINE OOM rollback: only when the cached-awaitable allocation throws | |||||
| HITCBC | 351 | 1 | ::operator delete(self->storage_); | 370 | 1 | ::operator delete(self->storage_); | ||
| HITCBC | 352 | 1 | self->storage_ = nullptr; | 371 | 1 | self->storage_ = nullptr; | ||
| HITCBC | 353 | 1 | self->stream_ = nullptr; | 372 | 1 | self->stream_ = nullptr; | ||
| 354 | } | 373 | } | |||||
| HITCBC | 355 | 4 | } | 374 | 4 | } | ||
| HITCBC | 356 | 4 | } g{this}; | 375 | 4 | } g{this}; | ||
| 357 | 376 | |||||||
| HITCBC | 358 | 4 | storage_ = ::operator new(sizeof(S)); | 377 | 4 | storage_ = ::operator new(sizeof(S)); | ||
| HITCBC | 359 | 4 | stream_ = ::new(storage_) S(std::move(s)); | 378 | 4 | stream_ = ::new(storage_) S(std::move(s)); | ||
| 360 | 379 | |||||||
| 361 | // Preallocate the awaitable storage | 380 | // Preallocate the awaitable storage | |||||
| HITCBC | 362 | 3 | cached_awaitable_ = ::operator new(vt_->awaitable_size); | 381 | 3 | cached_awaitable_ = ::operator new(vt_->awaitable_size); | ||
| 363 | 382 | |||||||
| HITCBC | 364 | 3 | g.committed = true; | 383 | 3 | g.committed = true; | ||
| HITCBC | 365 | 4 | } | 384 | 4 | } | ||
| 366 | 385 | |||||||
| 367 | template<WriteStream S> | 386 | template<WriteStream S> | |||||
| HITCBC | 368 | 103 | any_write_stream::any_write_stream(S* s) | 387 | 103 | any_write_stream::any_write_stream(S* s) | ||
| HITCBC | 369 | 103 | : stream_(s) | 388 | 103 | : stream_(s) | ||
| HITCBC | 370 | 103 | , vt_(&vtable_for_impl<S>::value) | 389 | 103 | , vt_(&vtable_for_impl<S>::value) | ||
| 371 | { | 390 | { | |||||
| 372 | // Preallocate the awaitable storage | 391 | // Preallocate the awaitable storage | |||||
| HITCBC | 373 | 103 | cached_awaitable_ = ::operator new(vt_->awaitable_size); | 392 | 103 | cached_awaitable_ = ::operator new(vt_->awaitable_size); | ||
| HITCBC | 374 | 103 | } | 393 | 103 | } | ||
| 375 | 394 | |||||||
| 376 | template<ConstBufferSequence CB> | 395 | template<ConstBufferSequence CB> | |||||
| 377 | auto | 396 | auto | |||||
| HITCBC | 378 | 93 | any_write_stream::write_some(CB buffers) | 397 | 93 | any_write_stream::write_some(CB buffers) | ||
| 379 | { | 398 | { | |||||
| 380 | struct awaitable | 399 | struct awaitable | |||||
| 381 | { | 400 | { | |||||
| 382 | any_write_stream* self_; | 401 | any_write_stream* self_; | |||||
| 383 | detail::const_buffer_array<detail::max_iovec_> ba_; | 402 | detail::const_buffer_array<detail::max_iovec_> ba_; | |||||
| 384 | 403 | |||||||
| HITCBC | 385 | 93 | awaitable( | 404 | 93 | awaitable( | ||
| 386 | any_write_stream* self, | 405 | any_write_stream* self, | |||||
| 387 | CB const& buffers) noexcept | 406 | CB const& buffers) noexcept | |||||
| HITCBC | 388 | 93 | : self_(self) | 407 | 93 | : self_(self) | ||
| HITCBC | 389 | 93 | , ba_(buffers) | 408 | 93 | , ba_(buffers) | ||
| 390 | { | 409 | { | |||||
| HITCBC | 391 | 93 | } | 410 | 93 | } | ||
| 392 | 411 | |||||||
| 393 | bool | 412 | bool | |||||
| HITCBC | 394 | 93 | await_ready() const noexcept | 413 | 93 | await_ready() const noexcept | ||
| 395 | { | 414 | { | |||||
| HITCBC | 396 | 93 | return ba_.to_span().empty(); | 415 | 93 | return ba_.to_span().empty(); | ||
| 397 | } | 416 | } | |||||
| 398 | 417 | |||||||
| 399 | std::coroutine_handle<> | 418 | std::coroutine_handle<> | |||||
| HITCBC | 400 | 89 | await_suspend(std::coroutine_handle<> h, io_env const* env) | 419 | 89 | await_suspend(std::coroutine_handle<> h, io_env const* env) | ||
| 401 | { | 420 | { | |||||
| HITCBC | 402 | 89 | self_->vt_->construct_awaitable( | 421 | 89 | self_->vt_->construct_awaitable( | ||
| HITCBC | 403 | 89 | self_->stream_, | 422 | 89 | self_->stream_, | ||
| HITCBC | 404 | 89 | self_->cached_awaitable_, | 423 | 89 | self_->cached_awaitable_, | ||
| HITCBC | 405 | 89 | ba_.to_span()); | 424 | 89 | ba_.to_span()); | ||
| HITCBC | 406 | 89 | self_->awaitable_active_ = true; | 425 | 89 | self_->awaitable_active_ = true; | ||
| 407 | 426 | |||||||
| HITCBC | 408 | 89 | if(self_->vt_->await_ready(self_->cached_awaitable_)) | 427 | 89 | if(self_->vt_->await_ready(self_->cached_awaitable_)) | ||
| HITCBC | 409 | 10 | return h; | 428 | 10 | return h; | ||
| 410 | 429 | |||||||
| HITCBC | 411 | 79 | return self_->vt_->await_suspend( | 430 | 79 | return self_->vt_->await_suspend( | ||
| HITCBC | 412 | 79 | self_->cached_awaitable_, h, env); | 431 | 79 | self_->cached_awaitable_, h, env); | ||
| 413 | } | 432 | } | |||||
| 414 | 433 | |||||||
| 415 | io_result<std::size_t> | 434 | io_result<std::size_t> | |||||
| HITCBC | 416 | 91 | await_resume() | 435 | 91 | await_resume() | ||
| 417 | { | 436 | { | |||||
| HITCBC | 418 | 91 | if(!self_->awaitable_active_) | 437 | 91 | if(!self_->awaitable_active_) | ||
| HITCBC | 419 | 4 | return {{}, 0}; | 438 | 4 | return {{}, 0}; | ||
| 420 | struct guard { | 439 | struct guard { | |||||
| 421 | any_write_stream* self; | 440 | any_write_stream* self; | |||||
| HITCBC | 422 | 87 | ~guard() { | 441 | 87 | ~guard() { | ||
| HITCBC | 423 | 87 | self->vt_->destroy_awaitable(self->cached_awaitable_); | 442 | 87 | self->vt_->destroy_awaitable(self->cached_awaitable_); | ||
| HITCBC | 424 | 87 | self->awaitable_active_ = false; | 443 | 87 | self->awaitable_active_ = false; | ||
| HITCBC | 425 | 87 | } | 444 | 87 | } | ||
| HITCBC | 426 | 87 | } g{self_}; | 445 | 87 | } g{self_}; | ||
| HITCBC | 427 | 87 | return self_->vt_->await_resume( | 446 | 87 | return self_->vt_->await_resume( | ||
| HITCBC | 428 | 87 | self_->cached_awaitable_); | 447 | 87 | self_->cached_awaitable_); | ||
| HITCBC | 429 | 87 | } | 448 | 87 | } | ||
| 430 | }; | 449 | }; | |||||
| HITCBC | 431 | 93 | return awaitable{this, buffers}; | 450 | 93 | return awaitable{this, buffers}; | ||
| 432 | } | 451 | } | |||||
| 433 | 452 | |||||||
| 434 | } // namespace capy | 453 | } // namespace capy | |||||
| 435 | } // namespace boost | 454 | } // namespace boost | |||||
| 436 | 455 | |||||||
| 437 | #endif | 456 | #endif | |||||