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
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ cc_library(
"//score/launch_manager/src/daemon/src/osal:semaphore",
"@score_baselibs//score/language/futurecpp",
"@score_baselibs//score/mw/log",
"@score_baselibs//score/result",
],
)

Expand All @@ -95,6 +96,7 @@ cc_library(
deps = [
":fixed_size_queue",
"@score_baselibs//score/language/futurecpp",
"@score_baselibs//score/result",
],
)

Expand Down
Original file line number Diff line number Diff line change
@@ -1,84 +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 CONCURRENCY_ERROR_DOMAIN_HPP_INCLUDED
#define CONCURRENCY_ERROR_DOMAIN_HPP_INCLUDED

#include <cstdint>
#include <ostream>

namespace score::mw::lifecycle::internal
{

enum class ConcurrencyErrc : std::uint8_t
{
/// @brief An OS call returned an error.
kOsError = 1,

// @brief The container has overflowed.
kOverflow = 2,

// @brief The container has stopped.
kStopped = 3,

// @brief A timeout was triggered.
kTimeout = 4,
};

inline std::ostream& operator<<(std::ostream& os, ConcurrencyErrc errc) noexcept
{
switch (errc)
{
case ConcurrencyErrc::kOsError:
return os << "kOsError";
case ConcurrencyErrc::kOverflow:
return os << "kOverflow";
case ConcurrencyErrc::kStopped:
return os << "kStopped";
case ConcurrencyErrc::kTimeout:
return os << "kTimeout";
default:
return os << static_cast<std::uint8_t>(errc);
}
}

} // namespace score::mw::lifecycle::internal

#ifdef LC_LOG_SCORE_MW_LOG
#include "score/mw/log/logger.h"

namespace score::mw::lifecycle::internal
{

inline score::mw::log::LogStream& operator<<(score::mw::log::LogStream& os, ConcurrencyErrc errc) noexcept
{
switch (errc)
{
case ConcurrencyErrc::kOsError:
return os << "kOsError";
case ConcurrencyErrc::kOverflow:
return os << "kOverflow";
case ConcurrencyErrc::kStopped:
return os << "kStopped";
case ConcurrencyErrc::kTimeout:
return os << "kTimeout";
default:
return os << static_cast<std::uint8_t>(errc);
}
}

} // namespace score::mw::lifecycle::internal

#endif // LC_LOG_SCORE_MW_LOG

