Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions score/launch_manager/src/daemon/src/common/concurrency/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,29 @@ cc_library(
],
)

cc_library(
name = "fixed_size_queue",
hdrs = [
"fixed_size_queue.hpp",
],
include_prefix = "score/mw/launch_manager/common/concurrency",
strip_include_prefix = "/score/launch_manager/src/daemon/src/common/concurrency",
visibility = ["//score:__subpackages__"],
deps = [
"@score_baselibs//score/language/futurecpp",
],
)

cc_test(
name = "fixed_size_queue_test",
srcs = ["fixed_size_queue_test.cpp"],
visibility = ["//tests:__subpackages__"],
deps = [
":fixed_size_queue",
"@googletest//:gtest_main",
],
)

cc_library(
name = "mpmc_concurrent_queue",
hdrs = [
Expand Down Expand Up @@ -68,6 +91,7 @@ cc_library(
strip_include_prefix = "/score/launch_manager/src/daemon/src/common/concurrency",
visibility = ["//score:__subpackages__"],
deps = [
":fixed_size_queue",
"@score_baselibs//score/language/futurecpp",
],
)
Expand Down
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
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;

Comment thread
paulquiring marked this conversation as resolved.
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);
}
Loading
Loading