From 1f0b1cc224ba68c508bb358823298a028e41ad99 Mon Sep 17 00:00:00 2001 From: William Roebuck <244554584+WilliamRoebuck@users.noreply.github.com> Date: Wed, 22 Jul 2026 14:25:34 +0100 Subject: [PATCH 01/21] Bring in files --- .../src/daemon/src/common/constants.hpp | 43 +++-- .../src/process_group_manager/details/BUILD | 94 ++++++++++- .../details/component_event.hpp | 131 +++++++++++++++ .../details/component_event_UT.cpp | 152 ++++++++++++++++++ .../details/icomponent.hpp | 53 ++++++ .../details/icomponent_controller.hpp | 34 ++++ .../details/process_monitor.cpp | 92 +++++++++++ .../details/process_monitor.hpp | 48 ++++++ .../details/run_target.hpp | 72 +++++++++ .../process_group_manager/details/task.hpp | 41 +++++ 10 files changed, 745 insertions(+), 15 deletions(-) create mode 100644 score/launch_manager/src/daemon/src/process_group_manager/details/component_event.hpp create mode 100644 score/launch_manager/src/daemon/src/process_group_manager/details/component_event_UT.cpp create mode 100644 score/launch_manager/src/daemon/src/process_group_manager/details/icomponent.hpp create mode 100644 score/launch_manager/src/daemon/src/process_group_manager/details/icomponent_controller.hpp create mode 100644 score/launch_manager/src/daemon/src/process_group_manager/details/process_monitor.cpp create mode 100644 score/launch_manager/src/daemon/src/process_group_manager/details/process_monitor.hpp create mode 100644 score/launch_manager/src/daemon/src/process_group_manager/details/run_target.hpp create mode 100644 score/launch_manager/src/daemon/src/process_group_manager/details/task.hpp diff --git a/score/launch_manager/src/daemon/src/common/constants.hpp b/score/launch_manager/src/daemon/src/common/constants.hpp index 22843d1c7..77f5b57a6 100644 --- a/score/launch_manager/src/daemon/src/common/constants.hpp +++ b/score/launch_manager/src/daemon/src/common/constants.hpp @@ -11,7 +11,6 @@ * SPDX-License-Identifier: Apache-2.0 ********************************************************************************/ - #ifndef CONSTANTS_HPP_INCLUDED #define CONSTANTS_HPP_INCLUDED @@ -19,11 +18,14 @@ #include #include -namespace score { +namespace score +{ -namespace lcm { +namespace lcm +{ -namespace internal { +namespace internal +{ // coverity[autosar_cpp14_a0_1_1_violation:INTENTIONAL] These are constants that are used globally. constexpr std::size_t kMaxArg = 20U; ///< Maximum number of arguments @@ -36,30 +38,43 @@ constexpr std::size_t kArgvArraySize = constexpr std::size_t kEnvArraySize = kMaxEnv + 1U; ///< As required by posix we need extra space in envp_ for NULL pointer -constexpr std::chrono::milliseconds kMaxQueueDelay{500}; ///< The maximum time to wait trying to add items to, or get items from, a queue -constexpr std::chrono::milliseconds kGraphTimeout{10000}; ///< Timeout duration for graph operations. +constexpr std::chrono::milliseconds kMaxQueueDelay{ + 500}; ///< The maximum time to wait trying to add items to, or get items from, a queue +constexpr std::chrono::milliseconds kGraphTimeout{10000}; ///< Timeout duration for graph operations. constexpr std::chrono::milliseconds kMaxSigKillDelay{500}; ///< The maximum time to wait for a process termination -constexpr std::chrono::milliseconds kControlClientPollingDelay{1}; ///< Time Control Client will wait during polling for acknowledgement +constexpr std::chrono::milliseconds kControlClientPollingDelay{ + 1}; ///< Time Control Client will wait during polling for acknowledgement -constexpr std::chrono::milliseconds kMaxRunningDelay{1000}; ///< report_running() API will wait for Launch Manager to respond +constexpr std::chrono::milliseconds kMaxRunningDelay{ + 1000}; ///< report_running() API will wait for Launch Manager to respond -constexpr std::chrono::milliseconds kControlClientMaxIpcDelay{500}; ///< The maximum time to wait, when trying to communicate with LCM. When this time is exceeded kCommunicationError will be returned +constexpr std::chrono::milliseconds kControlClientMaxIpcDelay{ + 500}; ///< The maximum time to wait, when trying to communicate with LCM. When this time is exceeded + ///< kCommunicationError will be returned constexpr std::chrono::milliseconds kControlClientBgThreadSleepTime{100}; -enum class ControlClientLimits : uint16_t { - kControlClientMaxInstances = - 256U, ///< Maximum number of ControlClient instances that should be created by state manager. If state manager create more instances than kMaxInstances, those instances will always return kCommunicationError when used +enum class ControlClientLimits : uint16_t +{ + kControlClientMaxInstances = 256U, ///< Maximum number of ControlClient instances that should be created by state + ///< manager. If state manager create more instances than kMaxInstances, those + ///< instances will always return kCommunicationError when used kControlClientMaxRequests = - 512U ///< Maximum number of active requests, for example SetState call, that ControlClient instance can send to LCM. If that number is exceeded ControlClient API will return kFailed, until one of the current requests is completed by LCM + 512U ///< Maximum number of active requests, for example SetState call, that ControlClient instance can send to + ///< LCM. If that number is exceeded ControlClient API will return kFailed, until one of the current + ///< requests is completed by LCM }; -enum class ProcessLimits : std::uint32_t { +enum class ProcessLimits : std::uint32_t +{ kMaxProcesses = 1024U, ///< Maximum number of processes allowed kNumWorkerThreads = 32U, ///< Maximum number of worker threads allowed maxLocalBuffSize = 32U ///< Maximum size for local buffer }; +/// @brief Capacity of the ComponentEventQueue. +constexpr std::size_t kComponentEventQueueSize = 1024U; + } // namespace internal } // namespace lcm diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/BUILD b/score/launch_manager/src/daemon/src/process_group_manager/details/BUILD index 2bc35491f..a9fcea488 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/BUILD +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/BUILD @@ -10,7 +10,64 @@ # # SPDX-License-Identifier: Apache-2.0 # ******************************************************************************* -load("@rules_cc//cc:defs.bzl", "cc_library") +load("@rules_cc//cc:defs.bzl", "cc_library", "cc_test") + +cc_library( + name = "task", + hdrs = ["task.hpp"], + include_prefix = "score/mw/launch_manager/process_group_manager/details", + strip_include_prefix = "/score/launch_manager/src/daemon/src/process_group_manager/details", + visibility = ["//score:__subpackages__"], + deps = [ + ":icomponent", + ] +) + +cc_library( + name = "icomponent_controller", + hdrs = ["icomponent_controller.hpp"], + include_prefix = "score/mw/launch_manager/process_group_manager/details", + strip_include_prefix = "/score/launch_manager/src/daemon/src/process_group_manager/details", + visibility = ["//score:__subpackages__"], + deps = [ + ":task", + ":icomponent", + ], +) + +cc_library( + name = "icomponent", + hdrs = ["icomponent.hpp"], + include_prefix = "score/mw/launch_manager/process_group_manager/details", + strip_include_prefix = "/score/launch_manager/src/daemon/src/process_group_manager/details", + visibility = ["//score:__subpackages__"], + deps = [ + "@score_baselibs//score/language/futurecpp" + ] +) + +cc_library( + name = "component_event", + hdrs = ["component_event.hpp"], + include_prefix = "score/mw/launch_manager/process_group_manager/details", + strip_include_prefix = "/score/launch_manager/src/daemon/src/process_group_manager/details", + visibility = ["//score/launch_manager/src/daemon/src/process_group_manager:__pkg__"], + deps = [ + ":icomponent", + "//score/launch_manager/src/daemon/src/common:constants", + "//score/launch_manager/src/daemon/src/common:identifier_hash", + "//score/launch_manager/src/daemon/src/common/concurrency:mpsc_bounded_queue", + ], +) + +cc_test( + name = "component_event_UT", + srcs = ["component_event_UT.cpp"], + deps = [ + ":component_event", + "@googletest//:gtest_main", + ], +) cc_library( name = "process_info_node", @@ -23,6 +80,7 @@ cc_library( strip_include_prefix = "/score/launch_manager/src/daemon/src/process_group_manager/details", visibility = ["//score/launch_manager/src/daemon/src/process_group_manager:__pkg__"], deps = [ + ":icomponent", ":safe_process_map", "//score/launch_manager/src/daemon/src/control:control_client_channel", "//score/launch_manager/src/daemon/src/osal:ipc_comms", @@ -39,6 +97,17 @@ cc_library( }), ) +cc_library( + name = "run_target", + hdrs = ["run_target.hpp"], + include_prefix = "score/mw/launch_manager/process_group_manager/details", + strip_include_prefix = "/score/launch_manager/src/daemon/src/process_group_manager/details", + visibility = ["//score/launch_manager/src/daemon/src/process_group_manager:__pkg__"], + deps = [ + ":icomponent", + ], +) + cc_library( name = "graph", hdrs = ["graph.hpp"], @@ -46,8 +115,11 @@ cc_library( strip_include_prefix = "/score/launch_manager/src/daemon/src/process_group_manager/details", visibility = ["//score/launch_manager/src/daemon/src/process_group_manager:__pkg__"], deps = [ + ":component_event", ":process_info_node", + ":run_target", ":safe_process_map", + ":task", "//score/launch_manager/src/daemon/src/common:identifier_hash", "//score/launch_manager/src/daemon/src/control:control_client_channel", "//score/launch_manager/src/daemon/src/osal:semaphore", @@ -63,6 +135,7 @@ cc_library( strip_include_prefix = "/score/launch_manager/src/daemon/src/process_group_manager/details", visibility = ["//score/launch_manager/src/daemon/src/process_group_manager:__pkg__"], deps = [ + ":icomponent_controller", "//score/launch_manager/src/daemon/src/process_group_manager:iprocess", ], ) @@ -92,6 +165,23 @@ cc_library( ], ) +cc_library( + name = "process_monitor", + srcs = ["process_monitor.cpp"], + hdrs = ["process_monitor.hpp"], + include_prefix = "score/mw/launch_manager/process_group_manager/details", + strip_include_prefix = "/score/launch_manager/src/daemon/src/process_group_manager/details", + visibility = ["//score/launch_manager/src/daemon/src/process_group_manager:__pkg__"], + deps = [ + ":component_event", + ":os_handler", + ":process_info_node", + ":safe_process_map", + ":graph", + ":icomponent_controller", + ], +) + cc_test( name = "oshandler_UT", srcs = ["oshandler_UT.cpp"], @@ -135,8 +225,10 @@ cc_library( }), visibility = ["//score/launch_manager/src/daemon/src/process_group_manager:__pkg__"], deps = [ + ":component_event", ":graph", ":os_handler", + ":process_monitor", ":process_info_node", ":process_launcher", ":safe_process_map", diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/component_event.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/component_event.hpp new file mode 100644 index 000000000..fa2682b41 --- /dev/null +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/component_event.hpp @@ -0,0 +1,131 @@ +/******************************************************************************** + * 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 SCORE_LCM_COMPONENT_EVENT_HPP_INCLUDED +#define SCORE_LCM_COMPONENT_EVENT_HPP_INCLUDED + +#include +#include +#include +#include +#include + +#include "score/mw/launch_manager/common/concurrency/concurrency_error_domain.hpp" +#include "score/mw/launch_manager/common/concurrency/mpsc_bounded_queue.hpp" +#include "score/mw/launch_manager/common/constants.hpp" +#include "score/mw/launch_manager/common/identifier_hash.hpp" +#include "score/mw/launch_manager/process_group_manager/details/icomponent.hpp" + +namespace score::lcm::internal +{ + +/// @brief A node finished activating successfully. +struct ActivationSuccessful +{ + uint32_t node_index; +}; + +/// @brief A node failed to activate. +struct ActivationFailed +{ + uint32_t node_index; + IComponent::ComponentError reason; +}; + +/// @brief A node finished deactivating. +struct DeactivationComplete +{ + uint32_t node_index; +}; + +/// @brief A node terminated without having been requested to. +struct UnexpectedTermination +{ + uint32_t node_index; +}; + +/// @brief Alive supervision has failed for the given process identifier. +struct SupervisionFailure +{ + IdentifierHash process_identifier; +}; + +/// @brief A graph-relevant state change. There is only ever a single graph, so no process-group +/// identifier is needed to route most events. SupervisionFailure is routed by process identifier. +using ComponentEvent = std:: + variant; + +/// @brief Queue of ComponentEvents produced by worker/OS-handler threads and consumed +/// exclusively by the main thread, backed by a fixed-capacity MpscBoundedQueue. +class ComponentEventQueue final +{ + public: + ComponentEventQueue() = default; + ~ComponentEventQueue() + { + stop(); + } + + ComponentEventQueue(const ComponentEventQueue&) = delete; + ComponentEventQueue& operator=(const ComponentEventQueue&) = delete; + ComponentEventQueue(ComponentEventQueue&&) = delete; + ComponentEventQueue& operator=(ComponentEventQueue&&) = delete; + + /// @brief Enqueues an event. If the queue is full, the event is dropped immediately and + /// getOverflow() will subsequently return true. + void push(ComponentEvent&& event) + { + auto result = queue_.push(std::move(event)); + if (!result.has_value() && result.error() == ConcurrencyErrc::kOverflow) + { + overflow_.store(true, std::memory_order_release); + } + } + + /// @brief Waits up to timeout for at least one event to become available. + /// @param timeout Maximum time to wait. A `timeout` of zero means "check once, don't + /// block", NOT "wait forever" -- see MpscBoundedQueue::wait(). + /// @return true if an event is available (drain via getNextEvent()), false on timeout. + bool waitForEvents(std::chrono::milliseconds timeout) + { + return queue_.wait(timeout).has_value(); + } + + /// @brief Returns the next available event without blocking, or std::nullopt if none right + /// now. Call repeatedly until nullopt to drain everything currently queued. + std::optional getNextEvent() + { + return queue_.tryPop(); + } + + /// @return True if an event was ever dropped due to the queue being full. + bool getOverflow() const + { + return overflow_.load(std::memory_order_acquire); + } + + /// @brief Permanently marks the queue stopped and wakes any thread currently blocked in + /// waitForEvents(). + void stop() + { + queue_.stop(); + } + + private: + MpscBoundedQueue queue_; + std::atomic overflow_{false}; +}; + +} // namespace score::lcm::internal + +#endif // SCORE_LCM_COMPONENT_EVENT_HPP_INCLUDED diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/component_event_UT.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/component_event_UT.cpp new file mode 100644 index 000000000..5d40f1c7c --- /dev/null +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/component_event_UT.cpp @@ -0,0 +1,152 @@ +/******************************************************************************** + * 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/process_group_manager/details/component_event.hpp" + +#include +#include + +namespace score::lcm::internal +{ + +class ComponentEventQueueTest : public ::testing::Test +{ + protected: + ComponentEventQueue queue_; +}; + +TEST_F(ComponentEventQueueTest, WaitForEventsReturnsFalseOnEmptyQueue) +{ + RecordProperty("Description", "Verify waitForEvents returns false promptly when no event has been pushed."); + EXPECT_FALSE(queue_.waitForEvents(std::chrono::milliseconds{1})); +} + +TEST_F(ComponentEventQueueTest, WaitForEventsReturnsTrueAfterPush) +{ + RecordProperty("Description", "Verify waitForEvents returns true once an event has been pushed."); + queue_.push(ActivationSuccessful{7U}); + EXPECT_TRUE(queue_.waitForEvents(std::chrono::milliseconds{1})); +} + +TEST_F(ComponentEventQueueTest, GetNextEventReturnsNulloptWhenEmpty) +{ + RecordProperty("Description", "Verify getNextEvent never blocks and returns nullopt when nothing is queued."); + EXPECT_FALSE(queue_.getNextEvent().has_value()); +} + +TEST_F(ComponentEventQueueTest, GetNextEventReturnsPushedEventWithPayloadIntact) +{ + RecordProperty("Description", "Verify a pushed event is returned by getNextEvent with its payload preserved."); + queue_.push(ActivationFailed{3U, IComponent::ComponentError::kErrorBeforeReady}); + + auto event = queue_.getNextEvent(); + ASSERT_TRUE(event.has_value()); + ASSERT_TRUE(std::holds_alternative(*event)); + const auto& failed = std::get(*event); + EXPECT_EQ(failed.node_index, 3U); + EXPECT_EQ(failed.reason, IComponent::ComponentError::kErrorBeforeReady); +} + +TEST_F(ComponentEventQueueTest, GetNextEventReturnsSupervisionFailureWithPayloadIntact) +{ + RecordProperty("Description", + "Verify a pushed SupervisionFailure event is returned by getNextEvent with process identifier " + "payload preserved."); + const IdentifierHash process_identifier{"proc_for_supervision_failure"}; + queue_.push(SupervisionFailure{process_identifier}); + + auto event = queue_.getNextEvent(); + ASSERT_TRUE(event.has_value()); + ASSERT_TRUE(std::holds_alternative(*event)); + const auto& failure = std::get(*event); + EXPECT_EQ(failure.process_identifier, process_identifier); +} + +TEST_F(ComponentEventQueueTest, GetNextEventDrainsMultipleEventsInFifoOrder) +{ + RecordProperty("Description", + "Verify the intended drain pattern -- waitForEvents() once, then getNextEvent() in a loop " + "until nullopt -- returns every queued event exactly once in FIFO order."); + queue_.push(ActivationSuccessful{1U}); + queue_.push(DeactivationComplete{2U}); + queue_.push(UnexpectedTermination{3U}); + + ASSERT_TRUE(queue_.waitForEvents(std::chrono::milliseconds{20})); + + auto first = queue_.getNextEvent(); + ASSERT_TRUE(first.has_value()); + ASSERT_TRUE(std::holds_alternative(*first)); + EXPECT_EQ(std::get(*first).node_index, 1U); + + auto second = queue_.getNextEvent(); + ASSERT_TRUE(second.has_value()); + ASSERT_TRUE(std::holds_alternative(*second)); + EXPECT_EQ(std::get(*second).node_index, 2U); + + auto third = queue_.getNextEvent(); + ASSERT_TRUE(third.has_value()); + ASSERT_TRUE(std::holds_alternative(*third)); + EXPECT_EQ(std::get(*third).node_index, 3U); + + EXPECT_FALSE(queue_.getNextEvent().has_value()); +} + +TEST_F(ComponentEventQueueTest, GetOverflowStaysFalseUnderNormalUsage) +{ + RecordProperty("Description", "Verify getOverflow() stays false when events are pushed and drained normally."); + queue_.push(ActivationSuccessful{1U}); + static_cast(queue_.getNextEvent()); + EXPECT_FALSE(queue_.getOverflow()); +} + +TEST_F(ComponentEventQueueTest, GetOverflowBecomesTrueOnceQueueIsFull) +{ + RecordProperty("Description", + "Verify getOverflow() becomes true once a push is dropped because the queue is full, " + "mirroring how ProcessGroupManager::run() detects lost events."); + for (std::size_t i = 0U; i < kComponentEventQueueSize; ++i) + { + queue_.push(ActivationSuccessful{static_cast(i)}); + } + EXPECT_FALSE(queue_.getOverflow()); + + // One more push while the queue is already at capacity and nobody is draining it: this + // push is dropped immediately, flagging overflow. + queue_.push(ActivationSuccessful{9999U}); + EXPECT_TRUE(queue_.getOverflow()); +} + +TEST_F(ComponentEventQueueTest, StopUnblocksWaitForEventsOnEmptyQueue) +{ + RecordProperty("Description", + "Verify stop() causes a subsequently-called waitForEvents() to return false immediately " + "rather than blocking, matching the shutdown usage in ProcessGroupManager::deinitialize()."); + queue_.stop(); + EXPECT_FALSE(queue_.waitForEvents(std::chrono::milliseconds{2000})); +} + +TEST_F(ComponentEventQueueTest, GetNextEventStillDrainsQueuedEventsAfterStop) +{ + RecordProperty("Description", + "Verify that events pushed before stop() was called are not silently discarded -- " + "getNextEvent() must still be able to drain them during shutdown."); + queue_.push(ActivationSuccessful{1U}); + queue_.stop(); + + auto event = queue_.getNextEvent(); + ASSERT_TRUE(event.has_value()); + EXPECT_TRUE(std::holds_alternative(*event)); + EXPECT_FALSE(queue_.getNextEvent().has_value()); +} + +} // namespace score::lcm::internal diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/icomponent.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/icomponent.hpp new file mode 100644 index 000000000..bea52dac6 --- /dev/null +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/icomponent.hpp @@ -0,0 +1,53 @@ +/******************************************************************************** + * 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 SCORE_LCM_ICOMPONENT_HPP_INCLUDED +#define SCORE_LCM_ICOMPONENT_HPP_INCLUDED + +#include +#include +#include + +namespace score::lcm::internal +{ + +class IComponent +{ + public: + enum class ComponentError : uint8_t + { + kErrorBeforeReady, + kErrorAfterReady, + kActivationTimedOut, + }; + + enum class RequestState : uint8_t + { + kSuccess, + kWaiting + }; + + using RequestResult = score::cpp::expected; + + virtual RequestResult activate(score::cpp::stop_token stop_token) = 0; + virtual RequestResult deactivate(score::cpp::stop_token stop_token) = 0; + virtual RequestResult tryHandleTermination(int32_t status) = 0; + virtual uint32_t getIndex() const = 0; + virtual bool active() const = 0; + + virtual ~IComponent() = default; +}; + +} // namespace score::lcm::internal + +#endif // SCORE_LCM_ICOMPONENT_HPP_INCLUDED diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/icomponent_controller.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/icomponent_controller.hpp new file mode 100644 index 000000000..639ab9ee2 --- /dev/null +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/icomponent_controller.hpp @@ -0,0 +1,34 @@ +/******************************************************************************** + * 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 SCORE_LCM_ICOMPONENT_CONTROLLER_HPP_INCLUDED +#define SCORE_LCM_ICOMPONENT_CONTROLLER_HPP_INCLUDED + +#include "score/mw/launch_manager/process_group_manager/details/icomponent.hpp" +#include "score/mw/launch_manager/process_group_manager/details/task.hpp" + +namespace score::lcm::internal +{ + +class IComponentController +{ + public: + virtual void doWork(Task task) = 0; + virtual void terminated(IComponent& component, int32_t status) = 0; + + virtual ~IComponentController() = default; +}; + +} // namespace score::lcm::internal + +#endif // SCORE_LCM_ICOMPONENT_CONTROLLER_HPP_INCLUDED diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/process_monitor.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/process_monitor.cpp new file mode 100644 index 000000000..635cdaf2d --- /dev/null +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/process_monitor.cpp @@ -0,0 +1,92 @@ +/******************************************************************************** + * 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/process_group_manager/details/process_monitor.hpp" + +namespace score::lcm::internal +{ + +ProcessMonitor::ProcessMonitor(ComponentEventQueue& event_queue) : event_queue_(event_queue) {} + +ProcessMonitor::~ProcessMonitor() = default; + +void ProcessMonitor::doWork(Task task) +{ + if (task.stop_token.stop_requested()) + { + return; + } + + auto& component = task.component.get(); + + IComponent::RequestResult res; + + switch (task.type) + { + case TaskType::kActivate: + res = component.activate(task.stop_token); + break; + case TaskType::kDeactivate: + res = component.deactivate(task.stop_token); + break; + default: + break; + } + + if (res.has_value() && res.value() == IComponent::RequestState::kWaiting) + { + return; + } + + if (res.has_value()) + { + taskFinished(task, {}); + } + else + { + taskFinished(task, score::cpp::make_unexpected(res.error())); + } +} + +void ProcessMonitor::terminated(IComponent& component, int32_t status) +{ + auto res = component.tryHandleTermination(status); + if (!res.has_value()) + { + event_queue_.push(UnexpectedTermination{component.getIndex()}); + } + else if (res.value() != IComponent::RequestState::kWaiting) + { + event_queue_.push(ActivationSuccessful{component.getIndex()}); + } +} + +void ProcessMonitor::taskFinished(const Task& task, const score::cpp::expected_blank& error) +{ + const uint32_t node_index = task.component.get().getIndex(); + + if (task.type == TaskType::kDeactivate) + { + event_queue_.push(DeactivationComplete{node_index}); + } + else if (error.has_value()) + { + event_queue_.push(ActivationSuccessful{node_index}); + } + else + { + event_queue_.push(ActivationFailed{node_index, error.error()}); + } +} + +} // namespace score::lcm::internal diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/process_monitor.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/process_monitor.hpp new file mode 100644 index 000000000..5902ad362 --- /dev/null +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/process_monitor.hpp @@ -0,0 +1,48 @@ +/******************************************************************************** + * 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 PROCESS_MONITOR_HPP_INCLUDED +#define PROCESS_MONITOR_HPP_INCLUDED + +#include "score/mw/launch_manager/process_group_manager/details/component_event.hpp" +#include "score/mw/launch_manager/process_group_manager/details/icomponent_controller.hpp" + +namespace score::lcm::internal +{ + +/// @brief Translates IComponentController callbacks (from worker threads and the OsHandler thread) +/// into ComponentEvents pushed onto the event queue for processing on the main thread. +/// @note Assumes a single graph/process group; events carry no process-group identifier. +class ProcessMonitor final : public IComponentController +{ + public: + explicit ProcessMonitor(ComponentEventQueue& event_queue); + ~ProcessMonitor() override; + + ProcessMonitor(const ProcessMonitor&) = delete; + ProcessMonitor& operator=(const ProcessMonitor&) = delete; + ProcessMonitor(ProcessMonitor&&) = delete; + ProcessMonitor& operator=(ProcessMonitor&&) = delete; + + void doWork(Task task) override; + void terminated(IComponent& component, int32_t status) override; + + private: + void taskFinished(const Task& task, const score::cpp::expected_blank& error); + + ComponentEventQueue& event_queue_; +}; + +} // namespace score::lcm::internal + +#endif // PROCESS_MONITOR_HPP_INCLUDED diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/run_target.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/run_target.hpp new file mode 100644 index 000000000..f51c425e3 --- /dev/null +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/run_target.hpp @@ -0,0 +1,72 @@ +/******************************************************************************** + * 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 SCORE_LCM_RUN_TARGET_HPP_INCLUDED +#define SCORE_LCM_RUN_TARGET_HPP_INCLUDED + +#include "score/mw/launch_manager/process_group_manager/details/icomponent.hpp" + +#include + +namespace score::lcm::internal +{ + +/// @brief A virtual node in the dependency graph corresponding to a ProcessGroupState. +/// A RunTarget does not correspond to any OS process; it is immediately active once all +/// of its dependencies are active, and immediately inactive once deactivated. +class RunTarget final : public IComponent +{ + public: + explicit RunTarget(uint32_t index) : index_(index) {} + + RunTarget(RunTarget&& other) noexcept : index_(other.index_), active_(other.active_.load()) {} + RunTarget(const RunTarget&) = delete; + RunTarget& operator=(const RunTarget&) = delete; + RunTarget& operator=(RunTarget&&) = delete; + ~RunTarget() override = default; + + RequestResult activate(score::cpp::stop_token) override + { + active_ = true; + return RequestState::kSuccess; + } + + RequestResult deactivate(score::cpp::stop_token) override + { + active_ = false; + return RequestState::kSuccess; + } + + RequestResult tryHandleTermination(int32_t) override + { + return RequestState::kSuccess; + } + + uint32_t getIndex() const override + { + return index_; + } + + bool active() const override + { + return active_; + } + + private: + uint32_t index_; + std::atomic active_{false}; +}; + +} // namespace score::lcm::internal + +#endif // SCORE_LCM_RUN_TARGET_HPP_INCLUDED diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/task.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/task.hpp new file mode 100644 index 000000000..1febf7b8b --- /dev/null +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/task.hpp @@ -0,0 +1,41 @@ +/******************************************************************************** + * 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 SCORE_LCM_TASK_HPP_INCLUDED +#define SCORE_LCM_TASK_HPP_INCLUDED + +#include "score/mw/launch_manager/process_group_manager/details/icomponent.hpp" +#include +#include +#include + + +namespace score::lcm::internal +{ + +enum class TaskType : std::uint_least8_t +{ + kActivate = 0U, + kDeactivate = 1U, +}; + +struct Task +{ + TaskType type; + std::reference_wrapper component; + score::cpp::stop_token stop_token; +}; + +} // namespace score::lcm::internal + +#endif // SCORE_LCM_TASK_HPP_INCLUDED From 049aa8b13e535e75b28b2de73bcda8374c8828fe Mon Sep 17 00:00:00 2001 From: William Roebuck <244554584+WilliamRoebuck@users.noreply.github.com> Date: Thu, 23 Jul 2026 09:16:50 +0100 Subject: [PATCH 02/21] Moved component event queue, add interface --- .clang-tidy | 2 +- .../src/process_group_manager/details/BUILD | 37 ++++++-- .../details/component_event.hpp | 67 ------------- .../details/component_event_queue.hpp | 93 +++++++++++++++++++ ...nt_UT.cpp => component_event_queue_UT.cpp} | 2 +- .../details/icomponent.hpp | 1 - .../details/icomponent_event_receiver.hpp | 31 +++++++ .../details/process_monitor.cpp | 2 +- .../details/process_monitor.hpp | 6 +- 9 files changed, 161 insertions(+), 80 deletions(-) create mode 100644 score/launch_manager/src/daemon/src/process_group_manager/details/component_event_queue.hpp rename score/launch_manager/src/daemon/src/process_group_manager/details/{component_event_UT.cpp => component_event_queue_UT.cpp} (99%) create mode 100644 score/launch_manager/src/daemon/src/process_group_manager/details/icomponent_event_receiver.hpp diff --git a/.clang-tidy b/.clang-tidy index d63a95901..4028b675e 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -1,2 +1,2 @@ Checks: ' -*, bugprone-*, cert-*, clang-analyzer-core.*, clang-analyzer-cplusplus.*, clang-analyzer-security.*, cppcoreguidelines-pro-bounds-pointer-arithmetic, cppcoreguidelines-pro-type-cstyle-cast, hicpp-* ' -WarningsAsErrors: 'bugprone-*,cert-*,clang-analyzer-*,hicpp-*' +WarningsAsErrors: 'bugprone-*,cert-*,clang-analyzer-* diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/BUILD b/score/launch_manager/src/daemon/src/process_group_manager/details/BUILD index a9fcea488..9affa85a0 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/BUILD +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/BUILD @@ -54,17 +54,42 @@ cc_library( visibility = ["//score/launch_manager/src/daemon/src/process_group_manager:__pkg__"], deps = [ ":icomponent", + "//score/launch_manager/src/daemon/src/common:identifier_hash", + ] +) + +cc_library( + name = "component_event_queue", + hdrs = ["component_event_queue.hpp"], + include_prefix = "score/mw/launch_manager/process_group_manager/details", + strip_include_prefix = "/score/launch_manager/src/daemon/src/process_group_manager/details", + visibility = ["//score/launch_manager/src/daemon/src/process_group_manager:__pkg__"], + deps = [ + ":icomponent_event_receiver", "//score/launch_manager/src/daemon/src/common:constants", "//score/launch_manager/src/daemon/src/common:identifier_hash", "//score/launch_manager/src/daemon/src/common/concurrency:mpsc_bounded_queue", + ] +) + +cc_library( + name = "icomponent_event_receiver", + hdrs = ["icomponent_event_receiver.hpp"], + include_prefix = "score/mw/launch_manager/process_group_manager/details", + strip_include_prefix = "/score/launch_manager/src/daemon/src/process_group_manager/details", + visibility = ["//score/launch_manager/src/daemon/src/process_group_manager:__pkg__"], + deps = [ + ":component_event", + "//score/launch_manager/src/daemon/src/common:constants", + "//score/launch_manager/src/daemon/src/common:identifier_hash", ], ) cc_test( - name = "component_event_UT", - srcs = ["component_event_UT.cpp"], + name = "component_event_queue_UT", + srcs = ["component_event_queue_UT.cpp"], deps = [ - ":component_event", + ":component_event_queue", "@googletest//:gtest_main", ], ) @@ -115,7 +140,7 @@ cc_library( strip_include_prefix = "/score/launch_manager/src/daemon/src/process_group_manager/details", visibility = ["//score/launch_manager/src/daemon/src/process_group_manager:__pkg__"], deps = [ - ":component_event", + ":component_event_queue", ":process_info_node", ":run_target", ":safe_process_map", @@ -173,7 +198,7 @@ cc_library( strip_include_prefix = "/score/launch_manager/src/daemon/src/process_group_manager/details", visibility = ["//score/launch_manager/src/daemon/src/process_group_manager:__pkg__"], deps = [ - ":component_event", + ":icomponent_event_receiver", ":os_handler", ":process_info_node", ":safe_process_map", @@ -225,7 +250,7 @@ cc_library( }), visibility = ["//score/launch_manager/src/daemon/src/process_group_manager:__pkg__"], deps = [ - ":component_event", + ":component_event_queue", ":graph", ":os_handler", ":process_monitor", diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/component_event.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/component_event.hpp index fa2682b41..80387ede9 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/component_event.hpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/component_event.hpp @@ -14,15 +14,9 @@ #ifndef SCORE_LCM_COMPONENT_EVENT_HPP_INCLUDED #define SCORE_LCM_COMPONENT_EVENT_HPP_INCLUDED -#include -#include #include -#include #include -#include "score/mw/launch_manager/common/concurrency/concurrency_error_domain.hpp" -#include "score/mw/launch_manager/common/concurrency/mpsc_bounded_queue.hpp" -#include "score/mw/launch_manager/common/constants.hpp" #include "score/mw/launch_manager/common/identifier_hash.hpp" #include "score/mw/launch_manager/process_group_manager/details/icomponent.hpp" @@ -65,67 +59,6 @@ struct SupervisionFailure using ComponentEvent = std:: variant; -/// @brief Queue of ComponentEvents produced by worker/OS-handler threads and consumed -/// exclusively by the main thread, backed by a fixed-capacity MpscBoundedQueue. -class ComponentEventQueue final -{ - public: - ComponentEventQueue() = default; - ~ComponentEventQueue() - { - stop(); - } - - ComponentEventQueue(const ComponentEventQueue&) = delete; - ComponentEventQueue& operator=(const ComponentEventQueue&) = delete; - ComponentEventQueue(ComponentEventQueue&&) = delete; - ComponentEventQueue& operator=(ComponentEventQueue&&) = delete; - - /// @brief Enqueues an event. If the queue is full, the event is dropped immediately and - /// getOverflow() will subsequently return true. - void push(ComponentEvent&& event) - { - auto result = queue_.push(std::move(event)); - if (!result.has_value() && result.error() == ConcurrencyErrc::kOverflow) - { - overflow_.store(true, std::memory_order_release); - } - } - - /// @brief Waits up to timeout for at least one event to become available. - /// @param timeout Maximum time to wait. A `timeout` of zero means "check once, don't - /// block", NOT "wait forever" -- see MpscBoundedQueue::wait(). - /// @return true if an event is available (drain via getNextEvent()), false on timeout. - bool waitForEvents(std::chrono::milliseconds timeout) - { - return queue_.wait(timeout).has_value(); - } - - /// @brief Returns the next available event without blocking, or std::nullopt if none right - /// now. Call repeatedly until nullopt to drain everything currently queued. - std::optional getNextEvent() - { - return queue_.tryPop(); - } - - /// @return True if an event was ever dropped due to the queue being full. - bool getOverflow() const - { - return overflow_.load(std::memory_order_acquire); - } - - /// @brief Permanently marks the queue stopped and wakes any thread currently blocked in - /// waitForEvents(). - void stop() - { - queue_.stop(); - } - - private: - MpscBoundedQueue queue_; - std::atomic overflow_{false}; -}; - } // namespace score::lcm::internal #endif // SCORE_LCM_COMPONENT_EVENT_HPP_INCLUDED diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/component_event_queue.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/component_event_queue.hpp new file mode 100644 index 000000000..d6a7046b9 --- /dev/null +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/component_event_queue.hpp @@ -0,0 +1,93 @@ +/******************************************************************************** + * 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 SCORE_LCM_COMPONENT_EVENT_QUEUE_HPP_INCLUDED +#define SCORE_LCM_COMPONENT_EVENT_QUEUE_HPP_INCLUDED + +#include +#include +#include + +#include "score/mw/launch_manager/common/concurrency/concurrency_error_domain.hpp" +#include "score/mw/launch_manager/common/concurrency/mpsc_bounded_queue.hpp" +#include "score/mw/launch_manager/common/constants.hpp" +#include "score/mw/launch_manager/process_group_manager/details/component_event.hpp" +#include "score/mw/launch_manager/process_group_manager/details/icomponent_event_receiver.hpp" + +namespace score::lcm::internal +{ + +/// @brief Queue of ComponentEvents produced by worker/OS-handler threads and consumed +/// exclusively by the main thread, backed by a fixed-capacity MpscBoundedQueue. +class ComponentEventQueue final : public IComponentEventReceiver +{ + public: + ComponentEventQueue() = default; + ~ComponentEventQueue() + { + stop(); + } + + ComponentEventQueue(const ComponentEventQueue&) = delete; + ComponentEventQueue& operator=(const ComponentEventQueue&) = delete; + ComponentEventQueue(ComponentEventQueue&&) = delete; + ComponentEventQueue& operator=(ComponentEventQueue&&) = delete; + + /// @brief Enqueues an event. If the queue is full, the event is dropped immediately and + /// getOverflow() will subsequently return true. + void push(ComponentEvent&& event) override + { + auto result = queue_.push(std::move(event)); + if (!result.has_value() && result.error() == ConcurrencyErrc::kOverflow) + { + overflow_.store(true, std::memory_order_release); + } + } + + /// @brief Waits up to timeout for at least one event to become available. + /// @param timeout Maximum time to wait. A `timeout` of zero means "check once, don't + /// block", NOT "wait forever" -- see MpscBoundedQueue::wait(). + /// @return true if an event is available (drain via getNextEvent()), false on timeout. + bool waitForEvents(std::chrono::milliseconds timeout) + { + return queue_.wait(timeout).has_value(); + } + + /// @brief Returns the next available event without blocking, or std::nullopt if none right + /// now. Call repeatedly until nullopt to drain everything currently queued. + std::optional getNextEvent() + { + return queue_.tryPop(); + } + + /// @return True if an event was ever dropped due to the queue being full. + bool getOverflow() const + { + return overflow_.load(std::memory_order_acquire); + } + + /// @brief Permanently marks the queue stopped and wakes any thread currently blocked in + /// waitForEvents(). + void stop() + { + queue_.stop(); + } + + private: + MpscBoundedQueue queue_; + std::atomic overflow_{false}; +}; + +} // namespace score::lcm::internal + +#endif // SCORE_LCM_COMPONENT_EVENT_QUEUE_HPP_INCLUDED diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/component_event_UT.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/component_event_queue_UT.cpp similarity index 99% rename from score/launch_manager/src/daemon/src/process_group_manager/details/component_event_UT.cpp rename to score/launch_manager/src/daemon/src/process_group_manager/details/component_event_queue_UT.cpp index 5d40f1c7c..0f74bf8df 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/component_event_UT.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/component_event_queue_UT.cpp @@ -11,7 +11,7 @@ * SPDX-License-Identifier: Apache-2.0 ********************************************************************************/ -#include "score/mw/launch_manager/process_group_manager/details/component_event.hpp" +#include "score/mw/launch_manager/process_group_manager/details/component_event_queue.hpp" #include #include diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/icomponent.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/icomponent.hpp index bea52dac6..5d058160c 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/icomponent.hpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/icomponent.hpp @@ -16,7 +16,6 @@ #include #include -#include namespace score::lcm::internal { diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/icomponent_event_receiver.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/icomponent_event_receiver.hpp new file mode 100644 index 000000000..ce678766c --- /dev/null +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/icomponent_event_receiver.hpp @@ -0,0 +1,31 @@ +/******************************************************************************** + * 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 SCORE_LCM_ICOMPONENT_EVENT_RECEIVER_HPP_INCLUDED +#define SCORE_LCM_ICOMPONENT_EVENT_RECEIVER_HPP_INCLUDED + +#include "score/mw/launch_manager/process_group_manager/details/component_event.hpp" + +namespace score::lcm::internal +{ + +class IComponentEventReceiver +{ + public: + virtual void push(ComponentEvent&& event) = 0; + virtual ~IComponentEventReceiver() = default; +}; + +} // namespace score::lcm::internal + +#endif // SCORE_LCM_ICOMPONENT_EVENT_RECEIVER_HPP_INCLUDED diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/process_monitor.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/process_monitor.cpp index 635cdaf2d..71c23a6a7 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/process_monitor.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/process_monitor.cpp @@ -16,7 +16,7 @@ namespace score::lcm::internal { -ProcessMonitor::ProcessMonitor(ComponentEventQueue& event_queue) : event_queue_(event_queue) {} +ProcessMonitor::ProcessMonitor(IComponentEventReceiver& event_queue) : event_queue_(event_queue) {} ProcessMonitor::~ProcessMonitor() = default; diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/process_monitor.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/process_monitor.hpp index 5902ad362..9677dedd0 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/process_monitor.hpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/process_monitor.hpp @@ -14,8 +14,8 @@ #ifndef PROCESS_MONITOR_HPP_INCLUDED #define PROCESS_MONITOR_HPP_INCLUDED -#include "score/mw/launch_manager/process_group_manager/details/component_event.hpp" #include "score/mw/launch_manager/process_group_manager/details/icomponent_controller.hpp" +#include "score/mw/launch_manager/process_group_manager/details/icomponent_event_receiver.hpp" namespace score::lcm::internal { @@ -26,7 +26,7 @@ namespace score::lcm::internal class ProcessMonitor final : public IComponentController { public: - explicit ProcessMonitor(ComponentEventQueue& event_queue); + explicit ProcessMonitor(IComponentEventReceiver& event_queue); ~ProcessMonitor() override; ProcessMonitor(const ProcessMonitor&) = delete; @@ -40,7 +40,7 @@ class ProcessMonitor final : public IComponentController private: void taskFinished(const Task& task, const score::cpp::expected_blank& error); - ComponentEventQueue& event_queue_; + IComponentEventReceiver& event_queue_; }; } // namespace score::lcm::internal From c795da988e2fba8bedf614d3e403858177ecf895 Mon Sep 17 00:00:00 2001 From: William Roebuck <244554584+WilliamRoebuck@users.noreply.github.com> Date: Thu, 23 Jul 2026 16:11:38 +0100 Subject: [PATCH 03/21] Add process monitor UT --- .../src/process_group_manager/details/BUILD | 9 ++ .../details/process_monitor_UT.cpp | 128 ++++++++++++++++++ 2 files changed, 137 insertions(+) create mode 100644 score/launch_manager/src/daemon/src/process_group_manager/details/process_monitor_UT.cpp diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/BUILD b/score/launch_manager/src/daemon/src/process_group_manager/details/BUILD index 9affa85a0..6ad0c7818 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/BUILD +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/BUILD @@ -207,6 +207,15 @@ cc_library( ], ) +cc_test( + name = "process_monitor_UT", + srcs = ["process_monitor_UT.cpp"], + deps = [ + ":process_monitor", + "@googletest//:gtest_main", + ], +) + cc_test( name = "oshandler_UT", srcs = ["oshandler_UT.cpp"], diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/process_monitor_UT.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/process_monitor_UT.cpp new file mode 100644 index 000000000..7ee48b046 --- /dev/null +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/process_monitor_UT.cpp @@ -0,0 +1,128 @@ +/******************************************************************************** + * 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/process_group_manager/details/process_monitor.hpp" +#include +#include + +using namespace testing; +using namespace score::lcm::internal; + +class MockComponent : public IComponent +{ + public: + MOCK_METHOD(RequestResult, activate, (score::cpp::stop_token stop_token), (override)); + MOCK_METHOD(RequestResult, deactivate, (score::cpp::stop_token stop_token), (override)); + MOCK_METHOD(RequestResult, tryHandleTermination, (int32_t status), (override)); + MOCK_METHOD(uint32_t, getIndex, (), (override, const)); + MOCK_METHOD(bool, active, (), (override, const)); +}; + +class MockComponentEventQueue : public IComponentEventReceiver +{ + public: + MOCK_METHOD(void, push, (ComponentEvent && event), (override)); +}; + +class ProcessMonitorTest : public ::testing::Test +{ + protected: + void SetUp() override + { + RecordProperty("TestType", "interface-test"); + RecordProperty("DerivationTechnique", "explorative-testing"); + + ON_CALL(mock_component, getIndex).WillByDefault(Return(1)); + } + + MockComponentEventQueue mock_queue{}; + ProcessMonitor process_monitor{mock_queue}; + MockComponent mock_component{}; + score::cpp::stop_source stop_source{}; +}; + +TEST_F(ProcessMonitorTest, DoWorkNormalActivation) +{ + RecordProperty("Description", "Verify that the process monitor activates components during activate tasks"); + // Given a valid activate task + Task task{TaskType::kActivate, mock_component, stop_source.get_token()}; + // Then + EXPECT_CALL(mock_component, activate).WillOnce(Return(IComponent::RequestState::kSuccess)); + EXPECT_CALL(mock_queue, push(VariantWith(Field(&ActivationSuccessful::node_index, 1)))) + .Times(1); + // When + process_monitor.doWork(task); +} + +TEST_F(ProcessMonitorTest, DoWorkNormalDeactivation) +{ + RecordProperty("Description", "Verify that the process monitor deactivates components during deactivate tasks"); + // Given a valid activate task + Task task{TaskType::kDeactivate, mock_component, stop_source.get_token()}; + // Then + EXPECT_CALL(mock_component, deactivate).WillOnce(Return(IComponent::RequestState::kSuccess)); + EXPECT_CALL(mock_queue, push(VariantWith(Field(&DeactivationComplete::node_index, 1)))) + .Times(1); + // When + process_monitor.doWork(task); +} + +TEST_F(ProcessMonitorTest, DoWorkOnTerminationDepProcess) +{ + RecordProperty( + "Description", "Verify that a process activated by its own termination recieves the correct instructions"); + // Given a task for starting a component that only reaches active when it terminates + Task task{TaskType::kActivate, mock_component, stop_source.get_token()}; + // Then + // The component is neither complete nor failed + EXPECT_CALL(mock_component, activate).WillOnce(Return(IComponent::RequestState::kWaiting)); + EXPECT_CALL(mock_component, tryHandleTermination).WillOnce(Return(IComponent::RequestState::kSuccess)); + EXPECT_CALL(mock_queue, push(VariantWith(Field(&ActivationSuccessful::node_index, 1)))) + .Times(1); + // When + process_monitor.doWork(task); + // The OS thread detects the termination: + process_monitor.terminated(mock_component, 0); +} + +TEST_F(ProcessMonitorTest, TerminatedUnexpectedly) +{ + RecordProperty( + "Description", + "Verify that the process monitor sends the correct event when a termination is not handled by the component"); + + // Given a terminated signal on a component that is not expected to terminate: + // Then + EXPECT_CALL(mock_component, tryHandleTermination) + .WillOnce(Return(score::cpp::make_unexpected(IComponent::ComponentError::kErrorAfterReady))); + EXPECT_CALL(mock_queue, push(VariantWith(Field(&UnexpectedTermination::node_index, 1)))) + .Times(1); + + process_monitor.terminated(mock_component, 0); +} + +TEST_F(ProcessMonitorTest, ActivationFailed) +{ + RecordProperty( + "Description", + "Verify that when a component activation fails, the correct error and data is pushed to the event queue"); + + // Given a task that will fail to activate + Task task{TaskType::kActivate, mock_component, stop_source.get_token()}; + auto errc = IComponent::ComponentError::kActivationTimedOut; + // Then + EXPECT_CALL(mock_component, activate).WillOnce(Return(score::cpp::make_unexpected(errc))); + EXPECT_CALL(mock_queue, push(VariantWith(Field(&ActivationFailed::reason, errc)))).Times(1); + // When + process_monitor.doWork(task); +} From 303e74bc5403fc27bf09d51971135c72d98ba2e6 Mon Sep 17 00:00:00 2001 From: William Roebuck <244554584+WilliamRoebuck@users.noreply.github.com> Date: Fri, 24 Jul 2026 15:56:46 +0100 Subject: [PATCH 04/21] More docs --- .clang-tidy | 2 +- .../src/process_group_manager/details/BUILD | 18 ++++++------- .../details/icomponent.hpp | 27 +++++++++++++++---- .../details/process_monitor.cpp | 4 ++- .../details/process_monitor.hpp | 4 ++- .../details/run_target.hpp | 8 ++++-- .../process_group_manager/details/task.hpp | 9 ++++--- 7 files changed, 50 insertions(+), 22 deletions(-) diff --git a/.clang-tidy b/.clang-tidy index 4028b675e..97548584d 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -1,2 +1,2 @@ Checks: ' -*, bugprone-*, cert-*, clang-analyzer-core.*, clang-analyzer-cplusplus.*, clang-analyzer-security.*, cppcoreguidelines-pro-bounds-pointer-arithmetic, cppcoreguidelines-pro-type-cstyle-cast, hicpp-* ' -WarningsAsErrors: 'bugprone-*,cert-*,clang-analyzer-* +WarningsAsErrors: 'bugprone-*,cert-*,clang-analyzer-*' diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/BUILD b/score/launch_manager/src/daemon/src/process_group_manager/details/BUILD index 6ad0c7818..d7fb00c04 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/BUILD +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/BUILD @@ -20,7 +20,7 @@ cc_library( visibility = ["//score:__subpackages__"], deps = [ ":icomponent", - ] + ], ) cc_library( @@ -30,8 +30,8 @@ cc_library( strip_include_prefix = "/score/launch_manager/src/daemon/src/process_group_manager/details", visibility = ["//score:__subpackages__"], deps = [ - ":task", ":icomponent", + ":task", ], ) @@ -42,8 +42,8 @@ cc_library( strip_include_prefix = "/score/launch_manager/src/daemon/src/process_group_manager/details", visibility = ["//score:__subpackages__"], deps = [ - "@score_baselibs//score/language/futurecpp" - ] + "@score_baselibs//score/language/futurecpp", + ], ) cc_library( @@ -55,7 +55,7 @@ cc_library( deps = [ ":icomponent", "//score/launch_manager/src/daemon/src/common:identifier_hash", - ] + ], ) cc_library( @@ -69,7 +69,7 @@ cc_library( "//score/launch_manager/src/daemon/src/common:constants", "//score/launch_manager/src/daemon/src/common:identifier_hash", "//score/launch_manager/src/daemon/src/common/concurrency:mpsc_bounded_queue", - ] + ], ) cc_library( @@ -198,12 +198,12 @@ cc_library( strip_include_prefix = "/score/launch_manager/src/daemon/src/process_group_manager/details", visibility = ["//score/launch_manager/src/daemon/src/process_group_manager:__pkg__"], deps = [ + ":graph", + ":icomponent_controller", ":icomponent_event_receiver", ":os_handler", ":process_info_node", ":safe_process_map", - ":graph", - ":icomponent_controller", ], ) @@ -262,9 +262,9 @@ cc_library( ":component_event_queue", ":graph", ":os_handler", - ":process_monitor", ":process_info_node", ":process_launcher", + ":process_monitor", ":safe_process_map", "//score/launch_manager/src/daemon/src/common:log", "//score/launch_manager/src/daemon/src/common/concurrency:mpmc_concurrent_queue", diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/icomponent.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/icomponent.hpp index 5d058160c..bd2d21675 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/icomponent.hpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/icomponent.hpp @@ -20,28 +20,45 @@ namespace score::lcm::internal { +/// @brief A class that implements IComponent is a node in the configuration that can be activated or deactivated. +/// For example, for a 'Native' process that is not self terminating, @c activate() means start the process +/// and @c deactivate() means terminate the process. +/// A run target is also a type of component, where activation and deactivation are symbolic. class IComponent { public: enum class ComponentError : uint8_t { - kErrorBeforeReady, - kErrorAfterReady, - kActivationTimedOut, + kErrorBeforeReady, // An error occurred during startup, before the component was considered ready. + kErrorAfterReady, // An error occurred any time after the component was considered ready. + kActivationTimedOut, // An error occurred during startup. Specifically, a timeout was reached. }; enum class RequestState : uint8_t { - kSuccess, - kWaiting + kSuccess, // Activation was successful and the component is now ready. + kWaiting // Activation is waiting on a notification from another thread. The component may not be ready. }; using RequestResult = score::cpp::expected; + /// @brief Begin activation of the component. + /// @p stop_token Token that can be stopped to exit the activation early. + /// @returns kSuccess if the component is now ready, kWaiting if the component is waiting for a notification, or an + /// error. virtual RequestResult activate(score::cpp::stop_token stop_token) = 0; + /// @brief Begin deactivation of the component. + /// @p stop_token Token that can be stopped to exit the deactivation early. + /// @returns kSuccess if the component is now ready, kWaiting if the component is waiting for a notification, or an + /// error. virtual RequestResult deactivate(score::cpp::stop_token stop_token) = 0; + /// @brief Notify the component that it has terminated with status @p status + /// @returns kSuccess if the component is now ready, kWaiting if the component is waiting for a notification, or an + /// error if the termination was not expected. virtual RequestResult tryHandleTermination(int32_t status) = 0; + /// @returns the index of the component in the graph. virtual uint32_t getIndex() const = 0; + /// @returns True if the component is active in the active run target. virtual bool active() const = 0; virtual ~IComponent() = default; diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/process_monitor.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/process_monitor.cpp index 71c23a6a7..e3da62a3b 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/process_monitor.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/process_monitor.cpp @@ -16,7 +16,9 @@ namespace score::lcm::internal { -ProcessMonitor::ProcessMonitor(IComponentEventReceiver& event_queue) : event_queue_(event_queue) {} +ProcessMonitor::ProcessMonitor(IComponentEventReceiver& event_queue) : event_queue_(event_queue) +{ +} ProcessMonitor::~ProcessMonitor() = default; diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/process_monitor.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/process_monitor.hpp index 9677dedd0..9fbabe68b 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/process_monitor.hpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/process_monitor.hpp @@ -22,7 +22,6 @@ namespace score::lcm::internal /// @brief Translates IComponentController callbacks (from worker threads and the OsHandler thread) /// into ComponentEvents pushed onto the event queue for processing on the main thread. -/// @note Assumes a single graph/process group; events carry no process-group identifier. class ProcessMonitor final : public IComponentController { public: @@ -34,7 +33,10 @@ class ProcessMonitor final : public IComponentController ProcessMonitor(ProcessMonitor&&) = delete; ProcessMonitor& operator=(ProcessMonitor&&) = delete; + /// @brief Start work on @p task and push the result to the event queue if the task completes void doWork(Task task) override; + /// @brief Notify @p component that it has terminated with status @p status. If this is an error or finishes a + /// component activation, report to the event queue void terminated(IComponent& component, int32_t status) override; private: diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/run_target.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/run_target.hpp index f51c425e3..efee6b927 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/run_target.hpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/run_target.hpp @@ -27,9 +27,13 @@ namespace score::lcm::internal class RunTarget final : public IComponent { public: - explicit RunTarget(uint32_t index) : index_(index) {} + explicit RunTarget(uint32_t index) : index_(index) + { + } - RunTarget(RunTarget&& other) noexcept : index_(other.index_), active_(other.active_.load()) {} + RunTarget(RunTarget&& other) noexcept : index_(other.index_), active_(other.active_.load()) + { + } RunTarget(const RunTarget&) = delete; RunTarget& operator=(const RunTarget&) = delete; RunTarget& operator=(RunTarget&&) = delete; diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/task.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/task.hpp index 1febf7b8b..863f5d13f 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/task.hpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/task.hpp @@ -19,20 +19,23 @@ #include #include - namespace score::lcm::internal { enum class TaskType : std::uint_least8_t { - kActivate = 0U, - kDeactivate = 1U, + kActivate = 0U, /// This task is to start activation of the component + kDeactivate = 1U, /// This task is to deactivate the component }; +/// @brief Work to perform on a component that can be sent to the job queue to be executed in parallel. struct Task { + /// @brief What kind of work to do TaskType type; + /// @brief The component to be acted upon std::reference_wrapper component; + /// @brief Token to exit and abandon the task early score::cpp::stop_token stop_token; }; From a797fccc5ff4dc9dc79f53fe5f55466931831be1 Mon Sep 17 00:00:00 2001 From: Daniel Thwaites Date: Mon, 27 Jul 2026 11:40:32 +0100 Subject: [PATCH 05/21] Use rvalue for parameter to `doWork` --- .../details/icomponent_controller.hpp | 2 +- .../details/process_monitor.cpp | 2 +- .../details/process_monitor.hpp | 2 +- .../details/process_monitor_UT.cpp | 12 ++++-------- 4 files changed, 7 insertions(+), 11 deletions(-) diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/icomponent_controller.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/icomponent_controller.hpp index 639ab9ee2..0bd854f9d 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/icomponent_controller.hpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/icomponent_controller.hpp @@ -23,7 +23,7 @@ namespace score::lcm::internal class IComponentController { public: - virtual void doWork(Task task) = 0; + virtual void doWork(Task&& task) = 0; virtual void terminated(IComponent& component, int32_t status) = 0; virtual ~IComponentController() = default; diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/process_monitor.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/process_monitor.cpp index e3da62a3b..935498ac6 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/process_monitor.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/process_monitor.cpp @@ -22,7 +22,7 @@ ProcessMonitor::ProcessMonitor(IComponentEventReceiver& event_queue) : event_que ProcessMonitor::~ProcessMonitor() = default; -void ProcessMonitor::doWork(Task task) +void ProcessMonitor::doWork(Task&& task) { if (task.stop_token.stop_requested()) { diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/process_monitor.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/process_monitor.hpp index 9fbabe68b..f09dda9a0 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/process_monitor.hpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/process_monitor.hpp @@ -34,7 +34,7 @@ class ProcessMonitor final : public IComponentController ProcessMonitor& operator=(ProcessMonitor&&) = delete; /// @brief Start work on @p task and push the result to the event queue if the task completes - void doWork(Task task) override; + void doWork(Task&& task) override; /// @brief Notify @p component that it has terminated with status @p status. If this is an error or finishes a /// component activation, report to the event queue void terminated(IComponent& component, int32_t status) override; diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/process_monitor_UT.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/process_monitor_UT.cpp index 7ee48b046..a29b3b522 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/process_monitor_UT.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/process_monitor_UT.cpp @@ -55,26 +55,24 @@ TEST_F(ProcessMonitorTest, DoWorkNormalActivation) { RecordProperty("Description", "Verify that the process monitor activates components during activate tasks"); // Given a valid activate task - Task task{TaskType::kActivate, mock_component, stop_source.get_token()}; // Then EXPECT_CALL(mock_component, activate).WillOnce(Return(IComponent::RequestState::kSuccess)); EXPECT_CALL(mock_queue, push(VariantWith(Field(&ActivationSuccessful::node_index, 1)))) .Times(1); // When - process_monitor.doWork(task); + process_monitor.doWork(Task{TaskType::kActivate, mock_component, stop_source.get_token()}); } TEST_F(ProcessMonitorTest, DoWorkNormalDeactivation) { RecordProperty("Description", "Verify that the process monitor deactivates components during deactivate tasks"); // Given a valid activate task - Task task{TaskType::kDeactivate, mock_component, stop_source.get_token()}; // Then EXPECT_CALL(mock_component, deactivate).WillOnce(Return(IComponent::RequestState::kSuccess)); EXPECT_CALL(mock_queue, push(VariantWith(Field(&DeactivationComplete::node_index, 1)))) .Times(1); // When - process_monitor.doWork(task); + process_monitor.doWork(Task{TaskType::kDeactivate, mock_component, stop_source.get_token()}); } TEST_F(ProcessMonitorTest, DoWorkOnTerminationDepProcess) @@ -82,7 +80,6 @@ TEST_F(ProcessMonitorTest, DoWorkOnTerminationDepProcess) RecordProperty( "Description", "Verify that a process activated by its own termination recieves the correct instructions"); // Given a task for starting a component that only reaches active when it terminates - Task task{TaskType::kActivate, mock_component, stop_source.get_token()}; // Then // The component is neither complete nor failed EXPECT_CALL(mock_component, activate).WillOnce(Return(IComponent::RequestState::kWaiting)); @@ -90,7 +87,7 @@ TEST_F(ProcessMonitorTest, DoWorkOnTerminationDepProcess) EXPECT_CALL(mock_queue, push(VariantWith(Field(&ActivationSuccessful::node_index, 1)))) .Times(1); // When - process_monitor.doWork(task); + process_monitor.doWork(Task{TaskType::kActivate, mock_component, stop_source.get_token()}); // The OS thread detects the termination: process_monitor.terminated(mock_component, 0); } @@ -118,11 +115,10 @@ TEST_F(ProcessMonitorTest, ActivationFailed) "Verify that when a component activation fails, the correct error and data is pushed to the event queue"); // Given a task that will fail to activate - Task task{TaskType::kActivate, mock_component, stop_source.get_token()}; auto errc = IComponent::ComponentError::kActivationTimedOut; // Then EXPECT_CALL(mock_component, activate).WillOnce(Return(score::cpp::make_unexpected(errc))); EXPECT_CALL(mock_queue, push(VariantWith(Field(&ActivationFailed::reason, errc)))).Times(1); // When - process_monitor.doWork(task); + process_monitor.doWork(Task{TaskType::kActivate, mock_component, stop_source.get_token()}); } From 3b7a6e97074bd7c06073b202bbc21a52d15c5144 Mon Sep 17 00:00:00 2001 From: Daniel Thwaites Date: Mon, 27 Jul 2026 11:54:25 +0100 Subject: [PATCH 06/21] Reduce `waitForEvents` timeout to zero in tests --- .../details/component_event_queue_UT.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/component_event_queue_UT.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/component_event_queue_UT.cpp index 0f74bf8df..dcd32c4a0 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/component_event_queue_UT.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/component_event_queue_UT.cpp @@ -28,14 +28,14 @@ class ComponentEventQueueTest : public ::testing::Test TEST_F(ComponentEventQueueTest, WaitForEventsReturnsFalseOnEmptyQueue) { RecordProperty("Description", "Verify waitForEvents returns false promptly when no event has been pushed."); - EXPECT_FALSE(queue_.waitForEvents(std::chrono::milliseconds{1})); + EXPECT_FALSE(queue_.waitForEvents(std::chrono::milliseconds{0})); } TEST_F(ComponentEventQueueTest, WaitForEventsReturnsTrueAfterPush) { RecordProperty("Description", "Verify waitForEvents returns true once an event has been pushed."); queue_.push(ActivationSuccessful{7U}); - EXPECT_TRUE(queue_.waitForEvents(std::chrono::milliseconds{1})); + EXPECT_TRUE(queue_.waitForEvents(std::chrono::milliseconds{0})); } TEST_F(ComponentEventQueueTest, GetNextEventReturnsNulloptWhenEmpty) @@ -81,7 +81,7 @@ TEST_F(ComponentEventQueueTest, GetNextEventDrainsMultipleEventsInFifoOrder) queue_.push(DeactivationComplete{2U}); queue_.push(UnexpectedTermination{3U}); - ASSERT_TRUE(queue_.waitForEvents(std::chrono::milliseconds{20})); + ASSERT_TRUE(queue_.waitForEvents(std::chrono::milliseconds{0})); auto first = queue_.getNextEvent(); ASSERT_TRUE(first.has_value()); From cc18bc72edf18a94fca445340118c85401d91b61 Mon Sep 17 00:00:00 2001 From: Daniel Thwaites Date: Mon, 27 Jul 2026 11:59:06 +0100 Subject: [PATCH 07/21] Rename `Task` to `ComponentTask` --- .../details/icomponent_controller.hpp | 2 +- .../details/process_monitor.cpp | 12 +++++++----- .../details/process_monitor.hpp | 4 ++-- .../details/process_monitor_UT.cpp | 8 ++++---- .../src/process_group_manager/details/task.hpp | 6 +++--- 5 files changed, 17 insertions(+), 15 deletions(-) diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/icomponent_controller.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/icomponent_controller.hpp index 0bd854f9d..cfabfb782 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/icomponent_controller.hpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/icomponent_controller.hpp @@ -23,7 +23,7 @@ namespace score::lcm::internal class IComponentController { public: - virtual void doWork(Task&& task) = 0; + virtual void doWork(ComponentTask&& task) = 0; virtual void terminated(IComponent& component, int32_t status) = 0; virtual ~IComponentController() = default; diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/process_monitor.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/process_monitor.cpp index 935498ac6..2b16c8255 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/process_monitor.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/process_monitor.cpp @@ -22,7 +22,7 @@ ProcessMonitor::ProcessMonitor(IComponentEventReceiver& event_queue) : event_que ProcessMonitor::~ProcessMonitor() = default; -void ProcessMonitor::doWork(Task&& task) +void ProcessMonitor::doWork(ComponentTask&& task) { if (task.stop_token.stop_requested()) { @@ -35,10 +35,10 @@ void ProcessMonitor::doWork(Task&& task) switch (task.type) { - case TaskType::kActivate: + case ComponentTaskType::kActivate: res = component.activate(task.stop_token); break; - case TaskType::kDeactivate: + case ComponentTaskType::kDeactivate: res = component.deactivate(task.stop_token); break; default: @@ -73,11 +73,13 @@ void ProcessMonitor::terminated(IComponent& component, int32_t status) } } -void ProcessMonitor::taskFinished(const Task& task, const score::cpp::expected_blank& error) +void ProcessMonitor::taskFinished( + const ComponentTask& task, + const score::cpp::expected_blank& error) { const uint32_t node_index = task.component.get().getIndex(); - if (task.type == TaskType::kDeactivate) + if (task.type == ComponentTaskType::kDeactivate) { event_queue_.push(DeactivationComplete{node_index}); } diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/process_monitor.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/process_monitor.hpp index f09dda9a0..cd7858fdc 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/process_monitor.hpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/process_monitor.hpp @@ -34,13 +34,13 @@ class ProcessMonitor final : public IComponentController ProcessMonitor& operator=(ProcessMonitor&&) = delete; /// @brief Start work on @p task and push the result to the event queue if the task completes - void doWork(Task&& task) override; + void doWork(ComponentTask&& task) override; /// @brief Notify @p component that it has terminated with status @p status. If this is an error or finishes a /// component activation, report to the event queue void terminated(IComponent& component, int32_t status) override; private: - void taskFinished(const Task& task, const score::cpp::expected_blank& error); + void taskFinished(const ComponentTask& task, const score::cpp::expected_blank& error); IComponentEventReceiver& event_queue_; }; diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/process_monitor_UT.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/process_monitor_UT.cpp index a29b3b522..f17bed163 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/process_monitor_UT.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/process_monitor_UT.cpp @@ -60,7 +60,7 @@ TEST_F(ProcessMonitorTest, DoWorkNormalActivation) EXPECT_CALL(mock_queue, push(VariantWith(Field(&ActivationSuccessful::node_index, 1)))) .Times(1); // When - process_monitor.doWork(Task{TaskType::kActivate, mock_component, stop_source.get_token()}); + process_monitor.doWork(ComponentTask{ComponentTaskType::kActivate, mock_component, stop_source.get_token()}); } TEST_F(ProcessMonitorTest, DoWorkNormalDeactivation) @@ -72,7 +72,7 @@ TEST_F(ProcessMonitorTest, DoWorkNormalDeactivation) EXPECT_CALL(mock_queue, push(VariantWith(Field(&DeactivationComplete::node_index, 1)))) .Times(1); // When - process_monitor.doWork(Task{TaskType::kDeactivate, mock_component, stop_source.get_token()}); + process_monitor.doWork(ComponentTask{ComponentTaskType::kDeactivate, mock_component, stop_source.get_token()}); } TEST_F(ProcessMonitorTest, DoWorkOnTerminationDepProcess) @@ -87,7 +87,7 @@ TEST_F(ProcessMonitorTest, DoWorkOnTerminationDepProcess) EXPECT_CALL(mock_queue, push(VariantWith(Field(&ActivationSuccessful::node_index, 1)))) .Times(1); // When - process_monitor.doWork(Task{TaskType::kActivate, mock_component, stop_source.get_token()}); + process_monitor.doWork(ComponentTask{ComponentTaskType::kActivate, mock_component, stop_source.get_token()}); // The OS thread detects the termination: process_monitor.terminated(mock_component, 0); } @@ -120,5 +120,5 @@ TEST_F(ProcessMonitorTest, ActivationFailed) EXPECT_CALL(mock_component, activate).WillOnce(Return(score::cpp::make_unexpected(errc))); EXPECT_CALL(mock_queue, push(VariantWith(Field(&ActivationFailed::reason, errc)))).Times(1); // When - process_monitor.doWork(Task{TaskType::kActivate, mock_component, stop_source.get_token()}); + process_monitor.doWork(ComponentTask{ComponentTaskType::kActivate, mock_component, stop_source.get_token()}); } diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/task.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/task.hpp index 863f5d13f..721bf6ae3 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/task.hpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/task.hpp @@ -22,17 +22,17 @@ namespace score::lcm::internal { -enum class TaskType : std::uint_least8_t +enum class ComponentTaskType : std::uint_least8_t { kActivate = 0U, /// This task is to start activation of the component kDeactivate = 1U, /// This task is to deactivate the component }; /// @brief Work to perform on a component that can be sent to the job queue to be executed in parallel. -struct Task +struct ComponentTask { /// @brief What kind of work to do - TaskType type; + ComponentTaskType type; /// @brief The component to be acted upon std::reference_wrapper component; /// @brief Token to exit and abandon the task early From cdf38338c604e0c0064ce070375ce1e891722db2 Mon Sep 17 00:00:00 2001 From: Daniel Thwaites Date: Mon, 27 Jul 2026 13:38:32 +0100 Subject: [PATCH 08/21] Refactor `doWork` method This resolves several review comments: - `taskFinished` is no longer present in the header - Confusing nesting is improved by having a clear switch statement for each purpose - By removing the default case we get a compiler warning if a possible value is not handled --- .../details/process_monitor.cpp | 98 +++++++++++-------- .../details/process_monitor.hpp | 2 - 2 files changed, 57 insertions(+), 43 deletions(-) diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/process_monitor.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/process_monitor.cpp index 2b16c8255..cac4083fc 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/process_monitor.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/process_monitor.cpp @@ -24,39 +24,75 @@ ProcessMonitor::~ProcessMonitor() = default; void ProcessMonitor::doWork(ComponentTask&& task) { - if (task.stop_token.stop_requested()) - { - return; - } + auto run_task = [&]() { + auto& component = task.component.get(); + IComponent::RequestResult result; - auto& component = task.component.get(); + switch (task.type) + { + case ComponentTaskType::kActivate: + result = component.activate(task.stop_token); + break; + case ComponentTaskType::kDeactivate: + result = component.deactivate(task.stop_token); + break; + } - IComponent::RequestResult res; + return result; + }; - switch (task.type) - { - case ComponentTaskType::kActivate: - res = component.activate(task.stop_token); - break; - case ComponentTaskType::kDeactivate: - res = component.deactivate(task.stop_token); - break; - default: - break; - } + auto handle_success = [&]() { + const uint32_t node_index = task.component.get().getIndex(); - if (res.has_value() && res.value() == IComponent::RequestState::kWaiting) + switch (task.type) + { + case ComponentTaskType::kActivate: + event_queue_.push(ActivationSuccessful{node_index}); + break; + case ComponentTaskType::kDeactivate: + event_queue_.push(DeactivationComplete{node_index}); + break; + } + }; + + auto handle_failure = [&](IComponent::ComponentError& error) { + const uint32_t node_index = task.component.get().getIndex(); + + switch (task.type) + { + case ComponentTaskType::kActivate: + event_queue_.push(ActivationFailed{node_index, error}); + break; + case ComponentTaskType::kDeactivate: + break; + } + }; + + auto handle_state = [&](IComponent::RequestState& state) { + switch (state) + { + case IComponent::RequestState::kSuccess: + handle_success(); + break; + case IComponent::RequestState::kWaiting: + break; + } + }; + + if (task.stop_token.stop_requested()) { return; } - if (res.has_value()) + auto result = run_task(); + + if (result.has_value()) { - taskFinished(task, {}); + handle_state(result.value()); } else { - taskFinished(task, score::cpp::make_unexpected(res.error())); + handle_failure(result.error()); } } @@ -73,24 +109,4 @@ void ProcessMonitor::terminated(IComponent& component, int32_t status) } } -void ProcessMonitor::taskFinished( - const ComponentTask& task, - const score::cpp::expected_blank& error) -{ - const uint32_t node_index = task.component.get().getIndex(); - - if (task.type == ComponentTaskType::kDeactivate) - { - event_queue_.push(DeactivationComplete{node_index}); - } - else if (error.has_value()) - { - event_queue_.push(ActivationSuccessful{node_index}); - } - else - { - event_queue_.push(ActivationFailed{node_index, error.error()}); - } -} - } // namespace score::lcm::internal diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/process_monitor.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/process_monitor.hpp index cd7858fdc..433ba0721 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/process_monitor.hpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/process_monitor.hpp @@ -40,8 +40,6 @@ class ProcessMonitor final : public IComponentController void terminated(IComponent& component, int32_t status) override; private: - void taskFinished(const ComponentTask& task, const score::cpp::expected_blank& error); - IComponentEventReceiver& event_queue_; }; From bcbda43c52c2b6c0ce1eae706152f72d1ce062ec Mon Sep 17 00:00:00 2001 From: William Roebuck <244554584+WilliamRoebuck@users.noreply.github.com> Date: Tue, 28 Jul 2026 09:55:25 +0100 Subject: [PATCH 09/21] Update namespaces, use new queue format --- .../src/daemon/src/common/constants.hpp | 3 --- .../details/component_event.hpp | 6 ++++-- .../details/component_event_queue.hpp | 19 ++++++++++++++----- .../details/component_event_queue_UT.cpp | 10 ++++++---- .../details/icomponent.hpp | 4 ++-- .../details/icomponent_controller.hpp | 4 ++-- .../details/icomponent_event_receiver.hpp | 4 ++-- .../details/process_monitor.cpp | 4 ++-- .../details/process_monitor.hpp | 4 ++-- .../details/process_monitor_UT.cpp | 2 +- .../details/run_target.hpp | 4 ++-- .../process_group_manager/details/task.hpp | 4 ++-- 12 files changed, 39 insertions(+), 29 deletions(-) diff --git a/score/launch_manager/src/daemon/src/common/constants.hpp b/score/launch_manager/src/daemon/src/common/constants.hpp index 77f5b57a6..b79f36166 100644 --- a/score/launch_manager/src/daemon/src/common/constants.hpp +++ b/score/launch_manager/src/daemon/src/common/constants.hpp @@ -72,9 +72,6 @@ enum class ProcessLimits : std::uint32_t maxLocalBuffSize = 32U ///< Maximum size for local buffer }; -/// @brief Capacity of the ComponentEventQueue. -constexpr std::size_t kComponentEventQueueSize = 1024U; - } // namespace internal } // namespace lcm diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/component_event.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/component_event.hpp index 80387ede9..c0244d15c 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/component_event.hpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/component_event.hpp @@ -20,9 +20,11 @@ #include "score/mw/launch_manager/common/identifier_hash.hpp" #include "score/mw/launch_manager/process_group_manager/details/icomponent.hpp" -namespace score::lcm::internal +namespace score::mw::lifecycle::internal { +using namespace score::lcm; + /// @brief A node finished activating successfully. struct ActivationSuccessful { @@ -59,6 +61,6 @@ struct SupervisionFailure using ComponentEvent = std:: variant; -} // namespace score::lcm::internal +} // namespace score::mw::lifecycle::internal #endif // SCORE_LCM_COMPONENT_EVENT_HPP_INCLUDED diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/component_event_queue.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/component_event_queue.hpp index d6a7046b9..f87514e17 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/component_event_queue.hpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/component_event_queue.hpp @@ -24,7 +24,7 @@ #include "score/mw/launch_manager/process_group_manager/details/component_event.hpp" #include "score/mw/launch_manager/process_group_manager/details/icomponent_event_receiver.hpp" -namespace score::lcm::internal +namespace score::mw::lifecycle::internal { /// @brief Queue of ComponentEvents produced by worker/OS-handler threads and consumed @@ -32,7 +32,10 @@ namespace score::lcm::internal class ComponentEventQueue final : public IComponentEventReceiver { public: - ComponentEventQueue() = default; + explicit ComponentEventQueue(std::size_t components) : queue_(components * 3U), capacity_(components * 3U) + { + } + ~ComponentEventQueue() { stop(); @@ -48,7 +51,7 @@ class ComponentEventQueue final : public IComponentEventReceiver void push(ComponentEvent&& event) override { auto result = queue_.push(std::move(event)); - if (!result.has_value() && result.error() == ConcurrencyErrc::kOverflow) + if (!result.has_value() && result.error() == lcm::internal::ConcurrencyErrc::kOverflow) { overflow_.store(true, std::memory_order_release); } @@ -83,11 +86,17 @@ class ComponentEventQueue final : public IComponentEventReceiver queue_.stop(); } + std::size_t capacity() + { + return capacity_; + } + private: - MpscBoundedQueue queue_; + lcm::internal::MpscBoundedQueue queue_; + std::size_t capacity_; std::atomic overflow_{false}; }; -} // namespace score::lcm::internal +} // namespace score::mw::lifecycle::internal #endif // SCORE_LCM_COMPONENT_EVENT_QUEUE_HPP_INCLUDED diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/component_event_queue_UT.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/component_event_queue_UT.cpp index dcd32c4a0..7a697d5fe 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/component_event_queue_UT.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/component_event_queue_UT.cpp @@ -16,13 +16,15 @@ #include #include -namespace score::lcm::internal +namespace score::mw::lifecycle::internal { +using namespace score::lcm; + class ComponentEventQueueTest : public ::testing::Test { protected: - ComponentEventQueue queue_; + ComponentEventQueue queue_{10}; }; TEST_F(ComponentEventQueueTest, WaitForEventsReturnsFalseOnEmptyQueue) @@ -114,7 +116,7 @@ TEST_F(ComponentEventQueueTest, GetOverflowBecomesTrueOnceQueueIsFull) RecordProperty("Description", "Verify getOverflow() becomes true once a push is dropped because the queue is full, " "mirroring how ProcessGroupManager::run() detects lost events."); - for (std::size_t i = 0U; i < kComponentEventQueueSize; ++i) + for (std::size_t i = 0U; i < queue_.capacity(); ++i) { queue_.push(ActivationSuccessful{static_cast(i)}); } @@ -149,4 +151,4 @@ TEST_F(ComponentEventQueueTest, GetNextEventStillDrainsQueuedEventsAfterStop) EXPECT_FALSE(queue_.getNextEvent().has_value()); } -} // namespace score::lcm::internal +} // namespace score::mw::lifecycle::internal diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/icomponent.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/icomponent.hpp index bd2d21675..c2b569074 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/icomponent.hpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/icomponent.hpp @@ -17,7 +17,7 @@ #include #include -namespace score::lcm::internal +namespace score::mw::lifecycle::internal { /// @brief A class that implements IComponent is a node in the configuration that can be activated or deactivated. @@ -64,6 +64,6 @@ class IComponent virtual ~IComponent() = default; }; -} // namespace score::lcm::internal +} // namespace score::mw::lifecycle::internal #endif // SCORE_LCM_ICOMPONENT_HPP_INCLUDED diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/icomponent_controller.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/icomponent_controller.hpp index cfabfb782..fdcd98115 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/icomponent_controller.hpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/icomponent_controller.hpp @@ -17,7 +17,7 @@ #include "score/mw/launch_manager/process_group_manager/details/icomponent.hpp" #include "score/mw/launch_manager/process_group_manager/details/task.hpp" -namespace score::lcm::internal +namespace score::mw::lifecycle::internal { class IComponentController @@ -29,6 +29,6 @@ class IComponentController virtual ~IComponentController() = default; }; -} // namespace score::lcm::internal +} // namespace score::mw::lifecycle::internal #endif // SCORE_LCM_ICOMPONENT_CONTROLLER_HPP_INCLUDED diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/icomponent_event_receiver.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/icomponent_event_receiver.hpp index ce678766c..308ec9e9a 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/icomponent_event_receiver.hpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/icomponent_event_receiver.hpp @@ -16,7 +16,7 @@ #include "score/mw/launch_manager/process_group_manager/details/component_event.hpp" -namespace score::lcm::internal +namespace score::mw::lifecycle::internal { class IComponentEventReceiver @@ -26,6 +26,6 @@ class IComponentEventReceiver virtual ~IComponentEventReceiver() = default; }; -} // namespace score::lcm::internal +} // namespace score::mw::lifecycle::internal #endif // SCORE_LCM_ICOMPONENT_EVENT_RECEIVER_HPP_INCLUDED diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/process_monitor.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/process_monitor.cpp index cac4083fc..62254ada4 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/process_monitor.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/process_monitor.cpp @@ -13,7 +13,7 @@ #include "score/mw/launch_manager/process_group_manager/details/process_monitor.hpp" -namespace score::lcm::internal +namespace score::mw::lifecycle::internal { ProcessMonitor::ProcessMonitor(IComponentEventReceiver& event_queue) : event_queue_(event_queue) @@ -109,4 +109,4 @@ void ProcessMonitor::terminated(IComponent& component, int32_t status) } } -} // namespace score::lcm::internal +} // namespace score::mw::lifecycle::internal diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/process_monitor.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/process_monitor.hpp index 433ba0721..99081f71f 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/process_monitor.hpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/process_monitor.hpp @@ -17,7 +17,7 @@ #include "score/mw/launch_manager/process_group_manager/details/icomponent_controller.hpp" #include "score/mw/launch_manager/process_group_manager/details/icomponent_event_receiver.hpp" -namespace score::lcm::internal +namespace score::mw::lifecycle::internal { /// @brief Translates IComponentController callbacks (from worker threads and the OsHandler thread) @@ -43,6 +43,6 @@ class ProcessMonitor final : public IComponentController IComponentEventReceiver& event_queue_; }; -} // namespace score::lcm::internal +} // namespace score::mw::lifecycle::internal #endif // PROCESS_MONITOR_HPP_INCLUDED diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/process_monitor_UT.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/process_monitor_UT.cpp index f17bed163..44557cffa 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/process_monitor_UT.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/process_monitor_UT.cpp @@ -16,7 +16,7 @@ #include using namespace testing; -using namespace score::lcm::internal; +using namespace score::mw::lifecycle::internal; class MockComponent : public IComponent { diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/run_target.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/run_target.hpp index efee6b927..a20dee27b 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/run_target.hpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/run_target.hpp @@ -18,7 +18,7 @@ #include -namespace score::lcm::internal +namespace score::mw::lifecycle::internal { /// @brief A virtual node in the dependency graph corresponding to a ProcessGroupState. @@ -71,6 +71,6 @@ class RunTarget final : public IComponent std::atomic active_{false}; }; -} // namespace score::lcm::internal +} // namespace score::mw::lifecycle::internal #endif // SCORE_LCM_RUN_TARGET_HPP_INCLUDED diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/task.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/task.hpp index 721bf6ae3..0a5319cfe 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/task.hpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/task.hpp @@ -19,7 +19,7 @@ #include #include -namespace score::lcm::internal +namespace score::mw::lifecycle::internal { enum class ComponentTaskType : std::uint_least8_t @@ -39,6 +39,6 @@ struct ComponentTask score::cpp::stop_token stop_token; }; -} // namespace score::lcm::internal +} // namespace score::mw::lifecycle::internal #endif // SCORE_LCM_TASK_HPP_INCLUDED From acaf3c9b9644dfb1d516bf21fa62921ff2239dea Mon Sep 17 00:00:00 2001 From: William Roebuck <244554584+WilliamRoebuck@users.noreply.github.com> Date: Tue, 28 Jul 2026 11:47:18 +0100 Subject: [PATCH 10/21] Changes after review --- .clang-tidy | 5 +- .../details/component_event_queue.hpp | 7 +- .../details/component_event_queue_UT.cpp | 65 +++++++------------ .../details/icomponent.hpp | 9 ++- .../details/icomponent_controller.hpp | 5 ++ .../details/icomponent_event_receiver.hpp | 9 ++- .../details/process_monitor.cpp | 2 +- .../details/process_monitor.hpp | 5 +- .../details/process_monitor_UT.cpp | 4 +- .../details/run_target.hpp | 6 +- 10 files changed, 58 insertions(+), 59 deletions(-) diff --git a/.clang-tidy b/.clang-tidy index 97548584d..f1a127ae6 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -1,2 +1,5 @@ Checks: ' -*, bugprone-*, cert-*, clang-analyzer-core.*, clang-analyzer-cplusplus.*, clang-analyzer-security.*, cppcoreguidelines-pro-bounds-pointer-arithmetic, cppcoreguidelines-pro-type-cstyle-cast, hicpp-* ' -WarningsAsErrors: 'bugprone-*,cert-*,clang-analyzer-*' +WarningsAsErrors: 'bugprone-*,cert-*,clang-analyzer-*,hicpp-*' +CheckOptions: +- key: 'hicpp-special-member-functions.AllowSoleDefaultDtor' +- value: 1 \ No newline at end of file diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/component_event_queue.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/component_event_queue.hpp index f87514e17..20c0b9562 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/component_event_queue.hpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/component_event_queue.hpp @@ -29,7 +29,7 @@ namespace score::mw::lifecycle::internal /// @brief Queue of ComponentEvents produced by worker/OS-handler threads and consumed /// exclusively by the main thread, backed by a fixed-capacity MpscBoundedQueue. -class ComponentEventQueue final : public IComponentEventReceiver +class ComponentEventQueue final : public IComponentEventPublisher { public: explicit ComponentEventQueue(std::size_t components) : queue_(components * 3U), capacity_(components * 3U) @@ -48,13 +48,16 @@ class ComponentEventQueue final : public IComponentEventReceiver /// @brief Enqueues an event. If the queue is full, the event is dropped immediately and /// getOverflow() will subsequently return true. - void push(ComponentEvent&& event) override + /// @returns False if the event is dropped. + bool push(ComponentEvent&& event) override { auto result = queue_.push(std::move(event)); if (!result.has_value() && result.error() == lcm::internal::ConcurrencyErrc::kOverflow) { overflow_.store(true, std::memory_order_release); + return false; } + return true; } /// @brief Waits up to timeout for at least one event to become available. diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/component_event_queue_UT.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/component_event_queue_UT.cpp index 7a697d5fe..8961d89b5 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/component_event_queue_UT.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/component_event_queue_UT.cpp @@ -24,6 +24,12 @@ using namespace score::lcm; class ComponentEventQueueTest : public ::testing::Test { protected: + void SetUp() override + { + RecordProperty("TestType", "interface-test"); + RecordProperty("DerivationTechnique", "explorative-testing"); + } + ComponentEventQueue queue_{10}; }; @@ -42,7 +48,7 @@ TEST_F(ComponentEventQueueTest, WaitForEventsReturnsTrueAfterPush) TEST_F(ComponentEventQueueTest, GetNextEventReturnsNulloptWhenEmpty) { - RecordProperty("Description", "Verify getNextEvent never blocks and returns nullopt when nothing is queued."); + RecordProperty("Description", "Verify getNextEvent returns nullopt when nothing is queued."); EXPECT_FALSE(queue_.getNextEvent().has_value()); } @@ -61,9 +67,10 @@ TEST_F(ComponentEventQueueTest, GetNextEventReturnsPushedEventWithPayloadIntact) TEST_F(ComponentEventQueueTest, GetNextEventReturnsSupervisionFailureWithPayloadIntact) { - RecordProperty("Description", - "Verify a pushed SupervisionFailure event is returned by getNextEvent with process identifier " - "payload preserved."); + RecordProperty( + "Description", + "Verify a pushed SupervisionFailure event is returned by getNextEvent with process identifier " + "payload preserved."); const IdentifierHash process_identifier{"proc_for_supervision_failure"}; queue_.push(SupervisionFailure{process_identifier}); @@ -74,35 +81,6 @@ TEST_F(ComponentEventQueueTest, GetNextEventReturnsSupervisionFailureWithPayload EXPECT_EQ(failure.process_identifier, process_identifier); } -TEST_F(ComponentEventQueueTest, GetNextEventDrainsMultipleEventsInFifoOrder) -{ - RecordProperty("Description", - "Verify the intended drain pattern -- waitForEvents() once, then getNextEvent() in a loop " - "until nullopt -- returns every queued event exactly once in FIFO order."); - queue_.push(ActivationSuccessful{1U}); - queue_.push(DeactivationComplete{2U}); - queue_.push(UnexpectedTermination{3U}); - - ASSERT_TRUE(queue_.waitForEvents(std::chrono::milliseconds{0})); - - auto first = queue_.getNextEvent(); - ASSERT_TRUE(first.has_value()); - ASSERT_TRUE(std::holds_alternative(*first)); - EXPECT_EQ(std::get(*first).node_index, 1U); - - auto second = queue_.getNextEvent(); - ASSERT_TRUE(second.has_value()); - ASSERT_TRUE(std::holds_alternative(*second)); - EXPECT_EQ(std::get(*second).node_index, 2U); - - auto third = queue_.getNextEvent(); - ASSERT_TRUE(third.has_value()); - ASSERT_TRUE(std::holds_alternative(*third)); - EXPECT_EQ(std::get(*third).node_index, 3U); - - EXPECT_FALSE(queue_.getNextEvent().has_value()); -} - TEST_F(ComponentEventQueueTest, GetOverflowStaysFalseUnderNormalUsage) { RecordProperty("Description", "Verify getOverflow() stays false when events are pushed and drained normally."); @@ -113,9 +91,10 @@ TEST_F(ComponentEventQueueTest, GetOverflowStaysFalseUnderNormalUsage) TEST_F(ComponentEventQueueTest, GetOverflowBecomesTrueOnceQueueIsFull) { - RecordProperty("Description", - "Verify getOverflow() becomes true once a push is dropped because the queue is full, " - "mirroring how ProcessGroupManager::run() detects lost events."); + RecordProperty( + "Description", + "Verify getOverflow() becomes true once a push is dropped because the queue is full, " + "mirroring how ProcessGroupManager::run() detects lost events."); for (std::size_t i = 0U; i < queue_.capacity(); ++i) { queue_.push(ActivationSuccessful{static_cast(i)}); @@ -130,18 +109,20 @@ TEST_F(ComponentEventQueueTest, GetOverflowBecomesTrueOnceQueueIsFull) TEST_F(ComponentEventQueueTest, StopUnblocksWaitForEventsOnEmptyQueue) { - RecordProperty("Description", - "Verify stop() causes a subsequently-called waitForEvents() to return false immediately " - "rather than blocking, matching the shutdown usage in ProcessGroupManager::deinitialize()."); + RecordProperty( + "Description", + "Verify stop() causes a subsequently-called waitForEvents() to return false immediately " + "rather than blocking, matching the shutdown usage in ProcessGroupManager::deinitialize()."); queue_.stop(); EXPECT_FALSE(queue_.waitForEvents(std::chrono::milliseconds{2000})); } TEST_F(ComponentEventQueueTest, GetNextEventStillDrainsQueuedEventsAfterStop) { - RecordProperty("Description", - "Verify that events pushed before stop() was called are not silently discarded -- " - "getNextEvent() must still be able to drain them during shutdown."); + RecordProperty( + "Description", + "Verify that events pushed before stop() was called are not silently discarded -- " + "getNextEvent() must still be able to drain them during shutdown."); queue_.push(ActivationSuccessful{1U}); queue_.stop(); diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/icomponent.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/icomponent.hpp index c2b569074..7bca1862d 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/icomponent.hpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/icomponent.hpp @@ -21,9 +21,12 @@ namespace score::mw::lifecycle::internal { /// @brief A class that implements IComponent is a node in the configuration that can be activated or deactivated. -/// For example, for a 'Native' process that is not self terminating, @c activate() means start the process -/// and @c deactivate() means terminate the process. -/// A run target is also a type of component, where activation and deactivation are symbolic. +/// +/// @details Each component type defines the implementation of how it shall reach the active and inactive states. +/// When a component becomes active, components that depend on it may be activated. When a component becomes +/// inactive, components it depends on may be deactivated. +/// For example, for a process implementation of IComponent, @c activate() could involve starting the process +/// and @c deactivate() could terminate the process. class IComponent { public: diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/icomponent_controller.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/icomponent_controller.hpp index fdcd98115..ca4541b24 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/icomponent_controller.hpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/icomponent_controller.hpp @@ -20,10 +20,15 @@ namespace score::mw::lifecycle::internal { +/// @brief Interface to send requests & notifications about components. The class should handle these notifications by +/// informing the component, the graph, or event queue. class IComponentController { public: + /// @brief Start work on @p task and push the result to the event queue if the task completes virtual void doWork(ComponentTask&& task) = 0; + /// @brief Notify @p component that it has terminated with status @p status. Forward the appropriate event to the + /// event queue virtual void terminated(IComponent& component, int32_t status) = 0; virtual ~IComponentController() = default; diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/icomponent_event_receiver.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/icomponent_event_receiver.hpp index 308ec9e9a..7526189f2 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/icomponent_event_receiver.hpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/icomponent_event_receiver.hpp @@ -19,11 +19,14 @@ namespace score::mw::lifecycle::internal { -class IComponentEventReceiver +/// @brief Interface an event producer can push component events to +class IComponentEventPublisher { public: - virtual void push(ComponentEvent&& event) = 0; - virtual ~IComponentEventReceiver() = default; + /// @brief Push an event to the reciever + /// @returns False if the event was dropped + virtual bool push(ComponentEvent&& event) = 0; + virtual ~IComponentEventPublisher() = default; }; } // namespace score::mw::lifecycle::internal diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/process_monitor.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/process_monitor.cpp index 62254ada4..130e33c01 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/process_monitor.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/process_monitor.cpp @@ -16,7 +16,7 @@ namespace score::mw::lifecycle::internal { -ProcessMonitor::ProcessMonitor(IComponentEventReceiver& event_queue) : event_queue_(event_queue) +ProcessMonitor::ProcessMonitor(IComponentEventPublisher& event_queue) : event_queue_(event_queue) { } diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/process_monitor.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/process_monitor.hpp index 99081f71f..ec8b33d38 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/process_monitor.hpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/process_monitor.hpp @@ -25,7 +25,7 @@ namespace score::mw::lifecycle::internal class ProcessMonitor final : public IComponentController { public: - explicit ProcessMonitor(IComponentEventReceiver& event_queue); + explicit ProcessMonitor(IComponentEventPublisher& event_queue); ~ProcessMonitor() override; ProcessMonitor(const ProcessMonitor&) = delete; @@ -35,12 +35,13 @@ class ProcessMonitor final : public IComponentController /// @brief Start work on @p task and push the result to the event queue if the task completes void doWork(ComponentTask&& task) override; + /// @brief Notify @p component that it has terminated with status @p status. If this is an error or finishes a /// component activation, report to the event queue void terminated(IComponent& component, int32_t status) override; private: - IComponentEventReceiver& event_queue_; + IComponentEventPublisher& event_queue_; }; } // namespace score::mw::lifecycle::internal diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/process_monitor_UT.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/process_monitor_UT.cpp index 44557cffa..cad4be882 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/process_monitor_UT.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/process_monitor_UT.cpp @@ -28,10 +28,10 @@ class MockComponent : public IComponent MOCK_METHOD(bool, active, (), (override, const)); }; -class MockComponentEventQueue : public IComponentEventReceiver +class MockComponentEventQueue : public IComponentEventPublisher { public: - MOCK_METHOD(void, push, (ComponentEvent && event), (override)); + MOCK_METHOD(bool, push, (ComponentEvent && event), (override)); }; class ProcessMonitorTest : public ::testing::Test diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/run_target.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/run_target.hpp index a20dee27b..71db95d9f 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/run_target.hpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/run_target.hpp @@ -21,9 +21,9 @@ namespace score::mw::lifecycle::internal { -/// @brief A virtual node in the dependency graph corresponding to a ProcessGroupState. -/// A RunTarget does not correspond to any OS process; it is immediately active once all -/// of its dependencies are active, and immediately inactive once deactivated. +/// @brief A virtual node in the dependency graph corresponding to a configured run target. +/// A RunTarget does not have any resources and is immediately active once requested, provided +/// all of its dependencies are active, and immediately inactive once deactivated. class RunTarget final : public IComponent { public: From dc6aa46c93f66913683318b67c249cbb89fe4ccb Mon Sep 17 00:00:00 2001 From: William Roebuck <244554584+WilliamRoebuck@users.noreply.github.com> Date: Tue, 28 Jul 2026 12:01:04 +0100 Subject: [PATCH 11/21] Renames, create mock_component.hpp --- .clang-tidy | 4 ++-- .../src/process_group_manager/details/BUILD | 21 ++++++++++++++---- .../details/{task.hpp => component_task.hpp} | 6 ++--- .../details/icomponent_controller.hpp | 2 +- .../details/mock_component.hpp | 22 +++++++++++++++++++ .../details/process_monitor_UT.cpp | 11 +--------- 6 files changed, 46 insertions(+), 20 deletions(-) rename score/launch_manager/src/daemon/src/process_group_manager/details/{task.hpp => component_task.hpp} (90%) create mode 100644 score/launch_manager/src/daemon/src/process_group_manager/details/mock_component.hpp diff --git a/.clang-tidy b/.clang-tidy index f1a127ae6..442969ca3 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -1,5 +1,5 @@ Checks: ' -*, bugprone-*, cert-*, clang-analyzer-core.*, clang-analyzer-cplusplus.*, clang-analyzer-security.*, cppcoreguidelines-pro-bounds-pointer-arithmetic, cppcoreguidelines-pro-type-cstyle-cast, hicpp-* ' WarningsAsErrors: 'bugprone-*,cert-*,clang-analyzer-*,hicpp-*' CheckOptions: -- key: 'hicpp-special-member-functions.AllowSoleDefaultDtor' -- value: 1 \ No newline at end of file + - key: 'hicpp-special-member-functions.AllowSoleDefaultDtor' + - value: 1 diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/BUILD b/score/launch_manager/src/daemon/src/process_group_manager/details/BUILD index d7fb00c04..550d989af 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/BUILD +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/BUILD @@ -13,8 +13,8 @@ load("@rules_cc//cc:defs.bzl", "cc_library", "cc_test") cc_library( - name = "task", - hdrs = ["task.hpp"], + name = "component_task", + hdrs = ["component_task.hpp"], include_prefix = "score/mw/launch_manager/process_group_manager/details", strip_include_prefix = "/score/launch_manager/src/daemon/src/process_group_manager/details", visibility = ["//score:__subpackages__"], @@ -30,8 +30,8 @@ cc_library( strip_include_prefix = "/score/launch_manager/src/daemon/src/process_group_manager/details", visibility = ["//score:__subpackages__"], deps = [ + ":component_task", ":icomponent", - ":task", ], ) @@ -46,6 +46,18 @@ cc_library( ], ) +cc_library( + name = "mock_component", + hdrs = ["mock_component.hpp"], + include_prefix = "score/mw/launch_manager/process_group_manager/details", + strip_include_prefix = "/score/launch_manager/src/daemon/src/process_group_manager/details", + visibility = ["//score:__subpackages__"], + deps = [ + ":icomponent", + "@googletest//:gtest_main", + ], +) + cc_library( name = "component_event", hdrs = ["component_event.hpp"], @@ -141,10 +153,10 @@ cc_library( visibility = ["//score/launch_manager/src/daemon/src/process_group_manager:__pkg__"], deps = [ ":component_event_queue", + ":component_task", ":process_info_node", ":run_target", ":safe_process_map", - ":task", "//score/launch_manager/src/daemon/src/common:identifier_hash", "//score/launch_manager/src/daemon/src/control:control_client_channel", "//score/launch_manager/src/daemon/src/osal:semaphore", @@ -211,6 +223,7 @@ cc_test( name = "process_monitor_UT", srcs = ["process_monitor_UT.cpp"], deps = [ + ":mock_component", ":process_monitor", "@googletest//:gtest_main", ], diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/task.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/component_task.hpp similarity index 90% rename from score/launch_manager/src/daemon/src/process_group_manager/details/task.hpp rename to score/launch_manager/src/daemon/src/process_group_manager/details/component_task.hpp index 0a5319cfe..a9dfdf548 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/task.hpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/component_task.hpp @@ -11,8 +11,8 @@ * SPDX-License-Identifier: Apache-2.0 ********************************************************************************/ -#ifndef SCORE_LCM_TASK_HPP_INCLUDED -#define SCORE_LCM_TASK_HPP_INCLUDED +#ifndef SCORE_LCM_COMPONENT_TASK_HPP_INCLUDED +#define SCORE_LCM_COMPONENT_TASK_HPP_INCLUDED #include "score/mw/launch_manager/process_group_manager/details/icomponent.hpp" #include @@ -41,4 +41,4 @@ struct ComponentTask } // namespace score::mw::lifecycle::internal -#endif // SCORE_LCM_TASK_HPP_INCLUDED +#endif // SCORE_LCM_COMPONENT_TASK_HPP_INCLUDED diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/icomponent_controller.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/icomponent_controller.hpp index ca4541b24..c7e6f2745 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/icomponent_controller.hpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/icomponent_controller.hpp @@ -14,8 +14,8 @@ #ifndef SCORE_LCM_ICOMPONENT_CONTROLLER_HPP_INCLUDED #define SCORE_LCM_ICOMPONENT_CONTROLLER_HPP_INCLUDED +#include "score/mw/launch_manager/process_group_manager/details/component_task.hpp" #include "score/mw/launch_manager/process_group_manager/details/icomponent.hpp" -#include "score/mw/launch_manager/process_group_manager/details/task.hpp" namespace score::mw::lifecycle::internal { diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/mock_component.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/mock_component.hpp new file mode 100644 index 000000000..6885d7581 --- /dev/null +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/mock_component.hpp @@ -0,0 +1,22 @@ +#ifndef MOCK_COMPONENT_HPP_INCLUDED +#define MOCK_COMPONENT_HPP_INCLUDED + +#include "score/mw/launch_manager/process_group_manager/details/icomponent.hpp" +#include + +namespace score::mw::lifecycle::internal +{ + +class MockComponent : public IComponent +{ + public: + MOCK_METHOD(RequestResult, activate, (score::cpp::stop_token stop_token), (override)); + MOCK_METHOD(RequestResult, deactivate, (score::cpp::stop_token stop_token), (override)); + MOCK_METHOD(RequestResult, tryHandleTermination, (int32_t status), (override)); + MOCK_METHOD(uint32_t, getIndex, (), (override, const)); + MOCK_METHOD(bool, active, (), (override, const)); +}; + +} // namespace score::mw::lifecycle::internal + +#endif \ No newline at end of file diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/process_monitor_UT.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/process_monitor_UT.cpp index cad4be882..078ef8ed5 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/process_monitor_UT.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/process_monitor_UT.cpp @@ -11,6 +11,7 @@ * SPDX-License-Identifier: Apache-2.0 ********************************************************************************/ +#include "score/mw/launch_manager/process_group_manager/details/mock_component.hpp" #include "score/mw/launch_manager/process_group_manager/details/process_monitor.hpp" #include #include @@ -18,16 +19,6 @@ using namespace testing; using namespace score::mw::lifecycle::internal; -class MockComponent : public IComponent -{ - public: - MOCK_METHOD(RequestResult, activate, (score::cpp::stop_token stop_token), (override)); - MOCK_METHOD(RequestResult, deactivate, (score::cpp::stop_token stop_token), (override)); - MOCK_METHOD(RequestResult, tryHandleTermination, (int32_t status), (override)); - MOCK_METHOD(uint32_t, getIndex, (), (override, const)); - MOCK_METHOD(bool, active, (), (override, const)); -}; - class MockComponentEventQueue : public IComponentEventPublisher { public: From 110b59d70e10c86f7c0b6a7bf23cc398e4cbe67b Mon Sep 17 00:00:00 2001 From: William Roebuck <244554584+WilliamRoebuck@users.noreply.github.com> Date: Tue, 28 Jul 2026 12:04:42 +0100 Subject: [PATCH 12/21] Revert changes in constants, add testonly --- .../src/daemon/src/common/constants.hpp | 40 +++++++------------ .../src/process_group_manager/details/BUILD | 1 + 2 files changed, 15 insertions(+), 26 deletions(-) diff --git a/score/launch_manager/src/daemon/src/common/constants.hpp b/score/launch_manager/src/daemon/src/common/constants.hpp index b79f36166..22843d1c7 100644 --- a/score/launch_manager/src/daemon/src/common/constants.hpp +++ b/score/launch_manager/src/daemon/src/common/constants.hpp @@ -11,6 +11,7 @@ * SPDX-License-Identifier: Apache-2.0 ********************************************************************************/ + #ifndef CONSTANTS_HPP_INCLUDED #define CONSTANTS_HPP_INCLUDED @@ -18,14 +19,11 @@ #include #include -namespace score -{ +namespace score { -namespace lcm -{ +namespace lcm { -namespace internal -{ +namespace internal { // coverity[autosar_cpp14_a0_1_1_violation:INTENTIONAL] These are constants that are used globally. constexpr std::size_t kMaxArg = 20U; ///< Maximum number of arguments @@ -38,35 +36,25 @@ constexpr std::size_t kArgvArraySize = constexpr std::size_t kEnvArraySize = kMaxEnv + 1U; ///< As required by posix we need extra space in envp_ for NULL pointer -constexpr std::chrono::milliseconds kMaxQueueDelay{ - 500}; ///< The maximum time to wait trying to add items to, or get items from, a queue -constexpr std::chrono::milliseconds kGraphTimeout{10000}; ///< Timeout duration for graph operations. +constexpr std::chrono::milliseconds kMaxQueueDelay{500}; ///< The maximum time to wait trying to add items to, or get items from, a queue +constexpr std::chrono::milliseconds kGraphTimeout{10000}; ///< Timeout duration for graph operations. constexpr std::chrono::milliseconds kMaxSigKillDelay{500}; ///< The maximum time to wait for a process termination -constexpr std::chrono::milliseconds kControlClientPollingDelay{ - 1}; ///< Time Control Client will wait during polling for acknowledgement +constexpr std::chrono::milliseconds kControlClientPollingDelay{1}; ///< Time Control Client will wait during polling for acknowledgement -constexpr std::chrono::milliseconds kMaxRunningDelay{ - 1000}; ///< report_running() API will wait for Launch Manager to respond +constexpr std::chrono::milliseconds kMaxRunningDelay{1000}; ///< report_running() API will wait for Launch Manager to respond -constexpr std::chrono::milliseconds kControlClientMaxIpcDelay{ - 500}; ///< The maximum time to wait, when trying to communicate with LCM. When this time is exceeded - ///< kCommunicationError will be returned +constexpr std::chrono::milliseconds kControlClientMaxIpcDelay{500}; ///< The maximum time to wait, when trying to communicate with LCM. When this time is exceeded kCommunicationError will be returned constexpr std::chrono::milliseconds kControlClientBgThreadSleepTime{100}; -enum class ControlClientLimits : uint16_t -{ - kControlClientMaxInstances = 256U, ///< Maximum number of ControlClient instances that should be created by state - ///< manager. If state manager create more instances than kMaxInstances, those - ///< instances will always return kCommunicationError when used +enum class ControlClientLimits : uint16_t { + kControlClientMaxInstances = + 256U, ///< Maximum number of ControlClient instances that should be created by state manager. If state manager create more instances than kMaxInstances, those instances will always return kCommunicationError when used kControlClientMaxRequests = - 512U ///< Maximum number of active requests, for example SetState call, that ControlClient instance can send to - ///< LCM. If that number is exceeded ControlClient API will return kFailed, until one of the current - ///< requests is completed by LCM + 512U ///< Maximum number of active requests, for example SetState call, that ControlClient instance can send to LCM. If that number is exceeded ControlClient API will return kFailed, until one of the current requests is completed by LCM }; -enum class ProcessLimits : std::uint32_t -{ +enum class ProcessLimits : std::uint32_t { kMaxProcesses = 1024U, ///< Maximum number of processes allowed kNumWorkerThreads = 32U, ///< Maximum number of worker threads allowed maxLocalBuffSize = 32U ///< Maximum size for local buffer diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/BUILD b/score/launch_manager/src/daemon/src/process_group_manager/details/BUILD index 550d989af..081a289e1 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/BUILD +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/BUILD @@ -51,6 +51,7 @@ cc_library( hdrs = ["mock_component.hpp"], include_prefix = "score/mw/launch_manager/process_group_manager/details", strip_include_prefix = "/score/launch_manager/src/daemon/src/process_group_manager/details", + testonly = True, visibility = ["//score:__subpackages__"], deps = [ ":icomponent", From 2052521081d89fc9e608110b29babe327fbafe10 Mon Sep 17 00:00:00 2001 From: William Roebuck <244554584+WilliamRoebuck@users.noreply.github.com> Date: Tue, 28 Jul 2026 13:14:16 +0100 Subject: [PATCH 13/21] Improve event queue interfaces --- .../src/process_group_manager/details/BUILD | 8 ++--- .../details/component_event_queue.hpp | 15 +++++----- ...eceiver.hpp => icomponent_event_queue.hpp} | 29 +++++++++++++++---- .../details/process_monitor.hpp | 2 +- .../details/process_monitor_UT.cpp | 2 ++ 5 files changed, 38 insertions(+), 18 deletions(-) rename score/launch_manager/src/daemon/src/process_group_manager/details/{icomponent_event_receiver.hpp => icomponent_event_queue.hpp} (55%) diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/BUILD b/score/launch_manager/src/daemon/src/process_group_manager/details/BUILD index 081a289e1..a8fd0e84f 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/BUILD +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/BUILD @@ -78,7 +78,7 @@ cc_library( strip_include_prefix = "/score/launch_manager/src/daemon/src/process_group_manager/details", visibility = ["//score/launch_manager/src/daemon/src/process_group_manager:__pkg__"], deps = [ - ":icomponent_event_receiver", + ":icomponent_event_queue", "//score/launch_manager/src/daemon/src/common:constants", "//score/launch_manager/src/daemon/src/common:identifier_hash", "//score/launch_manager/src/daemon/src/common/concurrency:mpsc_bounded_queue", @@ -86,8 +86,8 @@ cc_library( ) cc_library( - name = "icomponent_event_receiver", - hdrs = ["icomponent_event_receiver.hpp"], + name = "icomponent_event_queue", + hdrs = ["icomponent_event_queue.hpp"], include_prefix = "score/mw/launch_manager/process_group_manager/details", strip_include_prefix = "/score/launch_manager/src/daemon/src/process_group_manager/details", visibility = ["//score/launch_manager/src/daemon/src/process_group_manager:__pkg__"], @@ -213,7 +213,7 @@ cc_library( deps = [ ":graph", ":icomponent_controller", - ":icomponent_event_receiver", + ":icomponent_event_queue", ":os_handler", ":process_info_node", ":safe_process_map", diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/component_event_queue.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/component_event_queue.hpp index 20c0b9562..c0b5b622f 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/component_event_queue.hpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/component_event_queue.hpp @@ -20,16 +20,15 @@ #include "score/mw/launch_manager/common/concurrency/concurrency_error_domain.hpp" #include "score/mw/launch_manager/common/concurrency/mpsc_bounded_queue.hpp" -#include "score/mw/launch_manager/common/constants.hpp" #include "score/mw/launch_manager/process_group_manager/details/component_event.hpp" -#include "score/mw/launch_manager/process_group_manager/details/icomponent_event_receiver.hpp" +#include "score/mw/launch_manager/process_group_manager/details/icomponent_event_queue.hpp" namespace score::mw::lifecycle::internal { /// @brief Queue of ComponentEvents produced by worker/OS-handler threads and consumed /// exclusively by the main thread, backed by a fixed-capacity MpscBoundedQueue. -class ComponentEventQueue final : public IComponentEventPublisher +class ComponentEventQueue final : public IComponentEventQueue { public: explicit ComponentEventQueue(std::size_t components) : queue_(components * 3U), capacity_(components * 3U) @@ -64,32 +63,32 @@ class ComponentEventQueue final : public IComponentEventPublisher /// @param timeout Maximum time to wait. A `timeout` of zero means "check once, don't /// block", NOT "wait forever" -- see MpscBoundedQueue::wait(). /// @return true if an event is available (drain via getNextEvent()), false on timeout. - bool waitForEvents(std::chrono::milliseconds timeout) + bool waitForEvents(std::chrono::milliseconds timeout) override { return queue_.wait(timeout).has_value(); } /// @brief Returns the next available event without blocking, or std::nullopt if none right /// now. Call repeatedly until nullopt to drain everything currently queued. - std::optional getNextEvent() + std::optional getNextEvent() override { return queue_.tryPop(); } /// @return True if an event was ever dropped due to the queue being full. - bool getOverflow() const + bool getOverflow() const override { return overflow_.load(std::memory_order_acquire); } /// @brief Permanently marks the queue stopped and wakes any thread currently blocked in /// waitForEvents(). - void stop() + void stop() override { queue_.stop(); } - std::size_t capacity() + std::size_t capacity() override { return capacity_; } diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/icomponent_event_receiver.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/icomponent_event_queue.hpp similarity index 55% rename from score/launch_manager/src/daemon/src/process_group_manager/details/icomponent_event_receiver.hpp rename to score/launch_manager/src/daemon/src/process_group_manager/details/icomponent_event_queue.hpp index 7526189f2..91d32d594 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/icomponent_event_receiver.hpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/icomponent_event_queue.hpp @@ -11,8 +11,8 @@ * SPDX-License-Identifier: Apache-2.0 ********************************************************************************/ -#ifndef SCORE_LCM_ICOMPONENT_EVENT_RECEIVER_HPP_INCLUDED -#define SCORE_LCM_ICOMPONENT_EVENT_RECEIVER_HPP_INCLUDED +#ifndef SCORE_LCM_ICOMPONENT_EVENT_QUEUE_HPP_INCLUDED +#define SCORE_LCM_ICOMPONENT_EVENT_QUEUE_HPP_INCLUDED #include "score/mw/launch_manager/process_group_manager/details/component_event.hpp" @@ -23,12 +23,31 @@ namespace score::mw::lifecycle::internal class IComponentEventPublisher { public: - /// @brief Push an event to the reciever - /// @returns False if the event was dropped virtual bool push(ComponentEvent&& event) = 0; + virtual bool getOverflow() const = 0; + virtual std::size_t capacity() = 0; + virtual ~IComponentEventPublisher() = default; }; +/// @brief Interface an event consumer can get component events from +class IComponentEventConsumer +{ + public: + virtual bool waitForEvents(std::chrono::milliseconds timeout) = 0; + virtual std::optional getNextEvent() = 0; + virtual void stop() = 0; + + virtual ~IComponentEventConsumer() = default; +}; + +/// @brief Interface including publisher and consumer methods +class IComponentEventQueue : public IComponentEventPublisher, public IComponentEventConsumer +{ + public: + virtual ~IComponentEventQueue() = default; +}; + } // namespace score::mw::lifecycle::internal -#endif // SCORE_LCM_ICOMPONENT_EVENT_RECEIVER_HPP_INCLUDED +#endif // SCORE_LCM_ICOMPONENT_EVENT_QUEUE_HPP_INCLUDED diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/process_monitor.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/process_monitor.hpp index ec8b33d38..487e84d56 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/process_monitor.hpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/process_monitor.hpp @@ -15,7 +15,7 @@ #define PROCESS_MONITOR_HPP_INCLUDED #include "score/mw/launch_manager/process_group_manager/details/icomponent_controller.hpp" -#include "score/mw/launch_manager/process_group_manager/details/icomponent_event_receiver.hpp" +#include "score/mw/launch_manager/process_group_manager/details/icomponent_event_queue.hpp" namespace score::mw::lifecycle::internal { diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/process_monitor_UT.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/process_monitor_UT.cpp index 078ef8ed5..e40023c69 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/process_monitor_UT.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/process_monitor_UT.cpp @@ -23,6 +23,8 @@ class MockComponentEventQueue : public IComponentEventPublisher { public: MOCK_METHOD(bool, push, (ComponentEvent && event), (override)); + MOCK_METHOD(bool, getOverflow, (), (override, const)); + MOCK_METHOD(std::size_t, capacity, (), ()); }; class ProcessMonitorTest : public ::testing::Test From ebc75c33d55ecbcfb238a4f0acfa49c20e6888f2 Mon Sep 17 00:00:00 2001 From: William Roebuck <244554584+WilliamRoebuck@users.noreply.github.com> Date: Tue, 28 Jul 2026 13:31:49 +0100 Subject: [PATCH 14/21] Fix incorrect test --- .../details/component_event_queue_UT.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/component_event_queue_UT.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/component_event_queue_UT.cpp index 8961d89b5..9a18d887c 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/component_event_queue_UT.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/component_event_queue_UT.cpp @@ -107,14 +107,15 @@ TEST_F(ComponentEventQueueTest, GetOverflowBecomesTrueOnceQueueIsFull) EXPECT_TRUE(queue_.getOverflow()); } -TEST_F(ComponentEventQueueTest, StopUnblocksWaitForEventsOnEmptyQueue) +TEST_F(ComponentEventQueueTest, StopFailsWaitForEventsOnEmptyQueue) { RecordProperty( "Description", - "Verify stop() causes a subsequently-called waitForEvents() to return false immediately " - "rather than blocking, matching the shutdown usage in ProcessGroupManager::deinitialize()."); + "Verify stop() causes a subsequently-called waitForEvents() to return false, even if there's an event in the " + "queue"); + queue_.push(ActivationSuccessful{1}); queue_.stop(); - EXPECT_FALSE(queue_.waitForEvents(std::chrono::milliseconds{2000})); + EXPECT_FALSE(queue_.waitForEvents(std::chrono::milliseconds{0})); } TEST_F(ComponentEventQueueTest, GetNextEventStillDrainsQueuedEventsAfterStop) From 55c822b090132622e09037d495efc9c4832a2ee5 Mon Sep 17 00:00:00 2001 From: William Roebuck <244554584+WilliamRoebuck@users.noreply.github.com> Date: Tue, 28 Jul 2026 13:58:54 +0100 Subject: [PATCH 15/21] Fix format and copyright --- .../daemon/src/process_group_manager/details/BUILD | 2 +- .../process_group_manager/details/mock_component.hpp | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/BUILD b/score/launch_manager/src/daemon/src/process_group_manager/details/BUILD index a8fd0e84f..8f7c22edd 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/BUILD +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/BUILD @@ -48,10 +48,10 @@ cc_library( cc_library( name = "mock_component", + testonly = True, hdrs = ["mock_component.hpp"], include_prefix = "score/mw/launch_manager/process_group_manager/details", strip_include_prefix = "/score/launch_manager/src/daemon/src/process_group_manager/details", - testonly = True, visibility = ["//score:__subpackages__"], deps = [ ":icomponent", diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/mock_component.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/mock_component.hpp index 6885d7581..3862b136b 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/mock_component.hpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/mock_component.hpp @@ -1,3 +1,15 @@ +/******************************************************************************** + * 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 MOCK_COMPONENT_HPP_INCLUDED #define MOCK_COMPONENT_HPP_INCLUDED From 5b828ecd38a53538dbf7ada9e77c9986077a7a16 Mon Sep 17 00:00:00 2001 From: William Roebuck <244554584+WilliamRoebuck@users.noreply.github.com> Date: Tue, 28 Jul 2026 15:22:32 +0100 Subject: [PATCH 16/21] Add nodiscards & handle cases --- .../src/process_group_manager/details/BUILD | 1 + .../details/component_event.hpp | 10 +++---- .../details/component_event_queue.hpp | 11 ++++---- .../details/component_event_queue_UT.cpp | 16 ++++++------ .../details/component_task.hpp | 8 +++--- .../details/icomponent.hpp | 14 ++++++---- .../details/icomponent_event_queue.hpp | 10 +++---- .../details/mock_component.hpp | 2 +- .../details/process_monitor.cpp | 26 +++++++++++++++---- 9 files changed, 61 insertions(+), 37 deletions(-) diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/BUILD b/score/launch_manager/src/daemon/src/process_group_manager/details/BUILD index 8f7c22edd..eee60be0b 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/BUILD +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/BUILD @@ -217,6 +217,7 @@ cc_library( ":os_handler", ":process_info_node", ":safe_process_map", + "//score/launch_manager/src/daemon/src/common:log", ], ) diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/component_event.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/component_event.hpp index c0244d15c..2636e76e1 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/component_event.hpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/component_event.hpp @@ -26,32 +26,32 @@ namespace score::mw::lifecycle::internal using namespace score::lcm; /// @brief A node finished activating successfully. -struct ActivationSuccessful +struct [[nodiscard]] ActivationSuccessful { uint32_t node_index; }; /// @brief A node failed to activate. -struct ActivationFailed +struct [[nodiscard]] ActivationFailed { uint32_t node_index; IComponent::ComponentError reason; }; /// @brief A node finished deactivating. -struct DeactivationComplete +struct [[nodiscard]] DeactivationComplete { uint32_t node_index; }; /// @brief A node terminated without having been requested to. -struct UnexpectedTermination +struct [[nodiscard]] UnexpectedTermination { uint32_t node_index; }; /// @brief Alive supervision has failed for the given process identifier. -struct SupervisionFailure +struct [[nodiscard]] SupervisionFailure { IdentifierHash process_identifier; }; diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/component_event_queue.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/component_event_queue.hpp index c0b5b622f..1d19ea51a 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/component_event_queue.hpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/component_event_queue.hpp @@ -48,7 +48,7 @@ class ComponentEventQueue final : public IComponentEventQueue /// @brief Enqueues an event. If the queue is full, the event is dropped immediately and /// getOverflow() will subsequently return true. /// @returns False if the event is dropped. - bool push(ComponentEvent&& event) override + [[nodiscard]] bool push(ComponentEvent&& event) override { auto result = queue_.push(std::move(event)); if (!result.has_value() && result.error() == lcm::internal::ConcurrencyErrc::kOverflow) @@ -63,20 +63,20 @@ class ComponentEventQueue final : public IComponentEventQueue /// @param timeout Maximum time to wait. A `timeout` of zero means "check once, don't /// block", NOT "wait forever" -- see MpscBoundedQueue::wait(). /// @return true if an event is available (drain via getNextEvent()), false on timeout. - bool waitForEvents(std::chrono::milliseconds timeout) override + [[nodiscard]] bool waitForEvents(std::chrono::milliseconds timeout) override { return queue_.wait(timeout).has_value(); } /// @brief Returns the next available event without blocking, or std::nullopt if none right /// now. Call repeatedly until nullopt to drain everything currently queued. - std::optional getNextEvent() override + [[nodiscard]] std::optional getNextEvent() override { return queue_.tryPop(); } /// @return True if an event was ever dropped due to the queue being full. - bool getOverflow() const override + [[nodiscard]] bool getOverflow() const override { return overflow_.load(std::memory_order_acquire); } @@ -88,7 +88,8 @@ class ComponentEventQueue final : public IComponentEventQueue queue_.stop(); } - std::size_t capacity() override + /// @brief Get the max number of items that can be stored in this queue + [[nodiscard]] std::size_t capacity() override { return capacity_; } diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/component_event_queue_UT.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/component_event_queue_UT.cpp index 9a18d887c..d3e70d45b 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/component_event_queue_UT.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/component_event_queue_UT.cpp @@ -42,7 +42,7 @@ TEST_F(ComponentEventQueueTest, WaitForEventsReturnsFalseOnEmptyQueue) TEST_F(ComponentEventQueueTest, WaitForEventsReturnsTrueAfterPush) { RecordProperty("Description", "Verify waitForEvents returns true once an event has been pushed."); - queue_.push(ActivationSuccessful{7U}); + EXPECT_TRUE(queue_.push(ActivationSuccessful{7U})); EXPECT_TRUE(queue_.waitForEvents(std::chrono::milliseconds{0})); } @@ -55,7 +55,7 @@ TEST_F(ComponentEventQueueTest, GetNextEventReturnsNulloptWhenEmpty) TEST_F(ComponentEventQueueTest, GetNextEventReturnsPushedEventWithPayloadIntact) { RecordProperty("Description", "Verify a pushed event is returned by getNextEvent with its payload preserved."); - queue_.push(ActivationFailed{3U, IComponent::ComponentError::kErrorBeforeReady}); + EXPECT_TRUE(queue_.push(ActivationFailed{3U, IComponent::ComponentError::kErrorBeforeReady})); auto event = queue_.getNextEvent(); ASSERT_TRUE(event.has_value()); @@ -72,7 +72,7 @@ TEST_F(ComponentEventQueueTest, GetNextEventReturnsSupervisionFailureWithPayload "Verify a pushed SupervisionFailure event is returned by getNextEvent with process identifier " "payload preserved."); const IdentifierHash process_identifier{"proc_for_supervision_failure"}; - queue_.push(SupervisionFailure{process_identifier}); + EXPECT_TRUE(queue_.push(SupervisionFailure{process_identifier})); auto event = queue_.getNextEvent(); ASSERT_TRUE(event.has_value()); @@ -84,7 +84,7 @@ TEST_F(ComponentEventQueueTest, GetNextEventReturnsSupervisionFailureWithPayload TEST_F(ComponentEventQueueTest, GetOverflowStaysFalseUnderNormalUsage) { RecordProperty("Description", "Verify getOverflow() stays false when events are pushed and drained normally."); - queue_.push(ActivationSuccessful{1U}); + EXPECT_TRUE(queue_.push(ActivationSuccessful{1U})); static_cast(queue_.getNextEvent()); EXPECT_FALSE(queue_.getOverflow()); } @@ -97,13 +97,13 @@ TEST_F(ComponentEventQueueTest, GetOverflowBecomesTrueOnceQueueIsFull) "mirroring how ProcessGroupManager::run() detects lost events."); for (std::size_t i = 0U; i < queue_.capacity(); ++i) { - queue_.push(ActivationSuccessful{static_cast(i)}); + EXPECT_TRUE(queue_.push(ActivationSuccessful{static_cast(i)})); } EXPECT_FALSE(queue_.getOverflow()); // One more push while the queue is already at capacity and nobody is draining it: this // push is dropped immediately, flagging overflow. - queue_.push(ActivationSuccessful{9999U}); + EXPECT_FALSE(queue_.push(ActivationSuccessful{9999U})); EXPECT_TRUE(queue_.getOverflow()); } @@ -113,7 +113,7 @@ TEST_F(ComponentEventQueueTest, StopFailsWaitForEventsOnEmptyQueue) "Description", "Verify stop() causes a subsequently-called waitForEvents() to return false, even if there's an event in the " "queue"); - queue_.push(ActivationSuccessful{1}); + EXPECT_TRUE(queue_.push(ActivationSuccessful{1})); queue_.stop(); EXPECT_FALSE(queue_.waitForEvents(std::chrono::milliseconds{0})); } @@ -124,7 +124,7 @@ TEST_F(ComponentEventQueueTest, GetNextEventStillDrainsQueuedEventsAfterStop) "Description", "Verify that events pushed before stop() was called are not silently discarded -- " "getNextEvent() must still be able to drain them during shutdown."); - queue_.push(ActivationSuccessful{1U}); + EXPECT_TRUE(queue_.push(ActivationSuccessful{1U})); queue_.stop(); auto event = queue_.getNextEvent(); diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/component_task.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/component_task.hpp index a9dfdf548..0fc3daa11 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/component_task.hpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/component_task.hpp @@ -24,12 +24,14 @@ namespace score::mw::lifecycle::internal enum class ComponentTaskType : std::uint_least8_t { - kActivate = 0U, /// This task is to start activation of the component - kDeactivate = 1U, /// This task is to deactivate the component + /// @brief This task is to start activation of the component + kActivate = 0U, + /// @brief This task is to deactivate the component + kDeactivate = 1U, }; /// @brief Work to perform on a component that can be sent to the job queue to be executed in parallel. -struct ComponentTask +struct [[nodiscard]] ComponentTask { /// @brief What kind of work to do ComponentTaskType type; diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/icomponent.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/icomponent.hpp index 7bca1862d..7d220a6db 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/icomponent.hpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/icomponent.hpp @@ -49,20 +49,24 @@ class IComponent /// @p stop_token Token that can be stopped to exit the activation early. /// @returns kSuccess if the component is now ready, kWaiting if the component is waiting for a notification, or an /// error. - virtual RequestResult activate(score::cpp::stop_token stop_token) = 0; + [[nodiscard]] virtual RequestResult activate(score::cpp::stop_token stop_token) = 0; + /// @brief Begin deactivation of the component. /// @p stop_token Token that can be stopped to exit the deactivation early. /// @returns kSuccess if the component is now ready, kWaiting if the component is waiting for a notification, or an /// error. - virtual RequestResult deactivate(score::cpp::stop_token stop_token) = 0; + [[nodiscard]] virtual RequestResult deactivate(score::cpp::stop_token stop_token) = 0; + /// @brief Notify the component that it has terminated with status @p status /// @returns kSuccess if the component is now ready, kWaiting if the component is waiting for a notification, or an /// error if the termination was not expected. - virtual RequestResult tryHandleTermination(int32_t status) = 0; + [[nodiscard]] virtual RequestResult tryHandleTermination(int32_t status) = 0; + /// @returns the index of the component in the graph. - virtual uint32_t getIndex() const = 0; + [[nodiscard]] virtual uint32_t getIndex() const = 0; + /// @returns True if the component is active in the active run target. - virtual bool active() const = 0; + [[nodiscard]] virtual bool active() const = 0; virtual ~IComponent() = default; }; diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/icomponent_event_queue.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/icomponent_event_queue.hpp index 91d32d594..b52ee77bb 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/icomponent_event_queue.hpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/icomponent_event_queue.hpp @@ -23,9 +23,9 @@ namespace score::mw::lifecycle::internal class IComponentEventPublisher { public: - virtual bool push(ComponentEvent&& event) = 0; - virtual bool getOverflow() const = 0; - virtual std::size_t capacity() = 0; + [[nodiscard]] virtual bool push(ComponentEvent&& event) = 0; + [[nodiscard]] virtual bool getOverflow() const = 0; + [[nodiscard]] virtual std::size_t capacity() = 0; virtual ~IComponentEventPublisher() = default; }; @@ -34,8 +34,8 @@ class IComponentEventPublisher class IComponentEventConsumer { public: - virtual bool waitForEvents(std::chrono::milliseconds timeout) = 0; - virtual std::optional getNextEvent() = 0; + [[nodiscard]] virtual bool waitForEvents(std::chrono::milliseconds timeout) = 0; + [[nodiscard]] virtual std::optional getNextEvent() = 0; virtual void stop() = 0; virtual ~IComponentEventConsumer() = default; diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/mock_component.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/mock_component.hpp index 3862b136b..5fbe20165 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/mock_component.hpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/mock_component.hpp @@ -31,4 +31,4 @@ class MockComponent : public IComponent } // namespace score::mw::lifecycle::internal -#endif \ No newline at end of file +#endif // MOCK_COMPONENT_HPP_INCLUDED \ No newline at end of file diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/process_monitor.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/process_monitor.cpp index 130e33c01..3c8c204df 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/process_monitor.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/process_monitor.cpp @@ -12,6 +12,7 @@ ********************************************************************************/ #include "score/mw/launch_manager/process_group_manager/details/process_monitor.hpp" +#include "score/mw/launch_manager/common/log.hpp" namespace score::mw::lifecycle::internal { @@ -43,16 +44,22 @@ void ProcessMonitor::doWork(ComponentTask&& task) auto handle_success = [&]() { const uint32_t node_index = task.component.get().getIndex(); + bool push_res = true; switch (task.type) { case ComponentTaskType::kActivate: - event_queue_.push(ActivationSuccessful{node_index}); + push_res = event_queue_.push(ActivationSuccessful{node_index}); break; case ComponentTaskType::kDeactivate: - event_queue_.push(DeactivationComplete{node_index}); + push_res = event_queue_.push(DeactivationComplete{node_index}); break; } + + if (!push_res) + { + LM_LOG_ERROR() << "Failed to send success to event queue!"; + } }; auto handle_failure = [&](IComponent::ComponentError& error) { @@ -61,7 +68,10 @@ void ProcessMonitor::doWork(ComponentTask&& task) switch (task.type) { case ComponentTaskType::kActivate: - event_queue_.push(ActivationFailed{node_index, error}); + if (!event_queue_.push(ActivationFailed{node_index, error})) + { + LM_LOG_ERROR() << "Failed to send activation failed event to event queue!"; + } break; case ComponentTaskType::kDeactivate: break; @@ -99,13 +109,19 @@ void ProcessMonitor::doWork(ComponentTask&& task) void ProcessMonitor::terminated(IComponent& component, int32_t status) { auto res = component.tryHandleTermination(status); + bool push_res = true; if (!res.has_value()) { - event_queue_.push(UnexpectedTermination{component.getIndex()}); + push_res = event_queue_.push(UnexpectedTermination{component.getIndex()}); } else if (res.value() != IComponent::RequestState::kWaiting) { - event_queue_.push(ActivationSuccessful{component.getIndex()}); + push_res = event_queue_.push(ActivationSuccessful{component.getIndex()}); + } + + if (!push_res) + { + LM_LOG_ERROR() << "Failed to push terminated result to event queue!"; } } From 889b5241e5344a9907258705194922564f526b75 Mon Sep 17 00:00:00 2001 From: William Roebuck <244554584+WilliamRoebuck@users.noreply.github.com> Date: Tue, 28 Jul 2026 15:37:47 +0100 Subject: [PATCH 17/21] Extract mocks --- .../src/process_group_manager/details/BUILD | 29 +++++++++++++++++ .../details/mock_component_event_queue.hpp | 32 +++++++++++++++++++ .../details/mock_termination_callback.hpp | 30 +++++++++++++++++ .../details/oshandler_UT.cpp | 7 +--- .../details/process_monitor_UT.cpp | 9 +----- .../details/safeprocessmap_UT.cpp | 7 +--- 6 files changed, 94 insertions(+), 20 deletions(-) create mode 100644 score/launch_manager/src/daemon/src/process_group_manager/details/mock_component_event_queue.hpp create mode 100644 score/launch_manager/src/daemon/src/process_group_manager/details/mock_termination_callback.hpp diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/BUILD b/score/launch_manager/src/daemon/src/process_group_manager/details/BUILD index eee60be0b..ee4262922 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/BUILD +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/BUILD @@ -59,6 +59,32 @@ cc_library( ], ) +cc_library( + name = "mock_component_event_queue", + testonly = True, + hdrs = ["mock_component_event_queue.hpp"], + include_prefix = "score/mw/launch_manager/process_group_manager/details", + strip_include_prefix = "/score/launch_manager/src/daemon/src/process_group_manager/details", + visibility = ["//score:__subpackages__"], + deps = [ + ":icomponent_event_queue", + "@googletest//:gtest_main", + ], +) + +cc_library( + name = "mock_termination_callback", + testonly = True, + hdrs = ["mock_termination_callback.hpp"], + include_prefix = "score/mw/launch_manager/process_group_manager/details", + strip_include_prefix = "/score/launch_manager/src/daemon/src/process_group_manager/details", + visibility = ["//score:__subpackages__"], + deps = [ + ":safe_process_map", + "@googletest//:gtest_main", + ], +) + cc_library( name = "component_event", hdrs = ["component_event.hpp"], @@ -183,6 +209,7 @@ cc_test( timeout = "long", srcs = ["safeprocessmap_UT.cpp"], deps = [ + ":mock_termination_callback", ":safe_process_map", "@googletest//:gtest_main", ], @@ -226,6 +253,7 @@ cc_test( srcs = ["process_monitor_UT.cpp"], deps = [ ":mock_component", + ":mock_component_event_queue", ":process_monitor", "@googletest//:gtest_main", ], @@ -235,6 +263,7 @@ cc_test( name = "oshandler_UT", srcs = ["oshandler_UT.cpp"], deps = [ + ":mock_termination_callback", ":os_handler", ":safe_process_map", "@googletest//:gtest_main", diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/mock_component_event_queue.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/mock_component_event_queue.hpp new file mode 100644 index 000000000..fae19f15a --- /dev/null +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/mock_component_event_queue.hpp @@ -0,0 +1,32 @@ +/******************************************************************************** + * 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 MOCK_COMPONENT_EVENT_QUEUE_HPP_INCLUDED +#define MOCK_COMPONENT_EVENT_QUEUE_HPP_INCLUDED + +#include "score/mw/launch_manager/process_group_manager/details/icomponent_event_queue.hpp" +#include + +namespace score::mw::lifecycle::internal +{ + +class MockComponentEventQueue : public IComponentEventPublisher +{ + public: + MOCK_METHOD(bool, push, (ComponentEvent && event), (override)); + MOCK_METHOD(bool, getOverflow, (), (override, const)); + MOCK_METHOD(std::size_t, capacity, (), ()); +}; + +} // namespace score::mw::lifecycle::internal + +#endif // MOCK_COMPONENT_EVENT_QUEUE_HPP_INCLUDED diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/mock_termination_callback.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/mock_termination_callback.hpp new file mode 100644 index 000000000..904ffe040 --- /dev/null +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/mock_termination_callback.hpp @@ -0,0 +1,30 @@ +/******************************************************************************** + * 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 MOCK_TERMINATION_CALLBACK_HPP_INCLUDED +#define MOCK_TERMINATION_CALLBACK_HPP_INCLUDED + +#include "score/mw/launch_manager/process_group_manager/details/safe_process_map.hpp" +#include + +namespace score::lcm::internal +{ + +class MockTerminationCallback : public ITerminationCallback +{ + public: + MOCK_METHOD(void, terminated, (int32_t process_status), (override)); +}; + +} // namespace score::lcm::internal + +#endif // MOCK_TERMINATION_CALLBACK_HPP_INCLUDED diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/oshandler_UT.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/oshandler_UT.cpp index 997f6bcb0..c9c0735ca 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/oshandler_UT.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/oshandler_UT.cpp @@ -22,6 +22,7 @@ #include "score/mw/launch_manager/common/constants.hpp" #include "score/mw/launch_manager/process_group_manager/details/safe_process_map.hpp" +#include "score/mw/launch_manager/process_group_manager/details/mock_termination_callback.hpp" #include "score/os/mocklib/sys_wait_mock.h" using namespace testing; @@ -30,12 +31,6 @@ using namespace score::lcm::internal; namespace { -class MockTerminationCallback : public ITerminationCallback -{ - public: - MOCK_METHOD(void, terminated, (int32_t process_status), (override)); -}; - class OsHandlerTest : public ::testing::Test { protected: diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/process_monitor_UT.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/process_monitor_UT.cpp index e40023c69..8bba2f979 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/process_monitor_UT.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/process_monitor_UT.cpp @@ -12,6 +12,7 @@ ********************************************************************************/ #include "score/mw/launch_manager/process_group_manager/details/mock_component.hpp" +#include "score/mw/launch_manager/process_group_manager/details/mock_component_event_queue.hpp" #include "score/mw/launch_manager/process_group_manager/details/process_monitor.hpp" #include #include @@ -19,14 +20,6 @@ using namespace testing; using namespace score::mw::lifecycle::internal; -class MockComponentEventQueue : public IComponentEventPublisher -{ - public: - MOCK_METHOD(bool, push, (ComponentEvent && event), (override)); - MOCK_METHOD(bool, getOverflow, (), (override, const)); - MOCK_METHOD(std::size_t, capacity, (), ()); -}; - class ProcessMonitorTest : public ::testing::Test { protected: diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/safeprocessmap_UT.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/safeprocessmap_UT.cpp index a5469425c..63d2bd6dd 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/safeprocessmap_UT.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/safeprocessmap_UT.cpp @@ -21,6 +21,7 @@ #include "score/mw/launch_manager/common/constants.hpp" #include "score/mw/launch_manager/process_group_manager/details/safe_process_map.hpp" +#include "score/mw/launch_manager/process_group_manager/details/mock_termination_callback.hpp" using namespace testing; using namespace score::lcm::internal; @@ -41,12 +42,6 @@ constexpr int kPidsPerThread = 256; constexpr uint32_t kCapacity = static_cast(ProcessLimits::kMaxProcesses); -class MockTerminationCallback : public ITerminationCallback -{ - public: - MOCK_METHOD(void, terminated, (int32_t process_status), (override)); -}; - class SafeProcessMapTest : public ::testing::Test { protected: From fb4c26ea658ee3b4a2a4ab4ded4357d54e208791 Mon Sep 17 00:00:00 2001 From: William Roebuck <244554584+WilliamRoebuck@users.noreply.github.com> Date: Tue, 28 Jul 2026 16:05:50 +0100 Subject: [PATCH 18/21] Add some newlines --- .../daemon/src/process_group_manager/details/component_task.hpp | 2 ++ .../src/process_group_manager/details/icomponent_controller.hpp | 1 + 2 files changed, 3 insertions(+) diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/component_task.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/component_task.hpp index 0fc3daa11..a162f1885 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/component_task.hpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/component_task.hpp @@ -35,8 +35,10 @@ struct [[nodiscard]] ComponentTask { /// @brief What kind of work to do ComponentTaskType type; + /// @brief The component to be acted upon std::reference_wrapper component; + /// @brief Token to exit and abandon the task early score::cpp::stop_token stop_token; }; diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/icomponent_controller.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/icomponent_controller.hpp index c7e6f2745..9c212d351 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/icomponent_controller.hpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/icomponent_controller.hpp @@ -27,6 +27,7 @@ class IComponentController public: /// @brief Start work on @p task and push the result to the event queue if the task completes virtual void doWork(ComponentTask&& task) = 0; + /// @brief Notify @p component that it has terminated with status @p status. Forward the appropriate event to the /// event queue virtual void terminated(IComponent& component, int32_t status) = 0; From 783934052487f6f4d4fa401fa82b276d5d365e3d Mon Sep 17 00:00:00 2001 From: William Roebuck <244554584+WilliamRoebuck@users.noreply.github.com> Date: Tue, 28 Jul 2026 16:11:25 +0100 Subject: [PATCH 19/21] Rename event queue interface --- .../src/daemon/src/process_group_manager/details/BUILD | 10 +++++----- .../details/component_event_queue.hpp | 4 ++-- ...eue.hpp => icomponent_event_publisher_consumer.hpp} | 10 +++++----- .../details/mock_component_event_queue.hpp | 2 +- .../process_group_manager/details/process_monitor.hpp | 2 +- 5 files changed, 14 insertions(+), 14 deletions(-) rename score/launch_manager/src/daemon/src/process_group_manager/details/{icomponent_event_queue.hpp => icomponent_event_publisher_consumer.hpp} (80%) diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/BUILD b/score/launch_manager/src/daemon/src/process_group_manager/details/BUILD index ee4262922..6b0f00e10 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/BUILD +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/BUILD @@ -67,7 +67,7 @@ cc_library( strip_include_prefix = "/score/launch_manager/src/daemon/src/process_group_manager/details", visibility = ["//score:__subpackages__"], deps = [ - ":icomponent_event_queue", + ":icomponent_event_publisher_consumer", "@googletest//:gtest_main", ], ) @@ -104,7 +104,7 @@ cc_library( strip_include_prefix = "/score/launch_manager/src/daemon/src/process_group_manager/details", visibility = ["//score/launch_manager/src/daemon/src/process_group_manager:__pkg__"], deps = [ - ":icomponent_event_queue", + ":icomponent_event_publisher_consumer", "//score/launch_manager/src/daemon/src/common:constants", "//score/launch_manager/src/daemon/src/common:identifier_hash", "//score/launch_manager/src/daemon/src/common/concurrency:mpsc_bounded_queue", @@ -112,8 +112,8 @@ cc_library( ) cc_library( - name = "icomponent_event_queue", - hdrs = ["icomponent_event_queue.hpp"], + name = "icomponent_event_publisher_consumer", + hdrs = ["icomponent_event_publisher_consumer.hpp"], include_prefix = "score/mw/launch_manager/process_group_manager/details", strip_include_prefix = "/score/launch_manager/src/daemon/src/process_group_manager/details", visibility = ["//score/launch_manager/src/daemon/src/process_group_manager:__pkg__"], @@ -240,7 +240,7 @@ cc_library( deps = [ ":graph", ":icomponent_controller", - ":icomponent_event_queue", + ":icomponent_event_publisher_consumer", ":os_handler", ":process_info_node", ":safe_process_map", diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/component_event_queue.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/component_event_queue.hpp index 1d19ea51a..7af64d883 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/component_event_queue.hpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/component_event_queue.hpp @@ -21,14 +21,14 @@ #include "score/mw/launch_manager/common/concurrency/concurrency_error_domain.hpp" #include "score/mw/launch_manager/common/concurrency/mpsc_bounded_queue.hpp" #include "score/mw/launch_manager/process_group_manager/details/component_event.hpp" -#include "score/mw/launch_manager/process_group_manager/details/icomponent_event_queue.hpp" +#include "score/mw/launch_manager/process_group_manager/details/icomponent_event_publisher_consumer.hpp" namespace score::mw::lifecycle::internal { /// @brief Queue of ComponentEvents produced by worker/OS-handler threads and consumed /// exclusively by the main thread, backed by a fixed-capacity MpscBoundedQueue. -class ComponentEventQueue final : public IComponentEventQueue +class ComponentEventQueue final : public IComponentEventPublisherConsumer { public: explicit ComponentEventQueue(std::size_t components) : queue_(components * 3U), capacity_(components * 3U) diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/icomponent_event_queue.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/icomponent_event_publisher_consumer.hpp similarity index 80% rename from score/launch_manager/src/daemon/src/process_group_manager/details/icomponent_event_queue.hpp rename to score/launch_manager/src/daemon/src/process_group_manager/details/icomponent_event_publisher_consumer.hpp index b52ee77bb..fddf7f33f 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/icomponent_event_queue.hpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/icomponent_event_publisher_consumer.hpp @@ -11,8 +11,8 @@ * SPDX-License-Identifier: Apache-2.0 ********************************************************************************/ -#ifndef SCORE_LCM_ICOMPONENT_EVENT_QUEUE_HPP_INCLUDED -#define SCORE_LCM_ICOMPONENT_EVENT_QUEUE_HPP_INCLUDED +#ifndef SCORE_LCM_ICOMPONENT_EVENT_PUBLISHER_CONSUMER_HPP_INCLUDED +#define SCORE_LCM_ICOMPONENT_EVENT_PUBLISHER_CONSUMER_HPP_INCLUDED #include "score/mw/launch_manager/process_group_manager/details/component_event.hpp" @@ -42,12 +42,12 @@ class IComponentEventConsumer }; /// @brief Interface including publisher and consumer methods -class IComponentEventQueue : public IComponentEventPublisher, public IComponentEventConsumer +class IComponentEventPublisherConsumer : public IComponentEventPublisher, public IComponentEventConsumer { public: - virtual ~IComponentEventQueue() = default; + virtual ~IComponentEventPublisherConsumer() = default; }; } // namespace score::mw::lifecycle::internal -#endif // SCORE_LCM_ICOMPONENT_EVENT_QUEUE_HPP_INCLUDED +#endif // SCORE_LCM_ICOMPONENT_EVENT_PUBLISHER_CONSUMER_HPP_INCLUDED diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/mock_component_event_queue.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/mock_component_event_queue.hpp index fae19f15a..d5f008146 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/mock_component_event_queue.hpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/mock_component_event_queue.hpp @@ -13,7 +13,7 @@ #ifndef MOCK_COMPONENT_EVENT_QUEUE_HPP_INCLUDED #define MOCK_COMPONENT_EVENT_QUEUE_HPP_INCLUDED -#include "score/mw/launch_manager/process_group_manager/details/icomponent_event_queue.hpp" +#include "score/mw/launch_manager/process_group_manager/details/icomponent_event_publisher_consumer.hpp" #include namespace score::mw::lifecycle::internal diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/process_monitor.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/process_monitor.hpp index 487e84d56..d4c05fd9d 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/process_monitor.hpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/process_monitor.hpp @@ -15,7 +15,7 @@ #define PROCESS_MONITOR_HPP_INCLUDED #include "score/mw/launch_manager/process_group_manager/details/icomponent_controller.hpp" -#include "score/mw/launch_manager/process_group_manager/details/icomponent_event_queue.hpp" +#include "score/mw/launch_manager/process_group_manager/details/icomponent_event_publisher_consumer.hpp" namespace score::mw::lifecycle::internal { From c1fb57f09564289f4fce048d05319606be83a6f2 Mon Sep 17 00:00:00 2001 From: William Roebuck <244554584+WilliamRoebuck@users.noreply.github.com> Date: Tue, 28 Jul 2026 16:21:56 +0100 Subject: [PATCH 20/21] Correct enum comments --- .../process_group_manager/details/icomponent.hpp | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/icomponent.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/icomponent.hpp index 7d220a6db..aceda1399 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/icomponent.hpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/icomponent.hpp @@ -32,15 +32,20 @@ class IComponent public: enum class ComponentError : uint8_t { - kErrorBeforeReady, // An error occurred during startup, before the component was considered ready. - kErrorAfterReady, // An error occurred any time after the component was considered ready. - kActivationTimedOut, // An error occurred during startup. Specifically, a timeout was reached. + /// @brief An error occurred during startup, before the component was considered ready. + kErrorBeforeReady, + /// @brief An error occurred any time after the component was considered ready. + kErrorAfterReady, + /// @brief An error occurred during startup. Specifically, a timeout was reached. + kActivationTimedOut, }; enum class RequestState : uint8_t { - kSuccess, // Activation was successful and the component is now ready. - kWaiting // Activation is waiting on a notification from another thread. The component may not be ready. + /// @brief Activation was successful and the component is now ready. + kSuccess, + /// @brief Activation is waiting on a notification from another thread. The component may not be ready. + kWaiting }; using RequestResult = score::cpp::expected; From 365351189887fd007f7a41844694abdff01f3769 Mon Sep 17 00:00:00 2001 From: William Roebuck <244554584+WilliamRoebuck@users.noreply.github.com> Date: Tue, 28 Jul 2026 16:44:20 +0100 Subject: [PATCH 21/21] Exclude mocks from coverage --- .github/workflows/code_coverage.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/code_coverage.yml b/.github/workflows/code_coverage.yml index 8d75225e5..ff76864e5 100644 --- a/.github/workflows/code_coverage.yml +++ b/.github/workflows/code_coverage.yml @@ -28,7 +28,7 @@ jobs: with: bazel-target: "//score/..." bazel-config: "x86_64-linux" - extra-bazel-flags: "--test_output=errors --nocache_test_results --lockfile_mode=error --test_lang_filters=-rust,-miri" + extra-bazel-flags: "--test_output=errors --nocache_test_results --lockfile_mode=error --test_lang_filters=-rust,-miri --instrumentation_filter=-mock_*" artifact-name-suffix: "_cpp" retention-days: 10 min-coverage: 76