From 922e66b88a627bd836ce6957dc8a99debc7ce880 Mon Sep 17 00:00:00 2001 From: Andrei Avram <6795248+andreiavrammsd@users.noreply.github.com> Date: Wed, 18 Jun 2025 09:57:04 +0000 Subject: [PATCH 1/6] Add batch write --- .clang-tidy | 6 +-- include/msd/channel.hpp | 85 +++++++++++++++++++++++++----- include/msd/result.hpp | 111 ++++++++++++++++++++++++++++++++++++++++ tests/channel_test.cpp | 57 +++++++++++++++++++++ 4 files changed, 243 insertions(+), 16 deletions(-) create mode 100644 include/msd/result.hpp diff --git a/.clang-tidy b/.clang-tidy index be507db..5b01c9d 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -11,7 +11,8 @@ Checks: > -altera-unroll-loops, -llvmlibc-inline-function-decl, -cert-dcl21-cpp, - -fuchsia-default-arguments-declarations + -fuchsia-default-arguments-declarations, + -cppcoreguidelines-pro-type-union-access WarningsAsErrors: "*" @@ -28,8 +29,7 @@ CheckOptions: - { key: readability-identifier-naming.ClassMemberSuffix, value: _ } - { key: readability-identifier-naming.PrivateMemberSuffix, value: _ } - { key: readability-identifier-naming.ProtectedMemberSuffix, value: _ } - - { key: readability-identifier-naming.EnumConstantCase, value: CamelCase } - - { key: readability-identifier-naming.EnumConstantPrefix, value: k } + - { key: readability-identifier-naming.EnumConstantCase, value: lower_case } - { key: readability-identifier-naming.ConstexprVariableCase, value: lower_case } - { key: readability-identifier-naming.GlobalConstantCase, value: CamelCase } - { key: readability-identifier-naming.GlobalConstantPrefix, value: k } diff --git a/include/msd/channel.hpp b/include/msd/channel.hpp index ae49078..8425cb6 100644 --- a/include/msd/channel.hpp +++ b/include/msd/channel.hpp @@ -5,6 +5,7 @@ #include "blocking_iterator.hpp" #include "nodiscard.hpp" +#include "result.hpp" #include "storage.hpp" #include @@ -17,19 +18,6 @@ namespace msd { -/** - * @brief Exception thrown if trying to write on closed channel. - */ -class closed_channel : public std::runtime_error { - public: - /** - * @brief Constructs the exception with an error message. - * - * @param msg A descriptive message explaining the cause of the error. - */ - explicit closed_channel(const char* msg) : std::runtime_error{msg} {} -}; - /** * @brief Default storage for msd::channel. * @@ -74,6 +62,35 @@ struct is_static_storage : std::false_type {}; template struct is_static_storage : std::true_type {}; +/** + * @brief Exception thrown if trying to write on closed channel. + */ +class closed_channel : public std::runtime_error { + public: + /** + * @brief Constructs the exception with an error message. + * + * @param msg A descriptive message explaining the cause of the error. + */ + explicit closed_channel(const char* msg) : std::runtime_error{msg} {} +}; + +/** + * @brief Possible errors during a batch write operation. + */ +enum class batch_write_error { + /** + * @brief The specified range exceeds the available capacity. + */ + range_exceeds_capacity, + + /** + * @brief The receiving channel is closed and cannot accept data. + */ + channel_is_closed, +}; + + /** * @brief Thread-safe container for sharing data between threads. * @@ -180,6 +197,48 @@ class channel { return true; } + /** + * @brief Writes a range of elements into the channel in batch mode. + * + * This function attempts to write all elements from the input range [begin, end) + * into the channel. If the channel has a capacity and the range exceeds that capacity, + * the function returns an error. If the channel is closed, it also returns an error. + * + * @tparam InputIterator An input iterator type pointing to elements of type `T`. + * @param begin Iterator pointing to the beginning of the range to write. + * @param end Iterator pointing to the end of the range to write (exclusive). + * @return A result indicating success or containing a `batch_write_error`: + * - `batch_write_error::range_exceeds_capacity` if the range is too large to fit. + * - `batch_write_error::channel_is_closed` if the channel is already closed. + * - Empty (success) if all elements were successfully written. + * + * @note It takes the lock on the channel once for all elements and release it at the end. + */ + template + result batch_write(const InputIterator begin, const InputIterator end) + { + if (capacity_ > 0 && (static_cast(std::distance(begin, end)) + storage_.size()) > capacity_) { + return result{batch_write_error::range_exceeds_capacity}; + } + + { + std::unique_lock lock{mtx_}; + wait_before_write(lock); + + if (is_closed_) { + return result{batch_write_error::channel_is_closed}; + } + + for (InputIterator it = begin; it != end; ++it) { + storage_.push_back(*it); + } + } + + cnd_.notify_one(); + + return result{}; + } + /** * @brief Pops an element from the channel. * diff --git a/include/msd/result.hpp b/include/msd/result.hpp new file mode 100644 index 0000000..6767385 --- /dev/null +++ b/include/msd/result.hpp @@ -0,0 +1,111 @@ +// Copyright (C) 2020-2025 Andrei Avram + +#ifndef MSD_CHANNEL_RESULT_HPP_ +#define MSD_CHANNEL_RESULT_HPP_ + +/** @file */ + +namespace msd { + +/** + * @brief A result that contains either a value of type T or an error of type E. + * + * @tparam T The type of the value on success. + * @tparam E The type of the error on failure. + */ +template +class result { + public: + /** + * @brief Constructs an empty result (not valid). + */ + explicit result() = default; + + /** + * @brief Constructs a successful result with a value. + * + * @param value The value to store into the result. + */ + explicit result(T value) : value_{value} {} + + /** + * @brief Constructs an error result. + * + * @param error The error value to store. + */ + explicit result(E error) : has_error_{true}, error_{error} {} + + /** + * @brief Checks whether the result is a success. + * + * @return true if the result holds a value, false if it holds an error. + */ + explicit operator bool() const { return !has_error_; } + + /** + * @brief Gets the stored value. + * + * @return const T& Reference to the stored value. + * @warning Behavior is undefined if the result holds an error. + */ + const T& value() const { return value_; } + + /** + * @brief Gets the stored error. + * + * @return const E& Reference to the stored error. + * @warning Behavior is undefined if the result holds a value. + */ + const E& error() const { return error_; } + + private: + union { + T value_; + E error_; + }; + bool has_error_{}; +}; + +/** + * @brief Specialization of result for void success type. + * + * @tparam E The type of the error on failure. + */ +template +class result { + public: + /** + * @brief Constructs a successful void result. + */ + result() = default; + + /** + * @brief Constructs an error result. + * + * @param error The error value to store. + */ + explicit result(E error) : has_value_{false}, error_{error} {} + + /** + * @brief Checks whether the result is a success. + * + * @return true if the result is successful, false otherwise. + */ + explicit operator bool() const { return has_value_; } + + /** + * @brief Gets the stored error. + * + * @return Const reference to the stored error. + * @note Only valid if the result holds an error. + */ + const E& error() const { return error_; } + + private: + bool has_value_{true}; + E error_; +}; + +} // namespace msd + +#endif // MSD_CHANNEL_RESULT_HPP_ diff --git a/tests/channel_test.cpp b/tests/channel_test.cpp index 964a29c..a326dda 100644 --- a/tests/channel_test.cpp +++ b/tests/channel_test.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -155,6 +156,62 @@ TEST(ChannelTest, PushByMoveAndFetch) EXPECT_EQ("def", out); } +TEST(ChannelTest, BatchWriteOnUnbufferedChannel) +{ + msd::channel channel{}; + + msd::result result; + std::vector input(100); + std::iota(input.begin(), input.end(), 0); + + result = channel.batch_write(input.cbegin(), input.cend()); + EXPECT_TRUE(result); + EXPECT_EQ(channel.size(), 100); + EXPECT_EQ(input.size(), 100); + + std::vector output(100); + auto iter = output.begin(); + while (!channel.empty()) { + channel >> *iter; + ++iter; + } + for (std::size_t i = 0; i < output.size(); ++i) { + EXPECT_EQ(output[i], i); + } +} + +TEST(ChannelTest, BatchWriteOnBufferedChannel) +{ + msd::channel channel{10}; + + msd::result result; + std::vector input(100); + std::iota(input.begin(), input.end(), 0); + + // Too many + result = channel.batch_write(input.cbegin(), input.cend()); + EXPECT_FALSE(result); + EXPECT_EQ(result.error(), msd::batch_write_error::range_exceeds_capacity); + EXPECT_EQ(channel.size(), 0); + EXPECT_EQ(input.size(), 100); + + // Ok + result = channel.batch_write(std::next(input.begin(), 2), std::next(input.begin(), 7)); + EXPECT_TRUE(result); + EXPECT_EQ(channel.size(), 5); + EXPECT_EQ(input.size(), 100); + + std::vector output(5); + auto iter = output.begin(); + while (!channel.empty()) { + channel >> *iter; + ++iter; + } + for (std::size_t i = 0; i < output.size(); ++i) { + EXPECT_EQ(output[i], i + 2); + } +} + TEST(ChannelTest, size) { msd::channel channel; From 8eea5dce3d99abdbfa5884bd2243eba06d53f3f4 Mon Sep 17 00:00:00 2001 From: Andrei Avram <6795248+andreiavrammsd@users.noreply.github.com> Date: Wed, 18 Jun 2025 10:00:28 +0000 Subject: [PATCH 2/6] Ignore debug --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index d5180b2..d3f8fa8 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ install docs .cache +*.profraw From bcd64811a7483f41d5098efa575078f6d4de8884 Mon Sep 17 00:00:00 2001 From: Andrei Avram <6795248+andreiavrammsd@users.noreply.github.com> Date: Wed, 18 Jun 2025 10:01:08 +0000 Subject: [PATCH 3/6] Verify capacity after lock --- include/msd/channel.hpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/include/msd/channel.hpp b/include/msd/channel.hpp index 8425cb6..7431564 100644 --- a/include/msd/channel.hpp +++ b/include/msd/channel.hpp @@ -217,10 +217,6 @@ class channel { template result batch_write(const InputIterator begin, const InputIterator end) { - if (capacity_ > 0 && (static_cast(std::distance(begin, end)) + storage_.size()) > capacity_) { - return result{batch_write_error::range_exceeds_capacity}; - } - { std::unique_lock lock{mtx_}; wait_before_write(lock); @@ -229,6 +225,10 @@ class channel { return result{batch_write_error::channel_is_closed}; } + if (capacity_ > 0 && (static_cast(std::distance(begin, end)) + storage_.size()) > capacity_) { + return result{batch_write_error::range_exceeds_capacity}; + } + for (InputIterator it = begin; it != end; ++it) { storage_.push_back(*it); } From 2730039403b23010dccf575e29edf6e013f1c700 Mon Sep 17 00:00:00 2001 From: Andrei Avram <6795248+andreiavrammsd@users.noreply.github.com> Date: Wed, 18 Jun 2025 10:03:47 +0000 Subject: [PATCH 4/6] Use smaller size for batch_write_error --- include/msd/channel.hpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/include/msd/channel.hpp b/include/msd/channel.hpp index 7431564..caf8df4 100644 --- a/include/msd/channel.hpp +++ b/include/msd/channel.hpp @@ -9,6 +9,7 @@ #include "storage.hpp" #include +#include #include #include #include @@ -78,7 +79,7 @@ class closed_channel : public std::runtime_error { /** * @brief Possible errors during a batch write operation. */ -enum class batch_write_error { +enum class batch_write_error : std::int8_t { /** * @brief The specified range exceeds the available capacity. */ From 667ba32fa42ea7857eba42e0b2e394a210672d96 Mon Sep 17 00:00:00 2001 From: Andrei Avram <6795248+andreiavrammsd@users.noreply.github.com> Date: Wed, 18 Jun 2025 10:07:37 +0000 Subject: [PATCH 5/6] Format --- include/msd/channel.hpp | 1 - 1 file changed, 1 deletion(-) diff --git a/include/msd/channel.hpp b/include/msd/channel.hpp index caf8df4..3eecdf9 100644 --- a/include/msd/channel.hpp +++ b/include/msd/channel.hpp @@ -91,7 +91,6 @@ enum class batch_write_error : std::int8_t { channel_is_closed, }; - /** * @brief Thread-safe container for sharing data between threads. * From 3c5f998bd04dc1861552942c0992268e5d1901d7 Mon Sep 17 00:00:00 2001 From: Andrei Avram <6795248+andreiavrammsd@users.noreply.github.com> Date: Wed, 18 Jun 2025 10:07:54 +0000 Subject: [PATCH 6/6] BatchWriteOnClosedChannel --- tests/channel_test.cpp | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/tests/channel_test.cpp b/tests/channel_test.cpp index a326dda..9ad95b8 100644 --- a/tests/channel_test.cpp +++ b/tests/channel_test.cpp @@ -212,6 +212,23 @@ TEST(ChannelTest, BatchWriteOnBufferedChannel) } } +TEST(ChannelTest, BatchWriteOnClosedChannel) +{ + msd::channel channel{10}; + + msd::result result; + std::vector input(100); + std::iota(input.begin(), input.end(), 0); + + channel.close(); + + result = channel.batch_write(input.cbegin(), input.cend()); + EXPECT_FALSE(result); + EXPECT_EQ(result.error(), msd::batch_write_error::channel_is_closed); + EXPECT_TRUE(channel.empty()); + EXPECT_EQ(input.size(), 100); +} + TEST(ChannelTest, size) { msd::channel channel;