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/.gitignore b/.gitignore index d5180b2..d3f8fa8 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ install docs .cache +*.profraw diff --git a/include/msd/channel.hpp b/include/msd/channel.hpp index ae49078..3eecdf9 100644 --- a/include/msd/channel.hpp +++ b/include/msd/channel.hpp @@ -5,9 +5,11 @@ #include "blocking_iterator.hpp" #include "nodiscard.hpp" +#include "result.hpp" #include "storage.hpp" #include +#include #include #include #include @@ -17,19 +19,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 +63,34 @@ 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 : std::int8_t { + /** + * @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) + { + { + std::unique_lock lock{mtx_}; + wait_before_write(lock); + + if (is_closed_) { + 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); + } + } + + 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..9ad95b8 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,79 @@ 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, 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;