-
Notifications
You must be signed in to change notification settings - Fork 31
Carve FixedSizeQueue out of MpscBoundedQueue #367
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
MaciejKaszynski
merged 1 commit into
eclipse-score:main
from
etas-contrib:feature/carve-FixedSizeQueue-out-of-MpscBoundedQueue
Jul 28, 2026
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
115 changes: 115 additions & 0 deletions
115
score/launch_manager/src/daemon/src/common/concurrency/fixed_size_queue.hpp
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,115 @@ | ||
| /******************************************************************************** | ||
| * Copyright (c) 2026 Contributors to the Eclipse Foundation | ||
| * | ||
| * See the NOTICE file(s) distributed with this work for additional | ||
| * information regarding copyright ownership. | ||
| * | ||
| * This program and the accompanying materials are made available under the | ||
| * terms of the Apache License Version 2.0 which is available at | ||
| * https://www.apache.org/licenses/LICENSE-2.0 | ||
| * | ||
| * SPDX-License-Identifier: Apache-2.0 | ||
| ********************************************************************************/ | ||
|
|
||
| #ifndef FIXED_SIZED_QUEUE_HPP_INCLUDE | ||
| #define FIXED_SIZED_QUEUE_HPP_INCLUDE | ||
|
|
||
| #include <cstddef> | ||
| #include <optional> | ||
| #include <vector> | ||
|
|
||
| namespace score::lcm::internal | ||
| { | ||
|
|
||
| /// @brief Fixed-size FIFO queue | ||
| /// @details Uses std::optional to eliminate default construction of | ||
| /// type T elements at construction | ||
| /// @tparam T The type of elements stored in the queue. | ||
| /// Supports move-only and copy-only types. | ||
| template <typename T> | ||
| class FixedSizeQueue | ||
| { | ||
| public: | ||
| /// @brief Constructs a FixedSizeQueue with zero capacity. | ||
| /// @details push will always return false | ||
| /// tryPop will always return std::nullopt | ||
| FixedSizeQueue() : capacity_{0U}, slots_{} {} | ||
|
|
||
| /// @brief Constructs a FixedSizeQueue with a fixed runtime capacity. | ||
| /// @details If the specified size is 0, it is equivalent | ||
| /// to a default constructed FixedSizeQueue. | ||
| /// @param size The desired maximum number of elements. | ||
| explicit FixedSizeQueue(std::size_t size) : capacity_(size) { | ||
| slots_.resize(capacity_); | ||
| } | ||
|
|
||
| /// @brief Inserts a new element directly at the tail of the queue. | ||
| /// @tparam Args Variadic template arguments forwarded to the constructor of T. | ||
| /// @param args The arguments used to construct the object of type T. | ||
| /// @return true if the element was successfully inserted; false if the queue is full (overflow protection). | ||
| template <typename... Args> | ||
| bool push(Args&&... args) { | ||
| if(full()) { | ||
| return false; | ||
| } | ||
|
|
||
| slots_[tail_].emplace(std::forward<Args>(args)...); | ||
| tail_ = (tail_ + 1U) % capacity_; | ||
| count_++; | ||
|
|
||
| return true; | ||
| } | ||
|
|
||
|
|
||
| /// @brief Attempts to extract and remove the oldest element from the queue. | ||
| /// @details The popped element is immediately destroyed in the internal | ||
| /// buffer to free resources. | ||
| /// @return A std::optional containing the element, | ||
| /// or std::nullopt if the queue was empty (underflow protection). | ||
| std::optional<T> tryPop() { | ||
| if (empty()) | ||
| { | ||
| return std::nullopt; | ||
| } | ||
|
|
||
| std::optional<T> item = std::move(slots_[head_]); | ||
| slots_[head_] = std::nullopt; | ||
| head_ = (head_ + 1U) % capacity_; | ||
| count_--; | ||
|
|
||
| return item; | ||
| } | ||
|
|
||
| /// @brief Checks if the queue contains no elements. | ||
| /// @return true if empty, otherwise false. | ||
| bool empty() const { return (count_ == 0U); } | ||
|
|
||
| /// @brief Checks if the queue has reached its maximum capacity. | ||
| /// @return true if full, otherwise false. | ||
| bool full() const { return (count_ >= capacity_); } | ||
|
|
||
| /// @brief Retrieves the current number of active elements in the queue. | ||
| /// @return The count of stored elements. | ||
| std::size_t size() const { return count_;}; | ||
|
|
||
| /// @brief Retrieves the capacity of the queue. | ||
| /// @return The capacity of the queue | ||
| std::size_t capacity() const { return capacity_;}; | ||
|
|
||
|
|
||
| private: | ||
| /// @brief Index of the oldest element in the buffer (read index). | ||
| std::size_t head_ = 0U; | ||
| /// @brief Index where the next element will be inserted (write index). | ||
| std::size_t tail_ = 0U; | ||
| /// @brief Current number of active elements in the queue. | ||
| std::size_t count_ = 0U; | ||
| /// @brief Maximum capacity of the queue. | ||
| std::size_t capacity_; | ||
| /// @brief Internal buffer holding the optional elements. | ||
| std::vector<std::optional<T>> slots_; | ||
| }; | ||
|
|
||
| } // namespace score::lcm::internal | ||
|
|
||
| #endif // FIXED_SIZED_QUEUE_HPP_INCLUDE |
208 changes: 208 additions & 0 deletions
208
score/launch_manager/src/daemon/src/common/concurrency/fixed_size_queue_test.cpp
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,208 @@ | ||
| /******************************************************************************** | ||
| * Copyright (c) 2026 Contributors to the Eclipse Foundation | ||
| * | ||
| * See the NOTICE file(s) distributed with this work for additional | ||
| * information regarding copyright ownership. | ||
| * | ||
| * This program and the accompanying materials are made available under the | ||
| * terms of the Apache License Version 2.0 which is available at | ||
| * https://www.apache.org/licenses/LICENSE-2.0 | ||
| * | ||
| * SPDX-License-Identifier: Apache-2.0 | ||
| ********************************************************************************/ | ||
|
|
||
| #include "score/mw/launch_manager/common/concurrency/fixed_size_queue.hpp" | ||
|
|
||
| #include <gtest/gtest.h> | ||
|
|
||
| #include <optional> | ||
| #include <type_traits> | ||
| #include <vector> | ||
|
|
||
| using namespace score::lcm::internal; | ||
|
|
||
| class FixedSizeQueueTest : public ::testing::Test | ||
| { | ||
| protected: | ||
| void SetUp() override | ||
| { | ||
| RecordProperty("TestType", "interface-test"); | ||
| RecordProperty("DerivationTechnique", "explorative-testing"); | ||
| } | ||
| }; | ||
|
|
||
| TEST_F(FixedSizeQueueTest, StaticProperties) { | ||
|
|
||
| RecordProperty("Description", "Verify that all special member functions are defined"); | ||
|
|
||
| bool is_dc = std::is_default_constructible_v<FixedSizeQueue<int>>; | ||
| ASSERT_TRUE(is_dc); | ||
| bool is_mc = std::is_move_constructible_v<FixedSizeQueue<int>>; | ||
| ASSERT_TRUE(is_mc); | ||
| bool is_cc = std::is_copy_constructible_v<FixedSizeQueue<int>>; | ||
| ASSERT_TRUE(is_cc); | ||
| bool is_ma = std::is_move_assignable_v<FixedSizeQueue<int>>; | ||
| ASSERT_TRUE(is_ma); | ||
| bool is_ca = std::is_copy_assignable_v<FixedSizeQueue<int>>; | ||
| ASSERT_TRUE(is_ca); | ||
| bool is_de = std::is_destructible_v<FixedSizeQueue<int>>; | ||
| ASSERT_TRUE(is_de); | ||
| } | ||
|
|
||
| TEST_F(FixedSizeQueueTest, ZeroCapacityAllMemberFunctionsReturns) { | ||
|
|
||
| // clang-format off | ||
| RecordProperty("Description", "Verify that on zero capacity " | ||
| "full() returns true " | ||
| "empty() returns true " | ||
| "size() returns 0 " | ||
| "capacity() returns 0 " | ||
| "push returns false " | ||
| "tryPop() returns std::nullopt"); | ||
| // clang-format on | ||
|
|
||
| FixedSizeQueue<int> queue_dc; | ||
| FixedSizeQueue<int> queue0{0}; | ||
| FixedSizeQueue<int> zero_cap_queues[2] = {queue_dc, queue0}; | ||
|
|
||
| for(FixedSizeQueue<int> & q : zero_cap_queues) { | ||
| EXPECT_TRUE(q.full()); | ||
| EXPECT_TRUE(q.empty()); | ||
| EXPECT_EQ(q.size(), 0U); | ||
| EXPECT_EQ(q.capacity(), 0U); | ||
| EXPECT_FALSE(q.push(1)); | ||
| EXPECT_EQ(q.tryPop(), std::nullopt); | ||
| } | ||
| } | ||
|
|
||
| TEST_F(FixedSizeQueueTest, CapacityFunctions) { | ||
|
|
||
| // clang-format off | ||
| RecordProperty("Description", "Verify that on capacity bigger zero the capacity functions" | ||
| "full() returns false " | ||
| "empty() returns true " | ||
| "size() returns 0 " | ||
| "capacity() returns the capacity " | ||
| "initially"); | ||
| // clang-format on | ||
|
|
||
| const std::size_t cap{5U}; | ||
| FixedSizeQueue<int> queue_cap_bigger_zero{cap}; | ||
| EXPECT_FALSE(queue_cap_bigger_zero.full()); | ||
| EXPECT_TRUE(queue_cap_bigger_zero.empty()); | ||
| EXPECT_EQ(queue_cap_bigger_zero.size(), 0U); | ||
| EXPECT_EQ(queue_cap_bigger_zero.capacity(), cap); | ||
| } | ||
|
|
||
| TEST_F(FixedSizeQueueTest, CapacityFunctionSizeRetrunsNumberOfElementsInTheQueue) { | ||
|
|
||
| // clang-format off | ||
| RecordProperty("Description", "Verify that size() returns the number of elements in the queue, " | ||
| "that push increments the size by one, " | ||
| "and that full() returns true when the number of elements reaches the capacity."); | ||
| // clang-format on | ||
|
|
||
| const std::size_t cap{5U}; | ||
| std::size_t no_elements{0U}; | ||
| FixedSizeQueue<int> queue_cap_five{cap}; | ||
|
|
||
| EXPECT_EQ(queue_cap_five.size(), no_elements); | ||
|
|
||
| while(!queue_cap_five.full()) { | ||
| EXPECT_TRUE(queue_cap_five.push(1)); | ||
| ++no_elements; | ||
| EXPECT_EQ(queue_cap_five.size(), no_elements); | ||
| } | ||
|
|
||
| EXPECT_EQ(queue_cap_five.capacity(), no_elements); | ||
| EXPECT_EQ(cap, no_elements); | ||
| } | ||
|
|
||
| TEST_F(FixedSizeQueueTest, ModifierFunctionsPushAndTryPopImplementAFIFO) { | ||
|
|
||
| // clang-format off | ||
| RecordProperty("Description", "Verify that push and tryPop implement a FIFO: " | ||
| "tryPop returns the head value, " | ||
| "tryPop decrements the size by one, " | ||
| "empty() returns true and size() returns zero once the queue is drained."); | ||
| // clang-format on | ||
|
|
||
| const std::size_t cap{5U}; | ||
| FixedSizeQueue<int> queue_cap_five{cap}; | ||
| auto elements = {11 ,22, 33, 44, 55}; | ||
|
|
||
| for(auto & e : elements) { | ||
| EXPECT_TRUE(queue_cap_five.push(e)); | ||
| } | ||
|
|
||
| EXPECT_EQ(queue_cap_five.size(), cap); | ||
| EXPECT_TRUE(queue_cap_five.full()); | ||
|
|
||
| std::size_t no_elements{queue_cap_five.size()}; | ||
|
|
||
| for(auto & e : elements) { | ||
| EXPECT_EQ(queue_cap_five.tryPop(), e); | ||
| --no_elements; | ||
| EXPECT_EQ(queue_cap_five.size(), no_elements); | ||
| } | ||
|
|
||
| EXPECT_TRUE(queue_cap_five.empty()); | ||
| EXPECT_EQ(queue_cap_five.size(), 0U); | ||
| } | ||
|
|
||
| TEST_F(FixedSizeQueueTest, ModifierFunctionPushReturnsFalseIfTheQueueIsFull) { | ||
|
|
||
| RecordProperty("Description", "Verify that modifier function push returns false if the queue is full."); | ||
|
|
||
| const std::size_t cap{5U}; | ||
| FixedSizeQueue<int> queue_cap_five{cap}; | ||
| auto elements = {11, 22, 33, 44, 55}; | ||
|
|
||
| for(auto & e : elements) { | ||
| EXPECT_TRUE(queue_cap_five.push(e)); | ||
| } | ||
|
|
||
| EXPECT_TRUE(queue_cap_five.full()); | ||
| EXPECT_FALSE(queue_cap_five.push(66)); | ||
| } | ||
|
|
||
| TEST_F(FixedSizeQueueTest, TailAndHeadWrap) { | ||
|
|
||
| RecordProperty("Description", "Verify that tail and head wrap."); | ||
|
|
||
| const std::size_t cap{3U}; | ||
| FixedSizeQueue<int> queue_cap_three{cap}; | ||
|
|
||
| auto elements = {1, 2, 3}; | ||
| for(auto & e : elements) { | ||
| EXPECT_TRUE(queue_cap_three.push(e)); | ||
| } | ||
|
|
||
| EXPECT_EQ(queue_cap_three.size(), queue_cap_three.capacity()); | ||
| // {1, 2, 3} | ||
| // {H, 2, T} | ||
|
|
||
| EXPECT_EQ(queue_cap_three.tryPop(), 1); | ||
| EXPECT_EQ(queue_cap_three.size(), 2U); | ||
| // { , 2, 3} | ||
| // { , H, T} | ||
|
|
||
| EXPECT_TRUE(queue_cap_three.push(4)); | ||
| EXPECT_EQ(queue_cap_three.size(), queue_cap_three.capacity()); | ||
| // {4, 2, 3} | ||
| // {T, H, 3} | ||
|
|
||
| EXPECT_EQ(queue_cap_three.tryPop(), 2); | ||
| EXPECT_EQ(queue_cap_three.size(), 2U); | ||
| // {4, , 3} | ||
| // {T, , H} | ||
|
|
||
|
|
||
| EXPECT_EQ(queue_cap_three.tryPop(), 3); | ||
| EXPECT_EQ(queue_cap_three.size(), 1U); | ||
| // {4, , } | ||
| // {TH, , } | ||
|
|
||
| EXPECT_EQ(queue_cap_three.tryPop(), 4); | ||
| EXPECT_EQ(queue_cap_three.size(), 0U); | ||
| } | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.