TLA Line data Source code
1 : //
2 : // Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com)
3 : // Copyright (c) 2026 Michael Vandeberg
4 : //
5 : // Distributed under the Boost Software License, Version 1.0. (See accompanying
6 : // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
7 : //
8 : // Official repository: https://github.com/cppalliance/capy
9 : //
10 :
11 : /*
12 : COROUTINE BUFFER SEQUENCE LIFETIME REQUIREMENT
13 : ===============================================
14 : Buffer sequence parameters in coroutine APIs MUST be passed BY VALUE,
15 : never by reference. When a coroutine suspends, reference parameters may
16 : dangle if the caller's object goes out of scope before resumption.
17 :
18 : CORRECT: task<> read_some(MutableBufferSequence auto buffers)
19 : WRONG: task<> read_some(MutableBufferSequence auto& buffers)
20 : WRONG: task<> read_some(MutableBufferSequence auto const& buffers)
21 :
22 : The buffer_param class works with this model: it takes a const& in its
23 : constructor (for the non-coroutine scope) but the caller's template
24 : function accepts the buffer sequence by value, ensuring the sequence
25 : lives in the coroutine frame.
26 : */
27 :
28 : #ifndef BOOST_CAPY_BUFFERS_BUFFER_PARAM_HPP
29 : #define BOOST_CAPY_BUFFERS_BUFFER_PARAM_HPP
30 :
31 : #include <boost/capy/detail/config.hpp>
32 : #include <boost/capy/buffers.hpp>
33 :
34 : #include <new>
35 : #include <span>
36 : #include <type_traits>
37 :
38 : namespace boost {
39 : namespace capy {
40 :
41 : /** A buffer sequence wrapper providing windowed access.
42 :
43 : This template class wraps any buffer sequence and provides
44 : incremental access through a sliding window of buffer
45 : descriptors. It handles both const and mutable buffer
46 : sequences automatically.
47 :
48 : @par Coroutine Lifetime Requirement
49 :
50 : When used in coroutine APIs, the outer template function
51 : MUST accept the buffer sequence parameter BY VALUE:
52 :
53 : @code
54 : task<> write(ConstBufferSequence auto buffers); // CORRECT
55 : task<> write(ConstBufferSequence auto& buffers); // WRONG - dangling reference
56 : @endcode
57 :
58 : Pass-by-value ensures the buffer sequence is copied into
59 : the coroutine frame and remains valid across suspension
60 : points. References would dangle when the caller's scope
61 : exits before the coroutine resumes.
62 :
63 : @par Purpose
64 :
65 : When iterating through large buffer sequences, it is often
66 : more efficient to process buffers in batches rather than
67 : one at a time. This class maintains a window of up to a
68 : fixed, implementation-defined number of buffer descriptors
69 : (currently 16). It refills the window from the underlying
70 : sequence as buffers are consumed.
71 :
72 : @par Example
73 :
74 : Create a `buffer_param` from any buffer sequence and use
75 : `data()` to get the current window of buffers. After
76 : processing some bytes, call `consume()` to advance through
77 : the sequence.
78 :
79 : @code
80 : task<> send(ConstBufferSequence auto buffers)
81 : {
82 : buffer_param bp(buffers);
83 : while(true)
84 : {
85 : auto bufs = bp.data();
86 : if(bufs.empty())
87 : break;
88 : auto n = co_await do_something(bufs);
89 : bp.consume(n);
90 : }
91 : }
92 : @endcode
93 :
94 : @par Virtual Interface Pattern
95 :
96 : This class enables passing arbitrary buffer sequences through
97 : a virtual function boundary. The template function captures
98 : the buffer sequence by value and drives the iteration, while
99 : the virtual function receives a simple span. Plain CTAD
100 : (`buffer_param bp(buffers)`) deduces `BS`'s own buffer type, so a
101 : mutable sequence yields `span<mutable_buffer>`. That does not match
102 : `write_impl`'s `span<const_buffer>` parameter. Use @ref const_buffer_param
103 : to force `const_buffer` storage regardless of what `BS` is:
104 :
105 : @code
106 : class base
107 : {
108 : public:
109 : template<ConstBufferSequence BS>
110 : task<> write(BS buffers)
111 : {
112 : const_buffer_param<BS> bp(buffers);
113 : while(true)
114 : {
115 : auto bufs = bp.data();
116 : if(bufs.empty())
117 : break;
118 : std::size_t n = 0;
119 : co_await write_impl(bufs, n);
120 : bp.consume(n);
121 : }
122 : }
123 :
124 : protected:
125 : virtual task<> write_impl(
126 : std::span<const_buffer> buffers,
127 : std::size_t& bytes_written) = 0;
128 : };
129 : @endcode
130 :
131 : @tparam BS The buffer sequence type. Must satisfy either
132 : ConstBufferSequence or MutableBufferSequence.
133 :
134 : @see ConstBufferSequence, MutableBufferSequence
135 : */
136 : template<class BS, bool MakeConst = false>
137 : requires ConstBufferSequence<BS> || MutableBufferSequence<BS>
138 : class buffer_param
139 : {
140 : public:
141 : /// Names `const_buffer` when `MakeConst`, else `BS`'s own buffer type.
142 : using buffer_type = std::conditional_t<
143 : MakeConst,
144 : const_buffer,
145 : capy::buffer_type<BS>>;
146 :
147 : private:
148 : decltype(begin(std::declval<BS const&>())) it_;
149 : decltype(end(std::declval<BS const&>())) end_;
150 : union {
151 : int dummy_;
152 : buffer_type arr_[detail::max_iovec_];
153 : };
154 : std::size_t size_ = 0;
155 : std::size_t pos_ = 0;
156 :
157 : void
158 HIT 28 : refill()
159 : {
160 28 : pos_ = 0;
161 28 : size_ = 0;
162 128 : for(; it_ != end_ && size_ < detail::max_iovec_; ++it_)
163 : {
164 100 : buffer_type buf(*it_);
165 100 : if(buf.size() > 0)
166 96 : ::new(&arr_[size_++]) buffer_type(buf);
167 : }
168 28 : }
169 :
170 : public:
171 : /** Construct from a buffer sequence.
172 :
173 : @param bs The buffer sequence to wrap. The caller must
174 : ensure the buffer sequence remains valid for the
175 : lifetime of this object.
176 : */
177 : explicit
178 15 : buffer_param(BS const& bs)
179 15 : : it_(begin(bs))
180 15 : , end_(end(bs))
181 15 : , dummy_(0)
182 : {
183 15 : refill();
184 15 : }
185 :
186 : /** Return the current window of buffer descriptors.
187 :
188 : Returns a span of buffer descriptors representing the
189 : currently available portion of the buffer sequence.
190 : The span contains at most a fixed, implementation-defined
191 : number of buffers (currently 16).
192 :
193 : When the current window is exhausted, this function
194 : automatically refills from the underlying sequence.
195 :
196 : @return A span of buffer descriptors. Empty span
197 : indicates no more data is available.
198 : */
199 : std::span<buffer_type>
200 27 : data()
201 : {
202 27 : if(pos_ >= size_)
203 13 : refill();
204 27 : if(size_ == 0)
205 9 : return {};
206 18 : return {arr_ + pos_, size_ - pos_};
207 : }
208 :
209 : /** Check if more buffers exist beyond the current window.
210 :
211 : Returns `true` if the underlying buffer sequence has
212 : additional buffers that have not yet been loaded into
213 : the current window. Call after @ref data to determine
214 : whether the current window is the last one.
215 :
216 : @return `true` if more buffers remain in the sequence.
217 : */
218 : bool
219 5 : more() const noexcept
220 : {
221 5 : return it_ != end_;
222 : }
223 :
224 : /** Consume bytes from the buffer sequence.
225 :
226 : Advances the current position by `n` bytes, consuming
227 : data from the front of the sequence. Partially consumed
228 : buffers are adjusted in place.
229 :
230 : @param n Number of bytes to consume.
231 : */
232 : void
233 16 : consume(std::size_t n)
234 : {
235 98 : while(n > 0 && pos_ < size_)
236 : {
237 82 : auto avail = arr_[pos_].size();
238 82 : if(n < avail)
239 : {
240 5 : arr_[pos_] += n;
241 5 : n = 0;
242 : }
243 : else
244 : {
245 77 : n -= avail;
246 77 : ++pos_;
247 : }
248 : }
249 16 : }
250 : };
251 :
252 : /** Deduce the sequence type from the constructor argument.
253 :
254 : @tparam BS The buffer sequence type.
255 : */
256 : template<class BS>
257 : buffer_param(BS const&) -> buffer_param<BS>;
258 :
259 : /// Forces `buffer_param` to store windows as `const_buffer`, regardless of `BS`.
260 : template<class BS>
261 : using const_buffer_param = buffer_param<BS, true>;
262 :
263 : } // namespace capy
264 : } // namespace boost
265 :
266 : #endif
|