#endif // CONCURRENCY_ERROR_DOMAIN_HPP_INCLUDED
/********************************************************************************
* 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 CONCURRENCY_ERROR_DOMAIN_HPP_INCLUDED
#define CONCURRENCY_ERROR_DOMAIN_HPP_INCLUDED

#include "score/result/error_domain.h"
#include "score/result/result.h"

#include <ostream>

namespace score::mw::lifecycle::internal
{

enum class ConcurrencyErrc : score::result::ErrorCode
{
/// @brief An OS call returned an error.
kOsError = 1,

// @brief The container has overflowed.
kOverflow = 2,

// @brief The container has stopped.
kStopped = 3,

// @brief A timeout was triggered.
kTimeout = 4,
};

/// @brief Error domain for concurrency-related error codes.
class ConcurrencyErrorDomain final : public score::result::ErrorDomain
{
public:
std::string_view MessageFor(const score::result::ErrorCode& code) const noexcept override
{
switch (static_cast<ConcurrencyErrc>(code))
{
case ConcurrencyErrc::kOsError:
return "OS call returned an error";
case ConcurrencyErrc::kOverflow:
return "Container has overflowed";
case ConcurrencyErrc::kStopped:
return "Container has stopped";
case ConcurrencyErrc::kTimeout:
return "Timeout was triggered";
default:
return "Unknown concurrency error";
}
}
};

/// @brief Global domain instance — required for ADL-based MakeError() lookup.
constexpr ConcurrencyErrorDomain kConcurrencyErrorDomain{};

/// @brief Creates a score::result::Error from a ConcurrencyErrc value (enables score::MakeUnexpected).
inline score::result::Error MakeError(ConcurrencyErrc code, std::string_view user_message = "") noexcept
{
return {static_cast<score::result::ErrorCode>(code), kConcurrencyErrorDomain, user_message};
}

inline std::ostream& operator<<(std::ostream& os, ConcurrencyErrc errc) noexcept
{
switch (errc)
{
case ConcurrencyErrc::kOsError:
return os << "kOsError";
case ConcurrencyErrc::kOverflow:
return os << "kOverflow";
case ConcurrencyErrc::kStopped:
return os << "kStopped";
case ConcurrencyErrc::kTimeout:
return os << "kTimeout";
default:
return os << static_cast<score::result::ErrorCode>(errc);
}
}

} // namespace score::mw::lifecycle::internal

#ifdef LC_LOG_SCORE_MW_LOG
#include "score/mw/log/logger.h"

namespace score::mw::lifecycle::internal
{

inline score::mw::log::LogStream& operator<<(score::mw::log::LogStream& os, ConcurrencyErrc errc) noexcept
{
switch (errc)
{
case ConcurrencyErrc::kOsError:
return os << "kOsError";
case ConcurrencyErrc::kOverflow:
return os << "kOverflow";
case ConcurrencyErrc::kStopped:
return os << "kStopped";
case ConcurrencyErrc::kTimeout:
return os << "kTimeout";
default:
return os << static_cast<score::result::ErrorCode>(errc);
}
}

} // namespace score::mw::lifecycle::internal

#endif // LC_LOG_SCORE_MW_LOG

#endif // CONCURRENCY_ERROR_DOMAIN_HPP_INCLUDED
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@
#include "score/mw/launch_manager/common/concurrency/details/helgrind_annotations.hpp"
#include "score/mw/launch_manager/osal/return_types.hpp"
#include "score/mw/launch_manager/osal/semaphore.hpp"
#include <score/expected.hpp>

namespace score::mw::lifecycle::internal
{
Expand Down Expand Up @@ -116,9 +115,7 @@ class MPMCConcurrentQueue
/// @return Success if item was pushed, Error otherwise.
/// Note: If the push returns false, the object is still valid for
/// the user.
[[nodiscard]] score::cpp::expected_blank<ConcurrencyErrc> push(
T&& item,
std::chrono::milliseconds timeout = std::chrono::milliseconds{0})
[[nodiscard]] score::ResultBlank push(T&& item, std::chrono::milliseconds timeout = std::chrono::milliseconds{0})
{
return push_impl(std::move(item), timeout);
}
Expand All @@ -130,30 +127,30 @@ class MPMCConcurrentQueue
/// previous consumer has finished reading it.
/// @param timeout Maximum time to wait for a free slot. Zero means wait forever.
/// @return Success if item was pushed, Error otherwise.
[[nodiscard]] score::cpp::expected_blank<ConcurrencyErrc> push(
[[nodiscard]] score::ResultBlank push(
const T& item,
std::chrono::milliseconds timeout = std::chrono::milliseconds{0})
{
return push_impl(item, timeout);
}

/// @brief Signals all blocked pop() callers to return with a stopped error.
[[nodiscard]] score::cpp::expected_blank<ConcurrencyErrc> stop() noexcept
[[nodiscard]] score::ResultBlank stop() noexcept
{
m_stopped.store(true, std::memory_order_release);

// signal to consumers and publishers to wakeup
if (m_items.post() != osal::OsalReturnType::kSuccess)
{
return score::cpp::make_unexpected(ConcurrencyErrc::kOsError);
return score::MakeUnexpected(ConcurrencyErrc::kOsError);
}

if (m_spaces.post() != osal::OsalReturnType::kSuccess)
{
return score::cpp::make_unexpected(ConcurrencyErrc::kOsError);
return score::MakeUnexpected(ConcurrencyErrc::kOsError);
}

return score::cpp::blank{};
return score::ResultBlank{};
}

/// @brief Blocks until an item is available or stop() is called.
Expand All @@ -162,32 +159,31 @@ class MPMCConcurrentQueue
/// When stopped returns std::nullopt.
/// @param timeout Maximum time to wait for an item. Zero means wait forever.
/// @return The next item, or error.
[[nodiscard]] score::cpp::expected<T, ConcurrencyErrc> pop(
std::chrono::milliseconds timeout = std::chrono::milliseconds{0})
[[nodiscard]] score::Result<T> pop(std::chrono::milliseconds timeout = std::chrono::milliseconds{0})
{
const auto wait_result =
(timeout == std::chrono::milliseconds{0}) ? m_items.wait() : m_items.timedWait(timeout);

if (wait_result == osal::OsalReturnType::kTimeout)
{
return score::cpp::make_unexpected(ConcurrencyErrc::kTimeout);
return score::MakeUnexpected(ConcurrencyErrc::kTimeout);
}
else if (wait_result != osal::OsalReturnType::kSuccess)
{
return score::cpp::make_unexpected(ConcurrencyErrc::kOsError);
return score::MakeUnexpected(ConcurrencyErrc::kOsError);
}

if (m_stopped.load(std::memory_order_acquire))
{
static_cast<void>(m_items.post());
return score::cpp::make_unexpected(ConcurrencyErrc::kStopped);
return score::MakeUnexpected(ConcurrencyErrc::kStopped);
}

T item = consume_slot(m_head.fetch_add(1, std::memory_order_relaxed));

if (m_spaces.post() != osal::OsalReturnType::kSuccess)
{
return score::cpp::make_unexpected(ConcurrencyErrc::kOsError);
return score::MakeUnexpected(ConcurrencyErrc::kOsError);
}

return item;
Expand Down Expand Up @@ -226,25 +222,25 @@ class MPMCConcurrentQueue
}

template <class U>
[[nodiscard]] score::cpp::expected_blank<ConcurrencyErrc> push_impl(U&& item, std::chrono::milliseconds timeout)
[[nodiscard]] score::ResultBlank push_impl(U&& item, std::chrono::milliseconds timeout)
{
const auto wait_result =
(timeout == std::chrono::milliseconds{0}) ? m_spaces.wait() : m_spaces.timedWait(timeout);

if (wait_result == osal::OsalReturnType::kTimeout)
{
return score::cpp::make_unexpected(ConcurrencyErrc::kTimeout);
return score::MakeUnexpected(ConcurrencyErrc::kTimeout);
}
else if (wait_result != osal::OsalReturnType::kSuccess)
{
return score::cpp::make_unexpected(ConcurrencyErrc::kOsError);
return score::MakeUnexpected(ConcurrencyErrc::kOsError);
}

if (m_stopped.load(std::memory_order_acquire))
{
// chain-wake the next blocked producer then discard the item
static_cast<void>(m_spaces.post());
return score::cpp::make_unexpected(ConcurrencyErrc::kStopped);
return score::MakeUnexpected(ConcurrencyErrc::kStopped);
}

const auto tail = m_tail.fetch_add(1, std::memory_order_relaxed);
Expand Down Expand Up @@ -273,10 +269,10 @@ class MPMCConcurrentQueue

if (m_items.post() != osal::OsalReturnType::kSuccess)
{
return score::cpp::make_unexpected(ConcurrencyErrc::kOsError);
return score::MakeUnexpected(ConcurrencyErrc::kOsError);
}

return score::cpp::blank{};
return score::ResultBlank{};
}

/// @brief Underlying storage.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ TEST_F(MPMCConcurrentQueueTest_Basic, PopReturnsNulloptOnSemaphoreWaitFailure)
sa.sa_flags = 0;
sigaction(SIGUSR1, &sa, nullptr);

score::cpp::expected<int, ConcurrencyErrc> result = score::cpp::make_unexpected(ConcurrencyErrc::kOsError);
score::Result<int> result = score::MakeUnexpected(ConcurrencyErrc::kOsError);
std::atomic<bool> tid_ready{false};

std::thread consumer([&] {
Expand Down Expand Up @@ -194,7 +194,7 @@ class MPMCConcurrentQueueTest_Blocking : public ::testing::Test
TEST_F(MPMCConcurrentQueueTest_Blocking, PopBlocksUntilItemAvailable)
{
RecordProperty("Description", "Verify that pop blocks on an empty queue until a producer pushes an item.");
score::cpp::expected<int, ConcurrencyErrc> result = score::cpp::make_unexpected(ConcurrencyErrc::kOsError);
score::Result<int> result = score::MakeUnexpected(ConcurrencyErrc::kOsError);

std::thread consumer([&] {
result = queue8_.pop();
Expand All @@ -218,7 +218,7 @@ TEST_F(MPMCConcurrentQueueTest_Blocking, PushBlocksWhenFull)
}

std::atomic<bool> push_completed{false};
score::cpp::expected_blank<ConcurrencyErrc> pushed = score::cpp::make_unexpected(ConcurrencyErrc::kOsError);
score::ResultBlank pushed = score::MakeUnexpected(ConcurrencyErrc::kOsError);
std::thread producer([&] {
pushed = queue4_.push(99);
push_completed.store(true, std::memory_order_release);
Expand All @@ -235,7 +235,7 @@ TEST_F(MPMCConcurrentQueueTest_Blocking, PushBlocksWhenFull)
TEST_F(MPMCConcurrentQueueTest_Blocking, StopUnblocksBlockedConsumer)
{
RecordProperty("Description", "Verify that stop() unblocks a consumer thread waiting on an empty queue.");
score::cpp::expected<int, ConcurrencyErrc> result = score::cpp::make_unexpected(ConcurrencyErrc::kOsError);
score::Result<int> result = score::MakeUnexpected(ConcurrencyErrc::kOsError);

std::thread consumer([&] {
result = queue8_.pop();
Expand All @@ -256,7 +256,7 @@ TEST_F(MPMCConcurrentQueueTest_Blocking, StopUnblocksBlockedProducer)
ASSERT_TRUE(queue4_.push(i));
}

score::cpp::expected_blank<ConcurrencyErrc> pushed = score::cpp::make_unexpected(ConcurrencyErrc::kOsError);
score::ResultBlank pushed = score::MakeUnexpected(ConcurrencyErrc::kOsError);
std::thread producer([&] {
pushed = queue4_.push(99);
});
Expand Down
Loading
Loading