MySQL 8.0.40
Source Code Documentation
managed_buffer.h
Go to the documentation of this file.
1/* Copyright (c) 2023, 2024, Oracle and/or its affiliates.
2
3 This program is free software; you can redistribute it and/or modify
4 it under the terms of the GNU General Public License, version 2.0,
5 as published by the Free Software Foundation.
6
7 This program is designed to work with certain software (including
8 but not limited to OpenSSL) that is licensed under separate terms,
9 as designated in a particular file or component or in included license
10 documentation. The authors of MySQL hereby grant you an additional
11 permission to link the program and your derivative works with the
12 separately licensed software that they have either included with
13 the program or referenced in the documentation.
14
15 This program is distributed in the hope that it will be useful,
16 but WITHOUT ANY WARRANTY; without even the implied warranty of
17 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 GNU General Public License, version 2.0, for more details.
19
20 You should have received a copy of the GNU General Public License
21 along with this program; if not, write to the Free Software
22 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */
23
24/// @addtogroup Replication
25/// @{
26///
27/// @file managed_buffer.h
28///
29/// Container class that provides a contiguous memory buffer to
30/// the caller, which the caller can request to grow.
31///
32/// The growth rate is determined by a Grow_calculator.
33
34#ifndef MYSQL_BUFFER_MANAGED_BUFFER_H_
35#define MYSQL_BUFFER_MANAGED_BUFFER_H_
36
37#include <limits>
38
39#include "grow_calculator.h" // buffer::Grow_calculator
40#include "grow_status.h" // buffer::Grow_status
41#include "libbinlogevents/include/nodiscard.h" // NODISCARD
44#include "rw_buffer.h" // buffer::Rw_buffer
45
47
48namespace mysqlns::buffer {
49
50/// Owned, growable, contiguous memory buffer.
51///
52/// This class provides a single contiguous buffer. Therefore, it may
53/// have to move data when it grows. It is implemented as a Buffer
54/// that is resized using realloc.
55///
56/// The caller can provide a user-defined, pre-allocated buffer, which
57/// will then be used as long as it suffices; a new buffer will be
58/// allocated if not. This can be used to remove the need for
59/// allocation in use cases where the object is small.
60///
61/// Objects have a growable total capacity, which is split into two
62/// parts; the read part and the write part, each represented as a
63/// `Buffer_view`. The intended use case is where the user interacts
64/// with an API that produces data into a user-provided buffer. The
65/// user can then: (1) grow the buffer before invoking the API; (2)
66/// invoke the API to write data to the write part; (3) tell the
67/// Managed_buffer to move the written bytes to the read part.
68///
69/// Generally, std::stringstream or std::vector<char> are safer and
70/// simpler interfaces for buffers and should be preferred when
71/// possible. However they do not fit all use cases:
72///
73/// - std::stringstream is preferrable and more convenient when
74/// appending existing data to the stream. But it is not suitable
75/// for interaction with C-like APIs that produce data in a char*
76/// given by the caller. The user would need to allocate a buffer
77/// outside the stringsteam and then append the buffer to the
78/// stringstream, which would imply unnecessary memory and cpu
79/// overheads.
80///
81/// - When using a C-like API that produces data in a char* given by
82/// the caller, std::vector is often good. The user can reserve as
83/// much memory as needed and then pass the underlying data array to
84/// the API. However, the following properties are sometimes
85/// advantageous for Managed_buffer:
86///
87/// - Vector has no practical way to put an exact bound on the
88/// memory usage. Managed_buffer uses a Grow_calculator which
89/// allows exact control on memory usage, including a maximum
90/// capacity.
91///
92/// - Even for small buffer sizes, vector needs at least one heap
93/// allocation. Managed_buffer allows the use of a default buffer
94/// of fixed size, for example allocated on the stack. This can
95/// reduce the need for heap allocation in use patterns where the
96/// required buffer capacity is *usually* small.
97///
98/// The main drawback of Managed_buffer is that it is non-standard and
99/// has a minimal feature set.
100///
101/// The main difference between Buffer_sequence and Managed_buffer, is
102/// that Managed_buffer keeps data in a contiguous buffer, whereas
103/// Buffer_sequence never copies data.
104///
105/// This class never throws any exception.
106///
107/// @tparam Char_t The char type; usually char or unsigned char.
108///
109/// @tparam builtin_capacity Size of pre-allocated initial buffer.
110template <class Char_tp = unsigned char>
111class Managed_buffer : public buffer::Rw_buffer<Char_tp> {
112 public:
113 using Char_t = Char_tp;
116 using typename Rw_buffer_t::Const_iterator_t;
117 using typename Rw_buffer_t::Iterator_t;
118 using typename Rw_buffer_t::Size_t;
119 // As soon as all platforms support it, change to:
120 // using Allocator_t = std::pmr::polymorphic_allocator<Char_t>;
124
125 /// Construct a new object without a default buffer.
126 // Nolint: clang-tidy does not recognize that m_owns_default_buffer
127 // is initialized, despite it is initialized in the targed
128 // constructor.
129 // NOLINTBEGIN(cppcoreguidelines-pro-type-member-init)
131 const Memory_resource_t &memory_resource = Memory_resource_t())
132 : Managed_buffer(Size_t(0), memory_resource) {}
133 // NOLINTEND(cppcoreguidelines-pro-type-member-init)
134
135 /// Construct a new object that owns a default buffer.
136 ///
137 /// The default buffer is created when needed. Once
138 /// created, it survives calls to @c reset and will only be deleted
139 /// when the Managed_buffer is deleted.
140 ///
141 /// @param default_capacity The capacity of the default buffer.
142 ///
143 /// @param memory_resource Memory_resource used to allocate memory.
145 Size_t default_capacity,
146 const Memory_resource_t &memory_resource = Memory_resource_t())
147 : Rw_buffer<Char_t>(),
148 m_char_allocator(memory_resource),
150 m_default_capacity(default_capacity),
151 m_owns_default_buffer(true) {}
152
153 /// Construct a new object that uses the given default buffer.
154 ///
155 /// The default buffer is owned by the caller, so the caller must
156 /// ensure that it outlives the Managed_buffer.
157 ///
158 /// @param default_buffer The default buffer.
159 ///
160 /// @param memory_resource Memory_resource used to allocate memory.
162 Buffer_view_t default_buffer,
163 const Memory_resource_t &memory_resource = Memory_resource_t())
164 : Rw_buffer<Char_t>(default_buffer),
165 m_char_allocator(memory_resource),
166 m_default_buffer(default_buffer.begin()),
167 m_default_capacity(default_buffer.size()),
168 m_owns_default_buffer(false) {}
169
171 Managed_buffer(Managed_buffer &&other) noexcept = default;
173 Managed_buffer &operator=(Managed_buffer &&other) noexcept = default;
174
175 ~Managed_buffer() override {
176 auto *ptr = this->read_part().begin();
177 bool delete_default_buffer =
179 bool delete_buffer = ptr != nullptr && ptr != m_default_buffer;
180 if (delete_default_buffer)
182 if (delete_buffer) m_char_allocator.deallocate(ptr, this->capacity());
183 }
184
185 /// Reserve space so that the total buffer size is at least the
186 /// given number.
187 ///
188 /// The buffer will be resized if necessary. So, on successful
189 /// return, the caller should call begin() to get the new buffer
190 /// pointer.
191 ///
192 /// @note This may move existing data to a new address; consider any
193 /// existing pointers into the buffer as invalid after this call.
194 ///
195 /// @param requested_size The requested total size of the read part
196 /// and the write part.
197 ///
198 /// @retval Grow_status::success The object now has at least the
199 /// given size; either it was successfully re-allocated, or it
200 /// already had the requested size.
201 ///
202 /// @retval Grow_status::exceeds_max_size if requested_size
203 /// exceeds the configured maximum size.
204 ///
205 /// @retval Grow_status::out_of_memory Memory allocation failed.
208 auto capacity = this->capacity();
209 auto [error, new_capacity] =
212 if (new_capacity > capacity) {
213 if (new_capacity <= m_default_capacity) {
214 // We have capacity < new_capacity <= m_default_capacity.
215 // Since we never allocate capacity less than the default
216 // capacity, this situation only occurs when the capacity is
217 // 0. And since we make use of the default buffer as soon as
218 // we allocate it, it also means that the default buffer is
219 // nullptr.
220 assert(capacity == 0);
221 assert(m_default_buffer == nullptr);
223 if (m_default_buffer == nullptr) return Grow_status::out_of_memory;
225 } else {
226 // Use dynamic buffer.
227 Char_t *new_buffer = allocate_buffer(new_capacity);
228 if (new_buffer == nullptr) return Grow_status::out_of_memory;
229 replace_buffer(new_buffer, new_capacity);
230 }
231 }
233 }
234
235 /// Reserve space so that the write size is at least the given
236 /// number.
237 ///
238 /// @param requested_write_size The requested size of the write
239 /// part.
240 ///
241 /// @retval Grow_status::success The write part now has at least the
242 /// given size; either it was successfully re-allocated, or it
243 /// already had the requested size.
244 ///
245 /// @retval Grow_status::exceeds_max_size if the existing read size
246 /// plus requested_write_size exceeds the max size configured in the
247 /// Grow_calculator.
248 ///
249 /// @retval Grow_status::out_of_memory Memory allocation failed.
250 [[NODISCARD]] Grow_status reserve_write_size(Size_t requested_write_size) {
251 auto read_size = this->read_part().size();
252 if (requested_write_size > std::numeric_limits<Size_t>::max() - read_size)
254 return reserve_total_size(read_size + requested_write_size);
255 }
256
257 /// Reset the buffer.
258 ///
259 /// This makes the read part empty. The write part will point to
260 /// the default buffer if there is one; otherwise the write part
261 /// will be empty.
262 void reset() {
264 auto *rb = this->read_part().begin();
265 if (rb != nullptr && rb != m_default_buffer)
266 m_char_allocator.deallocate(this->read_part().begin(), this->capacity());
268 if (m_default_buffer == nullptr)
269 this->write_part() = Buffer_view_t();
270 else
272 }
273
274 /// Set the grow calculator.
275 ///
276 /// Details:
277 ///
278 /// - If the new Grow_calculator's maximum size is less than the
279 /// current buffer size, it does not change the existing buffer, but
280 /// subsequent calls to reserve will fail.
281 ///
282 /// - In case the new Grow_calculator's maximum size is less than
283 /// the default capacity, this object will provide capacity equal to
284 /// the default_size, exceeding the Grow_calculator's maximum size.
285 void set_grow_calculator(const Grow_calculator_t &grow_calculator) {
286 m_grow_calculator = grow_calculator;
287 }
288
289 /// Return a const reference to the grow calculator.
291 return m_grow_calculator;
292 }
293
294 /// Return the size of the default buffer.
296
297 private:
298 /// Allocate a new buffer and return it.
299 ///
300 /// This never throws; it returns nullptr on out of memory.
301 ///
302 /// @param new_size The size of the buffer.
303 ///
304 /// @returns the new buffer on success, nullptr on out of memory.
306 try {
307 return m_char_allocator.allocate(new_size);
308 } catch (std::bad_alloc &) {
309 return nullptr;
310 }
311 }
312
313 /// Replace the underlying data buffer by the given one.
314 ///
315 /// @param new_buffer The new buffer. This must be different from
316 /// the old buffer.
317 ///
318 /// @param new_size The size of the new buffer.
319 void replace_buffer(Char_t *new_buffer, Size_t new_size) {
320 assert(new_buffer != this->read_part().data());
321 auto &r = this->read_part();
322 auto &w = this->write_part();
323 auto read_size = r.size();
324 if (read_size) memcpy(new_buffer, r.begin(), read_size);
325 if (r.begin() != m_default_buffer && r.begin() != nullptr)
326 m_char_allocator.deallocate(r.begin(), this->capacity());
327 r = Buffer_view_t(new_buffer, read_size);
328 w = Buffer_view_t(new_buffer + read_size, new_size - read_size);
329 }
330
331 /// Calculator for growing the buffer.
333
334 /// Allocator to grow the buffer.
336
337 /// User-provided, user-owned buffer.
339
340 /// Size of user-provided, user-owned buffer.
342
343 /// If true, the default buffer will be deallocated by the destructor.
345};
346
348 std::size_t(8 * 1024);
349
350/// @see Managed_buffer
351///
352/// This class pre-allocates a fixed-size initial buffer,
353/// which is beneficial in use patterns where managed buffers are
354/// allocated on the stack and are usually only given small amount of
355/// data.
356template <class Char_t = unsigned char,
357 std::size_t preallocated_size =
360 public:
364
366 const Memory_resource_t &memory_resource = Memory_resource_t())
368 Buffer_view_t(m_preallocated_buffer, preallocated_size),
369 memory_resource) {}
370
371 private:
372 /// Preallocated buffer.
373 Char_t m_preallocated_buffer[preallocated_size == 0 ? 1 : preallocated_size];
374};
375
376} // namespace mysqlns::buffer
377
378/// @} (end of group Replication)
379#endif // MYSQL_BUFFER_MANAGED_BUFFER_H_
Iterator_t begin()
Return pointer to the first character of the data.
Definition: buffer_view.h:94
Size_t size() const
Return the number of bytes.
Definition: buffer_view.h:112
Description of a heuristic to determine how much memory to allocate.
Definition: grow_calculator.h:67
Result_t compute_new_size(Size_t old_size, Size_t requested_size) const
Compute the new size.
Definition: grow_calculator.cpp:39
Owned, growable, contiguous memory buffer.
Definition: managed_buffer.h:111
Managed_buffer & operator=(Managed_buffer &&other) noexcept=default
Managed_buffer(Managed_buffer &other)=delete
Char_t * allocate_buffer(Size_t new_size)
Allocate a new buffer and return it.
Definition: managed_buffer.h:305
Managed_buffer(Buffer_view_t default_buffer, const Memory_resource_t &memory_resource=Memory_resource_t())
Construct a new object that uses the given default buffer.
Definition: managed_buffer.h:161
Char_tp Char_t
Definition: managed_buffer.h:113
Grow_calculator_t m_grow_calculator
Calculator for growing the buffer.
Definition: managed_buffer.h:332
void replace_buffer(Char_t *new_buffer, Size_t new_size)
Replace the underlying data buffer by the given one.
Definition: managed_buffer.h:319
Grow_status reserve_total_size(Size_t requested_size)
Reserve space so that the total buffer size is at least the given number.
Definition: managed_buffer.h:206
void reset()
Reset the buffer.
Definition: managed_buffer.h:262
const Grow_calculator_t & get_grow_calculator() const
Return a const reference to the grow calculator.
Definition: managed_buffer.h:290
Managed_buffer(Size_t default_capacity, const Memory_resource_t &memory_resource=Memory_resource_t())
Construct a new object that owns a default buffer.
Definition: managed_buffer.h:144
Size_t get_default_capacity()
Return the size of the default buffer.
Definition: managed_buffer.h:295
Char_t * m_default_buffer
User-provided, user-owned buffer.
Definition: managed_buffer.h:338
Char_allocator_t m_char_allocator
Allocator to grow the buffer.
Definition: managed_buffer.h:335
Managed_buffer(const Memory_resource_t &memory_resource=Memory_resource_t())
Construct a new object without a default buffer.
Definition: managed_buffer.h:130
bool m_owns_default_buffer
If true, the default buffer will be deallocated by the destructor.
Definition: managed_buffer.h:344
mysqlns::resource::Memory_resource Memory_resource_t
Definition: managed_buffer.h:121
Size_t m_default_capacity
Size of user-provided, user-owned buffer.
Definition: managed_buffer.h:341
std::size_t Size_t
Definition: rw_buffer.h:63
Managed_buffer(Managed_buffer &&other) noexcept=default
void set_grow_calculator(const Grow_calculator_t &grow_calculator)
Set the grow calculator.
Definition: managed_buffer.h:285
~Managed_buffer() override
Definition: managed_buffer.h:175
Grow_status reserve_write_size(Size_t requested_write_size)
Reserve space so that the write size is at least the given number.
Definition: managed_buffer.h:250
Buffer_view< Char_t > Buffer_view_t
Definition: managed_buffer.h:114
Managed_buffer & operator=(Managed_buffer &other)=delete
Definition: managed_buffer.h:359
Preallocated_managed_buffer(const Memory_resource_t &memory_resource=Memory_resource_t())
Definition: managed_buffer.h:365
Char_t m_preallocated_buffer[preallocated_size==0 ? 1 :preallocated_size]
Preallocated buffer.
Definition: managed_buffer.h:373
Non-owning read/write memory buffer manager with a fixed size.
Definition: rw_buffer.h:60
const Char_t * Const_iterator_t
Definition: rw_buffer.h:66
Char_t * Iterator_t
Definition: rw_buffer.h:65
const Buffer_view_t & read_part() const
Return the read part.
Definition: rw_buffer.h:95
Size_t capacity() const
Return the total size of the read part and the write part.
Definition: rw_buffer.h:107
const Buffer_view_t & write_part() const
Return the write part.
Definition: rw_buffer.h:101
std::size_t Size_t
Definition: rw_buffer.h:63
constexpr T * allocate(size_type n)
Use the Memory_resource to allocate the given number of elements of type T.
Definition: allocator.h:84
constexpr void deallocate(T *p, size_type size)
Use the Memory_resource to deallocate the given pointer.
Definition: allocator.h:95
Polymorphism-free memory resource class with custom allocator and deallocator functions.
Definition: memory_resource.h:88
Fido Client Authentication nullptr
Definition: fido_client_plugin.cc:222
Allocator class that uses a polymorphic Memory_resource to allocate memory.
Class that wraps resources in a polymorphic manner.
Log error(cerr, "ERROR")
Definition: buffer_sequence_view.h:51
Grow_status
Error statuses for classes that use Grow_calculator.
Definition: grow_status.h:37
@ exceeds_max_size
A grow operation could not be performed because there is a configured maximum size.
@ success
A grow operation succeeded.
@ out_of_memory
A grow operation failed because memory allocation failed.
constexpr std::size_t default_preallocated_managed_buffer_size
Definition: managed_buffer.h:347
#define NODISCARD
The function attribute [[NODISCARD]] is a replacement for [[nodiscard]] to workaround a gcc bug.
Definition: nodiscard.h:47
const mysql_service_registry_t * r
Definition: pfs_example_plugin_employee.cc:86
Non-owning manager for a fixed memory buffer, which is split into a read part and a write part,...
Contains wrapper functions for memory allocation and deallocation.
#define BAPI_TRACE
Definition: wrapper_functions.h:66