From 554b9b13dd59db0d48eb5d05ce765e656b15ba34 Mon Sep 17 00:00:00 2001 From: vsoulgard Date: Mon, 16 Mar 2026 21:44:56 +0300 Subject: [PATCH 01/15] Optimize message memmoving by predicting next message size --- .../mysql/impl/internal/ema_calculator.hpp | 69 +++++++++++++++++++ .../impl/internal/sansio/message_reader.hpp | 14 +++- 2 files changed, 81 insertions(+), 2 deletions(-) create mode 100644 include/boost/mysql/impl/internal/ema_calculator.hpp diff --git a/include/boost/mysql/impl/internal/ema_calculator.hpp b/include/boost/mysql/impl/internal/ema_calculator.hpp new file mode 100644 index 000000000..533efe999 --- /dev/null +++ b/include/boost/mysql/impl/internal/ema_calculator.hpp @@ -0,0 +1,69 @@ +// +// Copyright (c) 2026 Vladislav Soulgard (vsoulgard at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BOOST_MYSQL_IMPL_INTERNAL_EMA_CALCULATOR_HPP +#define BOOST_MYSQL_IMPL_INTERNAL_EMA_CALCULATOR_HPP + +#include +#include + +#include +#include +#include + +namespace boost { +namespace mysql { +namespace detail { + +/** + * \brief Integer-based Exponential Moving Average (EMA). + * + * This class implements a fixed-point EMA using bitwise shifts. + * It maintains internal precision by scaling the value. + * + * Formula: average_new = average_old - (average_old >> shift) + next_value + * + * \tparam Shift The smoothing factor exponent (alpha = 1 / 2^shift). + * Higher values result in more smoothing. + */ +template +class ema_calculator +{ + // std::uint64_t provides a strong guarantee against overflow + using IntType = std::uint64_t; + + static_assert(Shift > 0, "Shift must be greater than 0."); + static_assert(Shift < (sizeof(IntType) * 4), "Shift exceeds half the bit-width of the type."); + + static BOOST_INLINE_CONSTEXPR IntType max_value = std::numeric_limits::max() >> Shift; + + IntType average_; + +public: + ema_calculator(IntType initial_value) : average_(initial_value) + { + BOOST_ASSERT(initial_value <= max_value); + } + + void update(IntType next_value) noexcept + { + BOOST_ASSERT(next_value <= max_value); + average_ = average_ - (average_ >> Shift) + next_value; + } + + inline IntType get_average() const noexcept + { + // Return the average by scaling back down + return average_ >> Shift; + } +}; + +} // namespace detail +} // namespace mysql +} // namespace boost + +#endif diff --git a/include/boost/mysql/impl/internal/sansio/message_reader.hpp b/include/boost/mysql/impl/internal/sansio/message_reader.hpp index cbe46a758..22c8b8e39 100644 --- a/include/boost/mysql/impl/internal/sansio/message_reader.hpp +++ b/include/boost/mysql/impl/internal/sansio/message_reader.hpp @@ -15,6 +15,7 @@ #include #include #include +#include #include #include @@ -42,7 +43,9 @@ class message_reader std::size_t max_buffer_size = static_cast(-1), std::size_t max_frame_size = max_packet_size ) - : buffer_(initial_buffer_size, max_buffer_size), max_frame_size_(max_frame_size) + : buffer_(initial_buffer_size, max_buffer_size), + max_frame_size_(max_frame_size), + avg_msg_size_calculator_(0) { } @@ -93,7 +96,11 @@ class message_reader BOOST_ATTRIBUTE_NODISCARD error_code prepare_buffer() { - buffer_.remove_reserved(); + auto avg_msg_size = avg_msg_size_calculator_.get_average(); + // Clear reserved area if predicted message size is + // bigger than free space (including pending) + if (avg_msg_size > buffer_.free_size() + buffer_.pending_size()) + buffer_.remove_reserved(); auto ec = buffer_.grow_to_fit(state_.required_size); if (ec) return ec; @@ -167,6 +174,8 @@ class message_reader // Check if we're done if (!state_.more_frames_follow) { + // Update average message size calculator + avg_msg_size_calculator_.update(buffer_.current_message_size()); state_.resume_point = -1; return; } @@ -180,6 +189,7 @@ class message_reader private: read_buffer buffer_; std::size_t max_frame_size_; + ema_calculator<4> avg_msg_size_calculator_; struct parse_state { From c8079708a6670381968004afcba2c06f3a8246cc Mon Sep 17 00:00:00 2001 From: vsoulgard Date: Mon, 16 Mar 2026 23:01:47 +0300 Subject: [PATCH 02/15] Fix calculator initial size --- include/boost/mysql/impl/internal/sansio/message_reader.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/boost/mysql/impl/internal/sansio/message_reader.hpp b/include/boost/mysql/impl/internal/sansio/message_reader.hpp index 22c8b8e39..ab670e58e 100644 --- a/include/boost/mysql/impl/internal/sansio/message_reader.hpp +++ b/include/boost/mysql/impl/internal/sansio/message_reader.hpp @@ -45,7 +45,7 @@ class message_reader ) : buffer_(initial_buffer_size, max_buffer_size), max_frame_size_(max_frame_size), - avg_msg_size_calculator_(0) + avg_msg_size_calculator_(initial_buffer_size) { } From 2f096bdd04aab474ca72dcd1a08149131d875137 Mon Sep 17 00:00:00 2001 From: vsoulgard Date: Tue, 17 Mar 2026 07:20:59 +0300 Subject: [PATCH 03/15] Fix comparison --- include/boost/mysql/impl/internal/sansio/message_reader.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/boost/mysql/impl/internal/sansio/message_reader.hpp b/include/boost/mysql/impl/internal/sansio/message_reader.hpp index ab670e58e..9b9eea39c 100644 --- a/include/boost/mysql/impl/internal/sansio/message_reader.hpp +++ b/include/boost/mysql/impl/internal/sansio/message_reader.hpp @@ -99,7 +99,7 @@ class message_reader auto avg_msg_size = avg_msg_size_calculator_.get_average(); // Clear reserved area if predicted message size is // bigger than free space (including pending) - if (avg_msg_size > buffer_.free_size() + buffer_.pending_size()) + if (avg_msg_size >= buffer_.free_size() + buffer_.pending_size()) buffer_.remove_reserved(); auto ec = buffer_.grow_to_fit(state_.required_size); if (ec) From e4980455bcf90ef30b726420e2ca41278438250d Mon Sep 17 00:00:00 2001 From: vsoulgard Date: Tue, 17 Mar 2026 07:43:48 +0300 Subject: [PATCH 04/15] Fix comparasion --- include/boost/mysql/impl/internal/sansio/message_reader.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/boost/mysql/impl/internal/sansio/message_reader.hpp b/include/boost/mysql/impl/internal/sansio/message_reader.hpp index 9b9eea39c..c27d1c696 100644 --- a/include/boost/mysql/impl/internal/sansio/message_reader.hpp +++ b/include/boost/mysql/impl/internal/sansio/message_reader.hpp @@ -99,7 +99,7 @@ class message_reader auto avg_msg_size = avg_msg_size_calculator_.get_average(); // Clear reserved area if predicted message size is // bigger than free space (including pending) - if (avg_msg_size >= buffer_.free_size() + buffer_.pending_size()) + if (avg_msg_size >= buffer_.free_size() + buffer_.pending_size() || avg_msg_size > buffer_.max_size()) buffer_.remove_reserved(); auto ec = buffer_.grow_to_fit(state_.required_size); if (ec) From 7f1d1986878ed9059041193a6484bfbdc1c832cf Mon Sep 17 00:00:00 2001 From: vsoulgard Date: Tue, 17 Mar 2026 08:30:12 +0300 Subject: [PATCH 05/15] Fix bug --- include/boost/mysql/impl/internal/ema_calculator.hpp | 2 +- include/boost/mysql/impl/internal/sansio/message_reader.hpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/include/boost/mysql/impl/internal/ema_calculator.hpp b/include/boost/mysql/impl/internal/ema_calculator.hpp index 533efe999..f6beb9255 100644 --- a/include/boost/mysql/impl/internal/ema_calculator.hpp +++ b/include/boost/mysql/impl/internal/ema_calculator.hpp @@ -44,7 +44,7 @@ class ema_calculator IntType average_; public: - ema_calculator(IntType initial_value) : average_(initial_value) + ema_calculator(IntType initial_value) : average_(initial_value << Shift) { BOOST_ASSERT(initial_value <= max_value); } diff --git a/include/boost/mysql/impl/internal/sansio/message_reader.hpp b/include/boost/mysql/impl/internal/sansio/message_reader.hpp index c27d1c696..9b9eea39c 100644 --- a/include/boost/mysql/impl/internal/sansio/message_reader.hpp +++ b/include/boost/mysql/impl/internal/sansio/message_reader.hpp @@ -99,7 +99,7 @@ class message_reader auto avg_msg_size = avg_msg_size_calculator_.get_average(); // Clear reserved area if predicted message size is // bigger than free space (including pending) - if (avg_msg_size >= buffer_.free_size() + buffer_.pending_size() || avg_msg_size > buffer_.max_size()) + if (avg_msg_size >= buffer_.free_size() + buffer_.pending_size()) buffer_.remove_reserved(); auto ec = buffer_.grow_to_fit(state_.required_size); if (ec) From 5c7f6cbc88398d15e4a0f1bc867ae6aa43fb894d Mon Sep 17 00:00:00 2001 From: vsoulgard Date: Sun, 22 Mar 2026 22:44:59 +0300 Subject: [PATCH 06/15] New solution --- .../mysql/impl/internal/ema_calculator.hpp | 69 ------------------- .../impl/internal/sansio/message_reader.hpp | 22 +++--- 2 files changed, 11 insertions(+), 80 deletions(-) delete mode 100644 include/boost/mysql/impl/internal/ema_calculator.hpp diff --git a/include/boost/mysql/impl/internal/ema_calculator.hpp b/include/boost/mysql/impl/internal/ema_calculator.hpp deleted file mode 100644 index f6beb9255..000000000 --- a/include/boost/mysql/impl/internal/ema_calculator.hpp +++ /dev/null @@ -1,69 +0,0 @@ -// -// Copyright (c) 2026 Vladislav Soulgard (vsoulgard at gmail dot com) -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// - -#ifndef BOOST_MYSQL_IMPL_INTERNAL_EMA_CALCULATOR_HPP -#define BOOST_MYSQL_IMPL_INTERNAL_EMA_CALCULATOR_HPP - -#include -#include - -#include -#include -#include - -namespace boost { -namespace mysql { -namespace detail { - -/** - * \brief Integer-based Exponential Moving Average (EMA). - * - * This class implements a fixed-point EMA using bitwise shifts. - * It maintains internal precision by scaling the value. - * - * Formula: average_new = average_old - (average_old >> shift) + next_value - * - * \tparam Shift The smoothing factor exponent (alpha = 1 / 2^shift). - * Higher values result in more smoothing. - */ -template -class ema_calculator -{ - // std::uint64_t provides a strong guarantee against overflow - using IntType = std::uint64_t; - - static_assert(Shift > 0, "Shift must be greater than 0."); - static_assert(Shift < (sizeof(IntType) * 4), "Shift exceeds half the bit-width of the type."); - - static BOOST_INLINE_CONSTEXPR IntType max_value = std::numeric_limits::max() >> Shift; - - IntType average_; - -public: - ema_calculator(IntType initial_value) : average_(initial_value << Shift) - { - BOOST_ASSERT(initial_value <= max_value); - } - - void update(IntType next_value) noexcept - { - BOOST_ASSERT(next_value <= max_value); - average_ = average_ - (average_ >> Shift) + next_value; - } - - inline IntType get_average() const noexcept - { - // Return the average by scaling back down - return average_ >> Shift; - } -}; - -} // namespace detail -} // namespace mysql -} // namespace boost - -#endif diff --git a/include/boost/mysql/impl/internal/sansio/message_reader.hpp b/include/boost/mysql/impl/internal/sansio/message_reader.hpp index 9b9eea39c..5fde41d1d 100644 --- a/include/boost/mysql/impl/internal/sansio/message_reader.hpp +++ b/include/boost/mysql/impl/internal/sansio/message_reader.hpp @@ -15,7 +15,6 @@ #include #include #include -#include #include #include @@ -43,9 +42,7 @@ class message_reader std::size_t max_buffer_size = static_cast(-1), std::size_t max_frame_size = max_packet_size ) - : buffer_(initial_buffer_size, max_buffer_size), - max_frame_size_(max_frame_size), - avg_msg_size_calculator_(initial_buffer_size) + : buffer_(initial_buffer_size, max_buffer_size), max_frame_size_(max_frame_size) { } @@ -96,11 +93,17 @@ class message_reader BOOST_ATTRIBUTE_NODISCARD error_code prepare_buffer() { - auto avg_msg_size = avg_msg_size_calculator_.get_average(); - // Clear reserved area if predicted message size is - // bigger than free space (including pending) - if (avg_msg_size >= buffer_.free_size() + buffer_.pending_size()) + constexpr std::size_t small_move_threshold = 1024; + const std::size_t occupied_space = buffer_.pending_size() + buffer_.current_message_size(); + // Compact the buffer (remove reserved area) if one of the following holds: + // 1. The cost of memmove is low: active data (current_message + pending) + // is small enough to make the copy cheap. + // 2. Compaction could prevent a reallocation. + if (occupied_space <= small_move_threshold || + (state_.required_size > buffer_.free_size())) + { buffer_.remove_reserved(); + } auto ec = buffer_.grow_to_fit(state_.required_size); if (ec) return ec; @@ -174,8 +177,6 @@ class message_reader // Check if we're done if (!state_.more_frames_follow) { - // Update average message size calculator - avg_msg_size_calculator_.update(buffer_.current_message_size()); state_.resume_point = -1; return; } @@ -189,7 +190,6 @@ class message_reader private: read_buffer buffer_; std::size_t max_frame_size_; - ema_calculator<4> avg_msg_size_calculator_; struct parse_state { From 09cb3082a7f0043cf579171c7a1f725312105a23 Mon Sep 17 00:00:00 2001 From: vsoulgard Date: Wed, 25 Mar 2026 01:06:25 +0300 Subject: [PATCH 07/15] Grow read buffer to next power of two --- .../mysql/impl/internal/next_power_of_two.hpp | 54 +++++++++++++++++++ .../impl/internal/sansio/read_buffer.hpp | 12 ++++- test/unit/test/sansio/message_reader.cpp | 6 +-- test/unit/test/sansio/read_buffer.cpp | 16 +++--- 4 files changed, 75 insertions(+), 13 deletions(-) create mode 100644 include/boost/mysql/impl/internal/next_power_of_two.hpp diff --git a/include/boost/mysql/impl/internal/next_power_of_two.hpp b/include/boost/mysql/impl/internal/next_power_of_two.hpp new file mode 100644 index 000000000..eeb840e1e --- /dev/null +++ b/include/boost/mysql/impl/internal/next_power_of_two.hpp @@ -0,0 +1,54 @@ +// +// Copyright (c) 2026 Vladislav Soulgard (vsoulgard at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BOOST_MYSQL_IMPL_INTERNAL_NEXT_POWER_OF_TWO_HPP +#define BOOST_MYSQL_IMPL_INTERNAL_NEXT_POWER_OF_TWO_HPP + +#include + +namespace boost { +namespace mysql { +namespace detail { + +template +struct recursive_shift_or; + +// Apply shift and continue to the next power of 2 +template +struct recursive_shift_or +{ + static void apply(UnsignedInt& n) + { + n |= n >> Shift; + recursive_shift_or::apply(n); + } +}; + +// Stop recursion when shift exceeds type width +template +struct recursive_shift_or +{ + static void apply(UnsignedInt&) {} +}; + +// Returns the smallest power of two greater than or equal to n +template +UnsignedInt next_power_of_two(UnsignedInt n) noexcept +{ + static_assert(std::is_unsigned::value, ""); + if (n == 0) return 1; + n--; + // Fill all lower bits + recursive_shift_or<1, UnsignedInt, (1 < sizeof(UnsignedInt) * 8)>::apply(n); + return n + 1; +} + +} // namespace detail +} // namespace mysql +} // namespace boost + +#endif \ No newline at end of file diff --git a/include/boost/mysql/impl/internal/sansio/read_buffer.hpp b/include/boost/mysql/impl/internal/sansio/read_buffer.hpp index ee17a608a..9197bbafe 100644 --- a/include/boost/mysql/impl/internal/sansio/read_buffer.hpp +++ b/include/boost/mysql/impl/internal/sansio/read_buffer.hpp @@ -11,6 +11,8 @@ #include #include +#include + #include #include #include @@ -138,14 +140,20 @@ class read_buffer } // Makes sure the free size is at least n bytes long; resizes the buffer if required + // Buffer grows to power of two, unless limited by max_size BOOST_ATTRIBUTE_NODISCARD error_code grow_to_fit(std::size_t n) { if (free_size() < n) { - std::size_t new_size = buffer_.size() + n - free_size(); + std::size_t required_size = buffer_.size() + n - free_size(); + std::size_t new_size = next_power_of_two(required_size); if (new_size > max_size_) - return client_errc::max_buffer_size_exceeded; + { + new_size = required_size; + if (new_size > max_size_) + return client_errc::max_buffer_size_exceeded; + } buffer_.resize(new_size); } return error_code(); diff --git a/test/unit/test/sansio/message_reader.cpp b/test/unit/test/sansio/message_reader.cpp index 411e12225..1f3b14caf 100644 --- a/test/unit/test/sansio/message_reader.cpp +++ b/test/unit/test/sansio/message_reader.cpp @@ -384,7 +384,7 @@ BOOST_AUTO_TEST_CASE(buffer_resizing_not_enough_space) ec = fix.reader.prepare_buffer(); BOOST_TEST(ec == error_code()); fix.record_buffer_first(); - BOOST_TEST(fix.buffsize() == 50u); + BOOST_TEST(fix.buffsize() == 64u); // Finish reading fix.read_bytes(50); @@ -406,7 +406,7 @@ BOOST_AUTO_TEST_CASE(buffer_resizing_old_messages_removed) fix.check_message(u8vec(60, 0x04)); // Record size, as this should not increase - BOOST_TEST(fix.buffsize() == 60u); + BOOST_TEST(fix.buffsize() == 64u); // Parse new messages for (std::uint8_t i = 0u; i < 100u; ++i) @@ -429,7 +429,7 @@ BOOST_AUTO_TEST_CASE(buffer_resizing_old_messages_removed) } // Buffer size should be the same - BOOST_TEST(fix.buffsize() == 60u); + BOOST_TEST(fix.buffsize() == 64u); } BOOST_AUTO_TEST_CASE(buffer_resizing_size_eq_max_size) diff --git a/test/unit/test/sansio/read_buffer.cpp b/test/unit/test/sansio/read_buffer.cpp index 93ee07d00..42efd0486 100644 --- a/test/unit/test/sansio/read_buffer.cpp +++ b/test/unit/test/sansio/read_buffer.cpp @@ -448,7 +448,7 @@ BOOST_AUTO_TEST_CASE(not_enough_space) auto ec = buff.grow_to_fit(100); BOOST_TEST(ec == error_code()); - check_buffer(buff, {}, {0x01, 0x02, 0x03, 0x04, 0x05, 0x06}, {0x07, 0x08}, 100); + check_buffer(buff, {}, {0x01, 0x02, 0x03, 0x04, 0x05, 0x06}, {0x07, 0x08}, 120); checker.check_reallocation(); } @@ -463,7 +463,7 @@ BOOST_AUTO_TEST_CASE(one_missing_byte) auto ec = buff.grow_to_fit(9); BOOST_TEST(ec == error_code()); - check_buffer(buff, {}, {0x01, 0x02, 0x03, 0x04, 0x05, 0x06}, {0x07, 0x08}, 9); + check_buffer(buff, {}, {0x01, 0x02, 0x03, 0x04, 0x05, 0x06}, {0x07, 0x08}, 24); checker.check_reallocation(); } @@ -518,7 +518,7 @@ BOOST_AUTO_TEST_CASE(lt_max_size) auto ec = buff.grow_to_fit(7); BOOST_TEST(ec == error_code()); - check_buffer(buff, {}, {0x01, 0x02, 0x03, 0x04, 0x05, 0x06}, {0x07, 0x08}, 7); + check_buffer(buff, {}, {0x01, 0x02, 0x03, 0x04, 0x05, 0x06}, {0x07, 0x08}, 8); checker.check_reallocation(); } @@ -563,25 +563,25 @@ BOOST_AUTO_TEST_CASE(several_grows) // Grow with reallocation auto ec = buff.grow_to_fit(4); BOOST_TEST(ec == error_code()); - check_buffer(buff, {}, {0x01, 0x02, 0x03, 0x04, 0x05, 0x06}, {0x07, 0x08}, 4); + check_buffer(buff, {}, {0x01, 0x02, 0x03, 0x04, 0x05, 0x06}, {0x07, 0x08}, 8); // Place some more bytes in the buffer copy_to_free_area(buff, {0x09, 0x0a}); buff.move_to_pending(2); - check_buffer(buff, {}, {0x01, 0x02, 0x03, 0x04, 0x05, 0x06}, {0x07, 0x08, 0x09, 0x0a}, 2); + check_buffer(buff, {}, {0x01, 0x02, 0x03, 0x04, 0x05, 0x06}, {0x07, 0x08, 0x09, 0x0a}, 6); // Grow without reallocation ec = buff.grow_to_fit(2); BOOST_TEST(ec == error_code()); - check_buffer(buff, {}, {0x01, 0x02, 0x03, 0x04, 0x05, 0x06}, {0x07, 0x08, 0x09, 0x0a}, 2); + check_buffer(buff, {}, {0x01, 0x02, 0x03, 0x04, 0x05, 0x06}, {0x07, 0x08, 0x09, 0x0a}, 6); copy_to_free_area(buff, {0x0b, 0x0c}); buff.move_to_pending(2); - check_buffer(buff, {}, {0x01, 0x02, 0x03, 0x04, 0x05, 0x06}, {0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c}, 0); + check_buffer(buff, {}, {0x01, 0x02, 0x03, 0x04, 0x05, 0x06}, {0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c}, 4); // Fail when attempting to grow past max size ec = buff.grow_to_fit(5); BOOST_TEST(ec == client_errc::max_buffer_size_exceeded); - check_buffer(buff, {}, {0x01, 0x02, 0x03, 0x04, 0x05, 0x06}, {0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c}, 0); + check_buffer(buff, {}, {0x01, 0x02, 0x03, 0x04, 0x05, 0x06}, {0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c}, 4); } BOOST_AUTO_TEST_SUITE_END() From ec8ba88962a38646822825b798518691bbe9ed04 Mon Sep 17 00:00:00 2001 From: vsoulgard Date: Thu, 26 Mar 2026 19:05:27 +0300 Subject: [PATCH 08/15] Add unit tests --- .../mysql/impl/internal/next_power_of_two.hpp | 18 ++- test/unit/CMakeLists.txt | 1 + test/unit/test/impl/next_power_of_two.cpp | 78 +++++++++++ test/unit/test/sansio/message_reader.cpp | 126 +++++++++++++++++- test/unit/test/sansio/read_buffer.cpp | 28 ++++ 5 files changed, 247 insertions(+), 4 deletions(-) create mode 100644 test/unit/test/impl/next_power_of_two.cpp diff --git a/include/boost/mysql/impl/internal/next_power_of_two.hpp b/include/boost/mysql/impl/internal/next_power_of_two.hpp index eeb840e1e..6b19d5c18 100644 --- a/include/boost/mysql/impl/internal/next_power_of_two.hpp +++ b/include/boost/mysql/impl/internal/next_power_of_two.hpp @@ -8,7 +8,10 @@ #ifndef BOOST_MYSQL_IMPL_INTERNAL_NEXT_POWER_OF_TWO_HPP #define BOOST_MYSQL_IMPL_INTERNAL_NEXT_POWER_OF_TWO_HPP +#include + #include +#include namespace boost { namespace mysql { @@ -35,11 +38,22 @@ struct recursive_shift_or static void apply(UnsignedInt&) {} }; -// Returns the smallest power of two greater than or equal to n +// Returns the smallest power of two greater than or equal to n. +// Precondition: n must not exceed the largest power of two that fits +// in UnsignedInt. For example: +// - uint8_t: n <= 128 (2^7) +// - uint16_t: n <= 32768 (2^15) +// - uint32_t: n <= 2147483648 (2^31) +// - uint64_t: n <= 9223372036854775808 (2^63) +// +// Passing a larger value results in undefined behavior (overflow). +// In debug builds, this is caught by BOOST_ASSERT. template UnsignedInt next_power_of_two(UnsignedInt n) noexcept { static_assert(std::is_unsigned::value, ""); + // Assert overflow (if value is bigger than maximum power) + BOOST_ASSERT(!(n > (std::numeric_limits::max() >> 1) + 1)); if (n == 0) return 1; n--; // Fill all lower bits @@ -51,4 +65,4 @@ UnsignedInt next_power_of_two(UnsignedInt n) noexcept } // namespace mysql } // namespace boost -#endif \ No newline at end of file +#endif diff --git a/test/unit/CMakeLists.txt b/test/unit/CMakeLists.txt index 85bcf8d6a..c2c0af888 100644 --- a/test/unit/CMakeLists.txt +++ b/test/unit/CMakeLists.txt @@ -79,6 +79,7 @@ add_executable( test/impl/dt_to_string.cpp test/impl/ssl_context_with_default.cpp test/impl/variant_stream.cpp + test/impl/next_power_of_two.cpp test/spotchecks/connection_use_after_move.cpp test/spotchecks/default_completion_tokens.cpp diff --git a/test/unit/test/impl/next_power_of_two.cpp b/test/unit/test/impl/next_power_of_two.cpp new file mode 100644 index 000000000..2a5cbb69e --- /dev/null +++ b/test/unit/test/impl/next_power_of_two.cpp @@ -0,0 +1,78 @@ +// +// Copyright (c) 2026 Vladislav Soulgard (vsoulgard at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#include + +#include + +#include + +using namespace boost::mysql::detail; + +BOOST_AUTO_TEST_SUITE(test_next_power_of_two) + +BOOST_AUTO_TEST_CASE(basic) +{ + // n = 0 (special case) + BOOST_TEST(next_power_of_two(0u) == 1u); + + // n is already power of two + BOOST_TEST(next_power_of_two(1u) == 1u); + BOOST_TEST(next_power_of_two(2u) == 2u); + BOOST_TEST(next_power_of_two(4u) == 4u); + BOOST_TEST(next_power_of_two(8u) == 8u); + BOOST_TEST(next_power_of_two(16u) == 16u); + BOOST_TEST(next_power_of_two(32u) == 32u); + BOOST_TEST(next_power_of_two(64u) == 64u); + BOOST_TEST(next_power_of_two(128u) == 128u); + + // n just below power of two + BOOST_TEST(next_power_of_two(3u) == 4u); + BOOST_TEST(next_power_of_two(7u) == 8u); + BOOST_TEST(next_power_of_two(15u) == 16u); + BOOST_TEST(next_power_of_two(31u) == 32u); + BOOST_TEST(next_power_of_two(63u) == 64u); + BOOST_TEST(next_power_of_two(127u) == 128u); + + // n just above power of two + BOOST_TEST(next_power_of_two(5u) == 8u); + BOOST_TEST(next_power_of_two(9u) == 16u); + BOOST_TEST(next_power_of_two(17u) == 32u); + BOOST_TEST(next_power_of_two(33u) == 64u); + BOOST_TEST(next_power_of_two(65u) == 128u); + BOOST_TEST(next_power_of_two(129u) == 256u); +} + +BOOST_AUTO_TEST_CASE(different_types) +{ + // uint8_t + BOOST_TEST(next_power_of_two(0u) == 1u); + BOOST_TEST(next_power_of_two(62u) == 64u); + BOOST_TEST(next_power_of_two(100u) == 128u); + BOOST_TEST(next_power_of_two(128u) == 128u); + + // uint16_t + BOOST_TEST(next_power_of_two(0u) == 1u); + BOOST_TEST(next_power_of_two(1000u) == 1024u); + BOOST_TEST(next_power_of_two(16383u) == 16384u); + BOOST_TEST(next_power_of_two(32768u) == 32768u); + + // uint32_t + BOOST_TEST(next_power_of_two(0u) == 1u); + BOOST_TEST(next_power_of_two(100000) == 131072); + BOOST_TEST(next_power_of_two(1u << 30) == 1u << 30); + BOOST_TEST(next_power_of_two((1u << 30) + 1) == 1u << 31); + + // uint64_t + BOOST_TEST(next_power_of_two(0u) == 1u); + BOOST_TEST(next_power_of_two(1ull << 40) == 1ull << 40); + BOOST_TEST(next_power_of_two((1ull << 40) + 1) == 1ull << 41); + BOOST_TEST(next_power_of_two(1ull << 62) == 1ull << 62); + BOOST_TEST(next_power_of_two((1ull << 62) + 1) == 1ull << 63); +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/test/unit/test/sansio/message_reader.cpp b/test/unit/test/sansio/message_reader.cpp index 1f3b14caf..844608563 100644 --- a/test/unit/test/sansio/message_reader.cpp +++ b/test/unit/test/sansio/message_reader.cpp @@ -43,9 +43,10 @@ class reader_fixture reader_fixture( std::vector contents, std::size_t buffsize = 512, - std::size_t max_size = static_cast(-1) + std::size_t max_size = static_cast(-1), + std::size_t max_frame_size = 64 // max frame size is 64 ) - : reader(buffsize, max_size, 64), // max frame size is 64 + : reader(buffsize, max_size, max_frame_size), contents_(std::move(contents)), buffer_first_(reader.internal_buffer().first()) { @@ -501,6 +502,49 @@ BOOST_AUTO_TEST_CASE(buffer_resizing_max_size_exceeded_subsequent_frames) BOOST_TEST(ec == client_errc::max_buffer_size_exceeded); } +BOOST_AUTO_TEST_CASE(buffer_resizing_size_power_of_two) +{ + // Setup + reader_fixture fix(create_frame(42, u8vec(4, 0x04)), 0, 1024, 1024); + fix.reader.prepare_read(fix.seqnum); + fix.read_until_completion(); + BOOST_TEST(fix.buffsize() == 4u); + std::size_t test_sizes[] = { + 5, 7, 8, + 9, 15, 16, + 17, 31, 32, + 33, 63, 64, + 65, 127, 128, + 129, 255, 256, + 257, 511, 512, 513 + }; + constexpr std::size_t num_tests = sizeof(test_sizes) / sizeof(test_sizes[0]); + + // Test that buffer capacity grows to powers of two for various payload sizes + for (std::size_t i = 0; i < num_tests; ++i) + { + // Setup + u8vec msg_body(test_sizes[i], 0x04); + fix.seqnum = i; + fix.set_contents(create_frame(i, msg_body)); + std::size_t next_power_of_two = 1; + while (next_power_of_two < test_sizes[i]) + next_power_of_two *= 2; + + // Prepare read + fix.reader.prepare_read(fix.seqnum); + + // Read the message into the buffer and trigger the op until completion. + // This will call prepare_buffer() internally + fix.read_until_completion(); + + // Check results + BOOST_TEST_REQUIRE(fix.reader.error() == error_code()); + BOOST_MYSQL_ASSERT_BUFFER_EQUALS(fix.reader.message(), msg_body); + BOOST_TEST(fix.buffsize() == next_power_of_two); + } +} + // Keep parsing state BOOST_AUTO_TEST_CASE(keep_state_continuation) { @@ -630,4 +674,82 @@ BOOST_AUTO_TEST_CASE(reset_keep_state_true) BOOST_TEST(fix.seqnum == 21u); } +BOOST_AUTO_TEST_CASE(memmove_avoided) +{ + // Test that std::memmove() is avoided for large messages (>1024 bytes) + // when the buffer has sufficient free space. + + // Setup + reader_fixture fix(create_frame(0, u8vec(0, 0)), 4096, 4096, 4096); + + // Buffer is free + std::size_t free_bytes = fix.reader.buffer().size(); + BOOST_TEST(free_bytes == 4096); + + // Small messages (<1024 bytes): memmove is expected + { + constexpr std::size_t small_msg_size = 200; + // First frame and second frame header expected to + // be memmoved, when second payload will be parsed + std::size_t expected = 4096 - small_msg_size; + auto first_frame = create_frame(42, u8vec(small_msg_size, 0x0a)); + auto second_frame = create_frame(43, u8vec(small_msg_size, 0x0a)); + fix.seqnum = 42; + fix.set_contents(concat(first_frame, second_frame)); + fix.reader.prepare_read(fix.seqnum); + auto ec = fix.reader.prepare_buffer(); + BOOST_TEST(ec == error_code()); + fix.read_bytes(2 * (frame_header_size + small_msg_size)); + fix.reader.prepare_read(fix.seqnum); + ec = fix.reader.prepare_buffer(); + BOOST_TEST(ec == error_code()); + free_bytes = fix.reader.buffer().size(); + BOOST_TEST(free_bytes == expected); + } + + // Large messages (>=1024 bytes): memmove is avoided if free space is big enough + { + constexpr std::size_t large_msg_size = 1025; + // First frame expected not to be memmoved, + // when we parse second message + constexpr std::size_t expected = 4096 - 2 * (frame_header_size + large_msg_size); + auto first_frame = create_frame(42, u8vec(large_msg_size, 0x0a)); + auto second_frame = create_frame(43, u8vec(large_msg_size, 0x0a)); + fix.seqnum = 42; + fix.set_contents(concat(first_frame, second_frame)); + fix.reader.prepare_read(fix.seqnum); + auto ec = fix.reader.prepare_buffer(); + BOOST_TEST(ec == error_code()); + fix.read_bytes(2 * (frame_header_size + large_msg_size)); + fix.reader.prepare_read(fix.seqnum); + ec = fix.reader.prepare_buffer(); + BOOST_TEST(ec == error_code()); + free_bytes = fix.reader.buffer().size(); + BOOST_TEST(free_bytes == expected); + } + + // Buffer cannot fit second message: memmove is expected + { + constexpr std::size_t very_large_msg_size = 2048; + // First frame and second frame header expected to + // be memmoved, when second payload will be parsed + constexpr std::size_t expected = 4096 - very_large_msg_size; + auto first_frame = create_frame(42, u8vec(very_large_msg_size, 0x0a)); + auto second_frame = create_frame(43, u8vec(very_large_msg_size, 0x0a)); + fix.seqnum = 42; + fix.set_contents(concat(first_frame, second_frame)); + fix.reader.prepare_read(fix.seqnum); + auto ec = fix.reader.prepare_buffer(); + BOOST_TEST(ec == error_code()); + fix.read_bytes(4092); + fix.reader.prepare_read(fix.seqnum); + ec = fix.reader.prepare_buffer(); + // Read what is left + fix.read_bytes(12); + BOOST_TEST(ec == error_code()); + free_bytes = fix.reader.buffer().size(); + BOOST_TEST(free_bytes == expected); + } +} + BOOST_AUTO_TEST_SUITE_END() diff --git a/test/unit/test/sansio/read_buffer.cpp b/test/unit/test/sansio/read_buffer.cpp index 42efd0486..463ce1bd7 100644 --- a/test/unit/test/sansio/read_buffer.cpp +++ b/test/unit/test/sansio/read_buffer.cpp @@ -584,6 +584,34 @@ BOOST_AUTO_TEST_CASE(several_grows) check_buffer(buff, {}, {0x01, 0x02, 0x03, 0x04, 0x05, 0x06}, {0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c}, 4); } +BOOST_AUTO_TEST_CASE(is_power_of_two) +{ + read_buffer buff(8); + + std::size_t test_sizes[] = { + 5, 7, 8, + 9, 15, 16, + 17, 31, 32, + 33, 63, 64, + 65, 127, 128, + 129, 255, 256, + 257, 511, 512, 513 + }; + constexpr std::size_t num_tests = sizeof(test_sizes) / sizeof(test_sizes[0]); + + // Test that buffer capacity grows to powers of two + for (std::size_t i = 0; i < num_tests; ++i) + { + std::size_t next_power_of_two = 1; + while (next_power_of_two < test_sizes[i]) + next_power_of_two *= 2; + auto ec = buff.grow_to_fit(test_sizes[i]); + BOOST_TEST(ec == error_code()); + BOOST_TEST(buff.size() == next_power_of_two); + } + BOOST_TEST(buff.size() == 1024); +} + BOOST_AUTO_TEST_SUITE_END() BOOST_AUTO_TEST_SUITE(reset) From 84a0556b9913b0f4705b903d1b4f3f8a0cbd21d9 Mon Sep 17 00:00:00 2001 From: vsoulgard Date: Thu, 26 Mar 2026 19:23:54 +0300 Subject: [PATCH 09/15] Fix types --- test/unit/test/sansio/message_reader.cpp | 2 +- test/unit/test/sansio/read_buffer.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/unit/test/sansio/message_reader.cpp b/test/unit/test/sansio/message_reader.cpp index 844608563..db66004bb 100644 --- a/test/unit/test/sansio/message_reader.cpp +++ b/test/unit/test/sansio/message_reader.cpp @@ -684,7 +684,7 @@ BOOST_AUTO_TEST_CASE(memmove_avoided) // Buffer is free std::size_t free_bytes = fix.reader.buffer().size(); - BOOST_TEST(free_bytes == 4096); + BOOST_TEST(free_bytes == 4096u); // Small messages (<1024 bytes): memmove is expected { diff --git a/test/unit/test/sansio/read_buffer.cpp b/test/unit/test/sansio/read_buffer.cpp index 463ce1bd7..d2c4271c7 100644 --- a/test/unit/test/sansio/read_buffer.cpp +++ b/test/unit/test/sansio/read_buffer.cpp @@ -609,7 +609,7 @@ BOOST_AUTO_TEST_CASE(is_power_of_two) BOOST_TEST(ec == error_code()); BOOST_TEST(buff.size() == next_power_of_two); } - BOOST_TEST(buff.size() == 1024); + BOOST_TEST(buff.size() == 1024u); } BOOST_AUTO_TEST_SUITE_END() From 3f931e3737bb91f9dae6beb2db91fd3f2dcbb9b4 Mon Sep 17 00:00:00 2001 From: vsoulgard Date: Thu, 26 Mar 2026 19:33:46 +0300 Subject: [PATCH 10/15] Fix typo --- test/unit/test/sansio/message_reader.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/unit/test/sansio/message_reader.cpp b/test/unit/test/sansio/message_reader.cpp index db66004bb..d889e0112 100644 --- a/test/unit/test/sansio/message_reader.cpp +++ b/test/unit/test/sansio/message_reader.cpp @@ -686,7 +686,7 @@ BOOST_AUTO_TEST_CASE(memmove_avoided) std::size_t free_bytes = fix.reader.buffer().size(); BOOST_TEST(free_bytes == 4096u); - // Small messages (<1024 bytes): memmove is expected + // Small messages (<=1024 bytes): memmove is expected { constexpr std::size_t small_msg_size = 200; // First frame and second frame header expected to @@ -707,7 +707,7 @@ BOOST_AUTO_TEST_CASE(memmove_avoided) BOOST_TEST(free_bytes == expected); } - // Large messages (>=1024 bytes): memmove is avoided if free space is big enough + // Large messages (>1024 bytes): memmove is avoided if free space is big enough { constexpr std::size_t large_msg_size = 1025; // First frame expected not to be memmoved, From 045ae725c1f42ba63f054b14a3ae9c5e26cbc00c Mon Sep 17 00:00:00 2001 From: vsoulgard Date: Thu, 26 Mar 2026 20:32:54 +0300 Subject: [PATCH 11/15] Fix type --- test/unit/test/sansio/message_reader.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/unit/test/sansio/message_reader.cpp b/test/unit/test/sansio/message_reader.cpp index d889e0112..f2c262951 100644 --- a/test/unit/test/sansio/message_reader.cpp +++ b/test/unit/test/sansio/message_reader.cpp @@ -525,7 +525,7 @@ BOOST_AUTO_TEST_CASE(buffer_resizing_size_power_of_two) { // Setup u8vec msg_body(test_sizes[i], 0x04); - fix.seqnum = i; + fix.seqnum = static_cast(i); fix.set_contents(create_frame(i, msg_body)); std::size_t next_power_of_two = 1; while (next_power_of_two < test_sizes[i]) From 42a504de908bf42c7f2aa072d36cedd75cf28954 Mon Sep 17 00:00:00 2001 From: vsoulgard Date: Thu, 26 Mar 2026 21:26:36 +0300 Subject: [PATCH 12/15] Fix msvc type warning --- test/unit/test/sansio/message_reader.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/unit/test/sansio/message_reader.cpp b/test/unit/test/sansio/message_reader.cpp index f2c262951..ad0f7054b 100644 --- a/test/unit/test/sansio/message_reader.cpp +++ b/test/unit/test/sansio/message_reader.cpp @@ -525,8 +525,8 @@ BOOST_AUTO_TEST_CASE(buffer_resizing_size_power_of_two) { // Setup u8vec msg_body(test_sizes[i], 0x04); - fix.seqnum = static_cast(i); - fix.set_contents(create_frame(i, msg_body)); + fix.seqnum = static_cast(i); + fix.set_contents(create_frame(fix.seqnum, msg_body)); std::size_t next_power_of_two = 1; while (next_power_of_two < test_sizes[i]) next_power_of_two *= 2; From 04131b4765704ee38bece270448a7e2b0fc9880b Mon Sep 17 00:00:00 2001 From: vsoulgard Date: Fri, 27 Mar 2026 23:19:13 +0300 Subject: [PATCH 13/15] Add test to Jamfile --- test/unit/CMakeLists.txt | 2 +- test/unit/Jamfile | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/test/unit/CMakeLists.txt b/test/unit/CMakeLists.txt index c2c0af888..1f5a0a5d0 100644 --- a/test/unit/CMakeLists.txt +++ b/test/unit/CMakeLists.txt @@ -78,8 +78,8 @@ add_executable( test/impl/dt_to_string.cpp test/impl/ssl_context_with_default.cpp - test/impl/variant_stream.cpp test/impl/next_power_of_two.cpp + test/impl/variant_stream.cpp test/spotchecks/connection_use_after_move.cpp test/spotchecks/default_completion_tokens.cpp diff --git a/test/unit/Jamfile b/test/unit/Jamfile index cc989d47f..2554c8572 100644 --- a/test/unit/Jamfile +++ b/test/unit/Jamfile @@ -87,6 +87,7 @@ run test/impl/dt_to_string.cpp test/impl/ssl_context_with_default.cpp + test/impl/next_power_of_two.cpp test/impl/variant_stream.cpp test/spotchecks/connection_use_after_move.cpp From bb3120ee63fb6bc22dce3b9dfa1043c9add6f153 Mon Sep 17 00:00:00 2001 From: vsoulgard Date: Fri, 27 Mar 2026 23:37:04 +0300 Subject: [PATCH 14/15] Fix another type comparison --- test/unit/test/impl/next_power_of_two.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/unit/test/impl/next_power_of_two.cpp b/test/unit/test/impl/next_power_of_two.cpp index 2a5cbb69e..5d5a6f2e5 100644 --- a/test/unit/test/impl/next_power_of_two.cpp +++ b/test/unit/test/impl/next_power_of_two.cpp @@ -63,7 +63,7 @@ BOOST_AUTO_TEST_CASE(different_types) // uint32_t BOOST_TEST(next_power_of_two(0u) == 1u); - BOOST_TEST(next_power_of_two(100000) == 131072); + BOOST_TEST(next_power_of_two(100000) == 131072u); BOOST_TEST(next_power_of_two(1u << 30) == 1u << 30); BOOST_TEST(next_power_of_two((1u << 30) + 1) == 1u << 31); From c22bed35f06f2272963441ffcdbcbefd0f568794 Mon Sep 17 00:00:00 2001 From: vsoulgard Date: Sat, 28 Mar 2026 22:55:31 +0300 Subject: [PATCH 15/15] Improve tests --- test/unit/test/impl/next_power_of_two.cpp | 14 +++++++- test/unit/test/sansio/message_reader.cpp | 41 ++++++++++++----------- test/unit/test/sansio/read_buffer.cpp | 18 +++++----- 3 files changed, 45 insertions(+), 28 deletions(-) diff --git a/test/unit/test/impl/next_power_of_two.cpp b/test/unit/test/impl/next_power_of_two.cpp index 5d5a6f2e5..06cea8b90 100644 --- a/test/unit/test/impl/next_power_of_two.cpp +++ b/test/unit/test/impl/next_power_of_two.cpp @@ -45,6 +45,18 @@ BOOST_AUTO_TEST_CASE(basic) BOOST_TEST(next_power_of_two(33u) == 64u); BOOST_TEST(next_power_of_two(65u) == 128u); BOOST_TEST(next_power_of_two(129u) == 256u); + + // n is random value + BOOST_TEST(next_power_of_two(6u) == 8u); + BOOST_TEST(next_power_of_two(13u) == 16u); + BOOST_TEST(next_power_of_two(21u) == 32u); + BOOST_TEST(next_power_of_two(45u) == 64u); + BOOST_TEST(next_power_of_two(89u) == 128u); + BOOST_TEST(next_power_of_two(200u) == 256u); + BOOST_TEST(next_power_of_two(300u) == 512u); + BOOST_TEST(next_power_of_two(400u) == 512u); + BOOST_TEST(next_power_of_two(505u) == 512u); + BOOST_TEST(next_power_of_two(888u) == 1024u); } BOOST_AUTO_TEST_CASE(different_types) @@ -63,7 +75,7 @@ BOOST_AUTO_TEST_CASE(different_types) // uint32_t BOOST_TEST(next_power_of_two(0u) == 1u); - BOOST_TEST(next_power_of_two(100000) == 131072u); + BOOST_TEST(next_power_of_two(100000u) == 131072u); BOOST_TEST(next_power_of_two(1u << 30) == 1u << 30); BOOST_TEST(next_power_of_two((1u << 30) + 1) == 1u << 31); diff --git a/test/unit/test/sansio/message_reader.cpp b/test/unit/test/sansio/message_reader.cpp index ad0f7054b..2658f0a7c 100644 --- a/test/unit/test/sansio/message_reader.cpp +++ b/test/unit/test/sansio/message_reader.cpp @@ -509,6 +509,7 @@ BOOST_AUTO_TEST_CASE(buffer_resizing_size_power_of_two) fix.reader.prepare_read(fix.seqnum); fix.read_until_completion(); BOOST_TEST(fix.buffsize() == 4u); + std::size_t test_sizes[] = { 5, 7, 8, 9, 15, 16, @@ -518,30 +519,32 @@ BOOST_AUTO_TEST_CASE(buffer_resizing_size_power_of_two) 129, 255, 256, 257, 511, 512, 513 }; - constexpr std::size_t num_tests = sizeof(test_sizes) / sizeof(test_sizes[0]); // Test that buffer capacity grows to powers of two for various payload sizes - for (std::size_t i = 0; i < num_tests; ++i) + for (auto new_size : test_sizes) { - // Setup - u8vec msg_body(test_sizes[i], 0x04); - fix.seqnum = static_cast(i); - fix.set_contents(create_frame(fix.seqnum, msg_body)); - std::size_t next_power_of_two = 1; - while (next_power_of_two < test_sizes[i]) - next_power_of_two *= 2; - - // Prepare read - fix.reader.prepare_read(fix.seqnum); + BOOST_TEST_CONTEXT(new_size) + { + // Setup + u8vec msg_body(new_size, 0x04); + fix.seqnum = static_cast(new_size); + fix.set_contents(create_frame(fix.seqnum, msg_body)); + std::size_t next_power_of_two = 1; + while (next_power_of_two < new_size) + next_power_of_two *= 2; + + // Prepare read + fix.reader.prepare_read(fix.seqnum); - // Read the message into the buffer and trigger the op until completion. - // This will call prepare_buffer() internally - fix.read_until_completion(); + // Read the message into the buffer and trigger the op until completion. + // This will call prepare_buffer() internally + fix.read_until_completion(); - // Check results - BOOST_TEST_REQUIRE(fix.reader.error() == error_code()); - BOOST_MYSQL_ASSERT_BUFFER_EQUALS(fix.reader.message(), msg_body); - BOOST_TEST(fix.buffsize() == next_power_of_two); + // Check results + BOOST_TEST_REQUIRE(fix.reader.error() == error_code()); + BOOST_MYSQL_ASSERT_BUFFER_EQUALS(fix.reader.message(), msg_body); + BOOST_TEST(fix.buffsize() == next_power_of_two); + } } } diff --git a/test/unit/test/sansio/read_buffer.cpp b/test/unit/test/sansio/read_buffer.cpp index d2c4271c7..00ca1097a 100644 --- a/test/unit/test/sansio/read_buffer.cpp +++ b/test/unit/test/sansio/read_buffer.cpp @@ -597,17 +597,19 @@ BOOST_AUTO_TEST_CASE(is_power_of_two) 129, 255, 256, 257, 511, 512, 513 }; - constexpr std::size_t num_tests = sizeof(test_sizes) / sizeof(test_sizes[0]); // Test that buffer capacity grows to powers of two - for (std::size_t i = 0; i < num_tests; ++i) + for (auto new_size : test_sizes) { - std::size_t next_power_of_two = 1; - while (next_power_of_two < test_sizes[i]) - next_power_of_two *= 2; - auto ec = buff.grow_to_fit(test_sizes[i]); - BOOST_TEST(ec == error_code()); - BOOST_TEST(buff.size() == next_power_of_two); + BOOST_TEST_CONTEXT(new_size) + { + std::size_t next_power_of_two = 1; + while (next_power_of_two < new_size) + next_power_of_two *= 2; + auto ec = buff.grow_to_fit(new_size); + BOOST_TEST(ec == error_code()); + BOOST_TEST(buff.size() == next_power_of_two); + } } BOOST_TEST(buff.size() == 1024u); }