Skip to content
Open
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,117 @@
/********************************************************************************
* 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>

#include <score/assert.hpp>

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,195 @@
/********************************************************************************
* 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;

TEST(FixedSizeQueue, 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(FixedSizeQueue, 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() turns 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(FixedSizeQueue, 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(FixedSizeQueue, CapacityFunctionSizeRetrunsNumberOfElementsInTheQueue) {

RecordProperty("Description", "Verify that the capacity function size retruns the number of elements in the queue.");

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;
RecordProperty("Description", "Verify that push increments the size by one.");
EXPECT_EQ(queue_cap_five.size(), no_elements);
}

RecordProperty("Description", "Verify that the capacity function full() retruns true if the number of elements reach the capacity.");
EXPECT_EQ(queue_cap_five.capacity(), no_elements);
EXPECT_EQ(cap, no_elements);
}

TEST(FixedSizeQueue, ModifierFunctionsPushAndTryPopImplementAFIFO) {

RecordProperty("Description", "Verify that modifier functions push and try pop implement a FIFO.");

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) {
RecordProperty("Description", "Verify that tryPop returns heads value.");
EXPECT_EQ(queue_cap_five.tryPop(), e);
--no_elements;
RecordProperty("Description", "Verify that tryPop decrements the size by one.");
EXPECT_EQ(queue_cap_five.size(), no_elements);
}

RecordProperty("Description", "Verify that empty() retruns true if the queue is drained.");
EXPECT_TRUE(queue_cap_five.empty());
RecordProperty("Description", "Verify that size() retruns zero if the queue is drained.");
EXPECT_EQ(queue_cap_five.size(), 0U);
}

TEST(FixedSizeQueue, 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(FixedSizeQueue, 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