From 3d8c5151c43ed01a4096481aacd05be9ae90a554 Mon Sep 17 00:00:00 2001 From: Daniel Thwaites Date: Thu, 24 Sep 2026 16:47:23 +0100 Subject: [PATCH 01/36] Implement new component class This uses the existing component interface, so it should be fully compatible with existing callers. --- .../src/process_group_manager/details/BUILD | 102 ++++++++++++++++ .../details/component.cpp | 67 +++++++++++ .../details/component.hpp | 51 ++++++++ .../details/component_UT.cpp | 110 ++++++++++++++++++ .../process_group_manager/details/handle.hpp | 36 ++++++ .../details/iready_condition.hpp | 31 +++++ .../details/istart_action.hpp | 31 +++++ .../details/istop_action.hpp | 31 +++++ .../details/mock_ready_condition.hpp | 31 +++++ .../details/mock_start_action.hpp | 31 +++++ .../details/mock_stop_action.hpp | 31 +++++ 11 files changed, 552 insertions(+) create mode 100644 score/launch_manager/src/daemon/src/process_group_manager/details/component.cpp create mode 100644 score/launch_manager/src/daemon/src/process_group_manager/details/component.hpp create mode 100644 score/launch_manager/src/daemon/src/process_group_manager/details/component_UT.cpp create mode 100644 score/launch_manager/src/daemon/src/process_group_manager/details/handle.hpp create mode 100644 score/launch_manager/src/daemon/src/process_group_manager/details/iready_condition.hpp create mode 100644 score/launch_manager/src/daemon/src/process_group_manager/details/istart_action.hpp create mode 100644 score/launch_manager/src/daemon/src/process_group_manager/details/istop_action.hpp create mode 100644 score/launch_manager/src/daemon/src/process_group_manager/details/mock_ready_condition.hpp create mode 100644 score/launch_manager/src/daemon/src/process_group_manager/details/mock_start_action.hpp create mode 100644 score/launch_manager/src/daemon/src/process_group_manager/details/mock_stop_action.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 77f3017404..855e8dfaa8 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 @@ -14,6 +14,34 @@ load("@rules_cc//cc:defs.bzl", "cc_library") load("//tests/utils/bazel:unit_test.bzl", "lm_cc_test") +cc_library( + name = "component", + srcs = ["component.cpp"], + hdrs = ["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/launch_manager/src/daemon/src/process_group_manager:__subpackages__"], + deps = [ + ":icomponent", + ":iready_condition", + ":istart_action", + ":istop_action", + ], +) + +lm_cc_test( + name = "component_UT", + srcs = ["component_UT.cpp"], + deps = [ + ":component", + ":mock_component", + ":mock_ready_condition", + ":mock_start_action", + ":mock_stop_action", + "@googletest//:gtest_main", + ], +) + cc_library( name = "component_task", hdrs = ["component_task.hpp"], @@ -49,6 +77,41 @@ cc_library( ], ) +cc_library( + name = "handle", + hdrs = ["handle.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:__subpackages__"], +) + +cc_library( + name = "istart_action", + hdrs = ["istart_action.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:__subpackages__"], + deps = [":handle"], +) + +cc_library( + name = "istop_action", + hdrs = ["istop_action.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:__subpackages__"], + deps = [":handle"], +) + +cc_library( + name = "iready_condition", + hdrs = ["iready_condition.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:__subpackages__"], + deps = [":handle"], +) + cc_library( name = "mock_component", testonly = True, @@ -62,6 +125,45 @@ cc_library( ], ) +cc_library( + name = "mock_start_action", + testonly = True, + hdrs = ["mock_start_action.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:__subpackages__"], + deps = [ + ":istart_action", + "@googletest//:gtest_main", + ], +) + +cc_library( + name = "mock_stop_action", + testonly = True, + hdrs = ["mock_stop_action.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:__subpackages__"], + deps = [ + ":istop_action", + "@googletest//:gtest_main", + ], +) + +cc_library( + name = "mock_ready_condition", + testonly = True, + hdrs = ["mock_ready_condition.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:__subpackages__"], + deps = [ + ":iready_condition", + "@googletest//:gtest_main", + ], +) + cc_library( name = "mock_component_event_queue", testonly = True, diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/component.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/component.cpp new file mode 100644 index 0000000000..ab7a7873bf --- /dev/null +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/component.cpp @@ -0,0 +1,67 @@ +/******************************************************************************** + * 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.hpp" + +namespace score::mw::lifecycle::internal +{ + +Component::Component( + const IStartAction* start_action, + const IStopAction* stop_action, + const std::vector ready_conditions, + IdentifierHash identifier) + : handle_(EmptyHandle{}), + start_action_(start_action), + stop_action_(stop_action), + ready_conditions_(ready_conditions), + identifier_(identifier) +{ +} + +IComponent::RequestResult Component::activate(score::cpp::stop_token stop_token) +{ + handle_ = start_action_->start(); + for (const IReadyCondition* ready_condition : ready_conditions_) + { + ready_condition->wait(handle_.value()); + } + return RequestState::kSuccess; +} + +IComponent::RequestResult Component::deactivate(score::cpp::stop_token stop_token) +{ + if (handle_.has_value()) + { + stop_action_->stop(handle_.value()); + } + handle_ = std::nullopt; + return RequestState::kSuccess; +} + +IComponent::RequestResult Component::tryHandleTermination(int32_t status) +{ + return RequestState::kWaiting; +} + +IdentifierHash Component::getIdentifier() const +{ + return identifier_; +} + +bool Component::active() const +{ + return handle_.has_value(); +} + +} // namespace score::mw::lifecycle::internal diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/component.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/component.hpp new file mode 100644 index 0000000000..1195637334 --- /dev/null +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/component.hpp @@ -0,0 +1,51 @@ +/******************************************************************************** + * 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_HPP_INCLUDED +#define SCORE_LCM_COMPONENT_HPP_INCLUDED + +#include "score/mw/launch_manager/process_group_manager/details/icomponent.hpp" +#include "score/mw/launch_manager/process_group_manager/details/iready_condition.hpp" +#include "score/mw/launch_manager/process_group_manager/details/istart_action.hpp" +#include "score/mw/launch_manager/process_group_manager/details/istop_action.hpp" +#include + +namespace score::mw::lifecycle::internal +{ + +class Component final : public IComponent +{ + public: + Component( + const IStartAction* start_action, + const IStopAction* stop_action, + const std::vector ready_conditions, + IdentifierHash identifier = IdentifierHash{}); + + RequestResult activate(score::cpp::stop_token stop_token) override; + RequestResult deactivate(score::cpp::stop_token stop_token) override; + RequestResult tryHandleTermination(int32_t status) override; + [[nodiscard]] IdentifierHash getIdentifier() const override; + [[nodiscard]] bool active() const override; + + private: + std::optional handle_; + const IStartAction* start_action_; + const IStopAction* stop_action_; + const std::vector ready_conditions_; + IdentifierHash identifier_; +}; + +} // namespace score::mw::lifecycle::internal + +#endif // SCORE_LCM_COMPONENT_HPP_INCLUDED diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/component_UT.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/component_UT.cpp new file mode 100644 index 0000000000..32f443eed9 --- /dev/null +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/component_UT.cpp @@ -0,0 +1,110 @@ +/******************************************************************************** + * 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.hpp" +#include "score/mw/launch_manager/process_group_manager/details/mock_ready_condition.hpp" +#include "score/mw/launch_manager/process_group_manager/details/mock_start_action.hpp" +#include "score/mw/launch_manager/process_group_manager/details/mock_stop_action.hpp" +#include +#include + +namespace score::mw::lifecycle::internal +{ +namespace +{ + +using namespace ::testing; + +const ProcessHandle mock_handle = ProcessHandle{42}; + +void expect_mock_handle(const Handle handle) +{ + EXPECT_EQ(std::get(handle).pid, mock_handle.pid); +} + +TEST(ComponentTest, StartSucceeds) +{ + MockStartAction start_action; + MockStopAction stop_action; + Component component(&start_action, &stop_action, {}); + + const auto result = component.activate(score::cpp::stop_token{}); + + EXPECT_TRUE(result.has_value()); +} + +TEST(ComponentTest, StartActionCalled) +{ + MockStartAction start_action; + MockStopAction stop_action; + Component component(&start_action, &stop_action, {}); + + EXPECT_CALL(start_action, start()).WillOnce(Return(mock_handle)); + + static_cast(component.activate(score::cpp::stop_token{})); +} + +TEST(ComponentTest, StopActionCalled) +{ + MockStartAction start_action; + MockStopAction stop_action; + Component component(&start_action, &stop_action, {}); + + ON_CALL(start_action, start()).WillByDefault(Return(mock_handle)); + EXPECT_CALL(stop_action, stop(_)).WillOnce(Invoke(expect_mock_handle)); + + static_cast(component.activate(score::cpp::stop_token{})); + component.deactivate(score::cpp::stop_token{}); +} + +TEST(ComponentTest, ReadyConditionsCalled) +{ + MockStartAction start_action; + MockStopAction stop_action; + MockReadyCondition ready_condition_1; + MockReadyCondition ready_condition_2; + Component component(&start_action, &stop_action, {&ready_condition_1, &ready_condition_2}); + + InSequence sequence; + ON_CALL(start_action, start()).WillByDefault(Return(mock_handle)); + EXPECT_CALL(ready_condition_1, wait(_)).WillOnce(Invoke(expect_mock_handle)); + EXPECT_CALL(ready_condition_2, wait(_)).WillOnce(Invoke(expect_mock_handle)); + + static_cast(component.activate(score::cpp::stop_token{})); +} + +TEST(ComponentTest, StartSetsActive) +{ + MockStartAction start_action; + MockStopAction stop_action; + Component component(&start_action, &stop_action, {}); + + static_cast(component.activate(score::cpp::stop_token{})); + + EXPECT_TRUE(component.active()); +} + +TEST(ComponentTest, StopClearsActive) +{ + MockStartAction start_action; + MockStopAction stop_action; + Component component(&start_action, &stop_action, {}); + + static_cast(component.activate(score::cpp::stop_token{})); + component.deactivate(score::cpp::stop_token{}); + + EXPECT_FALSE(component.active()); +} + +} // namespace +} // namespace score::mw::lifecycle::internal diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/handle.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/handle.hpp new file mode 100644 index 0000000000..1a519bf0fc --- /dev/null +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/handle.hpp @@ -0,0 +1,36 @@ +/******************************************************************************** + * 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_HANDLE_HPP_INCLUDED +#define SCORE_LCM_HANDLE_HPP_INCLUDED + +#include +#include + +namespace score::mw::lifecycle::internal +{ + +struct EmptyHandle +{ +}; + +struct ProcessHandle +{ + pid_t pid; +}; + +using Handle = std::variant; + +} // namespace score::mw::lifecycle::internal + +#endif // SCORE_LCM_HANDLE_HPP_INCLUDED diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/iready_condition.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/iready_condition.hpp new file mode 100644 index 0000000000..60f94a7519 --- /dev/null +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/iready_condition.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_IREADY_CONDITION_HPP_INCLUDED +#define SCORE_LCM_IREADY_CONDITION_HPP_INCLUDED + +#include "score/mw/launch_manager/process_group_manager/details/handle.hpp" + +namespace score::mw::lifecycle::internal +{ + +class IReadyCondition +{ + public: + virtual ~IReadyCondition() = default; + virtual void wait(const Handle handle) const = 0; +}; + +} // namespace score::mw::lifecycle::internal + +#endif // SCORE_LCM_IREADY_CONDITION_HPP_INCLUDED diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/istart_action.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/istart_action.hpp new file mode 100644 index 0000000000..81a6e94f83 --- /dev/null +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/istart_action.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_ISTART_ACTION_HPP_INCLUDED +#define SCORE_LCM_ISTART_ACTION_HPP_INCLUDED + +#include "score/mw/launch_manager/process_group_manager/details/handle.hpp" + +namespace score::mw::lifecycle::internal +{ + +class IStartAction +{ + public: + virtual ~IStartAction() = default; + virtual Handle start() const = 0; +}; + +} // namespace score::mw::lifecycle::internal + +#endif // SCORE_LCM_ISTART_ACTION_HPP_INCLUDED diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/istop_action.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/istop_action.hpp new file mode 100644 index 0000000000..8109a391c4 --- /dev/null +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/istop_action.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_ISTOP_ACTION_HPP_INCLUDED +#define SCORE_LCM_ISTOP_ACTION_HPP_INCLUDED + +#include "score/mw/launch_manager/process_group_manager/details/handle.hpp" + +namespace score::mw::lifecycle::internal +{ + +class IStopAction +{ + public: + virtual ~IStopAction() = default; + virtual void stop(const Handle handle) const = 0; +}; + +} // namespace score::mw::lifecycle::internal + +#endif // SCORE_LCM_ISTOP_ACTION_HPP_INCLUDED diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/mock_ready_condition.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/mock_ready_condition.hpp new file mode 100644 index 0000000000..b9f93fc517 --- /dev/null +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/mock_ready_condition.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 MOCK_READY_CONDITION_HPP_INCLUDED +#define MOCK_READY_CONDITION_HPP_INCLUDED + +#include "score/mw/launch_manager/process_group_manager/details/iready_condition.hpp" +#include + +namespace score::mw::lifecycle::internal +{ + +class MockReadyCondition : public IReadyCondition +{ + public: + MOCK_METHOD(void, wait, (const Handle handle), (override, const)); +}; + +} // namespace score::mw::lifecycle::internal + +#endif // MOCK_READY_CONDITION_HPP_INCLUDED diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/mock_start_action.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/mock_start_action.hpp new file mode 100644 index 0000000000..b1e20144ce --- /dev/null +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/mock_start_action.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 MOCK_START_ACTION_HPP_INCLUDED +#define MOCK_START_ACTION_HPP_INCLUDED + +#include "score/mw/launch_manager/process_group_manager/details/istart_action.hpp" +#include + +namespace score::mw::lifecycle::internal +{ + +class MockStartAction : public IStartAction +{ + public: + MOCK_METHOD(Handle, start, (), (override, const)); +}; + +} // namespace score::mw::lifecycle::internal + +#endif // MOCK_START_ACTION_HPP_INCLUDED diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/mock_stop_action.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/mock_stop_action.hpp new file mode 100644 index 0000000000..392e9b96e0 --- /dev/null +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/mock_stop_action.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 MOCK_STOP_ACTION_HPP_INCLUDED +#define MOCK_STOP_ACTION_HPP_INCLUDED + +#include "score/mw/launch_manager/process_group_manager/details/istop_action.hpp" +#include + +namespace score::mw::lifecycle::internal +{ + +class MockStopAction : public IStopAction +{ + public: + MOCK_METHOD(void, stop, (const Handle handle), (override, const)); +}; + +} // namespace score::mw::lifecycle::internal + +#endif // MOCK_STOP_ACTION_HPP_INCLUDED From 125fa2eac374e3ee93e43c870ba1e5b21abc02f8 Mon Sep 17 00:00:00 2001 From: Daniel Thwaites Date: Fri, 25 Sep 2026 11:25:06 +0100 Subject: [PATCH 02/36] Move start action, stop action, ready condition to subfolders --- .../src/process_group_manager/details/BUILD | 78 ++----------------- .../details/component.hpp | 6 +- .../details/component_UT.cpp | 6 +- .../details/ready_condition/BUILD | 34 ++++++++ .../iready_condition.hpp | 0 .../mock_ready_condition.hpp | 2 +- .../details/start_action/BUILD | 34 ++++++++ .../{ => start_action}/istart_action.hpp | 0 .../{ => start_action}/mock_start_action.hpp | 2 +- .../details/stop_action/BUILD | 34 ++++++++ .../{ => stop_action}/istop_action.hpp | 0 .../{ => stop_action}/mock_stop_action.hpp | 2 +- 12 files changed, 117 insertions(+), 81 deletions(-) create mode 100644 score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/BUILD rename score/launch_manager/src/daemon/src/process_group_manager/details/{ => ready_condition}/iready_condition.hpp (100%) rename score/launch_manager/src/daemon/src/process_group_manager/details/{ => ready_condition}/mock_ready_condition.hpp (96%) create mode 100644 score/launch_manager/src/daemon/src/process_group_manager/details/start_action/BUILD rename score/launch_manager/src/daemon/src/process_group_manager/details/{ => start_action}/istart_action.hpp (100%) rename score/launch_manager/src/daemon/src/process_group_manager/details/{ => start_action}/mock_start_action.hpp (96%) create mode 100644 score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/BUILD rename score/launch_manager/src/daemon/src/process_group_manager/details/{ => stop_action}/istop_action.hpp (100%) rename score/launch_manager/src/daemon/src/process_group_manager/details/{ => stop_action}/mock_stop_action.hpp (97%) 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 855e8dfaa8..a1185d27e1 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 @@ -23,9 +23,9 @@ cc_library( visibility = ["//score/launch_manager/src/daemon/src/process_group_manager:__subpackages__"], deps = [ ":icomponent", - ":iready_condition", - ":istart_action", - ":istop_action", + "//score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition:iready_condition", + "//score/launch_manager/src/daemon/src/process_group_manager/details/start_action:istart_action", + "//score/launch_manager/src/daemon/src/process_group_manager/details/stop_action:istop_action", ], ) @@ -35,9 +35,9 @@ lm_cc_test( deps = [ ":component", ":mock_component", - ":mock_ready_condition", - ":mock_start_action", - ":mock_stop_action", + "//score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition:mock_ready_condition", + "//score/launch_manager/src/daemon/src/process_group_manager/details/start_action:mock_start_action", + "//score/launch_manager/src/daemon/src/process_group_manager/details/stop_action:mock_stop_action", "@googletest//:gtest_main", ], ) @@ -85,33 +85,6 @@ cc_library( visibility = ["//score/launch_manager/src/daemon/src/process_group_manager:__subpackages__"], ) -cc_library( - name = "istart_action", - hdrs = ["istart_action.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:__subpackages__"], - deps = [":handle"], -) - -cc_library( - name = "istop_action", - hdrs = ["istop_action.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:__subpackages__"], - deps = [":handle"], -) - -cc_library( - name = "iready_condition", - hdrs = ["iready_condition.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:__subpackages__"], - deps = [":handle"], -) - cc_library( name = "mock_component", testonly = True, @@ -125,45 +98,6 @@ cc_library( ], ) -cc_library( - name = "mock_start_action", - testonly = True, - hdrs = ["mock_start_action.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:__subpackages__"], - deps = [ - ":istart_action", - "@googletest//:gtest_main", - ], -) - -cc_library( - name = "mock_stop_action", - testonly = True, - hdrs = ["mock_stop_action.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:__subpackages__"], - deps = [ - ":istop_action", - "@googletest//:gtest_main", - ], -) - -cc_library( - name = "mock_ready_condition", - testonly = True, - hdrs = ["mock_ready_condition.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:__subpackages__"], - deps = [ - ":iready_condition", - "@googletest//:gtest_main", - ], -) - cc_library( name = "mock_component_event_queue", testonly = True, diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/component.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/component.hpp index 1195637334..1a4ccb68f4 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/component.hpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/component.hpp @@ -15,9 +15,9 @@ #define SCORE_LCM_COMPONENT_HPP_INCLUDED #include "score/mw/launch_manager/process_group_manager/details/icomponent.hpp" -#include "score/mw/launch_manager/process_group_manager/details/iready_condition.hpp" -#include "score/mw/launch_manager/process_group_manager/details/istart_action.hpp" -#include "score/mw/launch_manager/process_group_manager/details/istop_action.hpp" +#include "score/mw/launch_manager/process_group_manager/details/ready_condition/iready_condition.hpp" +#include "score/mw/launch_manager/process_group_manager/details/start_action/istart_action.hpp" +#include "score/mw/launch_manager/process_group_manager/details/stop_action/istop_action.hpp" #include namespace score::mw::lifecycle::internal diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/component_UT.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/component_UT.cpp index 32f443eed9..d93d80f0de 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/component_UT.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/component_UT.cpp @@ -12,9 +12,9 @@ ********************************************************************************/ #include "score/mw/launch_manager/process_group_manager/details/component.hpp" -#include "score/mw/launch_manager/process_group_manager/details/mock_ready_condition.hpp" -#include "score/mw/launch_manager/process_group_manager/details/mock_start_action.hpp" -#include "score/mw/launch_manager/process_group_manager/details/mock_stop_action.hpp" +#include "score/mw/launch_manager/process_group_manager/details/ready_condition/mock_ready_condition.hpp" +#include "score/mw/launch_manager/process_group_manager/details/start_action/mock_start_action.hpp" +#include "score/mw/launch_manager/process_group_manager/details/stop_action/mock_stop_action.hpp" #include #include diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/BUILD b/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/BUILD new file mode 100644 index 0000000000..2f7376a6cd --- /dev/null +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/BUILD @@ -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 +# ******************************************************************************* + +cc_library( + name = "iready_condition", + hdrs = ["iready_condition.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:__subpackages__"], + deps = ["//score/launch_manager/src/daemon/src/process_group_manager/details:handle"], +) + +cc_library( + name = "mock_ready_condition", + testonly = True, + hdrs = ["mock_ready_condition.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:__subpackages__"], + deps = [ + ":iready_condition", + "@googletest//:gtest_main", + ], +) diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/iready_condition.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/iready_condition.hpp similarity index 100% rename from score/launch_manager/src/daemon/src/process_group_manager/details/iready_condition.hpp rename to score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/iready_condition.hpp diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/mock_ready_condition.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/mock_ready_condition.hpp similarity index 96% rename from score/launch_manager/src/daemon/src/process_group_manager/details/mock_ready_condition.hpp rename to score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/mock_ready_condition.hpp index b9f93fc517..e19d027412 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/mock_ready_condition.hpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/mock_ready_condition.hpp @@ -14,7 +14,7 @@ #ifndef MOCK_READY_CONDITION_HPP_INCLUDED #define MOCK_READY_CONDITION_HPP_INCLUDED -#include "score/mw/launch_manager/process_group_manager/details/iready_condition.hpp" +#include "score/mw/launch_manager/process_group_manager/details/ready_condition/iready_condition.hpp" #include namespace score::mw::lifecycle::internal diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/start_action/BUILD b/score/launch_manager/src/daemon/src/process_group_manager/details/start_action/BUILD new file mode 100644 index 0000000000..9feb561869 --- /dev/null +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/start_action/BUILD @@ -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 +# ******************************************************************************* + +cc_library( + name = "istart_action", + hdrs = ["istart_action.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:__subpackages__"], + deps = ["//score/launch_manager/src/daemon/src/process_group_manager/details:handle"], +) + +cc_library( + name = "mock_start_action", + testonly = True, + hdrs = ["mock_start_action.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:__subpackages__"], + deps = [ + ":istart_action", + "@googletest//:gtest_main", + ], +) diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/istart_action.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/start_action/istart_action.hpp similarity index 100% rename from score/launch_manager/src/daemon/src/process_group_manager/details/istart_action.hpp rename to score/launch_manager/src/daemon/src/process_group_manager/details/start_action/istart_action.hpp diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/mock_start_action.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/start_action/mock_start_action.hpp similarity index 96% rename from score/launch_manager/src/daemon/src/process_group_manager/details/mock_start_action.hpp rename to score/launch_manager/src/daemon/src/process_group_manager/details/start_action/mock_start_action.hpp index b1e20144ce..dadd15cb5c 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/mock_start_action.hpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/start_action/mock_start_action.hpp @@ -14,7 +14,7 @@ #ifndef MOCK_START_ACTION_HPP_INCLUDED #define MOCK_START_ACTION_HPP_INCLUDED -#include "score/mw/launch_manager/process_group_manager/details/istart_action.hpp" +#include "score/mw/launch_manager/process_group_manager/details/start_action/istart_action.hpp" #include namespace score::mw::lifecycle::internal diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/BUILD b/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/BUILD new file mode 100644 index 0000000000..de5b5a4cb7 --- /dev/null +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/BUILD @@ -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 +# ******************************************************************************* + +cc_library( + name = "istop_action", + hdrs = ["istop_action.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:__subpackages__"], + deps = ["//score/launch_manager/src/daemon/src/process_group_manager/details:handle"], +) + +cc_library( + name = "mock_stop_action", + testonly = True, + hdrs = ["mock_stop_action.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:__subpackages__"], + deps = [ + ":istop_action", + "@googletest//:gtest_main", + ], +) diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/istop_action.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/istop_action.hpp similarity index 100% rename from score/launch_manager/src/daemon/src/process_group_manager/details/istop_action.hpp rename to score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/istop_action.hpp diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/mock_stop_action.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/mock_stop_action.hpp similarity index 97% rename from score/launch_manager/src/daemon/src/process_group_manager/details/mock_stop_action.hpp rename to score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/mock_stop_action.hpp index 392e9b96e0..5073a1aefe 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/mock_stop_action.hpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/mock_stop_action.hpp @@ -14,7 +14,7 @@ #ifndef MOCK_STOP_ACTION_HPP_INCLUDED #define MOCK_STOP_ACTION_HPP_INCLUDED -#include "score/mw/launch_manager/process_group_manager/details/istop_action.hpp" +#include "score/mw/launch_manager/process_group_manager/details/stop_action/istop_action.hpp" #include namespace score::mw::lifecycle::internal From 6964d4a4f519c780f10b84445b4ed4dd14a4ef99 Mon Sep 17 00:00:00 2001 From: Daniel Thwaites Date: Fri, 25 Sep 2026 12:40:02 +0100 Subject: [PATCH 03/36] Add force stop actions This will be needed to remove the direct use of process identifiers in `Graph::forceKillProcesses`, making it flexible to other kinds of components. --- .../src/process_group_manager/details/BUILD | 2 ++ .../details/component.cpp | 2 ++ .../details/component.hpp | 3 ++ .../details/component_UT.cpp | 32 +++++++++++++---- .../details/force_stop_action/BUILD | 34 +++++++++++++++++++ .../force_stop_action/iforce_stop_action.hpp | 31 +++++++++++++++++ .../mock_force_stop_action.hpp | 31 +++++++++++++++++ 7 files changed, 129 insertions(+), 6 deletions(-) create mode 100644 score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/BUILD create mode 100644 score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/iforce_stop_action.hpp create mode 100644 score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/mock_force_stop_action.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 a1185d27e1..a16df2eb81 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 @@ -23,6 +23,7 @@ cc_library( visibility = ["//score/launch_manager/src/daemon/src/process_group_manager:__subpackages__"], deps = [ ":icomponent", + "//score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action:iforce_stop_action", "//score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition:iready_condition", "//score/launch_manager/src/daemon/src/process_group_manager/details/start_action:istart_action", "//score/launch_manager/src/daemon/src/process_group_manager/details/stop_action:istop_action", @@ -35,6 +36,7 @@ lm_cc_test( deps = [ ":component", ":mock_component", + "//score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action:mock_force_stop_action", "//score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition:mock_ready_condition", "//score/launch_manager/src/daemon/src/process_group_manager/details/start_action:mock_start_action", "//score/launch_manager/src/daemon/src/process_group_manager/details/stop_action:mock_stop_action", diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/component.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/component.cpp index ab7a7873bf..08b4763404 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/component.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/component.cpp @@ -19,11 +19,13 @@ namespace score::mw::lifecycle::internal Component::Component( const IStartAction* start_action, const IStopAction* stop_action, + const IForceStopAction* force_stop_action, const std::vector ready_conditions, IdentifierHash identifier) : handle_(EmptyHandle{}), start_action_(start_action), stop_action_(stop_action), + force_stop_action_(force_stop_action), ready_conditions_(ready_conditions), identifier_(identifier) { diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/component.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/component.hpp index 1a4ccb68f4..0b0c54abe6 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/component.hpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/component.hpp @@ -14,6 +14,7 @@ #ifndef SCORE_LCM_COMPONENT_HPP_INCLUDED #define SCORE_LCM_COMPONENT_HPP_INCLUDED +#include "score/mw/launch_manager/process_group_manager/details/force_stop_action/iforce_stop_action.hpp" #include "score/mw/launch_manager/process_group_manager/details/icomponent.hpp" #include "score/mw/launch_manager/process_group_manager/details/ready_condition/iready_condition.hpp" #include "score/mw/launch_manager/process_group_manager/details/start_action/istart_action.hpp" @@ -29,6 +30,7 @@ class Component final : public IComponent Component( const IStartAction* start_action, const IStopAction* stop_action, + const IForceStopAction* force_stop_action, const std::vector ready_conditions, IdentifierHash identifier = IdentifierHash{}); @@ -42,6 +44,7 @@ class Component final : public IComponent std::optional handle_; const IStartAction* start_action_; const IStopAction* stop_action_; + const IForceStopAction* force_stop_action_; const std::vector ready_conditions_; IdentifierHash identifier_; }; diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/component_UT.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/component_UT.cpp index d93d80f0de..1528c7cba5 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/component_UT.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/component_UT.cpp @@ -12,6 +12,7 @@ ********************************************************************************/ #include "score/mw/launch_manager/process_group_manager/details/component.hpp" +#include "score/mw/launch_manager/process_group_manager/details/force_stop_action/mock_force_stop_action.hpp" #include "score/mw/launch_manager/process_group_manager/details/ready_condition/mock_ready_condition.hpp" #include "score/mw/launch_manager/process_group_manager/details/start_action/mock_start_action.hpp" #include "score/mw/launch_manager/process_group_manager/details/stop_action/mock_stop_action.hpp" @@ -36,7 +37,8 @@ TEST(ComponentTest, StartSucceeds) { MockStartAction start_action; MockStopAction stop_action; - Component component(&start_action, &stop_action, {}); + MockForceStopAction force_stop_action; + Component component(&start_action, &stop_action, &force_stop_action, {}); const auto result = component.activate(score::cpp::stop_token{}); @@ -47,7 +49,8 @@ TEST(ComponentTest, StartActionCalled) { MockStartAction start_action; MockStopAction stop_action; - Component component(&start_action, &stop_action, {}); + MockForceStopAction force_stop_action; + Component component(&start_action, &stop_action, &force_stop_action, {}); EXPECT_CALL(start_action, start()).WillOnce(Return(mock_handle)); @@ -58,7 +61,8 @@ TEST(ComponentTest, StopActionCalled) { MockStartAction start_action; MockStopAction stop_action; - Component component(&start_action, &stop_action, {}); + MockForceStopAction force_stop_action; + Component component(&start_action, &stop_action, &force_stop_action, {}); ON_CALL(start_action, start()).WillByDefault(Return(mock_handle)); EXPECT_CALL(stop_action, stop(_)).WillOnce(Invoke(expect_mock_handle)); @@ -73,7 +77,8 @@ TEST(ComponentTest, ReadyConditionsCalled) MockStopAction stop_action; MockReadyCondition ready_condition_1; MockReadyCondition ready_condition_2; - Component component(&start_action, &stop_action, {&ready_condition_1, &ready_condition_2}); + MockForceStopAction force_stop_action; + Component component(&start_action, &stop_action, &force_stop_action, {&ready_condition_1, &ready_condition_2}); InSequence sequence; ON_CALL(start_action, start()).WillByDefault(Return(mock_handle)); @@ -87,7 +92,8 @@ TEST(ComponentTest, StartSetsActive) { MockStartAction start_action; MockStopAction stop_action; - Component component(&start_action, &stop_action, {}); + MockForceStopAction force_stop_action; + Component component(&start_action, &stop_action, &force_stop_action, {}); static_cast(component.activate(score::cpp::stop_token{})); @@ -98,7 +104,21 @@ TEST(ComponentTest, StopClearsActive) { MockStartAction start_action; MockStopAction stop_action; - Component component(&start_action, &stop_action, {}); + MockForceStopAction force_stop_action; + Component component(&start_action, &stop_action, &force_stop_action, {}); + + static_cast(component.activate(score::cpp::stop_token{})); + component.deactivate(score::cpp::stop_token{}); + + EXPECT_FALSE(component.active()); +} + +TEST(ComponentTest, ForceStopClearsActive) +{ + MockStartAction start_action; + MockStopAction stop_action; + MockForceStopAction force_stop_action; + Component component(&start_action, &stop_action, &force_stop_action, {}); static_cast(component.activate(score::cpp::stop_token{})); component.deactivate(score::cpp::stop_token{}); diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/BUILD b/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/BUILD new file mode 100644 index 0000000000..216bda6cea --- /dev/null +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/BUILD @@ -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 +# ******************************************************************************* + +cc_library( + name = "iforce_stop_action", + hdrs = ["iforce_stop_action.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:__subpackages__"], + deps = ["//score/launch_manager/src/daemon/src/process_group_manager/details:handle"], +) + +cc_library( + name = "mock_force_stop_action", + testonly = True, + hdrs = ["mock_force_stop_action.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:__subpackages__"], + deps = [ + ":iforce_stop_action", + "@googletest//:gtest_main", + ], +) diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/iforce_stop_action.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/iforce_stop_action.hpp new file mode 100644 index 0000000000..e3e1c3f3a3 --- /dev/null +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/iforce_stop_action.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_IFORCE_STOP_ACTION_HPP_INCLUDED +#define SCORE_LCM_IFORCE_STOP_ACTION_HPP_INCLUDED + +#include "score/mw/launch_manager/process_group_manager/details/handle.hpp" + +namespace score::mw::lifecycle::internal +{ + +class IForceStopAction +{ + public: + virtual ~IForceStopAction() = default; + virtual void force_stop(const Handle handle) const = 0; +}; + +} // namespace score::mw::lifecycle::internal + +#endif // SCORE_LCM_IFORCE_STOP_ACTION_HPP_INCLUDED diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/mock_force_stop_action.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/mock_force_stop_action.hpp new file mode 100644 index 0000000000..426476a0a1 --- /dev/null +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/mock_force_stop_action.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 MOCK_FORCE_STOP_ACTION_HPP_INCLUDED +#define MOCK_FORCE_STOP_ACTION_HPP_INCLUDED + +#include "score/mw/launch_manager/process_group_manager/details/force_stop_action/iforce_stop_action.hpp" +#include + +namespace score::mw::lifecycle::internal +{ + +class MockForceStopAction : public IForceStopAction +{ + public: + MOCK_METHOD(void, force_stop, (const Handle handle), (override, const)); +}; + +} // namespace score::mw::lifecycle::internal + +#endif // MOCK_FORCE_STOP_ACTION_HPP_INCLUDED From e5cb0c08bc3863d1d430cd9256b578cb0951db25 Mon Sep 17 00:00:00 2001 From: Daniel Thwaites Date: Mon, 28 Sep 2026 11:08:04 +0100 Subject: [PATCH 04/36] Add empty actions These will be used for run targets, which do not contain any underlying process. --- .../details/force_stop_action/BUILD | 10 +++++++ .../empty_force_stop_action.cpp | 23 ++++++++++++++ .../empty_force_stop_action.hpp | 30 +++++++++++++++++++ .../details/start_action/BUILD | 10 +++++++ .../start_action/empty_start_action.cpp | 24 +++++++++++++++ .../start_action/empty_start_action.hpp | 30 +++++++++++++++++++ .../details/stop_action/BUILD | 10 +++++++ .../details/stop_action/empty_stop_action.cpp | 23 ++++++++++++++ .../details/stop_action/empty_stop_action.hpp | 30 +++++++++++++++++++ 9 files changed, 190 insertions(+) create mode 100644 score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/empty_force_stop_action.cpp create mode 100644 score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/empty_force_stop_action.hpp create mode 100644 score/launch_manager/src/daemon/src/process_group_manager/details/start_action/empty_start_action.cpp create mode 100644 score/launch_manager/src/daemon/src/process_group_manager/details/start_action/empty_start_action.hpp create mode 100644 score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/empty_stop_action.cpp create mode 100644 score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/empty_stop_action.hpp diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/BUILD b/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/BUILD index 216bda6cea..d70b5064cd 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/BUILD +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/BUILD @@ -32,3 +32,13 @@ cc_library( "@googletest//:gtest_main", ], ) + +cc_library( + name = "empty_force_stop_action", + srcs = ["empty_force_stop_action.cpp"], + hdrs = ["empty_force_stop_action.hpp"], + include_prefix = "score/mw/launch_manager/process_group_manager/details/force_stop_action", + strip_include_prefix = "/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action", + visibility = ["//score/launch_manager/src/daemon/src/process_group_manager:__subpackages__"], + deps = [":iforce_stop_action"], +) diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/empty_force_stop_action.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/empty_force_stop_action.cpp new file mode 100644 index 0000000000..eb8b039e72 --- /dev/null +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/empty_force_stop_action.cpp @@ -0,0 +1,23 @@ +/******************************************************************************** + * 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/force_stop_action/empty_force_stop_action.hpp" + +namespace score::mw::lifecycle::internal +{ + +void EmptyForceStopAction::force_stop(const Handle) const +{ +} + +} // namespace score::mw::lifecycle::internal diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/empty_force_stop_action.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/empty_force_stop_action.hpp new file mode 100644 index 0000000000..5673346a43 --- /dev/null +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/empty_force_stop_action.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 SCORE_LCM_EMPTY_FORCE_STOP_ACTION_HPP_INCLUDED +#define SCORE_LCM_EMPTY_FORCE_STOP_ACTION_HPP_INCLUDED + +#include "score/mw/launch_manager/process_group_manager/details/force_stop_action/iforce_stop_action.hpp" + +namespace score::mw::lifecycle::internal +{ + +class EmptyForceStopAction final : public IForceStopAction +{ + public: + void force_stop(const Handle) const override; +}; + +} // namespace score::mw::lifecycle::internal + +#endif // SCORE_LCM_EMPTY_FORCE_STOP_ACTION_HPP_INCLUDED diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/start_action/BUILD b/score/launch_manager/src/daemon/src/process_group_manager/details/start_action/BUILD index 9feb561869..ed5270eddf 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/start_action/BUILD +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/start_action/BUILD @@ -32,3 +32,13 @@ cc_library( "@googletest//:gtest_main", ], ) + +cc_library( + name = "empty_start_action", + srcs = ["empty_start_action.cpp"], + hdrs = ["empty_start_action.hpp"], + include_prefix = "score/mw/launch_manager/process_group_manager/details/start_action", + strip_include_prefix = "/score/launch_manager/src/daemon/src/process_group_manager/details/start_action", + visibility = ["//score/launch_manager/src/daemon/src/process_group_manager:__subpackages__"], + deps = [":istart_action"], +) diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/start_action/empty_start_action.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/start_action/empty_start_action.cpp new file mode 100644 index 0000000000..3b2d0aa970 --- /dev/null +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/start_action/empty_start_action.cpp @@ -0,0 +1,24 @@ +/******************************************************************************** + * 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/start_action/empty_start_action.hpp" + +namespace score::mw::lifecycle::internal +{ + +Handle EmptyStartAction::start() const +{ + return EmptyHandle{}; +} + +} // namespace score::mw::lifecycle::internal diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/start_action/empty_start_action.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/start_action/empty_start_action.hpp new file mode 100644 index 0000000000..83d4f71741 --- /dev/null +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/start_action/empty_start_action.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 SCORE_LCM_EMPTY_START_ACTION_HPP_INCLUDED +#define SCORE_LCM_EMPTY_START_ACTION_HPP_INCLUDED + +#include "score/mw/launch_manager/process_group_manager/details/start_action/istart_action.hpp" + +namespace score::mw::lifecycle::internal +{ + +class EmptyStartAction final : public IStartAction +{ + public: + Handle start() const override; +}; + +} // namespace score::mw::lifecycle::internal + +#endif // SCORE_LCM_EMPTY_START_ACTION_HPP_INCLUDED diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/BUILD b/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/BUILD index de5b5a4cb7..43bce95780 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/BUILD +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/BUILD @@ -32,3 +32,13 @@ cc_library( "@googletest//:gtest_main", ], ) + +cc_library( + name = "empty_stop_action", + srcs = ["empty_stop_action.cpp"], + hdrs = ["empty_stop_action.hpp"], + include_prefix = "score/mw/launch_manager/process_group_manager/details/stop_action", + strip_include_prefix = "/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action", + visibility = ["//score/launch_manager/src/daemon/src/process_group_manager:__subpackages__"], + deps = [":istop_action"], +) diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/empty_stop_action.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/empty_stop_action.cpp new file mode 100644 index 0000000000..754ef26cbf --- /dev/null +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/empty_stop_action.cpp @@ -0,0 +1,23 @@ +/******************************************************************************** + * 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/stop_action/empty_stop_action.hpp" + +namespace score::mw::lifecycle::internal +{ + +void EmptyStopAction::stop(const Handle) const +{ +} + +} // namespace score::mw::lifecycle::internal diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/empty_stop_action.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/empty_stop_action.hpp new file mode 100644 index 0000000000..8bf3b7ab40 --- /dev/null +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/empty_stop_action.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 SCORE_LCM_EMPTY_STOP_ACTION_HPP_INCLUDED +#define SCORE_LCM_EMPTY_STOP_ACTION_HPP_INCLUDED + +#include "score/mw/launch_manager/process_group_manager/details/stop_action/istop_action.hpp" + +namespace score::mw::lifecycle::internal +{ + +class EmptyStopAction final : public IStopAction +{ + public: + void stop(const Handle) const override; +}; + +} // namespace score::mw::lifecycle::internal + +#endif // SCORE_LCM_EMPTY_STOP_ACTION_HPP_INCLUDED From 963c385a7d02933d205bf1d91a79b22468b3ba5b Mon Sep 17 00:00:00 2001 From: Daniel Thwaites Date: Tue, 29 Sep 2026 17:05:21 +0100 Subject: [PATCH 05/36] Allow actions to return an error code --- .../details/component.cpp | 19 ++++++++++++++++--- .../details/component_UT.cpp | 12 ++++++------ .../details/force_stop_action/BUILD | 5 ++++- .../empty_force_stop_action.cpp | 3 ++- .../empty_force_stop_action.hpp | 2 +- .../force_stop_action/iforce_stop_action.hpp | 3 ++- .../mock_force_stop_action.hpp | 2 +- .../details/ready_condition/BUILD | 5 ++++- .../ready_condition/iready_condition.hpp | 3 ++- .../ready_condition/mock_ready_condition.hpp | 2 +- .../details/start_action/BUILD | 5 ++++- .../start_action/empty_start_action.cpp | 2 +- .../start_action/empty_start_action.hpp | 2 +- .../details/start_action/istart_action.hpp | 3 ++- .../start_action/mock_start_action.hpp | 2 +- .../details/stop_action/BUILD | 5 ++++- .../details/stop_action/empty_stop_action.cpp | 3 ++- .../details/stop_action/empty_stop_action.hpp | 2 +- .../details/stop_action/istop_action.hpp | 3 ++- .../details/stop_action/mock_stop_action.hpp | 2 +- 20 files changed, 58 insertions(+), 27 deletions(-) diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/component.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/component.cpp index 08b4763404..da2e1ed9be 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/component.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/component.cpp @@ -33,11 +33,21 @@ Component::Component( IComponent::RequestResult Component::activate(score::cpp::stop_token stop_token) { - handle_ = start_action_->start(); + const auto start_result = start_action_->start(); + if (!start_result.has_value()) + { + return cpp::make_unexpected(ComponentError::kErrorBeforeReady); + } + handle_ = start_result.value(); + for (const IReadyCondition* ready_condition : ready_conditions_) { - ready_condition->wait(handle_.value()); + if (!ready_condition->wait(handle_.value()).has_value()) + { + return cpp::make_unexpected(ComponentError::kErrorBeforeReady); + } } + return RequestState::kSuccess; } @@ -45,7 +55,10 @@ IComponent::RequestResult Component::deactivate(score::cpp::stop_token stop_toke { if (handle_.has_value()) { - stop_action_->stop(handle_.value()); + if (!stop_action_->stop(handle_.value()).has_value()) + { + return cpp::make_unexpected(ComponentError::kErrorAfterReady); + } } handle_ = std::nullopt; return RequestState::kSuccess; diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/component_UT.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/component_UT.cpp index 1528c7cba5..2d650c6731 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/component_UT.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/component_UT.cpp @@ -52,7 +52,7 @@ TEST(ComponentTest, StartActionCalled) MockForceStopAction force_stop_action; Component component(&start_action, &stop_action, &force_stop_action, {}); - EXPECT_CALL(start_action, start()).WillOnce(Return(mock_handle)); + EXPECT_CALL(start_action, start()).WillOnce(Return(Result{mock_handle})); static_cast(component.activate(score::cpp::stop_token{})); } @@ -64,8 +64,8 @@ TEST(ComponentTest, StopActionCalled) MockForceStopAction force_stop_action; Component component(&start_action, &stop_action, &force_stop_action, {}); - ON_CALL(start_action, start()).WillByDefault(Return(mock_handle)); - EXPECT_CALL(stop_action, stop(_)).WillOnce(Invoke(expect_mock_handle)); + ON_CALL(start_action, start()).WillByDefault(Return(Result{mock_handle})); + EXPECT_CALL(stop_action, stop(_)).WillOnce(DoAll(Invoke(expect_mock_handle), Return(Result{}))); static_cast(component.activate(score::cpp::stop_token{})); component.deactivate(score::cpp::stop_token{}); @@ -81,9 +81,9 @@ TEST(ComponentTest, ReadyConditionsCalled) Component component(&start_action, &stop_action, &force_stop_action, {&ready_condition_1, &ready_condition_2}); InSequence sequence; - ON_CALL(start_action, start()).WillByDefault(Return(mock_handle)); - EXPECT_CALL(ready_condition_1, wait(_)).WillOnce(Invoke(expect_mock_handle)); - EXPECT_CALL(ready_condition_2, wait(_)).WillOnce(Invoke(expect_mock_handle)); + ON_CALL(start_action, start()).WillByDefault(Return(Result{mock_handle})); + EXPECT_CALL(ready_condition_1, wait(_)).WillOnce(DoAll(Invoke(expect_mock_handle), Return(Result{}))); + EXPECT_CALL(ready_condition_2, wait(_)).WillOnce(DoAll(Invoke(expect_mock_handle), Return(Result{}))); static_cast(component.activate(score::cpp::stop_token{})); } diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/BUILD b/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/BUILD index d70b5064cd..4863dacb35 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/BUILD +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/BUILD @@ -17,7 +17,10 @@ cc_library( 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:__subpackages__"], - deps = ["//score/launch_manager/src/daemon/src/process_group_manager/details:handle"], + deps = [ + "//score/launch_manager/src/daemon/src/process_group_manager/details:handle", + "@score_baselibs//score/result", + ], ) cc_library( diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/empty_force_stop_action.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/empty_force_stop_action.cpp index eb8b039e72..4897deb0f9 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/empty_force_stop_action.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/empty_force_stop_action.cpp @@ -16,8 +16,9 @@ namespace score::mw::lifecycle::internal { -void EmptyForceStopAction::force_stop(const Handle) const +Result EmptyForceStopAction::force_stop(const Handle) const { + return {}; } } // namespace score::mw::lifecycle::internal diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/empty_force_stop_action.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/empty_force_stop_action.hpp index 5673346a43..c9efd545b7 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/empty_force_stop_action.hpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/empty_force_stop_action.hpp @@ -22,7 +22,7 @@ namespace score::mw::lifecycle::internal class EmptyForceStopAction final : public IForceStopAction { public: - void force_stop(const Handle) const override; + Result force_stop(const Handle) const override; }; } // namespace score::mw::lifecycle::internal diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/iforce_stop_action.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/iforce_stop_action.hpp index e3e1c3f3a3..7b5d8898bf 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/iforce_stop_action.hpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/iforce_stop_action.hpp @@ -15,6 +15,7 @@ #define SCORE_LCM_IFORCE_STOP_ACTION_HPP_INCLUDED #include "score/mw/launch_manager/process_group_manager/details/handle.hpp" +#include "score/result/result.h" namespace score::mw::lifecycle::internal { @@ -23,7 +24,7 @@ class IForceStopAction { public: virtual ~IForceStopAction() = default; - virtual void force_stop(const Handle handle) const = 0; + virtual Result force_stop(const Handle handle) const = 0; }; } // namespace score::mw::lifecycle::internal diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/mock_force_stop_action.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/mock_force_stop_action.hpp index 426476a0a1..f0f5179822 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/mock_force_stop_action.hpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/mock_force_stop_action.hpp @@ -23,7 +23,7 @@ namespace score::mw::lifecycle::internal class MockForceStopAction : public IForceStopAction { public: - MOCK_METHOD(void, force_stop, (const Handle handle), (override, const)); + MOCK_METHOD(Result, force_stop, (const Handle handle), (override, const)); }; } // namespace score::mw::lifecycle::internal diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/BUILD b/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/BUILD index 2f7376a6cd..77fef7fafa 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/BUILD +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/BUILD @@ -17,7 +17,10 @@ cc_library( 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:__subpackages__"], - deps = ["//score/launch_manager/src/daemon/src/process_group_manager/details:handle"], + deps = [ + "//score/launch_manager/src/daemon/src/process_group_manager/details:handle", + "@score_baselibs//score/result", + ], ) cc_library( diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/iready_condition.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/iready_condition.hpp index 60f94a7519..45d4a95a3b 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/iready_condition.hpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/iready_condition.hpp @@ -15,6 +15,7 @@ #define SCORE_LCM_IREADY_CONDITION_HPP_INCLUDED #include "score/mw/launch_manager/process_group_manager/details/handle.hpp" +#include "score/result/result.h" namespace score::mw::lifecycle::internal { @@ -23,7 +24,7 @@ class IReadyCondition { public: virtual ~IReadyCondition() = default; - virtual void wait(const Handle handle) const = 0; + virtual Result wait(const Handle handle) const = 0; }; } // namespace score::mw::lifecycle::internal diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/mock_ready_condition.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/mock_ready_condition.hpp index e19d027412..db99780180 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/mock_ready_condition.hpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/mock_ready_condition.hpp @@ -23,7 +23,7 @@ namespace score::mw::lifecycle::internal class MockReadyCondition : public IReadyCondition { public: - MOCK_METHOD(void, wait, (const Handle handle), (override, const)); + MOCK_METHOD(Result, wait, (const Handle handle), (override, const)); }; } // namespace score::mw::lifecycle::internal diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/start_action/BUILD b/score/launch_manager/src/daemon/src/process_group_manager/details/start_action/BUILD index ed5270eddf..b176baf7f8 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/start_action/BUILD +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/start_action/BUILD @@ -17,7 +17,10 @@ cc_library( 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:__subpackages__"], - deps = ["//score/launch_manager/src/daemon/src/process_group_manager/details:handle"], + deps = [ + "//score/launch_manager/src/daemon/src/process_group_manager/details:handle", + "@score_baselibs//score/result", + ], ) cc_library( diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/start_action/empty_start_action.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/start_action/empty_start_action.cpp index 3b2d0aa970..2affc54a0a 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/start_action/empty_start_action.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/start_action/empty_start_action.cpp @@ -16,7 +16,7 @@ namespace score::mw::lifecycle::internal { -Handle EmptyStartAction::start() const +Result EmptyStartAction::start() const { return EmptyHandle{}; } diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/start_action/empty_start_action.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/start_action/empty_start_action.hpp index 83d4f71741..3ba5cbc8a9 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/start_action/empty_start_action.hpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/start_action/empty_start_action.hpp @@ -22,7 +22,7 @@ namespace score::mw::lifecycle::internal class EmptyStartAction final : public IStartAction { public: - Handle start() const override; + Result start() const override; }; } // namespace score::mw::lifecycle::internal diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/start_action/istart_action.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/start_action/istart_action.hpp index 81a6e94f83..d9a7094791 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/start_action/istart_action.hpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/start_action/istart_action.hpp @@ -15,6 +15,7 @@ #define SCORE_LCM_ISTART_ACTION_HPP_INCLUDED #include "score/mw/launch_manager/process_group_manager/details/handle.hpp" +#include "score/result/result.h" namespace score::mw::lifecycle::internal { @@ -23,7 +24,7 @@ class IStartAction { public: virtual ~IStartAction() = default; - virtual Handle start() const = 0; + virtual Result start() const = 0; }; } // namespace score::mw::lifecycle::internal diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/start_action/mock_start_action.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/start_action/mock_start_action.hpp index dadd15cb5c..e6c357e18e 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/start_action/mock_start_action.hpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/start_action/mock_start_action.hpp @@ -23,7 +23,7 @@ namespace score::mw::lifecycle::internal class MockStartAction : public IStartAction { public: - MOCK_METHOD(Handle, start, (), (override, const)); + MOCK_METHOD(Result, start, (), (override, const)); }; } // namespace score::mw::lifecycle::internal diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/BUILD b/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/BUILD index 43bce95780..31cf0c14cc 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/BUILD +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/BUILD @@ -17,7 +17,10 @@ cc_library( 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:__subpackages__"], - deps = ["//score/launch_manager/src/daemon/src/process_group_manager/details:handle"], + deps = [ + "//score/launch_manager/src/daemon/src/process_group_manager/details:handle", + "@score_baselibs//score/result", + ], ) cc_library( diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/empty_stop_action.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/empty_stop_action.cpp index 754ef26cbf..7bf4ea2fda 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/empty_stop_action.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/empty_stop_action.cpp @@ -16,8 +16,9 @@ namespace score::mw::lifecycle::internal { -void EmptyStopAction::stop(const Handle) const +Result EmptyStopAction::stop(const Handle) const { + return {}; } } // namespace score::mw::lifecycle::internal diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/empty_stop_action.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/empty_stop_action.hpp index 8bf3b7ab40..f3140acb97 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/empty_stop_action.hpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/empty_stop_action.hpp @@ -22,7 +22,7 @@ namespace score::mw::lifecycle::internal class EmptyStopAction final : public IStopAction { public: - void stop(const Handle) const override; + Result stop(const Handle) const override; }; } // namespace score::mw::lifecycle::internal diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/istop_action.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/istop_action.hpp index 8109a391c4..45675fce43 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/istop_action.hpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/istop_action.hpp @@ -15,6 +15,7 @@ #define SCORE_LCM_ISTOP_ACTION_HPP_INCLUDED #include "score/mw/launch_manager/process_group_manager/details/handle.hpp" +#include "score/result/result.h" namespace score::mw::lifecycle::internal { @@ -23,7 +24,7 @@ class IStopAction { public: virtual ~IStopAction() = default; - virtual void stop(const Handle handle) const = 0; + virtual Result stop(const Handle handle) const = 0; }; } // namespace score::mw::lifecycle::internal diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/mock_stop_action.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/mock_stop_action.hpp index 5073a1aefe..f6de99ae6c 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/mock_stop_action.hpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/mock_stop_action.hpp @@ -23,7 +23,7 @@ namespace score::mw::lifecycle::internal class MockStopAction : public IStopAction { public: - MOCK_METHOD(void, stop, (const Handle handle), (override, const)); + MOCK_METHOD(Result, stop, (const Handle handle), (override, const)); }; } // namespace score::mw::lifecycle::internal From 5a78414828987f5958513a73be3a852b7a0ed09e Mon Sep 17 00:00:00 2001 From: Daniel Thwaites Date: Wed, 30 Sep 2026 12:51:41 +0100 Subject: [PATCH 06/36] Add process actions These use the existing `ProcessLauncher` for the time being; eventually the code from `ProcessLauncher` should be migrated into the actions themselves. --- .../src/process_group_manager/details/BUILD | 3 +- .../details/force_stop_action/BUILD | 15 ++++++ .../process_force_stop_action.cpp | 48 +++++++++++++++++++ .../process_force_stop_action.hpp | 39 +++++++++++++++ .../process_group_manager/details/handle.hpp | 3 ++ .../details/start_action/BUILD | 16 +++++++ .../start_action/process_start_action.cpp | 42 ++++++++++++++++ .../start_action/process_start_action.hpp | 40 ++++++++++++++++ .../details/stop_action/BUILD | 15 ++++++ .../stop_action/process_stop_action.cpp | 48 +++++++++++++++++++ .../stop_action/process_stop_action.hpp | 39 +++++++++++++++ 11 files changed, 307 insertions(+), 1 deletion(-) create mode 100644 score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/process_force_stop_action.cpp create mode 100644 score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/process_force_stop_action.hpp create mode 100644 score/launch_manager/src/daemon/src/process_group_manager/details/start_action/process_start_action.cpp create mode 100644 score/launch_manager/src/daemon/src/process_group_manager/details/start_action/process_start_action.hpp create mode 100644 score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/process_stop_action.cpp create mode 100644 score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/process_stop_action.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 a16df2eb81..34e76e9975 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 @@ -85,6 +85,7 @@ cc_library( 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:__subpackages__"], + deps = ["//score/launch_manager/src/daemon/src/osal:ipc_comms"], ) cc_library( @@ -431,7 +432,7 @@ cc_library( hdrs = ["process_launcher.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__"], + visibility = ["//score/launch_manager/src/daemon/src/process_group_manager:__subpackages__"], deps = [ "//score/launch_manager/src/daemon/src/common:log", "//score/launch_manager/src/daemon/src/common:signal_safe_log", diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/BUILD b/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/BUILD index 4863dacb35..c0321c31ca 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/BUILD +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/BUILD @@ -45,3 +45,18 @@ cc_library( visibility = ["//score/launch_manager/src/daemon/src/process_group_manager:__subpackages__"], deps = [":iforce_stop_action"], ) + +cc_library( + name = "process_force_stop_action", + srcs = ["process_force_stop_action.cpp"], + hdrs = ["process_force_stop_action.hpp"], + include_prefix = "score/mw/launch_manager/process_group_manager/details/force_stop_action", + strip_include_prefix = "/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action", + visibility = ["//score/launch_manager/src/daemon/src/process_group_manager:__subpackages__"], + deps = [ + ":iforce_stop_action", + "//score/launch_manager:error", + "//score/launch_manager/src/daemon/src/osal:return_types", + "//score/launch_manager/src/daemon/src/process_group_manager/details:process_launcher", + ], +) diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/process_force_stop_action.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/process_force_stop_action.cpp new file mode 100644 index 0000000000..fb38e7272e --- /dev/null +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/process_force_stop_action.cpp @@ -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 + ********************************************************************************/ + +#include "score/mw/launch_manager/process_group_manager/details/force_stop_action/process_force_stop_action.hpp" +#include "score/mw/launch_manager/osal/return_types.hpp" +#include "score/mw/launch_manager/process_group_manager/details/process_launcher.hpp" +#include "score/mw/lifecycle/execution_error.h" + +namespace score::mw::lifecycle::internal +{ + +ProcessForceStopAction::ProcessForceStopAction(osal::ProcessLauncher& launcher) : launcher_(launcher) +{ +} + +Result ProcessForceStopAction::force_stop(const Handle handle) const +{ + return std::visit(*this, handle); +} + +Result ProcessForceStopAction::operator()(const ProcessHandle process) const +{ + const osal::OsalReturnType result = launcher_.forceTermination(process.pid); + + if (result == osal::OsalReturnType::kSuccess) + { + return {}; + } + + return MakeUnexpected(ExecErrc::kGeneralError); +} + +Result ProcessForceStopAction::operator()(const Handle handle) const +{ + return MakeUnexpected(ExecErrc::kNotImplemented); +} + +} // namespace score::mw::lifecycle::internal diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/process_force_stop_action.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/process_force_stop_action.hpp new file mode 100644 index 0000000000..ecf751539a --- /dev/null +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/process_force_stop_action.hpp @@ -0,0 +1,39 @@ +/******************************************************************************** + * 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_PROCESS_FORCE_STOP_ACTION_HPP_INCLUDED +#define SCORE_LCM_PROCESS_FORCE_STOP_ACTION_HPP_INCLUDED + +#include "score/mw/launch_manager/process_group_manager/details/force_stop_action/iforce_stop_action.hpp" +#include "score/mw/launch_manager/process_group_manager/details/process_launcher.hpp" +#include "score/result/result.h" + +namespace score::mw::lifecycle::internal +{ + +class ProcessForceStopAction final : public IForceStopAction +{ + public: + explicit ProcessForceStopAction(osal::ProcessLauncher& launcher); + + Result force_stop(const Handle handle) const override; + Result operator()(const ProcessHandle process) const; + Result operator()(const Handle handle) const; + + private: + osal::ProcessLauncher& launcher_; +}; + +} // namespace score::mw::lifecycle::internal + +#endif // SCORE_LCM_PROCESS_FORCE_STOP_ACTION_HPP_INCLUDED diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/handle.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/handle.hpp index 1a519bf0fc..92ab3ccb78 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/handle.hpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/handle.hpp @@ -17,6 +17,8 @@ #include #include +#include "score/mw/launch_manager/osal/ipc_comms.hpp" + namespace score::mw::lifecycle::internal { @@ -27,6 +29,7 @@ struct EmptyHandle struct ProcessHandle { pid_t pid; + osal::IpcCommsP sync; }; using Handle = std::variant; diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/start_action/BUILD b/score/launch_manager/src/daemon/src/process_group_manager/details/start_action/BUILD index b176baf7f8..b8f23769dd 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/start_action/BUILD +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/start_action/BUILD @@ -45,3 +45,19 @@ cc_library( visibility = ["//score/launch_manager/src/daemon/src/process_group_manager:__subpackages__"], deps = [":istart_action"], ) + +cc_library( + name = "process_start_action", + srcs = ["process_start_action.cpp"], + hdrs = ["process_start_action.hpp"], + include_prefix = "score/mw/launch_manager/process_group_manager/details/start_action", + strip_include_prefix = "/score/launch_manager/src/daemon/src/process_group_manager/details/start_action", + visibility = ["//score/launch_manager/src/daemon/src/process_group_manager:__subpackages__"], + deps = [ + ":istart_action", + "//score/launch_manager:error", + "//score/launch_manager/src/daemon/src/configuration:component_config", + "//score/launch_manager/src/daemon/src/osal:return_types", + "//score/launch_manager/src/daemon/src/process_group_manager/details:process_launcher", + ], +) diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/start_action/process_start_action.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/start_action/process_start_action.cpp new file mode 100644 index 0000000000..14b4285486 --- /dev/null +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/start_action/process_start_action.cpp @@ -0,0 +1,42 @@ +/******************************************************************************** + * 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/start_action/process_start_action.hpp" +#include "score/mw/launch_manager/osal/return_types.hpp" +#include "score/mw/launch_manager/process_group_manager/details/process_launcher.hpp" +#include "score/mw/lifecycle/execution_error.h" + +namespace score::mw::lifecycle::internal +{ + +ProcessStartAction::ProcessStartAction(osal::ProcessLauncher& launcher, const configuration::ComponentConfig& config) + : launcher_(launcher), config_(config) +{ +} + +Result ProcessStartAction::start() const +{ + osal::ProcessID pid = 0; + osal::IpcCommsP sync = nullptr; + + const auto result = launcher_.startProcess(pid, sync, config_); + + if (result == osal::OsalReturnType::kSuccess) + { + return ProcessHandle{pid, sync}; + } + + return MakeUnexpected(ExecErrc::kGeneralError); +} + +} // namespace score::mw::lifecycle::internal diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/start_action/process_start_action.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/start_action/process_start_action.hpp new file mode 100644 index 0000000000..c2d86fc08f --- /dev/null +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/start_action/process_start_action.hpp @@ -0,0 +1,40 @@ +/******************************************************************************** + * 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_PROCESS_START_ACTION_HPP_INCLUDED +#define SCORE_LCM_PROCESS_START_ACTION_HPP_INCLUDED + +#include "score/mw/launch_manager/configuration/component_config.hpp" +#include "score/mw/launch_manager/process_group_manager/details/handle.hpp" +#include "score/mw/launch_manager/process_group_manager/details/process_launcher.hpp" +#include "score/mw/launch_manager/process_group_manager/details/start_action/istart_action.hpp" +#include "score/result/result.h" + +namespace score::mw::lifecycle::internal +{ + +class ProcessStartAction final : public IStartAction +{ + public: + ProcessStartAction(osal::ProcessLauncher& launcher, const configuration::ComponentConfig& config); + + Result start() const override; + + private: + osal::ProcessLauncher& launcher_; + const configuration::ComponentConfig& config_; +}; + +} // namespace score::mw::lifecycle::internal + +#endif // SCORE_LCM_PROCESS_START_ACTION_HPP_INCLUDED diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/BUILD b/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/BUILD index 31cf0c14cc..5928c25b32 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/BUILD +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/BUILD @@ -45,3 +45,18 @@ cc_library( visibility = ["//score/launch_manager/src/daemon/src/process_group_manager:__subpackages__"], deps = [":istop_action"], ) + +cc_library( + name = "process_stop_action", + srcs = ["process_stop_action.cpp"], + hdrs = ["process_stop_action.hpp"], + include_prefix = "score/mw/launch_manager/process_group_manager/details/stop_action", + strip_include_prefix = "/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action", + visibility = ["//score/launch_manager/src/daemon/src/process_group_manager:__subpackages__"], + deps = [ + ":istop_action", + "//score/launch_manager:error", + "//score/launch_manager/src/daemon/src/osal:return_types", + "//score/launch_manager/src/daemon/src/process_group_manager/details:process_launcher", + ], +) diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/process_stop_action.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/process_stop_action.cpp new file mode 100644 index 0000000000..b61c9c6a14 --- /dev/null +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/process_stop_action.cpp @@ -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 + ********************************************************************************/ + +#include "score/mw/launch_manager/process_group_manager/details/stop_action/process_stop_action.hpp" +#include "score/mw/launch_manager/osal/return_types.hpp" +#include "score/mw/launch_manager/process_group_manager/details/process_launcher.hpp" +#include "score/mw/lifecycle/execution_error.h" + +namespace score::mw::lifecycle::internal +{ + +ProcessStopAction::ProcessStopAction(osal::ProcessLauncher& launcher) : launcher_(launcher) +{ +} + +Result ProcessStopAction::stop(const Handle handle) const +{ + return std::visit(*this, handle); +} + +Result ProcessStopAction::operator()(const ProcessHandle process) const +{ + const osal::OsalReturnType result = launcher_.requestTermination(process.pid); + + if (result == osal::OsalReturnType::kSuccess) + { + return {}; + } + + return MakeUnexpected(ExecErrc::kGeneralError); +} + +Result ProcessStopAction::operator()(const Handle handle) const +{ + return MakeUnexpected(ExecErrc::kNotImplemented); +} + +} // namespace score::mw::lifecycle::internal diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/process_stop_action.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/process_stop_action.hpp new file mode 100644 index 0000000000..400fac65e2 --- /dev/null +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/process_stop_action.hpp @@ -0,0 +1,39 @@ +/******************************************************************************** + * 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_PROCESS_STOP_ACTION_HPP_INCLUDED +#define SCORE_LCM_PROCESS_STOP_ACTION_HPP_INCLUDED + +#include "score/mw/launch_manager/process_group_manager/details/process_launcher.hpp" +#include "score/mw/launch_manager/process_group_manager/details/stop_action/istop_action.hpp" +#include "score/result/result.h" + +namespace score::mw::lifecycle::internal +{ + +class ProcessStopAction final : public IStopAction +{ + public: + explicit ProcessStopAction(osal::ProcessLauncher& launcher); + + Result stop(const Handle handle) const override; + Result operator()(const ProcessHandle process) const; + Result operator()(const Handle handle) const; + + private: + osal::ProcessLauncher& launcher_; +}; + +} // namespace score::mw::lifecycle::internal + +#endif // SCORE_LCM_PROCESS_STOP_ACTION_HPP_INCLUDED From 300256f2985a91f2e064f45cb1be4c7f4b6806ae Mon Sep 17 00:00:00 2001 From: Daniel Thwaites Date: Wed, 30 Sep 2026 14:35:16 +0100 Subject: [PATCH 07/36] Add unit tests for new actions --- .../details/force_stop_action/BUILD | 24 +++++- .../empty_force_stop_action_UT.cpp | 32 ++++++++ .../process_force_stop_action.cpp | 3 +- .../process_force_stop_action.hpp | 6 +- .../process_force_stop_action_UT.cpp | 76 +++++++++++++++++++ .../details/start_action/BUILD | 24 +++++- .../start_action/empty_start_action_UT.cpp | 41 ++++++++++ .../start_action/process_start_action.cpp | 3 +- .../start_action/process_start_action.hpp | 6 +- .../start_action/process_start_action_UT.cpp | 69 +++++++++++++++++ .../details/stop_action/BUILD | 24 +++++- .../stop_action/empty_stop_action_UT.cpp | 32 ++++++++ .../stop_action/process_stop_action.cpp | 3 +- .../stop_action/process_stop_action.hpp | 6 +- .../stop_action/process_stop_action_UT.cpp | 76 +++++++++++++++++++ 15 files changed, 407 insertions(+), 18 deletions(-) create mode 100644 score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/empty_force_stop_action_UT.cpp create mode 100644 score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/process_force_stop_action_UT.cpp create mode 100644 score/launch_manager/src/daemon/src/process_group_manager/details/start_action/empty_start_action_UT.cpp create mode 100644 score/launch_manager/src/daemon/src/process_group_manager/details/start_action/process_start_action_UT.cpp create mode 100644 score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/empty_stop_action_UT.cpp create mode 100644 score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/process_stop_action_UT.cpp diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/BUILD b/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/BUILD index c0321c31ca..5a25492886 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/BUILD +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/BUILD @@ -11,6 +11,8 @@ # SPDX-License-Identifier: Apache-2.0 # ******************************************************************************* +load("//tests/utils/bazel:unit_test.bzl", "lm_cc_test") + cc_library( name = "iforce_stop_action", hdrs = ["iforce_stop_action.hpp"], @@ -46,6 +48,15 @@ cc_library( deps = [":iforce_stop_action"], ) +lm_cc_test( + name = "empty_force_stop_action_UT", + srcs = ["empty_force_stop_action_UT.cpp"], + deps = [ + ":empty_force_stop_action", + "@googletest//:gtest_main", + ], +) + cc_library( name = "process_force_stop_action", srcs = ["process_force_stop_action.cpp"], @@ -57,6 +68,17 @@ cc_library( ":iforce_stop_action", "//score/launch_manager:error", "//score/launch_manager/src/daemon/src/osal:return_types", - "//score/launch_manager/src/daemon/src/process_group_manager/details:process_launcher", + "//score/launch_manager/src/daemon/src/process_group_manager:iprocess", + ], +) + +lm_cc_test( + name = "process_force_stop_action_UT", + srcs = ["process_force_stop_action_UT.cpp"], + deps = [ + ":process_force_stop_action", + "//score/launch_manager:error", + "//score/launch_manager/src/daemon/src/process_group_manager:mock_iprocess", + "@googletest//:gtest_main", ], ) diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/empty_force_stop_action_UT.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/empty_force_stop_action_UT.cpp new file mode 100644 index 0000000000..63d9efb9fa --- /dev/null +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/empty_force_stop_action_UT.cpp @@ -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 + ********************************************************************************/ + +#include "score/mw/launch_manager/process_group_manager/details/force_stop_action/empty_force_stop_action.hpp" +#include + +namespace score::mw::lifecycle::internal +{ +namespace +{ + +TEST(EmptyForceStopActionTest, ForceStopSucceeds) +{ + EmptyForceStopAction empty_force_stop_action; + + const auto result = empty_force_stop_action.force_stop(EmptyHandle{}); + + EXPECT_TRUE(result.has_value()); +} + +} // namespace +} // namespace score::mw::lifecycle::internal diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/process_force_stop_action.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/process_force_stop_action.cpp index fb38e7272e..adbabdc47f 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/process_force_stop_action.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/process_force_stop_action.cpp @@ -13,13 +13,12 @@ #include "score/mw/launch_manager/process_group_manager/details/force_stop_action/process_force_stop_action.hpp" #include "score/mw/launch_manager/osal/return_types.hpp" -#include "score/mw/launch_manager/process_group_manager/details/process_launcher.hpp" #include "score/mw/lifecycle/execution_error.h" namespace score::mw::lifecycle::internal { -ProcessForceStopAction::ProcessForceStopAction(osal::ProcessLauncher& launcher) : launcher_(launcher) +ProcessForceStopAction::ProcessForceStopAction(osal::IProcess& launcher) : launcher_(launcher) { } diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/process_force_stop_action.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/process_force_stop_action.hpp index ecf751539a..f9ef25cbef 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/process_force_stop_action.hpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/process_force_stop_action.hpp @@ -15,7 +15,7 @@ #define SCORE_LCM_PROCESS_FORCE_STOP_ACTION_HPP_INCLUDED #include "score/mw/launch_manager/process_group_manager/details/force_stop_action/iforce_stop_action.hpp" -#include "score/mw/launch_manager/process_group_manager/details/process_launcher.hpp" +#include "score/mw/launch_manager/process_group_manager/iprocess.hpp" #include "score/result/result.h" namespace score::mw::lifecycle::internal @@ -24,14 +24,14 @@ namespace score::mw::lifecycle::internal class ProcessForceStopAction final : public IForceStopAction { public: - explicit ProcessForceStopAction(osal::ProcessLauncher& launcher); + explicit ProcessForceStopAction(osal::IProcess& launcher); Result force_stop(const Handle handle) const override; Result operator()(const ProcessHandle process) const; Result operator()(const Handle handle) const; private: - osal::ProcessLauncher& launcher_; + osal::IProcess& launcher_; }; } // namespace score::mw::lifecycle::internal diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/process_force_stop_action_UT.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/process_force_stop_action_UT.cpp new file mode 100644 index 0000000000..792515805f --- /dev/null +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/process_force_stop_action_UT.cpp @@ -0,0 +1,76 @@ +/******************************************************************************** + * 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/force_stop_action/process_force_stop_action.hpp" +#include "score/mw/launch_manager/process_group_manager/mock_iprocess.hpp" +#include "score/mw/lifecycle/execution_error.h" +#include +#include + +namespace score::mw::lifecycle::internal +{ +namespace +{ + +using namespace ::testing; + +const ProcessHandle mock_handle = ProcessHandle{42}; + +TEST(ProcessForceStopActionTest, ForceStopCallsForceTerminationWithPid) +{ + StrictMock launcher; + const ProcessForceStopAction process_force_stop_action(launcher); + + EXPECT_CALL(launcher, forceTermination(mock_handle.pid)).WillOnce(Return(osal::OsalReturnType::kSuccess)); + + static_cast(process_force_stop_action.force_stop(mock_handle)); +} + +TEST(ProcessForceStopActionTest, ForceStopSucceedsReturnsSuccess) +{ + NiceMock launcher; + const ProcessForceStopAction process_force_stop_action(launcher); + + ON_CALL(launcher, forceTermination(_)).WillByDefault(Return(osal::OsalReturnType::kSuccess)); + + const Result result = process_force_stop_action.force_stop(mock_handle); + + EXPECT_TRUE(result.has_value()); +} + +TEST(ProcessForceStopActionTest, ForceStopFailsReturnsError) +{ + NiceMock launcher; + const ProcessForceStopAction process_force_stop_action(launcher); + + ON_CALL(launcher, forceTermination(_)).WillByDefault(Return(osal::OsalReturnType::kFail)); + + const Result result = process_force_stop_action.force_stop(mock_handle); + + EXPECT_FALSE(result.has_value()); + EXPECT_EQ(result.error(), ExecErrc::kGeneralError); +} + +TEST(ProcessForceStopActionTest, ForceStopWithEmptyHandleReturnsError) +{ + NiceMock launcher; + const ProcessForceStopAction process_force_stop_action(launcher); + + const Result result = process_force_stop_action.force_stop(EmptyHandle{}); + + EXPECT_FALSE(result.has_value()); + EXPECT_EQ(result.error(), ExecErrc::kNotImplemented); +} + +} // namespace +} // namespace score::mw::lifecycle::internal diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/start_action/BUILD b/score/launch_manager/src/daemon/src/process_group_manager/details/start_action/BUILD index b8f23769dd..c65b1630fb 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/start_action/BUILD +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/start_action/BUILD @@ -11,6 +11,8 @@ # SPDX-License-Identifier: Apache-2.0 # ******************************************************************************* +load("//tests/utils/bazel:unit_test.bzl", "lm_cc_test") + cc_library( name = "istart_action", hdrs = ["istart_action.hpp"], @@ -46,6 +48,15 @@ cc_library( deps = [":istart_action"], ) +lm_cc_test( + name = "empty_start_action_UT", + srcs = ["empty_start_action_UT.cpp"], + deps = [ + ":empty_start_action", + "@googletest//:gtest_main", + ], +) + cc_library( name = "process_start_action", srcs = ["process_start_action.cpp"], @@ -58,6 +69,17 @@ cc_library( "//score/launch_manager:error", "//score/launch_manager/src/daemon/src/configuration:component_config", "//score/launch_manager/src/daemon/src/osal:return_types", - "//score/launch_manager/src/daemon/src/process_group_manager/details:process_launcher", + "//score/launch_manager/src/daemon/src/process_group_manager:iprocess", + ], +) + +lm_cc_test( + name = "process_start_action_UT", + srcs = ["process_start_action_UT.cpp"], + deps = [ + ":process_start_action", + "//score/launch_manager:error", + "//score/launch_manager/src/daemon/src/process_group_manager:mock_iprocess", + "@googletest//:gtest_main", ], ) diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/start_action/empty_start_action_UT.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/start_action/empty_start_action_UT.cpp new file mode 100644 index 0000000000..3fee6f78f5 --- /dev/null +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/start_action/empty_start_action_UT.cpp @@ -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 + ********************************************************************************/ + +#include "score/mw/launch_manager/process_group_manager/details/start_action/empty_start_action.hpp" +#include + +namespace score::mw::lifecycle::internal +{ +namespace +{ + +TEST(EmptyStartActionTest, StartSucceeds) +{ + EmptyStartAction empty_start_action; + + const auto result = empty_start_action.start(); + + EXPECT_TRUE(result.has_value()); +} + +TEST(EmptyStartActionTest, StartReturnsEmptyHandle) +{ + EmptyStartAction empty_start_action; + + const auto result = empty_start_action.start(); + + EXPECT_TRUE(std::holds_alternative(result.value())); +} + +} // namespace +} // namespace score::mw::lifecycle::internal diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/start_action/process_start_action.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/start_action/process_start_action.cpp index 14b4285486..934566a010 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/start_action/process_start_action.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/start_action/process_start_action.cpp @@ -13,13 +13,12 @@ #include "score/mw/launch_manager/process_group_manager/details/start_action/process_start_action.hpp" #include "score/mw/launch_manager/osal/return_types.hpp" -#include "score/mw/launch_manager/process_group_manager/details/process_launcher.hpp" #include "score/mw/lifecycle/execution_error.h" namespace score::mw::lifecycle::internal { -ProcessStartAction::ProcessStartAction(osal::ProcessLauncher& launcher, const configuration::ComponentConfig& config) +ProcessStartAction::ProcessStartAction(osal::IProcess& launcher, const configuration::ComponentConfig& config) : launcher_(launcher), config_(config) { } diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/start_action/process_start_action.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/start_action/process_start_action.hpp index c2d86fc08f..ebddf3941c 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/start_action/process_start_action.hpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/start_action/process_start_action.hpp @@ -16,8 +16,8 @@ #include "score/mw/launch_manager/configuration/component_config.hpp" #include "score/mw/launch_manager/process_group_manager/details/handle.hpp" -#include "score/mw/launch_manager/process_group_manager/details/process_launcher.hpp" #include "score/mw/launch_manager/process_group_manager/details/start_action/istart_action.hpp" +#include "score/mw/launch_manager/process_group_manager/iprocess.hpp" #include "score/result/result.h" namespace score::mw::lifecycle::internal @@ -26,12 +26,12 @@ namespace score::mw::lifecycle::internal class ProcessStartAction final : public IStartAction { public: - ProcessStartAction(osal::ProcessLauncher& launcher, const configuration::ComponentConfig& config); + ProcessStartAction(osal::IProcess& launcher, const configuration::ComponentConfig& config); Result start() const override; private: - osal::ProcessLauncher& launcher_; + osal::IProcess& launcher_; const configuration::ComponentConfig& config_; }; diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/start_action/process_start_action_UT.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/start_action/process_start_action_UT.cpp new file mode 100644 index 0000000000..0f4371d7ae --- /dev/null +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/start_action/process_start_action_UT.cpp @@ -0,0 +1,69 @@ +/******************************************************************************** + * 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/start_action/process_start_action.hpp" +#include "score/mw/launch_manager/process_group_manager/mock_iprocess.hpp" +#include "score/mw/lifecycle/execution_error.h" +#include +#include + +namespace score::mw::lifecycle::internal +{ +namespace +{ + +using namespace ::testing; + +TEST(ProcessStartActionTest, StartCallsStartProcessWithConfig) +{ + StrictMock launcher; + const configuration::ComponentConfig config{}; + const ProcessStartAction process_start_action(launcher, config); + + EXPECT_CALL(launcher, startProcess(_, _, Ref(config))).WillOnce(Return(osal::OsalReturnType::kSuccess)); + + static_cast(process_start_action.start()); +} + +TEST(ProcessStartActionTest, StartSucceedsReturnsProcessHandle) +{ + NiceMock launcher; + const configuration::ComponentConfig config{}; + const ProcessStartAction process_start_action(launcher, config); + const osal::ProcessID expected_pid = 42; + + ON_CALL(launcher, startProcess(_, _, _)) + .WillByDefault(DoAll(SetArgReferee<0>(expected_pid), Return(osal::OsalReturnType::kSuccess))); + + const Result result = process_start_action.start(); + + EXPECT_TRUE(result.has_value()); + EXPECT_EQ(std::get(result.value()).pid, expected_pid); +} + +TEST(ProcessStartActionTest, StartFailsReturnsError) +{ + NiceMock launcher; + const configuration::ComponentConfig config{}; + const ProcessStartAction process_start_action(launcher, config); + + ON_CALL(launcher, startProcess(_, _, _)).WillByDefault(Return(osal::OsalReturnType::kFail)); + + const Result result = process_start_action.start(); + + EXPECT_FALSE(result.has_value()); + EXPECT_EQ(result.error(), ExecErrc::kGeneralError); +} + +} // namespace +} // namespace score::mw::lifecycle::internal diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/BUILD b/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/BUILD index 5928c25b32..e0c85371cd 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/BUILD +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/BUILD @@ -11,6 +11,8 @@ # SPDX-License-Identifier: Apache-2.0 # ******************************************************************************* +load("//tests/utils/bazel:unit_test.bzl", "lm_cc_test") + cc_library( name = "istop_action", hdrs = ["istop_action.hpp"], @@ -46,6 +48,15 @@ cc_library( deps = [":istop_action"], ) +lm_cc_test( + name = "empty_stop_action_UT", + srcs = ["empty_stop_action_UT.cpp"], + deps = [ + ":empty_stop_action", + "@googletest//:gtest_main", + ], +) + cc_library( name = "process_stop_action", srcs = ["process_stop_action.cpp"], @@ -57,6 +68,17 @@ cc_library( ":istop_action", "//score/launch_manager:error", "//score/launch_manager/src/daemon/src/osal:return_types", - "//score/launch_manager/src/daemon/src/process_group_manager/details:process_launcher", + "//score/launch_manager/src/daemon/src/process_group_manager:iprocess", + ], +) + +lm_cc_test( + name = "process_stop_action_UT", + srcs = ["process_stop_action_UT.cpp"], + deps = [ + ":process_stop_action", + "//score/launch_manager:error", + "//score/launch_manager/src/daemon/src/process_group_manager:mock_iprocess", + "@googletest//:gtest_main", ], ) diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/empty_stop_action_UT.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/empty_stop_action_UT.cpp new file mode 100644 index 0000000000..9aa446291e --- /dev/null +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/empty_stop_action_UT.cpp @@ -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 + ********************************************************************************/ + +#include "score/mw/launch_manager/process_group_manager/details/stop_action/empty_stop_action.hpp" +#include + +namespace score::mw::lifecycle::internal +{ +namespace +{ + +TEST(EmptyStopActionTest, StopSucceeds) +{ + EmptyStopAction empty_stop_action; + + const auto result = empty_stop_action.stop(EmptyHandle{}); + + EXPECT_TRUE(result.has_value()); +} + +} // namespace +} // namespace score::mw::lifecycle::internal diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/process_stop_action.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/process_stop_action.cpp index b61c9c6a14..486b569176 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/process_stop_action.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/process_stop_action.cpp @@ -13,13 +13,12 @@ #include "score/mw/launch_manager/process_group_manager/details/stop_action/process_stop_action.hpp" #include "score/mw/launch_manager/osal/return_types.hpp" -#include "score/mw/launch_manager/process_group_manager/details/process_launcher.hpp" #include "score/mw/lifecycle/execution_error.h" namespace score::mw::lifecycle::internal { -ProcessStopAction::ProcessStopAction(osal::ProcessLauncher& launcher) : launcher_(launcher) +ProcessStopAction::ProcessStopAction(osal::IProcess& launcher) : launcher_(launcher) { } diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/process_stop_action.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/process_stop_action.hpp index 400fac65e2..358e2d7f91 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/process_stop_action.hpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/process_stop_action.hpp @@ -14,8 +14,8 @@ #ifndef SCORE_LCM_PROCESS_STOP_ACTION_HPP_INCLUDED #define SCORE_LCM_PROCESS_STOP_ACTION_HPP_INCLUDED -#include "score/mw/launch_manager/process_group_manager/details/process_launcher.hpp" #include "score/mw/launch_manager/process_group_manager/details/stop_action/istop_action.hpp" +#include "score/mw/launch_manager/process_group_manager/iprocess.hpp" #include "score/result/result.h" namespace score::mw::lifecycle::internal @@ -24,14 +24,14 @@ namespace score::mw::lifecycle::internal class ProcessStopAction final : public IStopAction { public: - explicit ProcessStopAction(osal::ProcessLauncher& launcher); + explicit ProcessStopAction(osal::IProcess& launcher); Result stop(const Handle handle) const override; Result operator()(const ProcessHandle process) const; Result operator()(const Handle handle) const; private: - osal::ProcessLauncher& launcher_; + osal::IProcess& launcher_; }; } // namespace score::mw::lifecycle::internal diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/process_stop_action_UT.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/process_stop_action_UT.cpp new file mode 100644 index 0000000000..62e3a29083 --- /dev/null +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/process_stop_action_UT.cpp @@ -0,0 +1,76 @@ +/******************************************************************************** + * 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/stop_action/process_stop_action.hpp" +#include "score/mw/launch_manager/process_group_manager/mock_iprocess.hpp" +#include "score/mw/lifecycle/execution_error.h" +#include +#include + +namespace score::mw::lifecycle::internal +{ +namespace +{ + +using namespace ::testing; + +const ProcessHandle mock_handle = ProcessHandle{42}; + +TEST(ProcessStopActionTest, StopCallsRequestTerminationWithPid) +{ + StrictMock launcher; + const ProcessStopAction process_stop_action(launcher); + + EXPECT_CALL(launcher, requestTermination(mock_handle.pid)).WillOnce(Return(osal::OsalReturnType::kSuccess)); + + static_cast(process_stop_action.stop(mock_handle)); +} + +TEST(ProcessStopActionTest, StopSucceedsReturnsSuccess) +{ + NiceMock launcher; + const ProcessStopAction process_stop_action(launcher); + + ON_CALL(launcher, requestTermination(_)).WillByDefault(Return(osal::OsalReturnType::kSuccess)); + + const Result result = process_stop_action.stop(mock_handle); + + EXPECT_TRUE(result.has_value()); +} + +TEST(ProcessStopActionTest, StopFailsReturnsError) +{ + NiceMock launcher; + const ProcessStopAction process_stop_action(launcher); + + ON_CALL(launcher, requestTermination(_)).WillByDefault(Return(osal::OsalReturnType::kFail)); + + const Result result = process_stop_action.stop(mock_handle); + + EXPECT_FALSE(result.has_value()); + EXPECT_EQ(result.error(), ExecErrc::kGeneralError); +} + +TEST(ProcessStopActionTest, StopWithEmptyHandleReturnsError) +{ + NiceMock launcher; + const ProcessStopAction process_stop_action(launcher); + + const Result result = process_stop_action.stop(EmptyHandle{}); + + EXPECT_FALSE(result.has_value()); + EXPECT_EQ(result.error(), ExecErrc::kNotImplemented); +} + +} // namespace +} // namespace score::mw::lifecycle::internal From 42668b5d45d5ed9114a4799291c07a0d337048f0 Mon Sep 17 00:00:00 2001 From: Daniel Thwaites Date: Wed, 30 Sep 2026 15:32:11 +0100 Subject: [PATCH 08/36] Add report running ready condition --- .../details/ready_condition/BUILD | 28 +++++++ .../report_running_ready_condition.cpp | 50 ++++++++++++ .../report_running_ready_condition.hpp | 43 ++++++++++ .../report_running_ready_condition_UT.cpp | 78 +++++++++++++++++++ 4 files changed, 199 insertions(+) create mode 100644 score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/report_running_ready_condition.cpp create mode 100644 score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/report_running_ready_condition.hpp create mode 100644 score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/report_running_ready_condition_UT.cpp diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/BUILD b/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/BUILD index 77fef7fafa..7bc7d25af9 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/BUILD +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/BUILD @@ -11,6 +11,8 @@ # SPDX-License-Identifier: Apache-2.0 # ******************************************************************************* +load("//tests/utils/bazel:unit_test.bzl", "lm_cc_test") + cc_library( name = "iready_condition", hdrs = ["iready_condition.hpp"], @@ -35,3 +37,29 @@ cc_library( "@googletest//:gtest_main", ], ) + +cc_library( + name = "report_running_ready_condition", + srcs = ["report_running_ready_condition.cpp"], + hdrs = ["report_running_ready_condition.hpp"], + include_prefix = "score/mw/launch_manager/process_group_manager/details/ready_condition", + strip_include_prefix = "/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition", + visibility = ["//score/launch_manager/src/daemon/src/process_group_manager:__subpackages__"], + deps = [ + ":iready_condition", + "//score/launch_manager:error", + "//score/launch_manager/src/daemon/src/osal:return_types", + "//score/launch_manager/src/daemon/src/process_group_manager:iprocess", + ], +) + +lm_cc_test( + name = "report_running_ready_condition_UT", + srcs = ["report_running_ready_condition_UT.cpp"], + deps = [ + ":report_running_ready_condition", + "//score/launch_manager:error", + "//score/launch_manager/src/daemon/src/process_group_manager:mock_iprocess", + "@googletest//:gtest_main", + ], +) diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/report_running_ready_condition.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/report_running_ready_condition.cpp new file mode 100644 index 0000000000..3206813c0a --- /dev/null +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/report_running_ready_condition.cpp @@ -0,0 +1,50 @@ +/******************************************************************************** + * 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/ready_condition/report_running_ready_condition.hpp" +#include "score/mw/launch_manager/osal/return_types.hpp" +#include "score/mw/lifecycle/execution_error.h" + +namespace score::mw::lifecycle::internal +{ + +ReportRunningReadyCondition::ReportRunningReadyCondition( + osal::IProcess& launcher, + std::optional timeout) + : launcher_(launcher), timeout_(timeout) +{ +} + +Result ReportRunningReadyCondition::wait(const Handle handle) const +{ + return std::visit(*this, handle); +} + +Result ReportRunningReadyCondition::operator()(const ProcessHandle process) const +{ + const osal::OsalReturnType result = launcher_.waitForkRunning(process.sync, timeout_); + + if (result == osal::OsalReturnType::kSuccess) + { + return {}; + } + + return MakeUnexpected(ExecErrc::kGeneralError); +} + +Result ReportRunningReadyCondition::operator()(const Handle handle) const +{ + return MakeUnexpected(ExecErrc::kNotImplemented); +} + +} // namespace score::mw::lifecycle::internal diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/report_running_ready_condition.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/report_running_ready_condition.hpp new file mode 100644 index 0000000000..6c990402ab --- /dev/null +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/report_running_ready_condition.hpp @@ -0,0 +1,43 @@ +/******************************************************************************** + * 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_REPORT_RUNNING_HPP_INCLUDED +#define SCORE_LCM_REPORT_RUNNING_HPP_INCLUDED + +#include "score/mw/launch_manager/process_group_manager/details/handle.hpp" +#include "score/mw/launch_manager/process_group_manager/details/ready_condition/iready_condition.hpp" +#include "score/mw/launch_manager/process_group_manager/iprocess.hpp" +#include "score/result/result.h" +#include +#include + +namespace score::mw::lifecycle::internal +{ + +class ReportRunningReadyCondition final : public IReadyCondition +{ + public: + explicit ReportRunningReadyCondition(osal::IProcess& launcher, std::optional timeout); + + Result wait(const Handle handle) const override; + Result operator()(const ProcessHandle process) const; + Result operator()(const Handle handle) const; + + private: + osal::IProcess& launcher_; + const std::optional timeout_; +}; + +} // namespace score::mw::lifecycle::internal + +#endif // SCORE_LCM_REPORT_RUNNING_HPP_INCLUDED diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/report_running_ready_condition_UT.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/report_running_ready_condition_UT.cpp new file mode 100644 index 0000000000..08b223049b --- /dev/null +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/report_running_ready_condition_UT.cpp @@ -0,0 +1,78 @@ +/******************************************************************************** + * 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/ready_condition/report_running_ready_condition.hpp" +#include "score/mw/launch_manager/process_group_manager/mock_iprocess.hpp" +#include "score/mw/lifecycle/execution_error.h" +#include +#include + +namespace score::mw::lifecycle::internal +{ +namespace +{ + +using namespace ::testing; +using namespace std::chrono_literals; + +const ProcessHandle mock_handle = ProcessHandle{42}; + +TEST(ReportRunningReadyConditionTest, WaitCallsWaitForkRunningWithSyncAndTimeout) +{ + StrictMock launcher; + const ReportRunningReadyCondition report_running_ready_condition(launcher, 10ms); + + EXPECT_CALL(launcher, waitForkRunning(mock_handle.sync, std::optional(10ms))) + .WillOnce(Return(osal::OsalReturnType::kSuccess)); + + static_cast(report_running_ready_condition.wait(mock_handle)); +} + +TEST(ReportRunningReadyConditionTest, WaitSucceedsReturnsSuccess) +{ + NiceMock launcher; + const ReportRunningReadyCondition report_running_ready_condition(launcher, std::nullopt); + + ON_CALL(launcher, waitForkRunning(_, _)).WillByDefault(Return(osal::OsalReturnType::kSuccess)); + + const Result result = report_running_ready_condition.wait(mock_handle); + + EXPECT_TRUE(result.has_value()); +} + +TEST(ReportRunningReadyConditionTest, WaitFailsReturnsError) +{ + NiceMock launcher; + const ReportRunningReadyCondition report_running_ready_condition(launcher, std::nullopt); + + ON_CALL(launcher, waitForkRunning(_, _)).WillByDefault(Return(osal::OsalReturnType::kFail)); + + const Result result = report_running_ready_condition.wait(mock_handle); + + EXPECT_FALSE(result.has_value()); + EXPECT_EQ(result.error(), ExecErrc::kGeneralError); +} + +TEST(ReportRunningReadyConditionTest, WaitWithEmptyHandleReturnsError) +{ + NiceMock launcher; + const ReportRunningReadyCondition report_running_ready_condition(launcher, std::nullopt); + + const Result result = report_running_ready_condition.wait(EmptyHandle{}); + + EXPECT_FALSE(result.has_value()); + EXPECT_EQ(result.error(), ExecErrc::kNotImplemented); +} + +} // namespace +} // namespace score::mw::lifecycle::internal From 61e9dbb388b48ba43666249a0ac826fed7a761ae Mon Sep 17 00:00:00 2001 From: Daniel Thwaites Date: Thu, 1 Oct 2026 09:26:50 +0100 Subject: [PATCH 09/36] Pass through `stop_token` from the component to the actions --- .../details/component.cpp | 10 ++-- .../details/component.hpp | 4 +- .../details/component_UT.cpp | 56 +++++++++++++------ .../details/force_stop_action/BUILD | 1 + .../empty_force_stop_action.cpp | 2 +- .../empty_force_stop_action.hpp | 2 +- .../empty_force_stop_action_UT.cpp | 2 +- .../force_stop_action/iforce_stop_action.hpp | 3 +- .../mock_force_stop_action.hpp | 2 +- .../process_force_stop_action.cpp | 2 +- .../process_force_stop_action.hpp | 2 +- .../process_force_stop_action_UT.cpp | 8 +-- .../details/ready_condition/BUILD | 1 + .../ready_condition/iready_condition.hpp | 3 +- .../ready_condition/mock_ready_condition.hpp | 2 +- .../report_running_ready_condition.cpp | 2 +- .../report_running_ready_condition.hpp | 2 +- .../report_running_ready_condition_UT.cpp | 8 +-- .../details/start_action/BUILD | 1 + .../start_action/empty_start_action.cpp | 2 +- .../start_action/empty_start_action.hpp | 2 +- .../start_action/empty_start_action_UT.cpp | 4 +- .../details/start_action/istart_action.hpp | 3 +- .../start_action/mock_start_action.hpp | 2 +- .../start_action/process_start_action.cpp | 2 +- .../start_action/process_start_action.hpp | 2 +- .../start_action/process_start_action_UT.cpp | 6 +- .../details/stop_action/BUILD | 1 + .../details/stop_action/empty_stop_action.cpp | 2 +- .../details/stop_action/empty_stop_action.hpp | 2 +- .../stop_action/empty_stop_action_UT.cpp | 2 +- .../details/stop_action/istop_action.hpp | 3 +- .../details/stop_action/mock_stop_action.hpp | 2 +- .../stop_action/process_stop_action.cpp | 2 +- .../stop_action/process_stop_action.hpp | 2 +- .../stop_action/process_stop_action_UT.cpp | 8 +-- 36 files changed, 95 insertions(+), 65 deletions(-) diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/component.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/component.cpp index da2e1ed9be..4df72e871e 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/component.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/component.cpp @@ -31,9 +31,9 @@ Component::Component( { } -IComponent::RequestResult Component::activate(score::cpp::stop_token stop_token) +IComponent::RequestResult Component::activate(cpp::stop_token stop_token) { - const auto start_result = start_action_->start(); + const auto start_result = start_action_->start(stop_token); if (!start_result.has_value()) { return cpp::make_unexpected(ComponentError::kErrorBeforeReady); @@ -42,7 +42,7 @@ IComponent::RequestResult Component::activate(score::cpp::stop_token stop_token) for (const IReadyCondition* ready_condition : ready_conditions_) { - if (!ready_condition->wait(handle_.value()).has_value()) + if (!ready_condition->wait(stop_token, handle_.value()).has_value()) { return cpp::make_unexpected(ComponentError::kErrorBeforeReady); } @@ -51,11 +51,11 @@ IComponent::RequestResult Component::activate(score::cpp::stop_token stop_token) return RequestState::kSuccess; } -IComponent::RequestResult Component::deactivate(score::cpp::stop_token stop_token) +IComponent::RequestResult Component::deactivate(cpp::stop_token stop_token) { if (handle_.has_value()) { - if (!stop_action_->stop(handle_.value()).has_value()) + if (!stop_action_->stop(stop_token, handle_.value()).has_value()) { return cpp::make_unexpected(ComponentError::kErrorAfterReady); } diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/component.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/component.hpp index 0b0c54abe6..0f1f4b3f16 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/component.hpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/component.hpp @@ -34,8 +34,8 @@ class Component final : public IComponent const std::vector ready_conditions, IdentifierHash identifier = IdentifierHash{}); - RequestResult activate(score::cpp::stop_token stop_token) override; - RequestResult deactivate(score::cpp::stop_token stop_token) override; + RequestResult activate(cpp::stop_token stop_token) override; + RequestResult deactivate(cpp::stop_token stop_token) override; RequestResult tryHandleTermination(int32_t status) override; [[nodiscard]] IdentifierHash getIdentifier() const override; [[nodiscard]] bool active() const override; diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/component_UT.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/component_UT.cpp index 2d650c6731..fa2bd04342 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/component_UT.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/component_UT.cpp @@ -18,6 +18,7 @@ #include "score/mw/launch_manager/process_group_manager/details/stop_action/mock_stop_action.hpp" #include #include +#include namespace score::mw::lifecycle::internal { @@ -28,11 +29,19 @@ using namespace ::testing; const ProcessHandle mock_handle = ProcessHandle{42}; +const cpp::stop_source mock_stop_source; +const cpp::stop_token mock_stop_token = mock_stop_source.get_token(); + void expect_mock_handle(const Handle handle) { EXPECT_EQ(std::get(handle).pid, mock_handle.pid); } +void expect_mock_stop_token(const cpp::stop_token token) +{ + EXPECT_EQ(token, mock_stop_token); +} + TEST(ComponentTest, StartSucceeds) { MockStartAction start_action; @@ -40,7 +49,7 @@ TEST(ComponentTest, StartSucceeds) MockForceStopAction force_stop_action; Component component(&start_action, &stop_action, &force_stop_action, {}); - const auto result = component.activate(score::cpp::stop_token{}); + const auto result = component.activate(cpp::stop_token{}); EXPECT_TRUE(result.has_value()); } @@ -52,9 +61,10 @@ TEST(ComponentTest, StartActionCalled) MockForceStopAction force_stop_action; Component component(&start_action, &stop_action, &force_stop_action, {}); - EXPECT_CALL(start_action, start()).WillOnce(Return(Result{mock_handle})); + EXPECT_CALL(start_action, start(_)) + .WillOnce(DoAll(WithArg<0>(Invoke(expect_mock_stop_token)), Return(Result{mock_handle}))); - static_cast(component.activate(score::cpp::stop_token{})); + static_cast(component.activate(mock_stop_token)); } TEST(ComponentTest, StopActionCalled) @@ -64,11 +74,15 @@ TEST(ComponentTest, StopActionCalled) MockForceStopAction force_stop_action; Component component(&start_action, &stop_action, &force_stop_action, {}); - ON_CALL(start_action, start()).WillByDefault(Return(Result{mock_handle})); - EXPECT_CALL(stop_action, stop(_)).WillOnce(DoAll(Invoke(expect_mock_handle), Return(Result{}))); + ON_CALL(start_action, start(_)).WillByDefault(Return(Result{mock_handle})); + EXPECT_CALL(stop_action, stop(_, _)) + .WillOnce(DoAll( + WithArg<0>(Invoke(expect_mock_stop_token)), + WithArg<1>(Invoke(expect_mock_handle)), + Return(Result{}))); - static_cast(component.activate(score::cpp::stop_token{})); - component.deactivate(score::cpp::stop_token{}); + static_cast(component.activate(cpp::stop_token{})); + component.deactivate(mock_stop_token); } TEST(ComponentTest, ReadyConditionsCalled) @@ -81,11 +95,19 @@ TEST(ComponentTest, ReadyConditionsCalled) Component component(&start_action, &stop_action, &force_stop_action, {&ready_condition_1, &ready_condition_2}); InSequence sequence; - ON_CALL(start_action, start()).WillByDefault(Return(Result{mock_handle})); - EXPECT_CALL(ready_condition_1, wait(_)).WillOnce(DoAll(Invoke(expect_mock_handle), Return(Result{}))); - EXPECT_CALL(ready_condition_2, wait(_)).WillOnce(DoAll(Invoke(expect_mock_handle), Return(Result{}))); - - static_cast(component.activate(score::cpp::stop_token{})); + ON_CALL(start_action, start(_)).WillByDefault(Return(Result{mock_handle})); + EXPECT_CALL(ready_condition_1, wait(_, _)) + .WillOnce(DoAll( + WithArg<0>(Invoke(expect_mock_stop_token)), + WithArg<1>(Invoke(expect_mock_handle)), + Return(Result{}))); + EXPECT_CALL(ready_condition_2, wait(_, _)) + .WillOnce(DoAll( + WithArg<0>(Invoke(expect_mock_stop_token)), + WithArg<1>(Invoke(expect_mock_handle)), + Return(Result{}))); + + static_cast(component.activate(mock_stop_token)); } TEST(ComponentTest, StartSetsActive) @@ -95,7 +117,7 @@ TEST(ComponentTest, StartSetsActive) MockForceStopAction force_stop_action; Component component(&start_action, &stop_action, &force_stop_action, {}); - static_cast(component.activate(score::cpp::stop_token{})); + static_cast(component.activate(cpp::stop_token{})); EXPECT_TRUE(component.active()); } @@ -107,8 +129,8 @@ TEST(ComponentTest, StopClearsActive) MockForceStopAction force_stop_action; Component component(&start_action, &stop_action, &force_stop_action, {}); - static_cast(component.activate(score::cpp::stop_token{})); - component.deactivate(score::cpp::stop_token{}); + static_cast(component.activate(cpp::stop_token{})); + component.deactivate(cpp::stop_token{}); EXPECT_FALSE(component.active()); } @@ -120,8 +142,8 @@ TEST(ComponentTest, ForceStopClearsActive) MockForceStopAction force_stop_action; Component component(&start_action, &stop_action, &force_stop_action, {}); - static_cast(component.activate(score::cpp::stop_token{})); - component.deactivate(score::cpp::stop_token{}); + static_cast(component.activate(cpp::stop_token{})); + component.deactivate(cpp::stop_token{}); EXPECT_FALSE(component.active()); } diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/BUILD b/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/BUILD index 5a25492886..d9a88d1541 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/BUILD +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/BUILD @@ -21,6 +21,7 @@ cc_library( visibility = ["//score/launch_manager/src/daemon/src/process_group_manager:__subpackages__"], deps = [ "//score/launch_manager/src/daemon/src/process_group_manager/details:handle", + "@score_baselibs//score/language/futurecpp", "@score_baselibs//score/result", ], ) diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/empty_force_stop_action.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/empty_force_stop_action.cpp index 4897deb0f9..b2cb919142 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/empty_force_stop_action.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/empty_force_stop_action.cpp @@ -16,7 +16,7 @@ namespace score::mw::lifecycle::internal { -Result EmptyForceStopAction::force_stop(const Handle) const +Result EmptyForceStopAction::force_stop(cpp::stop_token stop_token, const Handle) const { return {}; } diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/empty_force_stop_action.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/empty_force_stop_action.hpp index c9efd545b7..fb611ddc9e 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/empty_force_stop_action.hpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/empty_force_stop_action.hpp @@ -22,7 +22,7 @@ namespace score::mw::lifecycle::internal class EmptyForceStopAction final : public IForceStopAction { public: - Result force_stop(const Handle) const override; + Result force_stop(cpp::stop_token stop_token, const Handle) const override; }; } // namespace score::mw::lifecycle::internal diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/empty_force_stop_action_UT.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/empty_force_stop_action_UT.cpp index 63d9efb9fa..ff81bd2ebe 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/empty_force_stop_action_UT.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/empty_force_stop_action_UT.cpp @@ -23,7 +23,7 @@ TEST(EmptyForceStopActionTest, ForceStopSucceeds) { EmptyForceStopAction empty_force_stop_action; - const auto result = empty_force_stop_action.force_stop(EmptyHandle{}); + const auto result = empty_force_stop_action.force_stop(cpp::stop_token{}, EmptyHandle{}); EXPECT_TRUE(result.has_value()); } diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/iforce_stop_action.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/iforce_stop_action.hpp index 7b5d8898bf..a5530724fa 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/iforce_stop_action.hpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/iforce_stop_action.hpp @@ -16,6 +16,7 @@ #include "score/mw/launch_manager/process_group_manager/details/handle.hpp" #include "score/result/result.h" +#include namespace score::mw::lifecycle::internal { @@ -24,7 +25,7 @@ class IForceStopAction { public: virtual ~IForceStopAction() = default; - virtual Result force_stop(const Handle handle) const = 0; + virtual Result force_stop(cpp::stop_token stop_token, const Handle handle) const = 0; }; } // namespace score::mw::lifecycle::internal diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/mock_force_stop_action.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/mock_force_stop_action.hpp index f0f5179822..32304da5c3 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/mock_force_stop_action.hpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/mock_force_stop_action.hpp @@ -23,7 +23,7 @@ namespace score::mw::lifecycle::internal class MockForceStopAction : public IForceStopAction { public: - MOCK_METHOD(Result, force_stop, (const Handle handle), (override, const)); + MOCK_METHOD(Result, force_stop, (cpp::stop_token, const Handle handle), (override, const)); }; } // namespace score::mw::lifecycle::internal diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/process_force_stop_action.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/process_force_stop_action.cpp index adbabdc47f..17f8f83a3c 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/process_force_stop_action.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/process_force_stop_action.cpp @@ -22,7 +22,7 @@ ProcessForceStopAction::ProcessForceStopAction(osal::IProcess& launcher) : launc { } -Result ProcessForceStopAction::force_stop(const Handle handle) const +Result ProcessForceStopAction::force_stop(cpp::stop_token stop_token, const Handle handle) const { return std::visit(*this, handle); } diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/process_force_stop_action.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/process_force_stop_action.hpp index f9ef25cbef..218d9b972b 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/process_force_stop_action.hpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/process_force_stop_action.hpp @@ -26,7 +26,7 @@ class ProcessForceStopAction final : public IForceStopAction public: explicit ProcessForceStopAction(osal::IProcess& launcher); - Result force_stop(const Handle handle) const override; + Result force_stop(cpp::stop_token stop_token, const Handle handle) const override; Result operator()(const ProcessHandle process) const; Result operator()(const Handle handle) const; diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/process_force_stop_action_UT.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/process_force_stop_action_UT.cpp index 792515805f..bb7b74c29d 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/process_force_stop_action_UT.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/process_force_stop_action_UT.cpp @@ -33,7 +33,7 @@ TEST(ProcessForceStopActionTest, ForceStopCallsForceTerminationWithPid) EXPECT_CALL(launcher, forceTermination(mock_handle.pid)).WillOnce(Return(osal::OsalReturnType::kSuccess)); - static_cast(process_force_stop_action.force_stop(mock_handle)); + static_cast(process_force_stop_action.force_stop(cpp::stop_token{}, mock_handle)); } TEST(ProcessForceStopActionTest, ForceStopSucceedsReturnsSuccess) @@ -43,7 +43,7 @@ TEST(ProcessForceStopActionTest, ForceStopSucceedsReturnsSuccess) ON_CALL(launcher, forceTermination(_)).WillByDefault(Return(osal::OsalReturnType::kSuccess)); - const Result result = process_force_stop_action.force_stop(mock_handle); + const Result result = process_force_stop_action.force_stop(cpp::stop_token{}, mock_handle); EXPECT_TRUE(result.has_value()); } @@ -55,7 +55,7 @@ TEST(ProcessForceStopActionTest, ForceStopFailsReturnsError) ON_CALL(launcher, forceTermination(_)).WillByDefault(Return(osal::OsalReturnType::kFail)); - const Result result = process_force_stop_action.force_stop(mock_handle); + const Result result = process_force_stop_action.force_stop(cpp::stop_token{}, mock_handle); EXPECT_FALSE(result.has_value()); EXPECT_EQ(result.error(), ExecErrc::kGeneralError); @@ -66,7 +66,7 @@ TEST(ProcessForceStopActionTest, ForceStopWithEmptyHandleReturnsError) NiceMock launcher; const ProcessForceStopAction process_force_stop_action(launcher); - const Result result = process_force_stop_action.force_stop(EmptyHandle{}); + const Result result = process_force_stop_action.force_stop(cpp::stop_token{}, EmptyHandle{}); EXPECT_FALSE(result.has_value()); EXPECT_EQ(result.error(), ExecErrc::kNotImplemented); diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/BUILD b/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/BUILD index 7bc7d25af9..4823634d16 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/BUILD +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/BUILD @@ -21,6 +21,7 @@ cc_library( visibility = ["//score/launch_manager/src/daemon/src/process_group_manager:__subpackages__"], deps = [ "//score/launch_manager/src/daemon/src/process_group_manager/details:handle", + "@score_baselibs//score/language/futurecpp", "@score_baselibs//score/result", ], ) diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/iready_condition.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/iready_condition.hpp index 45d4a95a3b..1539e9e9f1 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/iready_condition.hpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/iready_condition.hpp @@ -16,6 +16,7 @@ #include "score/mw/launch_manager/process_group_manager/details/handle.hpp" #include "score/result/result.h" +#include namespace score::mw::lifecycle::internal { @@ -24,7 +25,7 @@ class IReadyCondition { public: virtual ~IReadyCondition() = default; - virtual Result wait(const Handle handle) const = 0; + virtual Result wait(cpp::stop_token stop_token, const Handle handle) const = 0; }; } // namespace score::mw::lifecycle::internal diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/mock_ready_condition.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/mock_ready_condition.hpp index db99780180..93003c7072 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/mock_ready_condition.hpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/mock_ready_condition.hpp @@ -23,7 +23,7 @@ namespace score::mw::lifecycle::internal class MockReadyCondition : public IReadyCondition { public: - MOCK_METHOD(Result, wait, (const Handle handle), (override, const)); + MOCK_METHOD(Result, wait, (cpp::stop_token, const Handle handle), (override, const)); }; } // namespace score::mw::lifecycle::internal diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/report_running_ready_condition.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/report_running_ready_condition.cpp index 3206813c0a..90ea6d01df 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/report_running_ready_condition.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/report_running_ready_condition.cpp @@ -25,7 +25,7 @@ ReportRunningReadyCondition::ReportRunningReadyCondition( { } -Result ReportRunningReadyCondition::wait(const Handle handle) const +Result ReportRunningReadyCondition::wait(cpp::stop_token stop_token, const Handle handle) const { return std::visit(*this, handle); } diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/report_running_ready_condition.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/report_running_ready_condition.hpp index 6c990402ab..a1068441d1 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/report_running_ready_condition.hpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/report_running_ready_condition.hpp @@ -29,7 +29,7 @@ class ReportRunningReadyCondition final : public IReadyCondition public: explicit ReportRunningReadyCondition(osal::IProcess& launcher, std::optional timeout); - Result wait(const Handle handle) const override; + Result wait(cpp::stop_token stop_token, const Handle handle) const override; Result operator()(const ProcessHandle process) const; Result operator()(const Handle handle) const; diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/report_running_ready_condition_UT.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/report_running_ready_condition_UT.cpp index 08b223049b..7d6cf9bc69 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/report_running_ready_condition_UT.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/report_running_ready_condition_UT.cpp @@ -35,7 +35,7 @@ TEST(ReportRunningReadyConditionTest, WaitCallsWaitForkRunningWithSyncAndTimeout EXPECT_CALL(launcher, waitForkRunning(mock_handle.sync, std::optional(10ms))) .WillOnce(Return(osal::OsalReturnType::kSuccess)); - static_cast(report_running_ready_condition.wait(mock_handle)); + static_cast(report_running_ready_condition.wait(cpp::stop_token{}, mock_handle)); } TEST(ReportRunningReadyConditionTest, WaitSucceedsReturnsSuccess) @@ -45,7 +45,7 @@ TEST(ReportRunningReadyConditionTest, WaitSucceedsReturnsSuccess) ON_CALL(launcher, waitForkRunning(_, _)).WillByDefault(Return(osal::OsalReturnType::kSuccess)); - const Result result = report_running_ready_condition.wait(mock_handle); + const Result result = report_running_ready_condition.wait(cpp::stop_token{}, mock_handle); EXPECT_TRUE(result.has_value()); } @@ -57,7 +57,7 @@ TEST(ReportRunningReadyConditionTest, WaitFailsReturnsError) ON_CALL(launcher, waitForkRunning(_, _)).WillByDefault(Return(osal::OsalReturnType::kFail)); - const Result result = report_running_ready_condition.wait(mock_handle); + const Result result = report_running_ready_condition.wait(cpp::stop_token{}, mock_handle); EXPECT_FALSE(result.has_value()); EXPECT_EQ(result.error(), ExecErrc::kGeneralError); @@ -68,7 +68,7 @@ TEST(ReportRunningReadyConditionTest, WaitWithEmptyHandleReturnsError) NiceMock launcher; const ReportRunningReadyCondition report_running_ready_condition(launcher, std::nullopt); - const Result result = report_running_ready_condition.wait(EmptyHandle{}); + const Result result = report_running_ready_condition.wait(cpp::stop_token{}, EmptyHandle{}); EXPECT_FALSE(result.has_value()); EXPECT_EQ(result.error(), ExecErrc::kNotImplemented); diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/start_action/BUILD b/score/launch_manager/src/daemon/src/process_group_manager/details/start_action/BUILD index c65b1630fb..b8cb1aad9e 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/start_action/BUILD +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/start_action/BUILD @@ -21,6 +21,7 @@ cc_library( visibility = ["//score/launch_manager/src/daemon/src/process_group_manager:__subpackages__"], deps = [ "//score/launch_manager/src/daemon/src/process_group_manager/details:handle", + "@score_baselibs//score/language/futurecpp", "@score_baselibs//score/result", ], ) diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/start_action/empty_start_action.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/start_action/empty_start_action.cpp index 2affc54a0a..89df46bc84 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/start_action/empty_start_action.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/start_action/empty_start_action.cpp @@ -16,7 +16,7 @@ namespace score::mw::lifecycle::internal { -Result EmptyStartAction::start() const +Result EmptyStartAction::start(cpp::stop_token stop_token) const { return EmptyHandle{}; } diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/start_action/empty_start_action.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/start_action/empty_start_action.hpp index 3ba5cbc8a9..d641f0b043 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/start_action/empty_start_action.hpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/start_action/empty_start_action.hpp @@ -22,7 +22,7 @@ namespace score::mw::lifecycle::internal class EmptyStartAction final : public IStartAction { public: - Result start() const override; + Result start(cpp::stop_token stop_token) const override; }; } // namespace score::mw::lifecycle::internal diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/start_action/empty_start_action_UT.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/start_action/empty_start_action_UT.cpp index 3fee6f78f5..321dd13bc6 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/start_action/empty_start_action_UT.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/start_action/empty_start_action_UT.cpp @@ -23,7 +23,7 @@ TEST(EmptyStartActionTest, StartSucceeds) { EmptyStartAction empty_start_action; - const auto result = empty_start_action.start(); + const auto result = empty_start_action.start(cpp::stop_token{}); EXPECT_TRUE(result.has_value()); } @@ -32,7 +32,7 @@ TEST(EmptyStartActionTest, StartReturnsEmptyHandle) { EmptyStartAction empty_start_action; - const auto result = empty_start_action.start(); + const auto result = empty_start_action.start(cpp::stop_token{}); EXPECT_TRUE(std::holds_alternative(result.value())); } diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/start_action/istart_action.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/start_action/istart_action.hpp index d9a7094791..b7d6dffbe9 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/start_action/istart_action.hpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/start_action/istart_action.hpp @@ -16,6 +16,7 @@ #include "score/mw/launch_manager/process_group_manager/details/handle.hpp" #include "score/result/result.h" +#include namespace score::mw::lifecycle::internal { @@ -24,7 +25,7 @@ class IStartAction { public: virtual ~IStartAction() = default; - virtual Result start() const = 0; + virtual Result start(cpp::stop_token stop_token) const = 0; }; } // namespace score::mw::lifecycle::internal diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/start_action/mock_start_action.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/start_action/mock_start_action.hpp index e6c357e18e..7f8bbb4d18 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/start_action/mock_start_action.hpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/start_action/mock_start_action.hpp @@ -23,7 +23,7 @@ namespace score::mw::lifecycle::internal class MockStartAction : public IStartAction { public: - MOCK_METHOD(Result, start, (), (override, const)); + MOCK_METHOD(Result, start, (cpp::stop_token), (override, const)); }; } // namespace score::mw::lifecycle::internal diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/start_action/process_start_action.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/start_action/process_start_action.cpp index 934566a010..d306aaa5d8 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/start_action/process_start_action.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/start_action/process_start_action.cpp @@ -23,7 +23,7 @@ ProcessStartAction::ProcessStartAction(osal::IProcess& launcher, const configura { } -Result ProcessStartAction::start() const +Result ProcessStartAction::start(cpp::stop_token stop_token) const { osal::ProcessID pid = 0; osal::IpcCommsP sync = nullptr; diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/start_action/process_start_action.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/start_action/process_start_action.hpp index ebddf3941c..999bc79ec1 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/start_action/process_start_action.hpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/start_action/process_start_action.hpp @@ -28,7 +28,7 @@ class ProcessStartAction final : public IStartAction public: ProcessStartAction(osal::IProcess& launcher, const configuration::ComponentConfig& config); - Result start() const override; + Result start(cpp::stop_token stop_token) const override; private: osal::IProcess& launcher_; diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/start_action/process_start_action_UT.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/start_action/process_start_action_UT.cpp index 0f4371d7ae..1de9c8f3aa 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/start_action/process_start_action_UT.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/start_action/process_start_action_UT.cpp @@ -32,7 +32,7 @@ TEST(ProcessStartActionTest, StartCallsStartProcessWithConfig) EXPECT_CALL(launcher, startProcess(_, _, Ref(config))).WillOnce(Return(osal::OsalReturnType::kSuccess)); - static_cast(process_start_action.start()); + static_cast(process_start_action.start(cpp::stop_token{})); } TEST(ProcessStartActionTest, StartSucceedsReturnsProcessHandle) @@ -45,7 +45,7 @@ TEST(ProcessStartActionTest, StartSucceedsReturnsProcessHandle) ON_CALL(launcher, startProcess(_, _, _)) .WillByDefault(DoAll(SetArgReferee<0>(expected_pid), Return(osal::OsalReturnType::kSuccess))); - const Result result = process_start_action.start(); + const Result result = process_start_action.start(cpp::stop_token{}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(std::get(result.value()).pid, expected_pid); @@ -59,7 +59,7 @@ TEST(ProcessStartActionTest, StartFailsReturnsError) ON_CALL(launcher, startProcess(_, _, _)).WillByDefault(Return(osal::OsalReturnType::kFail)); - const Result result = process_start_action.start(); + const Result result = process_start_action.start(cpp::stop_token{}); EXPECT_FALSE(result.has_value()); EXPECT_EQ(result.error(), ExecErrc::kGeneralError); diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/BUILD b/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/BUILD index e0c85371cd..051454f096 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/BUILD +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/BUILD @@ -21,6 +21,7 @@ cc_library( visibility = ["//score/launch_manager/src/daemon/src/process_group_manager:__subpackages__"], deps = [ "//score/launch_manager/src/daemon/src/process_group_manager/details:handle", + "@score_baselibs//score/language/futurecpp", "@score_baselibs//score/result", ], ) diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/empty_stop_action.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/empty_stop_action.cpp index 7bf4ea2fda..76efcf8858 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/empty_stop_action.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/empty_stop_action.cpp @@ -16,7 +16,7 @@ namespace score::mw::lifecycle::internal { -Result EmptyStopAction::stop(const Handle) const +Result EmptyStopAction::stop(cpp::stop_token stop_token, const Handle) const { return {}; } diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/empty_stop_action.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/empty_stop_action.hpp index f3140acb97..c35ec671e9 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/empty_stop_action.hpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/empty_stop_action.hpp @@ -22,7 +22,7 @@ namespace score::mw::lifecycle::internal class EmptyStopAction final : public IStopAction { public: - Result stop(const Handle) const override; + Result stop(cpp::stop_token stop_token, const Handle) const override; }; } // namespace score::mw::lifecycle::internal diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/empty_stop_action_UT.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/empty_stop_action_UT.cpp index 9aa446291e..ceddb9b131 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/empty_stop_action_UT.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/empty_stop_action_UT.cpp @@ -23,7 +23,7 @@ TEST(EmptyStopActionTest, StopSucceeds) { EmptyStopAction empty_stop_action; - const auto result = empty_stop_action.stop(EmptyHandle{}); + const auto result = empty_stop_action.stop(cpp::stop_token{}, EmptyHandle{}); EXPECT_TRUE(result.has_value()); } diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/istop_action.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/istop_action.hpp index 45675fce43..9f8399d46b 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/istop_action.hpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/istop_action.hpp @@ -16,6 +16,7 @@ #include "score/mw/launch_manager/process_group_manager/details/handle.hpp" #include "score/result/result.h" +#include namespace score::mw::lifecycle::internal { @@ -24,7 +25,7 @@ class IStopAction { public: virtual ~IStopAction() = default; - virtual Result stop(const Handle handle) const = 0; + virtual Result stop(cpp::stop_token stop_token, const Handle handle) const = 0; }; } // namespace score::mw::lifecycle::internal diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/mock_stop_action.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/mock_stop_action.hpp index f6de99ae6c..ca4b3c2bd9 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/mock_stop_action.hpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/mock_stop_action.hpp @@ -23,7 +23,7 @@ namespace score::mw::lifecycle::internal class MockStopAction : public IStopAction { public: - MOCK_METHOD(Result, stop, (const Handle handle), (override, const)); + MOCK_METHOD(Result, stop, (cpp::stop_token, const Handle handle), (override, const)); }; } // namespace score::mw::lifecycle::internal diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/process_stop_action.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/process_stop_action.cpp index 486b569176..a1077306d4 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/process_stop_action.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/process_stop_action.cpp @@ -22,7 +22,7 @@ ProcessStopAction::ProcessStopAction(osal::IProcess& launcher) : launcher_(launc { } -Result ProcessStopAction::stop(const Handle handle) const +Result ProcessStopAction::stop(cpp::stop_token stop_token, const Handle handle) const { return std::visit(*this, handle); } diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/process_stop_action.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/process_stop_action.hpp index 358e2d7f91..1d512b615a 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/process_stop_action.hpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/process_stop_action.hpp @@ -26,7 +26,7 @@ class ProcessStopAction final : public IStopAction public: explicit ProcessStopAction(osal::IProcess& launcher); - Result stop(const Handle handle) const override; + Result stop(cpp::stop_token stop_token, const Handle handle) const override; Result operator()(const ProcessHandle process) const; Result operator()(const Handle handle) const; diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/process_stop_action_UT.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/process_stop_action_UT.cpp index 62e3a29083..20d0c179e1 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/process_stop_action_UT.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/process_stop_action_UT.cpp @@ -33,7 +33,7 @@ TEST(ProcessStopActionTest, StopCallsRequestTerminationWithPid) EXPECT_CALL(launcher, requestTermination(mock_handle.pid)).WillOnce(Return(osal::OsalReturnType::kSuccess)); - static_cast(process_stop_action.stop(mock_handle)); + static_cast(process_stop_action.stop(cpp::stop_token{}, mock_handle)); } TEST(ProcessStopActionTest, StopSucceedsReturnsSuccess) @@ -43,7 +43,7 @@ TEST(ProcessStopActionTest, StopSucceedsReturnsSuccess) ON_CALL(launcher, requestTermination(_)).WillByDefault(Return(osal::OsalReturnType::kSuccess)); - const Result result = process_stop_action.stop(mock_handle); + const Result result = process_stop_action.stop(cpp::stop_token{}, mock_handle); EXPECT_TRUE(result.has_value()); } @@ -55,7 +55,7 @@ TEST(ProcessStopActionTest, StopFailsReturnsError) ON_CALL(launcher, requestTermination(_)).WillByDefault(Return(osal::OsalReturnType::kFail)); - const Result result = process_stop_action.stop(mock_handle); + const Result result = process_stop_action.stop(cpp::stop_token{}, mock_handle); EXPECT_FALSE(result.has_value()); EXPECT_EQ(result.error(), ExecErrc::kGeneralError); @@ -66,7 +66,7 @@ TEST(ProcessStopActionTest, StopWithEmptyHandleReturnsError) NiceMock launcher; const ProcessStopAction process_stop_action(launcher); - const Result result = process_stop_action.stop(EmptyHandle{}); + const Result result = process_stop_action.stop(cpp::stop_token{}, EmptyHandle{}); EXPECT_FALSE(result.has_value()); EXPECT_EQ(result.error(), ExecErrc::kNotImplemented); From 9bbe70b2536933d05b82cf8f99cf6be6272a48fe Mon Sep 17 00:00:00 2001 From: Daniel Thwaites Date: Thu, 1 Oct 2026 10:01:13 +0100 Subject: [PATCH 10/36] Use references rather than pointers in `Component` --- .../process_group_manager/details/component.cpp | 16 ++++++++-------- .../process_group_manager/details/component.hpp | 17 +++++++++-------- .../details/component_UT.cpp | 14 +++++++------- 3 files changed, 24 insertions(+), 23 deletions(-) diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/component.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/component.cpp index 4df72e871e..22702a2050 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/component.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/component.cpp @@ -17,10 +17,10 @@ namespace score::mw::lifecycle::internal { Component::Component( - const IStartAction* start_action, - const IStopAction* stop_action, - const IForceStopAction* force_stop_action, - const std::vector ready_conditions, + const IStartAction& start_action, + const IStopAction& stop_action, + const IForceStopAction& force_stop_action, + const std::vector> ready_conditions, IdentifierHash identifier) : handle_(EmptyHandle{}), start_action_(start_action), @@ -33,16 +33,16 @@ Component::Component( IComponent::RequestResult Component::activate(cpp::stop_token stop_token) { - const auto start_result = start_action_->start(stop_token); + const auto start_result = start_action_.start(stop_token); if (!start_result.has_value()) { return cpp::make_unexpected(ComponentError::kErrorBeforeReady); } handle_ = start_result.value(); - for (const IReadyCondition* ready_condition : ready_conditions_) + for (const IReadyCondition& ready_condition : ready_conditions_) { - if (!ready_condition->wait(stop_token, handle_.value()).has_value()) + if (!ready_condition.wait(stop_token, handle_.value()).has_value()) { return cpp::make_unexpected(ComponentError::kErrorBeforeReady); } @@ -55,7 +55,7 @@ IComponent::RequestResult Component::deactivate(cpp::stop_token stop_token) { if (handle_.has_value()) { - if (!stop_action_->stop(stop_token, handle_.value()).has_value()) + if (!stop_action_.stop(stop_token, handle_.value()).has_value()) { return cpp::make_unexpected(ComponentError::kErrorAfterReady); } diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/component.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/component.hpp index 0f1f4b3f16..7ab2ebd0d5 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/component.hpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/component.hpp @@ -19,6 +19,7 @@ #include "score/mw/launch_manager/process_group_manager/details/ready_condition/iready_condition.hpp" #include "score/mw/launch_manager/process_group_manager/details/start_action/istart_action.hpp" #include "score/mw/launch_manager/process_group_manager/details/stop_action/istop_action.hpp" +#include #include namespace score::mw::lifecycle::internal @@ -28,10 +29,10 @@ class Component final : public IComponent { public: Component( - const IStartAction* start_action, - const IStopAction* stop_action, - const IForceStopAction* force_stop_action, - const std::vector ready_conditions, + const IStartAction& start_action, + const IStopAction& stop_action, + const IForceStopAction& force_stop_action, + std::vector> ready_conditions, IdentifierHash identifier = IdentifierHash{}); RequestResult activate(cpp::stop_token stop_token) override; @@ -42,10 +43,10 @@ class Component final : public IComponent private: std::optional handle_; - const IStartAction* start_action_; - const IStopAction* stop_action_; - const IForceStopAction* force_stop_action_; - const std::vector ready_conditions_; + const IStartAction& start_action_; + const IStopAction& stop_action_; + const IForceStopAction& force_stop_action_; + const std::vector> ready_conditions_; IdentifierHash identifier_; }; diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/component_UT.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/component_UT.cpp index fa2bd04342..c9826c5d46 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/component_UT.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/component_UT.cpp @@ -47,7 +47,7 @@ TEST(ComponentTest, StartSucceeds) MockStartAction start_action; MockStopAction stop_action; MockForceStopAction force_stop_action; - Component component(&start_action, &stop_action, &force_stop_action, {}); + Component component(start_action, stop_action, force_stop_action, {}); const auto result = component.activate(cpp::stop_token{}); @@ -59,7 +59,7 @@ TEST(ComponentTest, StartActionCalled) MockStartAction start_action; MockStopAction stop_action; MockForceStopAction force_stop_action; - Component component(&start_action, &stop_action, &force_stop_action, {}); + Component component(start_action, stop_action, force_stop_action, {}); EXPECT_CALL(start_action, start(_)) .WillOnce(DoAll(WithArg<0>(Invoke(expect_mock_stop_token)), Return(Result{mock_handle}))); @@ -72,7 +72,7 @@ TEST(ComponentTest, StopActionCalled) MockStartAction start_action; MockStopAction stop_action; MockForceStopAction force_stop_action; - Component component(&start_action, &stop_action, &force_stop_action, {}); + Component component(start_action, stop_action, force_stop_action, {}); ON_CALL(start_action, start(_)).WillByDefault(Return(Result{mock_handle})); EXPECT_CALL(stop_action, stop(_, _)) @@ -92,7 +92,7 @@ TEST(ComponentTest, ReadyConditionsCalled) MockReadyCondition ready_condition_1; MockReadyCondition ready_condition_2; MockForceStopAction force_stop_action; - Component component(&start_action, &stop_action, &force_stop_action, {&ready_condition_1, &ready_condition_2}); + Component component(start_action, stop_action, force_stop_action, {ready_condition_1, ready_condition_2}); InSequence sequence; ON_CALL(start_action, start(_)).WillByDefault(Return(Result{mock_handle})); @@ -115,7 +115,7 @@ TEST(ComponentTest, StartSetsActive) MockStartAction start_action; MockStopAction stop_action; MockForceStopAction force_stop_action; - Component component(&start_action, &stop_action, &force_stop_action, {}); + Component component(start_action, stop_action, force_stop_action, {}); static_cast(component.activate(cpp::stop_token{})); @@ -127,7 +127,7 @@ TEST(ComponentTest, StopClearsActive) MockStartAction start_action; MockStopAction stop_action; MockForceStopAction force_stop_action; - Component component(&start_action, &stop_action, &force_stop_action, {}); + Component component(start_action, stop_action, force_stop_action, {}); static_cast(component.activate(cpp::stop_token{})); component.deactivate(cpp::stop_token{}); @@ -140,7 +140,7 @@ TEST(ComponentTest, ForceStopClearsActive) MockStartAction start_action; MockStopAction stop_action; MockForceStopAction force_stop_action; - Component component(&start_action, &stop_action, &force_stop_action, {}); + Component component(start_action, stop_action, force_stop_action, {}); static_cast(component.activate(cpp::stop_token{})); component.deactivate(cpp::stop_token{}); From 3e34721e15b9f13aa36fd91b8513134964f448ba Mon Sep 17 00:00:00 2001 From: Daniel Thwaites Date: Thu, 1 Oct 2026 11:06:29 +0100 Subject: [PATCH 11/36] Use `cpp::span` rather than `std::vector` for ready conditions --- .../src/process_group_manager/details/BUILD | 1 + .../details/component.cpp | 2 +- .../details/component.hpp | 6 ++++-- .../details/component_UT.cpp | 18 ++++++++++-------- 4 files changed, 16 insertions(+), 11 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 34e76e9975..ce3a2fff42 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 @@ -27,6 +27,7 @@ cc_library( "//score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition:iready_condition", "//score/launch_manager/src/daemon/src/process_group_manager/details/start_action:istart_action", "//score/launch_manager/src/daemon/src/process_group_manager/details/stop_action:istop_action", + "@score_baselibs//score/language/futurecpp", ], ) diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/component.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/component.cpp index 22702a2050..fec93d1a4d 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/component.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/component.cpp @@ -20,7 +20,7 @@ Component::Component( const IStartAction& start_action, const IStopAction& stop_action, const IForceStopAction& force_stop_action, - const std::vector> ready_conditions, + const cpp::span> ready_conditions, IdentifierHash identifier) : handle_(EmptyHandle{}), start_action_(start_action), diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/component.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/component.hpp index 7ab2ebd0d5..8cd7995c4a 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/component.hpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/component.hpp @@ -19,6 +19,7 @@ #include "score/mw/launch_manager/process_group_manager/details/ready_condition/iready_condition.hpp" #include "score/mw/launch_manager/process_group_manager/details/start_action/istart_action.hpp" #include "score/mw/launch_manager/process_group_manager/details/stop_action/istop_action.hpp" +#include #include #include @@ -32,7 +33,8 @@ class Component final : public IComponent const IStartAction& start_action, const IStopAction& stop_action, const IForceStopAction& force_stop_action, - std::vector> ready_conditions, + cpp::span> ready_conditions = + cpp::span>{}, IdentifierHash identifier = IdentifierHash{}); RequestResult activate(cpp::stop_token stop_token) override; @@ -46,7 +48,7 @@ class Component final : public IComponent const IStartAction& start_action_; const IStopAction& stop_action_; const IForceStopAction& force_stop_action_; - const std::vector> ready_conditions_; + const cpp::span> ready_conditions_; IdentifierHash identifier_; }; diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/component_UT.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/component_UT.cpp index c9826c5d46..c0220c537b 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/component_UT.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/component_UT.cpp @@ -19,6 +19,7 @@ #include #include #include +#include namespace score::mw::lifecycle::internal { @@ -47,7 +48,7 @@ TEST(ComponentTest, StartSucceeds) MockStartAction start_action; MockStopAction stop_action; MockForceStopAction force_stop_action; - Component component(start_action, stop_action, force_stop_action, {}); + Component component(start_action, stop_action, force_stop_action); const auto result = component.activate(cpp::stop_token{}); @@ -59,7 +60,7 @@ TEST(ComponentTest, StartActionCalled) MockStartAction start_action; MockStopAction stop_action; MockForceStopAction force_stop_action; - Component component(start_action, stop_action, force_stop_action, {}); + Component component(start_action, stop_action, force_stop_action); EXPECT_CALL(start_action, start(_)) .WillOnce(DoAll(WithArg<0>(Invoke(expect_mock_stop_token)), Return(Result{mock_handle}))); @@ -72,7 +73,7 @@ TEST(ComponentTest, StopActionCalled) MockStartAction start_action; MockStopAction stop_action; MockForceStopAction force_stop_action; - Component component(start_action, stop_action, force_stop_action, {}); + Component component(start_action, stop_action, force_stop_action); ON_CALL(start_action, start(_)).WillByDefault(Return(Result{mock_handle})); EXPECT_CALL(stop_action, stop(_, _)) @@ -89,10 +90,11 @@ TEST(ComponentTest, ReadyConditionsCalled) { MockStartAction start_action; MockStopAction stop_action; + MockForceStopAction force_stop_action; MockReadyCondition ready_condition_1; MockReadyCondition ready_condition_2; - MockForceStopAction force_stop_action; - Component component(start_action, stop_action, force_stop_action, {ready_condition_1, ready_condition_2}); + std::array, 2> ready_conditions{ready_condition_1, ready_condition_2}; + Component component(start_action, stop_action, force_stop_action, ready_conditions); InSequence sequence; ON_CALL(start_action, start(_)).WillByDefault(Return(Result{mock_handle})); @@ -115,7 +117,7 @@ TEST(ComponentTest, StartSetsActive) MockStartAction start_action; MockStopAction stop_action; MockForceStopAction force_stop_action; - Component component(start_action, stop_action, force_stop_action, {}); + Component component(start_action, stop_action, force_stop_action); static_cast(component.activate(cpp::stop_token{})); @@ -127,7 +129,7 @@ TEST(ComponentTest, StopClearsActive) MockStartAction start_action; MockStopAction stop_action; MockForceStopAction force_stop_action; - Component component(start_action, stop_action, force_stop_action, {}); + Component component(start_action, stop_action, force_stop_action); static_cast(component.activate(cpp::stop_token{})); component.deactivate(cpp::stop_token{}); @@ -140,7 +142,7 @@ TEST(ComponentTest, ForceStopClearsActive) MockStartAction start_action; MockStopAction stop_action; MockForceStopAction force_stop_action; - Component component(start_action, stop_action, force_stop_action, {}); + Component component(start_action, stop_action, force_stop_action); static_cast(component.activate(cpp::stop_token{})); component.deactivate(cpp::stop_token{}); From 9150542641fd0e24dc9addd97a74ccf38c692eac Mon Sep 17 00:00:00 2001 From: Daniel Thwaites Date: Fri, 2 Oct 2026 09:14:22 +0100 Subject: [PATCH 12/36] Add docstrings for new classes --- .../details/component.cpp | 2 +- .../details/component.hpp | 38 +++++++++++++++++++ .../empty_force_stop_action.hpp | 5 +++ .../force_stop_action/iforce_stop_action.hpp | 6 +++ .../process_force_stop_action.hpp | 18 +++++++++ .../process_group_manager/details/handle.hpp | 5 +++ .../ready_condition/iready_condition.hpp | 6 +++ .../report_running_ready_condition.hpp | 22 +++++++++++ .../start_action/empty_start_action.hpp | 4 ++ .../details/start_action/istart_action.hpp | 5 +++ .../start_action/process_start_action.hpp | 11 ++++++ .../details/stop_action/empty_stop_action.hpp | 5 +++ .../details/stop_action/istop_action.hpp | 6 +++ .../stop_action/process_stop_action.hpp | 18 +++++++++ 14 files changed, 150 insertions(+), 1 deletion(-) diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/component.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/component.cpp index fec93d1a4d..d38d858fc4 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/component.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/component.cpp @@ -22,7 +22,7 @@ Component::Component( const IForceStopAction& force_stop_action, const cpp::span> ready_conditions, IdentifierHash identifier) - : handle_(EmptyHandle{}), + : handle_(std::nullopt), start_action_(start_action), stop_action_(stop_action), force_stop_action_(force_stop_action), diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/component.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/component.hpp index 8cd7995c4a..c7d362cad2 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/component.hpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/component.hpp @@ -26,9 +26,16 @@ namespace score::mw::lifecycle::internal { +/// @brief Manages and tracks the state of a resource. class Component final : public IComponent { public: + /// @brief Creates a new component from the given actions. + /// @param start_action How to start the resource. + /// @param stop_action How to gracefully stop the resource. + /// @param force_stop_action How to forcefully stop the resource. + /// @param ready_conditions How to decide when the resource has finished its startup. + /// @param identifier Name of the component. Component( const IStartAction& start_action, const IStopAction& stop_action, @@ -37,18 +44,49 @@ class Component final : public IComponent cpp::span>{}, IdentifierHash identifier = IdentifierHash{}); + /// @brief Start the resource and set the component to active. + /// @param stop_token Token which can be used to interrupt the activation. + /// @return Whether the component activation was successful, is waiting for another + /// thread, or an error was encountered. RequestResult activate(cpp::stop_token stop_token) override; + + /// @brief Stop the resource and set the component to inactive. + /// @param stop_token Token which can be used to interrupt the deactivation. + /// @return Whether the component deactivation was successful, is waiting for another + /// thread, or an error was encountered. RequestResult deactivate(cpp::stop_token stop_token) override; + + /// @brief Notify the component that a POSIX process has terminated. + /// @param status Exit code of the process. + /// @return Whether the component activation was successful, is waiting for another + /// thread, or an error was encountered. RequestResult tryHandleTermination(int32_t status) override; + + /// @brief Return the name of the component. + /// @return The name of the component. [[nodiscard]] IdentifierHash getIdentifier() const override; + + /// @brief Return whether the component is active. + /// @return Whether the component is active. [[nodiscard]] bool active() const override; private: + /// @brief The resource being managed. std::optional handle_; + + /// @brief How to start the resource. const IStartAction& start_action_; + + /// @brief How to gracefully stop the resource. const IStopAction& stop_action_; + + /// @brief How to forcefully stop the resource. const IForceStopAction& force_stop_action_; + + /// @brief How to decide when the resource has finished its startup. const cpp::span> ready_conditions_; + + /// @brief Name of the component. IdentifierHash identifier_; }; diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/empty_force_stop_action.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/empty_force_stop_action.hpp index fb611ddc9e..bcd6d74f4d 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/empty_force_stop_action.hpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/empty_force_stop_action.hpp @@ -19,9 +19,14 @@ namespace score::mw::lifecycle::internal { +/// @brief A force stop action which does nothing. class EmptyForceStopAction final : public IForceStopAction { public: + /// @brief Does nothing and returns success. + /// @param stop_token Token which can be used to interrupt the action. + /// @param handle The resource to act upon. + /// @return Always successful. Result force_stop(cpp::stop_token stop_token, const Handle) const override; }; diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/iforce_stop_action.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/iforce_stop_action.hpp index a5530724fa..978f2aa93c 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/iforce_stop_action.hpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/iforce_stop_action.hpp @@ -21,10 +21,16 @@ namespace score::mw::lifecycle::internal { +/// @brief An action which describes how to forcefully stop a resource. class IForceStopAction { public: virtual ~IForceStopAction() = default; + + /// @brief Forcefully stop the resource represented by the given handle. + /// @param stop_token Token which can be used to interrupt the action. + /// @param handle The resource to act upon. + /// @return Whether the action was successful, or failed with an error. virtual Result force_stop(cpp::stop_token stop_token, const Handle handle) const = 0; }; diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/process_force_stop_action.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/process_force_stop_action.hpp index 218d9b972b..809e0bcca1 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/process_force_stop_action.hpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/process_force_stop_action.hpp @@ -21,16 +21,34 @@ namespace score::mw::lifecycle::internal { +/// @brief A force stop action which forcefully stops a POSIX process. +/// @details The process is stopped by sending SIGKILL, which cannot be caught +/// by the process and results in immediate termination. class ProcessForceStopAction final : public IForceStopAction { public: + /// @brief Creates a new process force stop action. + /// @param launcher The process launcher used to stop the process. explicit ProcessForceStopAction(osal::IProcess& launcher); + /// @brief Forcefully stop the process represented by the given handle. + /// @param stop_token Token which can be used to interrupt the action. + /// @param handle The resource to act upon. + /// @return Whether the action was successful, or failed with an error. Result force_stop(cpp::stop_token stop_token, const Handle handle) const override; + + /// @brief Forcefully stop the process represented by the given handle. + /// @param process The process to act upon. + /// @return Whether the action was successful, or failed with an error. Result operator()(const ProcessHandle process) const; + + /// @brief Forcefully stop the resource represented by the given handle. + /// @param handle The resource to act upon. + /// @return Always returns a not-implemented error. Result operator()(const Handle handle) const; private: + /// @brief The process launcher used to stop the process. osal::IProcess& launcher_; }; diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/handle.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/handle.hpp index 92ab3ccb78..b89f64ff0d 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/handle.hpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/handle.hpp @@ -22,16 +22,21 @@ namespace score::mw::lifecycle::internal { +/// @brief Signifies that we are not managing any resource. +/// @details This is useful for run targets and synchronisation points, +/// which exist only to depend on other components. struct EmptyHandle { }; +/// @brief Represents a POSIX process that we are managing. struct ProcessHandle { pid_t pid; osal::IpcCommsP sync; }; +/// @brief Represents some resource that we are managing. using Handle = std::variant; } // namespace score::mw::lifecycle::internal diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/iready_condition.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/iready_condition.hpp index 1539e9e9f1..1f912b48a0 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/iready_condition.hpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/iready_condition.hpp @@ -21,10 +21,16 @@ namespace score::mw::lifecycle::internal { +/// @brief A condition which decides when a resource has finished its startup. class IReadyCondition { public: virtual ~IReadyCondition() = default; + + /// @brief Wait until the resource represented by the given handle is ready. + /// @param stop_token Token which can be used to interrupt the wait. + /// @param handle The resource to wait on. + /// @return Whether the wait was successful, or failed with an error. virtual Result wait(cpp::stop_token stop_token, const Handle handle) const = 0; }; diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/report_running_ready_condition.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/report_running_ready_condition.hpp index a1068441d1..4c87857bb0 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/report_running_ready_condition.hpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/report_running_ready_condition.hpp @@ -24,17 +24,39 @@ namespace score::mw::lifecycle::internal { +/// @brief A ready condition which waits for a process to report running +/// through a shared memory channel. class ReportRunningReadyCondition final : public IReadyCondition { public: + /// @brief Creates a new report running ready condition. + /// @param launcher The process launcher used to wait for the process. + /// @param timeout The maximum duration to wait for the process to report running, + /// or no timeout if not specified. explicit ReportRunningReadyCondition(osal::IProcess& launcher, std::optional timeout); + /// @brief Wait until the resource represented by the given handle is ready. + /// @param stop_token Token which can be used to interrupt the wait. + /// @param handle The resource to wait on. + /// @return Whether the wait was successful, or failed with an error. Result wait(cpp::stop_token stop_token, const Handle handle) const override; + + /// @brief Wait until the process represented by the given handle reports running. + /// @param process The process to wait on. + /// @return Whether the wait was successful, or failed with an error. Result operator()(const ProcessHandle process) const; + + /// @brief Wait until the resource represented by the given handle is ready. + /// @param handle The resource to wait on. + /// @return Always returns a not-implemented error. Result operator()(const Handle handle) const; private: + /// @brief The process launcher used to wait for the process. osal::IProcess& launcher_; + + /// @brief The maximum duration to wait for the process to report running, + /// or no timeout if not specified. const std::optional timeout_; }; diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/start_action/empty_start_action.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/start_action/empty_start_action.hpp index d641f0b043..f9b9641d9d 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/start_action/empty_start_action.hpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/start_action/empty_start_action.hpp @@ -19,9 +19,13 @@ namespace score::mw::lifecycle::internal { +/// @brief A start action which does nothing. class EmptyStartAction final : public IStartAction { public: + /// @brief Does nothing and returns an empty handle. + /// @param stop_token Token which can be used to interrupt the action. + /// @return An empty handle. Result start(cpp::stop_token stop_token) const override; }; diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/start_action/istart_action.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/start_action/istart_action.hpp index b7d6dffbe9..3b50e65e8e 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/start_action/istart_action.hpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/start_action/istart_action.hpp @@ -21,10 +21,15 @@ namespace score::mw::lifecycle::internal { +/// @brief An action which describes how to start a resource. class IStartAction { public: virtual ~IStartAction() = default; + + /// @brief Start the resource. + /// @param stop_token Token which can be used to interrupt the action. + /// @return The handle of the started resource, or an error if the action failed. virtual Result start(cpp::stop_token stop_token) const = 0; }; diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/start_action/process_start_action.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/start_action/process_start_action.hpp index 999bc79ec1..8adbdc27bc 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/start_action/process_start_action.hpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/start_action/process_start_action.hpp @@ -23,15 +23,26 @@ namespace score::mw::lifecycle::internal { +/// @brief A start action which starts a POSIX process. class ProcessStartAction final : public IStartAction { public: + /// @brief Creates a new process start action. + /// @param launcher The process launcher used to start the process. + /// @param config Configuration describing the executable to launch + /// and its environment. ProcessStartAction(osal::IProcess& launcher, const configuration::ComponentConfig& config); + /// @brief Start a process. + /// @param stop_token Token which can be used to interrupt the action. + /// @return The handle of the started process, or an error if the action failed. Result start(cpp::stop_token stop_token) const override; private: + /// @brief The process launcher used to start the process. osal::IProcess& launcher_; + + /// @brief Configuration describing the executable to launch and its environment. const configuration::ComponentConfig& config_; }; diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/empty_stop_action.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/empty_stop_action.hpp index c35ec671e9..e957788b5a 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/empty_stop_action.hpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/empty_stop_action.hpp @@ -19,9 +19,14 @@ namespace score::mw::lifecycle::internal { +/// @brief A stop action which does nothing. class EmptyStopAction final : public IStopAction { public: + /// @brief Does nothing and returns success. + /// @param stop_token Token which can be used to interrupt the action. + /// @param handle The resource to act upon. + /// @return Always successful. Result stop(cpp::stop_token stop_token, const Handle) const override; }; diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/istop_action.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/istop_action.hpp index 9f8399d46b..27426b3780 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/istop_action.hpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/istop_action.hpp @@ -21,10 +21,16 @@ namespace score::mw::lifecycle::internal { +/// @brief An action which describes how to gracefully stop a resource. class IStopAction { public: virtual ~IStopAction() = default; + + /// @brief Gracefully stop the resource represented by the given handle. + /// @param stop_token Token which can be used to interrupt the action. + /// @param handle The resource to act upon. + /// @return Whether the action was successful, or failed with an error. virtual Result stop(cpp::stop_token stop_token, const Handle handle) const = 0; }; diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/process_stop_action.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/process_stop_action.hpp index 1d512b615a..03bb4526c2 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/process_stop_action.hpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/process_stop_action.hpp @@ -21,16 +21,34 @@ namespace score::mw::lifecycle::internal { +/// @brief A stop action which gracefully stops a POSIX process. +/// @details The process is stopped by sending SIGTERM, which the process may handle +/// to perform cleanup before exiting. class ProcessStopAction final : public IStopAction { public: + /// @brief Creates a new process stop action. + /// @param launcher The process launcher used to stop the process. explicit ProcessStopAction(osal::IProcess& launcher); + /// @brief Gracefully stop the process represented by the given handle. + /// @param stop_token Token which can be used to interrupt the action. + /// @param handle The resource to act upon. + /// @return Whether the action was successful, or failed with an error. Result stop(cpp::stop_token stop_token, const Handle handle) const override; + + /// @brief Gracefully stop the process represented by the given handle. + /// @param process The process to act upon. + /// @return Whether the action was successful, or failed with an error. Result operator()(const ProcessHandle process) const; + + /// @brief Gracefully stop the resource represented by the given handle. + /// @param handle The resource to act upon. + /// @return Always returns a not-implemented error. Result operator()(const Handle handle) const; private: + /// @brief The process launcher used to stop the process. osal::IProcess& launcher_; }; From 4601d01505c56566c152921049c7f88d48762701 Mon Sep 17 00:00:00 2001 From: Daniel Thwaites Date: Fri, 2 Oct 2026 10:40:16 +0100 Subject: [PATCH 13/36] Add file ready condition --- .../details/ready_condition/BUILD | 28 +++++ .../ready_condition/file_ready_condition.cpp | 43 ++++++++ .../ready_condition/file_ready_condition.hpp | 71 +++++++++++++ .../file_ready_condition_UT.cpp | 100 ++++++++++++++++++ 4 files changed, 242 insertions(+) create mode 100644 score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/file_ready_condition.cpp create mode 100644 score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/file_ready_condition.hpp create mode 100644 score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/file_ready_condition_UT.cpp diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/BUILD b/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/BUILD index 4823634d16..58398d843b 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/BUILD +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/BUILD @@ -39,6 +39,34 @@ cc_library( ], ) +cc_library( + name = "file_ready_condition", + srcs = ["file_ready_condition.cpp"], + hdrs = ["file_ready_condition.hpp"], + include_prefix = "score/mw/launch_manager/process_group_manager/details/ready_condition", + strip_include_prefix = "/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition", + visibility = ["//score/launch_manager/src/daemon/src/process_group_manager:__subpackages__"], + deps = [ + ":iready_condition", + "//score/launch_manager:error", + "//score/launch_manager/src/daemon/src/configuration:component_config", + "//score/launch_manager/src/daemon/src/osal:ifile_waiter", + "//score/launch_manager/src/daemon/src/osal:return_types", + "@score_baselibs//score/language/safecpp/string_view:zstring_view", + ], +) + +lm_cc_test( + name = "file_ready_condition_UT", + srcs = ["file_ready_condition_UT.cpp"], + deps = [ + ":file_ready_condition", + "//score/launch_manager:error", + "//score/launch_manager/src/daemon/src/osal:mock_ifile_waiter", + "@googletest//:gtest_main", + ], +) + cc_library( name = "report_running_ready_condition", srcs = ["report_running_ready_condition.cpp"], diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/file_ready_condition.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/file_ready_condition.cpp new file mode 100644 index 0000000000..99c9944689 --- /dev/null +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/file_ready_condition.cpp @@ -0,0 +1,43 @@ +/******************************************************************************** + * 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/ready_condition/file_ready_condition.hpp" +#include "score/mw/launch_manager/osal/return_types.hpp" +#include "score/mw/lifecycle/execution_error.h" + +namespace score::mw::lifecycle::internal +{ + +FileReadyCondition::FileReadyCondition( + const osal::IFileWaiter& file_waiter, + score::safecpp::zstring_view file_path, + configuration::FileExistenceState state, + std::chrono::milliseconds timeout, + std::chrono::milliseconds interval) + : file_waiter_(file_waiter), file_path_(file_path), state_(state), timeout_(timeout), interval_(interval) +{ +} + +Result FileReadyCondition::wait(cpp::stop_token stop_token, [[maybe_unused]] const Handle) const +{ + const osal::OsalReturnType result = file_waiter_.waitForFile(file_path_, state_, timeout_, interval_, stop_token); + + if (result == osal::OsalReturnType::kSuccess) + { + return {}; + } + + return MakeUnexpected(ExecErrc::kGeneralError); +} + +} // namespace score::mw::lifecycle::internal diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/file_ready_condition.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/file_ready_condition.hpp new file mode 100644 index 0000000000..c53abafbec --- /dev/null +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/file_ready_condition.hpp @@ -0,0 +1,71 @@ +/******************************************************************************** + * 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_FILE_READY_CONDITION_HPP_INCLUDED +#define SCORE_LCM_FILE_READY_CONDITION_HPP_INCLUDED + +#include "score/language/safecpp/string_view/zstring_view.h" +#include "score/mw/launch_manager/osal/ifile_waiter.hpp" +#include "score/mw/launch_manager/process_group_manager/details/handle.hpp" +#include "score/mw/launch_manager/process_group_manager/details/ready_condition/iready_condition.hpp" +#include "score/result/result.h" +#include + +namespace score::mw::lifecycle::internal +{ + +/// @brief A ready condition which waits for a file to exist/not exist. +/// @details This may be useful for components which mount filesystems, +/// or set up devices under `/dev`. +class FileReadyCondition final : public IReadyCondition +{ + public: + /// @brief Creates a new file ready condition. + /// @param file_waiter The waiter used to check the state of the file. + /// @param file_path The path to the file to wait for. + /// @param state The desired state of the file. + /// @param timeout The maximum duration to wait for the file condition. + /// @param interval How often to poll for the file. + explicit FileReadyCondition( + const osal::IFileWaiter& file_waiter, + score::safecpp::zstring_view file_path, + configuration::FileExistenceState state, + std::chrono::milliseconds timeout, + std::chrono::milliseconds interval); + + /// @brief Wait until the resource represented by the given handle is ready. + /// @param stop_token Token which can be used to interrupt the wait. + /// @param handle The resource to wait on. + /// @return Whether the wait was successful, or failed with an error. + Result wait(cpp::stop_token stop_token, const Handle handle) const override; + + private: + /// @brief The waiter used to check file state. + const osal::IFileWaiter& file_waiter_; + + /// @brief The path to the file to wait for. + const score::safecpp::zstring_view file_path_; + + /// @brief The desired state of the file. + const configuration::FileExistenceState state_; + + /// @brief The maximum duration to wait for the file to exist/not exist. + const std::chrono::milliseconds timeout_; + + /// @brief How often to poll for the file. + const std::chrono::milliseconds interval_; +}; + +} // namespace score::mw::lifecycle::internal + +#endif // SCORE_LCM_FILE_READY_CONDITION_HPP_INCLUDED diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/file_ready_condition_UT.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/file_ready_condition_UT.cpp new file mode 100644 index 0000000000..a944efac58 --- /dev/null +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/file_ready_condition_UT.cpp @@ -0,0 +1,100 @@ +/******************************************************************************** + * 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/osal/mock_ifile_waiter.hpp" +#include "score/mw/launch_manager/process_group_manager/details/ready_condition/file_ready_condition.hpp" +#include "score/mw/lifecycle/execution_error.h" +#include +#include + +namespace score::mw::lifecycle::internal +{ +namespace +{ + +using namespace ::testing; +using namespace std::chrono_literals; + +constexpr score::safecpp::zstring_view kFilePath = "/some/path"; + +TEST(FileReadyConditionTest, WaitCallsWaitForFile) +{ + StrictMock file_waiter; + const FileReadyCondition file_ready_condition( + file_waiter, kFilePath, configuration::FileExistenceState::Exists, 10ms, 5ms); + + EXPECT_CALL( + file_waiter, waitForFile(Eq(kFilePath), Eq(configuration::FileExistenceState::Exists), Eq(10ms), Eq(5ms), _)) + .WillOnce(Return(osal::OsalReturnType::kSuccess)); + + static_cast(file_ready_condition.wait(cpp::stop_token{}, EmptyHandle{})); +} + +TEST(FileReadyConditionTest, WaitSucceedsReturnsSuccess) +{ + NiceMock file_waiter; + const FileReadyCondition file_ready_condition( + file_waiter, kFilePath, configuration::FileExistenceState::Exists, 10ms, 5ms); + + ON_CALL(file_waiter, waitForFile(_, _, _, _, _)).WillByDefault(Return(osal::OsalReturnType::kSuccess)); + + const Result result = file_ready_condition.wait(cpp::stop_token{}, EmptyHandle{}); + + EXPECT_TRUE(result.has_value()); +} + +TEST(FileReadyConditionTest, WaitFailsReturnsError) +{ + NiceMock file_waiter; + const FileReadyCondition file_ready_condition( + file_waiter, kFilePath, configuration::FileExistenceState::Exists, 10ms, 5ms); + + ON_CALL(file_waiter, waitForFile(_, _, _, _, _)).WillByDefault(Return(osal::OsalReturnType::kFail)); + + const Result result = file_ready_condition.wait(cpp::stop_token{}, EmptyHandle{}); + + EXPECT_FALSE(result.has_value()); + EXPECT_EQ(result.error(), ExecErrc::kGeneralError); +} + +TEST(FileReadyConditionTest, WaitTimesOutReturnsError) +{ + NiceMock file_waiter; + const FileReadyCondition file_ready_condition( + file_waiter, kFilePath, configuration::FileExistenceState::Exists, 10ms, 5ms); + + ON_CALL(file_waiter, waitForFile(_, _, _, _, _)).WillByDefault(Return(osal::OsalReturnType::kTimeout)); + + const Result result = file_ready_condition.wait(cpp::stop_token{}, EmptyHandle{}); + + EXPECT_FALSE(result.has_value()); + EXPECT_EQ(result.error(), ExecErrc::kGeneralError); +} + +TEST(FileReadyConditionTest, WaitAcceptsVariousHandles) +{ + NiceMock file_waiter; + const FileReadyCondition file_ready_condition( + file_waiter, kFilePath, configuration::FileExistenceState::Exists, 10ms, 5ms); + + ON_CALL(file_waiter, waitForFile(_, _, _, _, _)).WillByDefault(Return(osal::OsalReturnType::kSuccess)); + + const Result result_empty = file_ready_condition.wait(cpp::stop_token{}, EmptyHandle{}); + EXPECT_TRUE(result_empty.has_value()); + + const Result result_process = file_ready_condition.wait(cpp::stop_token{}, ProcessHandle{}); + EXPECT_TRUE(result_process.has_value()); +} + +} // namespace +} // namespace score::mw::lifecycle::internal From 0bf8cd6988ed0fb5fa1869b391cde81c7a356380 Mon Sep 17 00:00:00 2001 From: Daniel Thwaites Date: Fri, 2 Oct 2026 10:51:14 +0100 Subject: [PATCH 14/36] Rename `force_stop` to `forceStop` Follow the naming conventions. --- .../details/force_stop_action/empty_force_stop_action.cpp | 2 +- .../details/force_stop_action/empty_force_stop_action.hpp | 2 +- .../force_stop_action/empty_force_stop_action_UT.cpp | 2 +- .../details/force_stop_action/iforce_stop_action.hpp | 2 +- .../details/force_stop_action/mock_force_stop_action.hpp | 2 +- .../force_stop_action/process_force_stop_action.cpp | 2 +- .../force_stop_action/process_force_stop_action.hpp | 2 +- .../force_stop_action/process_force_stop_action_UT.cpp | 8 ++++---- 8 files changed, 11 insertions(+), 11 deletions(-) diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/empty_force_stop_action.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/empty_force_stop_action.cpp index b2cb919142..b997cbebbf 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/empty_force_stop_action.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/empty_force_stop_action.cpp @@ -16,7 +16,7 @@ namespace score::mw::lifecycle::internal { -Result EmptyForceStopAction::force_stop(cpp::stop_token stop_token, const Handle) const +Result EmptyForceStopAction::forceStop(cpp::stop_token stop_token, const Handle) const { return {}; } diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/empty_force_stop_action.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/empty_force_stop_action.hpp index bcd6d74f4d..e62c414c4a 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/empty_force_stop_action.hpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/empty_force_stop_action.hpp @@ -27,7 +27,7 @@ class EmptyForceStopAction final : public IForceStopAction /// @param stop_token Token which can be used to interrupt the action. /// @param handle The resource to act upon. /// @return Always successful. - Result force_stop(cpp::stop_token stop_token, const Handle) const override; + Result forceStop(cpp::stop_token stop_token, const Handle) const override; }; } // namespace score::mw::lifecycle::internal diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/empty_force_stop_action_UT.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/empty_force_stop_action_UT.cpp index ff81bd2ebe..5e57524a6b 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/empty_force_stop_action_UT.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/empty_force_stop_action_UT.cpp @@ -23,7 +23,7 @@ TEST(EmptyForceStopActionTest, ForceStopSucceeds) { EmptyForceStopAction empty_force_stop_action; - const auto result = empty_force_stop_action.force_stop(cpp::stop_token{}, EmptyHandle{}); + const auto result = empty_force_stop_action.forceStop(cpp::stop_token{}, EmptyHandle{}); EXPECT_TRUE(result.has_value()); } diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/iforce_stop_action.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/iforce_stop_action.hpp index 978f2aa93c..8e03af0fcc 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/iforce_stop_action.hpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/iforce_stop_action.hpp @@ -31,7 +31,7 @@ class IForceStopAction /// @param stop_token Token which can be used to interrupt the action. /// @param handle The resource to act upon. /// @return Whether the action was successful, or failed with an error. - virtual Result force_stop(cpp::stop_token stop_token, const Handle handle) const = 0; + virtual Result forceStop(cpp::stop_token stop_token, const Handle handle) const = 0; }; } // namespace score::mw::lifecycle::internal diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/mock_force_stop_action.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/mock_force_stop_action.hpp index 32304da5c3..dbe4890f8f 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/mock_force_stop_action.hpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/mock_force_stop_action.hpp @@ -23,7 +23,7 @@ namespace score::mw::lifecycle::internal class MockForceStopAction : public IForceStopAction { public: - MOCK_METHOD(Result, force_stop, (cpp::stop_token, const Handle handle), (override, const)); + MOCK_METHOD(Result, forceStop, (cpp::stop_token, const Handle handle), (override, const)); }; } // namespace score::mw::lifecycle::internal diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/process_force_stop_action.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/process_force_stop_action.cpp index 17f8f83a3c..99b09c86ed 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/process_force_stop_action.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/process_force_stop_action.cpp @@ -22,7 +22,7 @@ ProcessForceStopAction::ProcessForceStopAction(osal::IProcess& launcher) : launc { } -Result ProcessForceStopAction::force_stop(cpp::stop_token stop_token, const Handle handle) const +Result ProcessForceStopAction::forceStop(cpp::stop_token stop_token, const Handle handle) const { return std::visit(*this, handle); } diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/process_force_stop_action.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/process_force_stop_action.hpp index 809e0bcca1..a9d1402a88 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/process_force_stop_action.hpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/process_force_stop_action.hpp @@ -35,7 +35,7 @@ class ProcessForceStopAction final : public IForceStopAction /// @param stop_token Token which can be used to interrupt the action. /// @param handle The resource to act upon. /// @return Whether the action was successful, or failed with an error. - Result force_stop(cpp::stop_token stop_token, const Handle handle) const override; + Result forceStop(cpp::stop_token stop_token, const Handle handle) const override; /// @brief Forcefully stop the process represented by the given handle. /// @param process The process to act upon. diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/process_force_stop_action_UT.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/process_force_stop_action_UT.cpp index bb7b74c29d..21f4df5a38 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/process_force_stop_action_UT.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/process_force_stop_action_UT.cpp @@ -33,7 +33,7 @@ TEST(ProcessForceStopActionTest, ForceStopCallsForceTerminationWithPid) EXPECT_CALL(launcher, forceTermination(mock_handle.pid)).WillOnce(Return(osal::OsalReturnType::kSuccess)); - static_cast(process_force_stop_action.force_stop(cpp::stop_token{}, mock_handle)); + static_cast(process_force_stop_action.forceStop(cpp::stop_token{}, mock_handle)); } TEST(ProcessForceStopActionTest, ForceStopSucceedsReturnsSuccess) @@ -43,7 +43,7 @@ TEST(ProcessForceStopActionTest, ForceStopSucceedsReturnsSuccess) ON_CALL(launcher, forceTermination(_)).WillByDefault(Return(osal::OsalReturnType::kSuccess)); - const Result result = process_force_stop_action.force_stop(cpp::stop_token{}, mock_handle); + const Result result = process_force_stop_action.forceStop(cpp::stop_token{}, mock_handle); EXPECT_TRUE(result.has_value()); } @@ -55,7 +55,7 @@ TEST(ProcessForceStopActionTest, ForceStopFailsReturnsError) ON_CALL(launcher, forceTermination(_)).WillByDefault(Return(osal::OsalReturnType::kFail)); - const Result result = process_force_stop_action.force_stop(cpp::stop_token{}, mock_handle); + const Result result = process_force_stop_action.forceStop(cpp::stop_token{}, mock_handle); EXPECT_FALSE(result.has_value()); EXPECT_EQ(result.error(), ExecErrc::kGeneralError); @@ -66,7 +66,7 @@ TEST(ProcessForceStopActionTest, ForceStopWithEmptyHandleReturnsError) NiceMock launcher; const ProcessForceStopAction process_force_stop_action(launcher); - const Result result = process_force_stop_action.force_stop(cpp::stop_token{}, EmptyHandle{}); + const Result result = process_force_stop_action.forceStop(cpp::stop_token{}, EmptyHandle{}); EXPECT_FALSE(result.has_value()); EXPECT_EQ(result.error(), ExecErrc::kNotImplemented); From df2e12cfe729d89cfa7185e8597ff4143fcfdfaa Mon Sep 17 00:00:00 2001 From: Daniel Thwaites Date: Fri, 2 Oct 2026 11:05:06 +0100 Subject: [PATCH 15/36] Replace `kNotImplemented` returns with panics --- .../force_stop_action/process_force_stop_action.cpp | 2 +- .../force_stop_action/process_force_stop_action_UT.cpp | 8 +++----- .../ready_condition/report_running_ready_condition.cpp | 3 ++- .../ready_condition/report_running_ready_condition_UT.cpp | 8 +++----- .../details/stop_action/process_stop_action.cpp | 2 +- .../details/stop_action/process_stop_action_UT.cpp | 7 ++----- 6 files changed, 12 insertions(+), 18 deletions(-) diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/process_force_stop_action.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/process_force_stop_action.cpp index 99b09c86ed..3d751fc070 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/process_force_stop_action.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/process_force_stop_action.cpp @@ -41,7 +41,7 @@ Result ProcessForceStopAction::operator()(const ProcessHandle process) con Result ProcessForceStopAction::operator()(const Handle handle) const { - return MakeUnexpected(ExecErrc::kNotImplemented); + SCORE_LANGUAGE_FUTURECPP_UNREACHABLE_MESSAGE("ProcessForceStopAction should only be called with a ProcessHandle"); } } // namespace score::mw::lifecycle::internal diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/process_force_stop_action_UT.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/process_force_stop_action_UT.cpp index 21f4df5a38..b11f3202bb 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/process_force_stop_action_UT.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/process_force_stop_action_UT.cpp @@ -61,15 +61,13 @@ TEST(ProcessForceStopActionTest, ForceStopFailsReturnsError) EXPECT_EQ(result.error(), ExecErrc::kGeneralError); } -TEST(ProcessForceStopActionTest, ForceStopWithEmptyHandleReturnsError) +TEST(ProcessForceStopActionTest, ForceStopWithEmptyHandleAborts) { NiceMock launcher; const ProcessForceStopAction process_force_stop_action(launcher); - const Result result = process_force_stop_action.forceStop(cpp::stop_token{}, EmptyHandle{}); - - EXPECT_FALSE(result.has_value()); - EXPECT_EQ(result.error(), ExecErrc::kNotImplemented); + EXPECT_DEATH( + static_cast(process_force_stop_action.forceStop(cpp::stop_token{}, EmptyHandle{})), "Unreachable_Code"); } } // namespace diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/report_running_ready_condition.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/report_running_ready_condition.cpp index 90ea6d01df..eb394d4802 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/report_running_ready_condition.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/report_running_ready_condition.cpp @@ -44,7 +44,8 @@ Result ReportRunningReadyCondition::operator()(const ProcessHandle process Result ReportRunningReadyCondition::operator()(const Handle handle) const { - return MakeUnexpected(ExecErrc::kNotImplemented); + SCORE_LANGUAGE_FUTURECPP_UNREACHABLE_MESSAGE( + "ReportRunningReadyCondition should only be called with a ProcessHandle"); } } // namespace score::mw::lifecycle::internal diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/report_running_ready_condition_UT.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/report_running_ready_condition_UT.cpp index 7d6cf9bc69..eb0516d844 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/report_running_ready_condition_UT.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/report_running_ready_condition_UT.cpp @@ -63,15 +63,13 @@ TEST(ReportRunningReadyConditionTest, WaitFailsReturnsError) EXPECT_EQ(result.error(), ExecErrc::kGeneralError); } -TEST(ReportRunningReadyConditionTest, WaitWithEmptyHandleReturnsError) +TEST(ReportRunningReadyConditionTest, WaitWithEmptyHandleAborts) { NiceMock launcher; const ReportRunningReadyCondition report_running_ready_condition(launcher, std::nullopt); - const Result result = report_running_ready_condition.wait(cpp::stop_token{}, EmptyHandle{}); - - EXPECT_FALSE(result.has_value()); - EXPECT_EQ(result.error(), ExecErrc::kNotImplemented); + EXPECT_DEATH( + static_cast(report_running_ready_condition.wait(cpp::stop_token{}, EmptyHandle{})), "Unreachable_Code"); } } // namespace diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/process_stop_action.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/process_stop_action.cpp index a1077306d4..74a18b55b0 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/process_stop_action.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/process_stop_action.cpp @@ -41,7 +41,7 @@ Result ProcessStopAction::operator()(const ProcessHandle process) const Result ProcessStopAction::operator()(const Handle handle) const { - return MakeUnexpected(ExecErrc::kNotImplemented); + SCORE_LANGUAGE_FUTURECPP_UNREACHABLE_MESSAGE("ProcessStopAction should only be called with a ProcessHandle"); } } // namespace score::mw::lifecycle::internal diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/process_stop_action_UT.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/process_stop_action_UT.cpp index 20d0c179e1..ce0a778adc 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/process_stop_action_UT.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/process_stop_action_UT.cpp @@ -61,15 +61,12 @@ TEST(ProcessStopActionTest, StopFailsReturnsError) EXPECT_EQ(result.error(), ExecErrc::kGeneralError); } -TEST(ProcessStopActionTest, StopWithEmptyHandleReturnsError) +TEST(ProcessStopActionTest, StopWithEmptyHandleAborts) { NiceMock launcher; const ProcessStopAction process_stop_action(launcher); - const Result result = process_stop_action.stop(cpp::stop_token{}, EmptyHandle{}); - - EXPECT_FALSE(result.has_value()); - EXPECT_EQ(result.error(), ExecErrc::kNotImplemented); + EXPECT_DEATH(static_cast(process_stop_action.stop(cpp::stop_token{}, EmptyHandle{})), "Unreachable_Code"); } } // namespace From 307bd2f04df55805ce372f60aa1dee648abc13dc Mon Sep 17 00:00:00 2001 From: Daniel Thwaites Date: Mon, 5 Oct 2026 13:53:28 +0100 Subject: [PATCH 16/36] Add component state machine This is based on the diagram introduced in #736. --- .../details/component.cpp | 121 +++++++++++++++--- .../details/component.hpp | 45 ++++++- .../details/component_UT.cpp | 94 ++++++++++++++ 3 files changed, 241 insertions(+), 19 deletions(-) diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/component.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/component.cpp index d38d858fc4..89f4214847 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/component.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/component.cpp @@ -22,53 +22,142 @@ Component::Component( const IForceStopAction& force_stop_action, const cpp::span> ready_conditions, IdentifierHash identifier) - : handle_(std::nullopt), - start_action_(start_action), + : start_action_(start_action), stop_action_(stop_action), force_stop_action_(force_stop_action), ready_conditions_(ready_conditions), - identifier_(identifier) + identifier_(identifier), + state_(TerminatedState{}) { } IComponent::RequestResult Component::activate(cpp::stop_token stop_token) { - const auto start_result = start_action_.start(stop_token); + return std::visit( + [&](auto state) { + return state.activate(*this, stop_token); + }, + state_); +} + +IComponent::RequestResult Component::TerminatedState::activate(Component& component, cpp::stop_token stop_token) +{ + const Result start_result = component.start_action_.start(stop_token); if (!start_result.has_value()) { return cpp::make_unexpected(ComponentError::kErrorBeforeReady); } - handle_ = start_result.value(); + const Handle handle = start_result.value(); - for (const IReadyCondition& ready_condition : ready_conditions_) + component.state_ = StartingState{handle}; + + for (const IReadyCondition& ready_condition : component.ready_conditions_) { - if (!ready_condition.wait(stop_token, handle_.value()).has_value()) + if (!ready_condition.wait(stop_token, handle).has_value()) { return cpp::make_unexpected(ComponentError::kErrorBeforeReady); } } + component.state_ = ReadyState{handle}; + return RequestState::kSuccess; } +IComponent::RequestResult Component::StartingState::activate( + [[maybe_unused]] Component& component, + [[maybe_unused]] cpp::stop_token stop_token) +{ + SCORE_LANGUAGE_FUTURECPP_UNREACHABLE_MESSAGE("Cannot activate component in StartingState"); +} + +IComponent::RequestResult Component::ReadyState::activate( + [[maybe_unused]] Component& component, + [[maybe_unused]] cpp::stop_token stop_token) +{ + SCORE_LANGUAGE_FUTURECPP_UNREACHABLE_MESSAGE("Cannot activate component in ReadyState"); +} + +IComponent::RequestResult Component::TerminatingState::activate( + [[maybe_unused]] Component& component, + [[maybe_unused]] cpp::stop_token stop_token) +{ + SCORE_LANGUAGE_FUTURECPP_UNREACHABLE_MESSAGE("Cannot activate component in TerminatingState"); +} + +IComponent::RequestResult Component::FaultState::activate( + [[maybe_unused]] Component& component, + [[maybe_unused]] cpp::stop_token stop_token) +{ + SCORE_LANGUAGE_FUTURECPP_UNREACHABLE_MESSAGE("Cannot activate component in FaultState"); +} + IComponent::RequestResult Component::deactivate(cpp::stop_token stop_token) { - if (handle_.has_value()) + return std::visit( + [&](auto state) { + return state.deactivate(*this, stop_token); + }, + state_); +} + +IComponent::RequestResult Component::TerminatedState::deactivate( + [[maybe_unused]] Component& component, + [[maybe_unused]] cpp::stop_token stop_token) +{ + SCORE_LANGUAGE_FUTURECPP_UNREACHABLE_MESSAGE("Cannot deactivate component in TerminatedState"); +} + +IComponent::RequestResult Component::StartingState::deactivate(Component& component, cpp::stop_token stop_token) +{ + if (!component.stop_action_.stop(stop_token, handle_).has_value()) { - if (!stop_action_.stop(stop_token, handle_.value()).has_value()) - { - return cpp::make_unexpected(ComponentError::kErrorAfterReady); - } + return cpp::make_unexpected(ComponentError::kErrorAfterReady); } - handle_ = std::nullopt; - return RequestState::kSuccess; + + component.state_ = TerminatingState{handle_}; + return RequestState::kWaiting; } -IComponent::RequestResult Component::tryHandleTermination(int32_t status) +IComponent::RequestResult Component::ReadyState::deactivate(Component& component, cpp::stop_token stop_token) { + if (!component.stop_action_.stop(stop_token, handle_).has_value()) + { + return cpp::make_unexpected(ComponentError::kErrorAfterReady); + } + + component.state_ = TerminatingState{handle_}; return RequestState::kWaiting; } +IComponent::RequestResult Component::TerminatingState::deactivate( + [[maybe_unused]] Component& component, + [[maybe_unused]] cpp::stop_token stop_token) +{ + SCORE_LANGUAGE_FUTURECPP_UNREACHABLE_MESSAGE("Cannot deactivate component in TerminatingState"); +} + +IComponent::RequestResult Component::FaultState::deactivate( + [[maybe_unused]] Component& component, + [[maybe_unused]] cpp::stop_token stop_token) +{ + SCORE_LANGUAGE_FUTURECPP_UNREACHABLE_MESSAGE("Cannot deactivate component in FaultState"); +} + +IComponent::RequestResult Component::tryHandleTermination(int32_t status) +{ + if (status == 0) + { + state_ = TerminatedState{}; + } + else + { + state_ = FaultState{}; + } + + return RequestState::kSuccess; +} + IdentifierHash Component::getIdentifier() const { return identifier_; @@ -76,7 +165,7 @@ IdentifierHash Component::getIdentifier() const bool Component::active() const { - return handle_.has_value(); + return std::holds_alternative(state_); } } // namespace score::mw::lifecycle::internal diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/component.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/component.hpp index c7d362cad2..9b97d06bf3 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/component.hpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/component.hpp @@ -71,9 +71,6 @@ class Component final : public IComponent [[nodiscard]] bool active() const override; private: - /// @brief The resource being managed. - std::optional handle_; - /// @brief How to start the resource. const IStartAction& start_action_; @@ -88,6 +85,48 @@ class Component final : public IComponent /// @brief Name of the component. IdentifierHash identifier_; + + class TerminatedState final + { + public: + RequestResult activate(Component& component, cpp::stop_token stop_token); + RequestResult deactivate(Component& component, cpp::stop_token stop_token); + }; + + class StartingState final + { + public: + Handle handle_; + RequestResult activate(Component& component, cpp::stop_token stop_token); + RequestResult deactivate(Component& component, cpp::stop_token stop_token); + }; + + class ReadyState final + { + public: + Handle handle_; + RequestResult activate(Component& component, cpp::stop_token stop_token); + RequestResult deactivate(Component& component, cpp::stop_token stop_token); + }; + + class TerminatingState final + { + public: + Handle handle_; + RequestResult activate(Component& component, cpp::stop_token stop_token); + RequestResult deactivate(Component& component, cpp::stop_token stop_token); + }; + + class FaultState final + { + public: + Handle handle_; + RequestResult activate(Component& component, cpp::stop_token stop_token); + RequestResult deactivate(Component& component, cpp::stop_token stop_token); + }; + + /// @brief The current state of the component. + std::variant state_; }; } // namespace score::mw::lifecycle::internal diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/component_UT.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/component_UT.cpp index c0220c537b..c1614715b1 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/component_UT.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/component_UT.cpp @@ -150,5 +150,99 @@ TEST(ComponentTest, ForceStopClearsActive) EXPECT_FALSE(component.active()); } +TEST(ComponentTest, CannotDeactivateInTerminatedState) +{ + MockStartAction start_action; + MockStopAction stop_action; + MockForceStopAction force_stop_action; + Component component(start_action, stop_action, force_stop_action); + + EXPECT_DEATH( + { static_cast(component.deactivate(cpp::stop_token{})); }, + "Cannot deactivate component in TerminatedState"); +} + +TEST(ComponentTest, CannotActivateInReadyState) +{ + MockStartAction start_action; + MockStopAction stop_action; + MockForceStopAction force_stop_action; + Component component(start_action, stop_action, force_stop_action); + + ON_CALL(start_action, start(_)).WillByDefault(Return(Result{mock_handle})); + + static_cast(component.activate(cpp::stop_token{})); + + EXPECT_DEATH( + { static_cast(component.activate(cpp::stop_token{})); }, "Cannot activate component in ReadyState"); +} + +TEST(ComponentTest, CannotActivateInTerminatingState) +{ + MockStartAction start_action; + MockStopAction stop_action; + MockForceStopAction force_stop_action; + Component component(start_action, stop_action, force_stop_action); + + ON_CALL(start_action, start(_)).WillByDefault(Return(Result{mock_handle})); + ON_CALL(stop_action, stop(_, _)).WillByDefault(Return(Result{})); + + static_cast(component.activate(cpp::stop_token{})); + static_cast(component.deactivate(cpp::stop_token{})); + + EXPECT_DEATH( + { static_cast(component.activate(cpp::stop_token{})); }, "Cannot activate component in TerminatingState"); +} + +TEST(ComponentTest, CannotDeactivateInTerminatingState) +{ + MockStartAction start_action; + MockStopAction stop_action; + MockForceStopAction force_stop_action; + Component component(start_action, stop_action, force_stop_action); + + ON_CALL(start_action, start(_)).WillByDefault(Return(Result{mock_handle})); + ON_CALL(stop_action, stop(_, _)).WillByDefault(Return(Result{})); + + static_cast(component.activate(cpp::stop_token{})); + static_cast(component.deactivate(cpp::stop_token{})); + + EXPECT_DEATH( + { static_cast(component.deactivate(cpp::stop_token{})); }, + "Cannot deactivate component in TerminatingState"); +} + +TEST(ComponentTest, CannotActivateInFaultState) +{ + MockStartAction start_action; + MockStopAction stop_action; + MockForceStopAction force_stop_action; + Component component(start_action, stop_action, force_stop_action); + + ON_CALL(start_action, start(_)).WillByDefault(Return(Result{mock_handle})); + + static_cast(component.activate(cpp::stop_token{})); + component.tryHandleTermination(1); + + EXPECT_DEATH( + { static_cast(component.activate(cpp::stop_token{})); }, "Cannot activate component in FaultState"); +} + +TEST(ComponentTest, CannotDeactivateInFaultState) +{ + MockStartAction start_action; + MockStopAction stop_action; + MockForceStopAction force_stop_action; + Component component(start_action, stop_action, force_stop_action); + + ON_CALL(start_action, start(_)).WillByDefault(Return(Result{mock_handle})); + + static_cast(component.activate(cpp::stop_token{})); + component.tryHandleTermination(1); + + EXPECT_DEATH( + { static_cast(component.deactivate(cpp::stop_token{})); }, "Cannot deactivate component in FaultState"); +} + } // namespace } // namespace score::mw::lifecycle::internal From e8fe4ebaddb32140275b24b75a003d69c4664427 Mon Sep 17 00:00:00 2001 From: Daniel Thwaites Date: Mon, 5 Oct 2026 14:17:05 +0100 Subject: [PATCH 17/36] Make component identifier `const` --- .../src/daemon/src/process_group_manager/details/component.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/component.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/component.hpp index 9b97d06bf3..09816b853d 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/component.hpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/component.hpp @@ -84,7 +84,7 @@ class Component final : public IComponent const cpp::span> ready_conditions_; /// @brief Name of the component. - IdentifierHash identifier_; + const IdentifierHash identifier_; class TerminatedState final { From 74dbc008a9cc247753947763956fe631174a7768 Mon Sep 17 00:00:00 2001 From: Daniel Thwaites Date: Wed, 7 Oct 2026 12:48:31 +0100 Subject: [PATCH 18/36] Implement `tryHandleTermination` on `Component` --- .../details/component.cpp | 57 +++++++++++++++++-- .../details/component.hpp | 10 ++++ .../ready_condition/iready_condition.hpp | 8 +++ 3 files changed, 71 insertions(+), 4 deletions(-) diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/component.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/component.cpp index 89f4214847..40c85b47c9 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/component.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/component.cpp @@ -21,11 +21,13 @@ Component::Component( const IStopAction& stop_action, const IForceStopAction& force_stop_action, const cpp::span> ready_conditions, + bool self_terminating, IdentifierHash identifier) : start_action_(start_action), stop_action_(stop_action), force_stop_action_(force_stop_action), ready_conditions_(ready_conditions), + self_terminating_(self_terminating), identifier_(identifier), state_(TerminatedState{}) { @@ -146,18 +148,65 @@ IComponent::RequestResult Component::FaultState::deactivate( IComponent::RequestResult Component::tryHandleTermination(int32_t status) { - if (status == 0) + return std::visit( + [&](auto state) { + return state.tryHandleTermination(*this, status); + }, + state_); +} + +IComponent::RequestResult Component::TerminatedState::tryHandleTermination( + [[maybe_unused]] Component& component, + [[maybe_unused]] int32_t status) +{ + SCORE_LANGUAGE_FUTURECPP_UNREACHABLE_MESSAGE("Cannot handle termination for a component in TerminatedState"); +} + +IComponent::RequestResult Component::StartingState::tryHandleTermination(Component& component, int32_t status) +{ + for (const IReadyCondition& ready_condition : component.ready_conditions_) { - state_ = TerminatedState{}; + if (ready_condition.tryHandleTermination(status)) + { + return RequestState::kSuccess; + } } - else + + // TODO: We need to interrupt the worker thread which is sitting in the activate() method + // (and prevent it from changing the state to successful) + + component.state_ = FaultState{}; + return cpp::make_unexpected(ComponentError::kErrorAfterReady); +} + +IComponent::RequestResult Component::ReadyState::tryHandleTermination(Component& component, int32_t status) +{ + if (component.self_terminating_ && status == 0) { - state_ = FaultState{}; + component.state_ = TerminatedState{}; + return RequestState::kSuccess; } + component.state_ = FaultState{}; + return cpp::make_unexpected(ComponentError::kErrorAfterReady); +} + +IComponent::RequestResult Component::TerminatingState::tryHandleTermination( + Component& component, + [[maybe_unused]] int32_t status) +{ + // Since we requested termination, we do not care about the exit code. + component.state_ = TerminatedState{}; return RequestState::kSuccess; } +IComponent::RequestResult Component::FaultState::tryHandleTermination( + [[maybe_unused]] Component& component, + [[maybe_unused]] int32_t status) +{ + SCORE_LANGUAGE_FUTURECPP_UNREACHABLE_MESSAGE("Cannot handle termination for a component in FaultState"); +} + IdentifierHash Component::getIdentifier() const { return identifier_; diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/component.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/component.hpp index 09816b853d..1d83bbdd72 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/component.hpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/component.hpp @@ -35,6 +35,7 @@ class Component final : public IComponent /// @param stop_action How to gracefully stop the resource. /// @param force_stop_action How to forcefully stop the resource. /// @param ready_conditions How to decide when the resource has finished its startup. + /// @param self_terminating Whether the resource is allowed to terminate on its own. /// @param identifier Name of the component. Component( const IStartAction& start_action, @@ -42,6 +43,7 @@ class Component final : public IComponent const IForceStopAction& force_stop_action, cpp::span> ready_conditions = cpp::span>{}, + bool self_terminating = false, IdentifierHash identifier = IdentifierHash{}); /// @brief Start the resource and set the component to active. @@ -83,6 +85,9 @@ class Component final : public IComponent /// @brief How to decide when the resource has finished its startup. const cpp::span> ready_conditions_; + /// @brief Whether the resource is allowed to terminate on its own. + const bool self_terminating_; + /// @brief Name of the component. const IdentifierHash identifier_; @@ -91,6 +96,7 @@ class Component final : public IComponent public: RequestResult activate(Component& component, cpp::stop_token stop_token); RequestResult deactivate(Component& component, cpp::stop_token stop_token); + RequestResult tryHandleTermination(Component& component, int32_t status); }; class StartingState final @@ -99,6 +105,7 @@ class Component final : public IComponent Handle handle_; RequestResult activate(Component& component, cpp::stop_token stop_token); RequestResult deactivate(Component& component, cpp::stop_token stop_token); + RequestResult tryHandleTermination(Component& component, int32_t status); }; class ReadyState final @@ -107,6 +114,7 @@ class Component final : public IComponent Handle handle_; RequestResult activate(Component& component, cpp::stop_token stop_token); RequestResult deactivate(Component& component, cpp::stop_token stop_token); + RequestResult tryHandleTermination(Component& component, int32_t status); }; class TerminatingState final @@ -115,6 +123,7 @@ class Component final : public IComponent Handle handle_; RequestResult activate(Component& component, cpp::stop_token stop_token); RequestResult deactivate(Component& component, cpp::stop_token stop_token); + RequestResult tryHandleTermination(Component& component, int32_t status); }; class FaultState final @@ -123,6 +132,7 @@ class Component final : public IComponent Handle handle_; RequestResult activate(Component& component, cpp::stop_token stop_token); RequestResult deactivate(Component& component, cpp::stop_token stop_token); + RequestResult tryHandleTermination(Component& component, int32_t status); }; /// @brief The current state of the component. diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/iready_condition.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/iready_condition.hpp index 1f912b48a0..d68f857cba 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/iready_condition.hpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/iready_condition.hpp @@ -32,6 +32,14 @@ class IReadyCondition /// @param handle The resource to wait on. /// @return Whether the wait was successful, or failed with an error. virtual Result wait(cpp::stop_token stop_token, const Handle handle) const = 0; + + /// @brief Notify the ready condition that a POSIX process has terminated. + /// @param status Exit code of the process. + /// @return Whether this ready condition accepts the event. + bool tryHandleTermination(int32_t status) const + { + return false; + } }; } // namespace score::mw::lifecycle::internal From 1f654778f6aa5a9be54a5ddc8f4065a7c506cfc9 Mon Sep 17 00:00:00 2001 From: Daniel Thwaites Date: Thu, 8 Oct 2026 10:31:40 +0100 Subject: [PATCH 19/36] Interrupt ready conditions when the process terminates --- .../details/component.cpp | 44 ++++++++++------ .../details/component.hpp | 24 ++++++++- .../details/component_UT.cpp | 52 ++++++++++++------- 3 files changed, 85 insertions(+), 35 deletions(-) diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/component.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/component.cpp index 40c85b47c9..f459118f89 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/component.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/component.cpp @@ -39,7 +39,7 @@ IComponent::RequestResult Component::activate(cpp::stop_token stop_token) [&](auto state) { return state.activate(*this, stop_token); }, - state_); + getState()); } IComponent::RequestResult Component::TerminatedState::activate(Component& component, cpp::stop_token stop_token) @@ -51,18 +51,30 @@ IComponent::RequestResult Component::TerminatedState::activate(Component& compon } const Handle handle = start_result.value(); - component.state_ = StartingState{handle}; + // Create a child token, which can be stopped from tryHandleTermination + // in addition to forwarding requests from the parent. + cpp::stop_source stop_activation; + const cpp::stop_callback forward_stop{stop_token, [&stop_activation] { + const bool stopped = stop_activation.request_stop(); + SCORE_LANGUAGE_FUTURECPP_ASSERT(stopped); + }}; + component.setState(StartingState{handle, stop_activation}); for (const IReadyCondition& ready_condition : component.ready_conditions_) { - if (!ready_condition.wait(stop_token, handle).has_value()) + if (!ready_condition.wait(stop_activation.get_token(), handle).has_value()) { return cpp::make_unexpected(ComponentError::kErrorBeforeReady); } } - component.state_ = ReadyState{handle}; + if (stop_activation.stop_requested()) + { + // Startup was cancelled, either by the caller or by an unexpected termination. + return cpp::make_unexpected(ComponentError::kErrorBeforeReady); + } + component.setState(ReadyState{handle}); return RequestState::kSuccess; } @@ -100,7 +112,7 @@ IComponent::RequestResult Component::deactivate(cpp::stop_token stop_token) [&](auto state) { return state.deactivate(*this, stop_token); }, - state_); + getState()); } IComponent::RequestResult Component::TerminatedState::deactivate( @@ -112,12 +124,14 @@ IComponent::RequestResult Component::TerminatedState::deactivate( IComponent::RequestResult Component::StartingState::deactivate(Component& component, cpp::stop_token stop_token) { + static_cast(stop_activation_.request_stop()); + if (!component.stop_action_.stop(stop_token, handle_).has_value()) { return cpp::make_unexpected(ComponentError::kErrorAfterReady); } - component.state_ = TerminatingState{handle_}; + component.setState(TerminatingState{handle_}); return RequestState::kWaiting; } @@ -128,7 +142,7 @@ IComponent::RequestResult Component::ReadyState::deactivate(Component& component return cpp::make_unexpected(ComponentError::kErrorAfterReady); } - component.state_ = TerminatingState{handle_}; + component.setState(TerminatingState{handle_}); return RequestState::kWaiting; } @@ -152,7 +166,7 @@ IComponent::RequestResult Component::tryHandleTermination(int32_t status) [&](auto state) { return state.tryHandleTermination(*this, status); }, - state_); + getState()); } IComponent::RequestResult Component::TerminatedState::tryHandleTermination( @@ -172,10 +186,10 @@ IComponent::RequestResult Component::StartingState::tryHandleTermination(Compone } } - // TODO: We need to interrupt the worker thread which is sitting in the activate() method - // (and prevent it from changing the state to successful) + // Interrupt the worker thread waiting in activate(). + static_cast(stop_activation_.request_stop()); - component.state_ = FaultState{}; + component.setState(FaultState{}); return cpp::make_unexpected(ComponentError::kErrorAfterReady); } @@ -183,11 +197,11 @@ IComponent::RequestResult Component::ReadyState::tryHandleTermination(Component& { if (component.self_terminating_ && status == 0) { - component.state_ = TerminatedState{}; + component.setState(TerminatedState{}); return RequestState::kSuccess; } - component.state_ = FaultState{}; + component.setState(FaultState{}); return cpp::make_unexpected(ComponentError::kErrorAfterReady); } @@ -196,7 +210,7 @@ IComponent::RequestResult Component::TerminatingState::tryHandleTermination( [[maybe_unused]] int32_t status) { // Since we requested termination, we do not care about the exit code. - component.state_ = TerminatedState{}; + component.setState(TerminatedState{}); return RequestState::kSuccess; } @@ -214,7 +228,7 @@ IdentifierHash Component::getIdentifier() const bool Component::active() const { - return std::holds_alternative(state_); + return std::holds_alternative(getState()); } } // namespace score::mw::lifecycle::internal diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/component.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/component.hpp index 1d83bbdd72..14239a3cf3 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/component.hpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/component.hpp @@ -21,6 +21,8 @@ #include "score/mw/launch_manager/process_group_manager/details/stop_action/istop_action.hpp" #include #include +#include +#include #include namespace score::mw::lifecycle::internal @@ -103,6 +105,7 @@ class Component final : public IComponent { public: Handle handle_; + cpp::stop_source stop_activation_; RequestResult activate(Component& component, cpp::stop_token stop_token); RequestResult deactivate(Component& component, cpp::stop_token stop_token); RequestResult tryHandleTermination(Component& component, int32_t status); @@ -135,8 +138,27 @@ class Component final : public IComponent RequestResult tryHandleTermination(Component& component, int32_t status); }; + using State = std::variant; + + /// @brief Protects the state from concurrent modifications. + mutable std::mutex state_mutex_; + /// @brief The current state of the component. - std::variant state_; + State state_; + + /// @brief Set the current state. + void setState(State&& state) + { + const std::lock_guard lock{state_mutex_}; + state_ = state; + } + + /// @brief Get the current state. + [[nodiscard]] State getState() const + { + const std::lock_guard lock{state_mutex_}; + return state_; + } }; } // namespace score::mw::lifecycle::internal diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/component_UT.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/component_UT.cpp index c1614715b1..dab8d15c94 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/component_UT.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/component_UT.cpp @@ -20,6 +20,7 @@ #include #include #include +#include namespace score::mw::lifecycle::internal { @@ -38,11 +39,6 @@ void expect_mock_handle(const Handle handle) EXPECT_EQ(std::get(handle).pid, mock_handle.pid); } -void expect_mock_stop_token(const cpp::stop_token token) -{ - EXPECT_EQ(token, mock_stop_token); -} - TEST(ComponentTest, StartSucceeds) { MockStartAction start_action; @@ -62,8 +58,7 @@ TEST(ComponentTest, StartActionCalled) MockForceStopAction force_stop_action; Component component(start_action, stop_action, force_stop_action); - EXPECT_CALL(start_action, start(_)) - .WillOnce(DoAll(WithArg<0>(Invoke(expect_mock_stop_token)), Return(Result{mock_handle}))); + EXPECT_CALL(start_action, start(_)).WillOnce(Return(Result{mock_handle})); static_cast(component.activate(mock_stop_token)); } @@ -77,10 +72,7 @@ TEST(ComponentTest, StopActionCalled) ON_CALL(start_action, start(_)).WillByDefault(Return(Result{mock_handle})); EXPECT_CALL(stop_action, stop(_, _)) - .WillOnce(DoAll( - WithArg<0>(Invoke(expect_mock_stop_token)), - WithArg<1>(Invoke(expect_mock_handle)), - Return(Result{}))); + .WillOnce(DoAll(WithArg<1>(Invoke(expect_mock_handle)), Return(Result{}))); static_cast(component.activate(cpp::stop_token{})); component.deactivate(mock_stop_token); @@ -99,19 +91,41 @@ TEST(ComponentTest, ReadyConditionsCalled) InSequence sequence; ON_CALL(start_action, start(_)).WillByDefault(Return(Result{mock_handle})); EXPECT_CALL(ready_condition_1, wait(_, _)) - .WillOnce(DoAll( - WithArg<0>(Invoke(expect_mock_stop_token)), - WithArg<1>(Invoke(expect_mock_handle)), - Return(Result{}))); + .WillOnce(DoAll(WithArg<1>(Invoke(expect_mock_handle)), Return(Result{}))); EXPECT_CALL(ready_condition_2, wait(_, _)) - .WillOnce(DoAll( - WithArg<0>(Invoke(expect_mock_stop_token)), - WithArg<1>(Invoke(expect_mock_handle)), - Return(Result{}))); + .WillOnce(DoAll(WithArg<1>(Invoke(expect_mock_handle)), Return(Result{}))); static_cast(component.activate(mock_stop_token)); } +TEST(ComponentTest, TerminationDuringStartupInterruptsActivation) +{ + MockStartAction start_action; + MockStopAction stop_action; + MockForceStopAction force_stop_action; + MockReadyCondition ready_condition; + std::array, 1> ready_conditions{ready_condition}; + Component component(start_action, stop_action, force_stop_action, ready_conditions); + + ON_CALL(start_action, start(_)).WillByDefault(Return(Result{mock_handle})); + EXPECT_CALL(ready_condition, wait(_, _)).WillOnce(Invoke([&component](cpp::stop_token token, const Handle) { + // While the ready condition is waiting, a termination comes in from + // another thread. We test with a real thread to catch deadlocks. + std::thread termination_thread([&component]() { + static_cast(component.tryHandleTermination(1)); + }); + termination_thread.join(); + + // As a result of the termination, the ready condition is stopped. + EXPECT_TRUE(token.stop_requested()); + return Result{}; + })); + + const auto result = component.activate(cpp::stop_token{}); + + EXPECT_FALSE(result.has_value()); +} + TEST(ComponentTest, StartSetsActive) { MockStartAction start_action; From 2ed0e8d5355802b639ce982f2e476f42e86b4293 Mon Sep 17 00:00:00 2001 From: Daniel Thwaites Date: Thu, 8 Oct 2026 10:46:08 +0100 Subject: [PATCH 20/36] Encapsulate visitor methods within actions While `operator()` was slightly less code, the methods had to be public for it to work. --- .../process_force_stop_action.cpp | 10 +++++++--- .../process_force_stop_action.hpp | 14 +++++--------- .../report_running_ready_condition.cpp | 10 +++++++--- .../report_running_ready_condition.hpp | 14 +++++--------- .../details/stop_action/process_stop_action.cpp | 10 +++++++--- .../details/stop_action/process_stop_action.hpp | 14 +++++--------- 6 files changed, 36 insertions(+), 36 deletions(-) diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/process_force_stop_action.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/process_force_stop_action.cpp index 3d751fc070..d7a4b18aa1 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/process_force_stop_action.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/process_force_stop_action.cpp @@ -24,10 +24,14 @@ ProcessForceStopAction::ProcessForceStopAction(osal::IProcess& launcher) : launc Result ProcessForceStopAction::forceStop(cpp::stop_token stop_token, const Handle handle) const { - return std::visit(*this, handle); + return std::visit( + [this](auto&& handle) { + return visitForceStop(handle); + }, + handle); } -Result ProcessForceStopAction::operator()(const ProcessHandle process) const +Result ProcessForceStopAction::visitForceStop(const ProcessHandle process) const { const osal::OsalReturnType result = launcher_.forceTermination(process.pid); @@ -39,7 +43,7 @@ Result ProcessForceStopAction::operator()(const ProcessHandle process) con return MakeUnexpected(ExecErrc::kGeneralError); } -Result ProcessForceStopAction::operator()(const Handle handle) const +Result ProcessForceStopAction::visitForceStop(const Handle handle) const { SCORE_LANGUAGE_FUTURECPP_UNREACHABLE_MESSAGE("ProcessForceStopAction should only be called with a ProcessHandle"); } diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/process_force_stop_action.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/process_force_stop_action.hpp index a9d1402a88..1cd6668b2d 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/process_force_stop_action.hpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/process_force_stop_action.hpp @@ -37,17 +37,13 @@ class ProcessForceStopAction final : public IForceStopAction /// @return Whether the action was successful, or failed with an error. Result forceStop(cpp::stop_token stop_token, const Handle handle) const override; - /// @brief Forcefully stop the process represented by the given handle. - /// @param process The process to act upon. - /// @return Whether the action was successful, or failed with an error. - Result operator()(const ProcessHandle process) const; + private: + /// @brief Visitor method for process handles. + Result visitForceStop(const ProcessHandle process) const; - /// @brief Forcefully stop the resource represented by the given handle. - /// @param handle The resource to act upon. - /// @return Always returns a not-implemented error. - Result operator()(const Handle handle) const; + /// @brief Visitor method for unsupported handles. + Result visitForceStop(const Handle handle) const; - private: /// @brief The process launcher used to stop the process. osal::IProcess& launcher_; }; diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/report_running_ready_condition.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/report_running_ready_condition.cpp index eb394d4802..d9aae4354e 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/report_running_ready_condition.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/report_running_ready_condition.cpp @@ -27,10 +27,14 @@ ReportRunningReadyCondition::ReportRunningReadyCondition( Result ReportRunningReadyCondition::wait(cpp::stop_token stop_token, const Handle handle) const { - return std::visit(*this, handle); + return std::visit( + [this](auto&& handle) { + return visitWait(handle); + }, + handle); } -Result ReportRunningReadyCondition::operator()(const ProcessHandle process) const +Result ReportRunningReadyCondition::visitWait(const ProcessHandle process) const { const osal::OsalReturnType result = launcher_.waitForkRunning(process.sync, timeout_); @@ -42,7 +46,7 @@ Result ReportRunningReadyCondition::operator()(const ProcessHandle process return MakeUnexpected(ExecErrc::kGeneralError); } -Result ReportRunningReadyCondition::operator()(const Handle handle) const +Result ReportRunningReadyCondition::visitWait(const Handle handle) const { SCORE_LANGUAGE_FUTURECPP_UNREACHABLE_MESSAGE( "ReportRunningReadyCondition should only be called with a ProcessHandle"); diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/report_running_ready_condition.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/report_running_ready_condition.hpp index 4c87857bb0..2b0062bc15 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/report_running_ready_condition.hpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/report_running_ready_condition.hpp @@ -41,17 +41,13 @@ class ReportRunningReadyCondition final : public IReadyCondition /// @return Whether the wait was successful, or failed with an error. Result wait(cpp::stop_token stop_token, const Handle handle) const override; - /// @brief Wait until the process represented by the given handle reports running. - /// @param process The process to wait on. - /// @return Whether the wait was successful, or failed with an error. - Result operator()(const ProcessHandle process) const; + private: + /// @brief Visitor method for process handles. + Result visitWait(const ProcessHandle process) const; - /// @brief Wait until the resource represented by the given handle is ready. - /// @param handle The resource to wait on. - /// @return Always returns a not-implemented error. - Result operator()(const Handle handle) const; + /// @brief Visitor method for unsupported handles. + Result visitWait(const Handle handle) const; - private: /// @brief The process launcher used to wait for the process. osal::IProcess& launcher_; diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/process_stop_action.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/process_stop_action.cpp index 74a18b55b0..5c3218f720 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/process_stop_action.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/process_stop_action.cpp @@ -24,10 +24,14 @@ ProcessStopAction::ProcessStopAction(osal::IProcess& launcher) : launcher_(launc Result ProcessStopAction::stop(cpp::stop_token stop_token, const Handle handle) const { - return std::visit(*this, handle); + return std::visit( + [this](auto&& handle) { + return visitStop(handle); + }, + handle); } -Result ProcessStopAction::operator()(const ProcessHandle process) const +Result ProcessStopAction::visitStop(const ProcessHandle process) const { const osal::OsalReturnType result = launcher_.requestTermination(process.pid); @@ -39,7 +43,7 @@ Result ProcessStopAction::operator()(const ProcessHandle process) const return MakeUnexpected(ExecErrc::kGeneralError); } -Result ProcessStopAction::operator()(const Handle handle) const +Result ProcessStopAction::visitStop(const Handle handle) const { SCORE_LANGUAGE_FUTURECPP_UNREACHABLE_MESSAGE("ProcessStopAction should only be called with a ProcessHandle"); } diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/process_stop_action.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/process_stop_action.hpp index 03bb4526c2..c902a489aa 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/process_stop_action.hpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/process_stop_action.hpp @@ -37,17 +37,13 @@ class ProcessStopAction final : public IStopAction /// @return Whether the action was successful, or failed with an error. Result stop(cpp::stop_token stop_token, const Handle handle) const override; - /// @brief Gracefully stop the process represented by the given handle. - /// @param process The process to act upon. - /// @return Whether the action was successful, or failed with an error. - Result operator()(const ProcessHandle process) const; + private: + /// @brief Visitor method for process handles. + Result visitStop(const ProcessHandle process) const; - /// @brief Gracefully stop the resource represented by the given handle. - /// @param handle The resource to act upon. - /// @return Always returns a not-implemented error. - Result operator()(const Handle handle) const; + /// @brief Visitor method for unsupported handles. + Result visitStop(const Handle handle) const; - private: /// @brief The process launcher used to stop the process. osal::IProcess& launcher_; }; From 649598b973ece1071eab0a9507a582807bb187ec Mon Sep 17 00:00:00 2001 From: Daniel Thwaites Date: Thu, 8 Oct 2026 12:12:04 +0100 Subject: [PATCH 21/36] Improve handling of self-terminating components --- .../details/component.cpp | 12 +- .../details/component_UT.cpp | 172 +++++++++++++++++- 2 files changed, 177 insertions(+), 7 deletions(-) diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/component.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/component.cpp index f459118f89..fca94ae13f 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/component.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/component.cpp @@ -186,6 +186,15 @@ IComponent::RequestResult Component::StartingState::tryHandleTermination(Compone } } + if (component.self_terminating_ && status == 0) + { + // Self-terminating means the component is logically still running + // even though the resource has stopped. + // Termination by itself does not make the component ready; if this + // is desired then a ready condition should handle it above. + return RequestState::kSuccess; + } + // Interrupt the worker thread waiting in activate(). static_cast(stop_activation_.request_stop()); @@ -197,7 +206,8 @@ IComponent::RequestResult Component::ReadyState::tryHandleTermination(Component& { if (component.self_terminating_ && status == 0) { - component.setState(TerminatedState{}); + // Self-terminating means the component is logically still running + // even though the resource has stopped. return RequestState::kSuccess; } diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/component_UT.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/component_UT.cpp index dab8d15c94..a8175c4af2 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/component_UT.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/component_UT.cpp @@ -39,6 +39,19 @@ void expect_mock_handle(const Handle handle) EXPECT_EQ(std::get(handle).pid, mock_handle.pid); } +IComponent::RequestResult terminateFromOtherThread(Component& component, const int32_t status) +{ + // Termination notifications come from another thread in reality, so + // replacate it in the test to catch concurrency problems. + + IComponent::RequestResult result; + std::thread termination_thread([&component, &result, status]() { + result = component.tryHandleTermination(status); + }); + termination_thread.join(); + return result; +} + TEST(ComponentTest, StartSucceeds) { MockStartAction start_action; @@ -109,12 +122,7 @@ TEST(ComponentTest, TerminationDuringStartupInterruptsActivation) ON_CALL(start_action, start(_)).WillByDefault(Return(Result{mock_handle})); EXPECT_CALL(ready_condition, wait(_, _)).WillOnce(Invoke([&component](cpp::stop_token token, const Handle) { - // While the ready condition is waiting, a termination comes in from - // another thread. We test with a real thread to catch deadlocks. - std::thread termination_thread([&component]() { - static_cast(component.tryHandleTermination(1)); - }); - termination_thread.join(); + static_cast(terminateFromOtherThread(component, 1)); // As a result of the termination, the ready condition is stopped. EXPECT_TRUE(token.stop_requested()); @@ -258,5 +266,157 @@ TEST(ComponentTest, CannotDeactivateInFaultState) { static_cast(component.deactivate(cpp::stop_token{})); }, "Cannot deactivate component in FaultState"); } +TEST(ComponentTest, SelfTerminatingExitZeroInReadyStateIsSuccess) +{ + MockStartAction start_action; + MockStopAction stop_action; + MockForceStopAction force_stop_action; + cpp::span> ready_conditions; + Component component(start_action, stop_action, force_stop_action, ready_conditions, true); + + ON_CALL(start_action, start(_)).WillByDefault(Return(Result{mock_handle})); + + static_cast(component.activate(cpp::stop_token{})); + const auto result = component.tryHandleTermination(0); + + ASSERT_TRUE(result.has_value()); + EXPECT_EQ(result.value(), IComponent::RequestState::kSuccess); +} + +TEST(ComponentTest, SelfTerminatingExitZeroInReadyStateIsActive) +{ + MockStartAction start_action; + MockStopAction stop_action; + MockForceStopAction force_stop_action; + cpp::span> ready_conditions; + Component component(start_action, stop_action, force_stop_action, ready_conditions, true); + + ON_CALL(start_action, start(_)).WillByDefault(Return(Result{mock_handle})); + + static_cast(component.activate(cpp::stop_token{})); + static_cast(component.tryHandleTermination(0)); + + EXPECT_TRUE(component.active()); +} + +TEST(ComponentTest, SelfTerminatingExitNonZeroInReadyStateIsError) +{ + MockStartAction start_action; + MockStopAction stop_action; + MockForceStopAction force_stop_action; + cpp::span> ready_conditions; + Component component(start_action, stop_action, force_stop_action, ready_conditions, true); + + ON_CALL(start_action, start(_)).WillByDefault(Return(Result{mock_handle})); + + static_cast(component.activate(cpp::stop_token{})); + const auto result = component.tryHandleTermination(1); + + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error(), IComponent::ComponentError::kErrorAfterReady); +} + +TEST(ComponentTest, SelfTerminatingExitNonZeroInReadyStateIsNotActive) +{ + MockStartAction start_action; + MockStopAction stop_action; + MockForceStopAction force_stop_action; + cpp::span> ready_conditions; + Component component(start_action, stop_action, force_stop_action, ready_conditions, true); + + ON_CALL(start_action, start(_)).WillByDefault(Return(Result{mock_handle})); + + static_cast(component.activate(cpp::stop_token{})); + static_cast(component.tryHandleTermination(1)); + + EXPECT_FALSE(component.active()); +} + +TEST(ComponentTest, SelfTerminatingExitZeroInStartingStateIsSuccess) +{ + MockStartAction start_action; + MockStopAction stop_action; + MockForceStopAction force_stop_action; + MockReadyCondition ready_condition; + std::array, 1> ready_conditions{ready_condition}; + Component component(start_action, stop_action, force_stop_action, ready_conditions, true); + + ON_CALL(start_action, start(_)).WillByDefault(Return(Result{mock_handle})); + + IComponent::RequestResult result; + EXPECT_CALL(ready_condition, wait(_, _)).WillOnce(Invoke([&](cpp::stop_token, const Handle) { + result = terminateFromOtherThread(component, 0); + return Result{}; + })); + + static_cast(component.activate(cpp::stop_token{})); + + ASSERT_TRUE(result.has_value()); + EXPECT_EQ(result.value(), IComponent::RequestState::kSuccess); +} + +TEST(ComponentTest, SelfTerminatingExitZeroInStartingStateIsActive) +{ + MockStartAction start_action; + MockStopAction stop_action; + MockForceStopAction force_stop_action; + MockReadyCondition ready_condition; + std::array, 1> ready_conditions{ready_condition}; + Component component(start_action, stop_action, force_stop_action, ready_conditions, true); + + ON_CALL(start_action, start(_)).WillByDefault(Return(Result{mock_handle})); + EXPECT_CALL(ready_condition, wait(_, _)).WillOnce(Invoke([&](cpp::stop_token, const Handle) { + static_cast(terminateFromOtherThread(component, 0)); + return Result{}; + })); + + static_cast(component.activate(cpp::stop_token{})); + + EXPECT_TRUE(component.active()); +} + +TEST(ComponentTest, SelfTerminatingExitNonZeroInStartingStateIsError) +{ + MockStartAction start_action; + MockStopAction stop_action; + MockForceStopAction force_stop_action; + MockReadyCondition ready_condition; + std::array, 1> ready_conditions{ready_condition}; + Component component(start_action, stop_action, force_stop_action, ready_conditions, true); + + ON_CALL(start_action, start(_)).WillByDefault(Return(Result{mock_handle})); + + IComponent::RequestResult result; + EXPECT_CALL(ready_condition, wait(_, _)).WillOnce(Invoke([&](cpp::stop_token, const Handle) { + result = terminateFromOtherThread(component, 1); + return Result{}; + })); + + static_cast(component.activate(cpp::stop_token{})); + + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error(), IComponent::ComponentError::kErrorAfterReady); +} + +TEST(ComponentTest, SelfTerminatingExitNonZeroInStartingStateIsNotActive) +{ + MockStartAction start_action; + MockStopAction stop_action; + MockForceStopAction force_stop_action; + MockReadyCondition ready_condition; + std::array, 1> ready_conditions{ready_condition}; + Component component(start_action, stop_action, force_stop_action, ready_conditions, true); + + ON_CALL(start_action, start(_)).WillByDefault(Return(Result{mock_handle})); + EXPECT_CALL(ready_condition, wait(_, _)).WillOnce(Invoke([&](cpp::stop_token, const Handle) { + static_cast(terminateFromOtherThread(component, 1)); + return Result{}; + })); + + static_cast(component.activate(cpp::stop_token{})); + + EXPECT_FALSE(component.active()); +} + } // namespace } // namespace score::mw::lifecycle::internal From c10319d04877d28b32cb9362a3efc43ce067b416 Mon Sep 17 00:00:00 2001 From: Daniel Thwaites Date: Fri, 9 Oct 2026 09:45:31 +0100 Subject: [PATCH 22/36] Invalidate process handle when the process terminates --- .../details/component.cpp | 17 ++++++++- .../details/component_UT.cpp | 35 +++++++++++++++++++ .../process_force_stop_action.cpp | 5 +++ .../process_force_stop_action_UT.cpp | 12 +++++++ .../stop_action/process_stop_action.cpp | 5 +++ .../stop_action/process_stop_action_UT.cpp | 12 +++++++ 6 files changed, 85 insertions(+), 1 deletion(-) diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/component.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/component.cpp index fca94ae13f..a26fbbd273 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/component.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/component.cpp @@ -74,7 +74,12 @@ IComponent::RequestResult Component::TerminatedState::activate(Component& compon return cpp::make_unexpected(ComponentError::kErrorBeforeReady); } - component.setState(ReadyState{handle}); + // The handle may have been invalidated while starting, so don't reuse the local copy. + const std::lock_guard lock{component.state_mutex_}; + if (const StartingState* const starting = std::get_if(&component.state_)) + { + component.state_ = ReadyState{starting->handle_}; + } return RequestState::kSuccess; } @@ -178,6 +183,11 @@ IComponent::RequestResult Component::TerminatedState::tryHandleTermination( IComponent::RequestResult Component::StartingState::tryHandleTermination(Component& component, int32_t status) { + if (ProcessHandle* handle = std::get_if(&handle_)) + { + handle->pid = -1; + } + for (const IReadyCondition& ready_condition : component.ready_conditions_) { if (ready_condition.tryHandleTermination(status)) @@ -204,6 +214,11 @@ IComponent::RequestResult Component::StartingState::tryHandleTermination(Compone IComponent::RequestResult Component::ReadyState::tryHandleTermination(Component& component, int32_t status) { + if (ProcessHandle* handle = std::get_if(&handle_)) + { + handle->pid = -1; + } + if (component.self_terminating_ && status == 0) { // Self-terminating means the component is logically still running diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/component_UT.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/component_UT.cpp index a8175c4af2..562904df98 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/component_UT.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/component_UT.cpp @@ -418,5 +418,40 @@ TEST(ComponentTest, SelfTerminatingExitNonZeroInStartingStateIsNotActive) EXPECT_FALSE(component.active()); } +TEST(ComponentTest, DeactivateAfterSelfTerminationReturnsSuccess) +{ + StrictMock start_action; + StrictMock stop_action; + MockForceStopAction force_stop_action; + cpp::span> ready_conditions; + Component component(start_action, stop_action, force_stop_action, ready_conditions, true); + + EXPECT_CALL(start_action, start(_)).WillOnce(Return(Result{mock_handle})); + + static_cast(component.activate(cpp::stop_token{})); + static_cast(component.tryHandleTermination(0)); + const auto result = component.deactivate(cpp::stop_token{}); + + ASSERT_TRUE(result.has_value()); + EXPECT_EQ(result.value(), IComponent::RequestState::kSuccess); +} + +TEST(ComponentTest, DeactivateWithRunningProcessReturnsWaiting) +{ + MockStartAction start_action; + MockStopAction stop_action; + MockForceStopAction force_stop_action; + Component component(start_action, stop_action, force_stop_action); + + ON_CALL(start_action, start(_)).WillByDefault(Return(Result{mock_handle})); + EXPECT_CALL(stop_action, stop(_, _)).WillOnce(Return(Result{})); + + static_cast(component.activate(cpp::stop_token{})); + const auto result = component.deactivate(cpp::stop_token{}); + + ASSERT_TRUE(result.has_value()); + EXPECT_EQ(result.value(), IComponent::RequestState::kWaiting); +} + } // namespace } // namespace score::mw::lifecycle::internal diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/process_force_stop_action.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/process_force_stop_action.cpp index d7a4b18aa1..b474c76aee 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/process_force_stop_action.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/process_force_stop_action.cpp @@ -33,6 +33,11 @@ Result ProcessForceStopAction::forceStop(cpp::stop_token stop_token, const Result ProcessForceStopAction::visitForceStop(const ProcessHandle process) const { + if (process.pid == -1) + { + return {}; + } + const osal::OsalReturnType result = launcher_.forceTermination(process.pid); if (result == osal::OsalReturnType::kSuccess) diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/process_force_stop_action_UT.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/process_force_stop_action_UT.cpp index b11f3202bb..45f402a4f6 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/process_force_stop_action_UT.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/process_force_stop_action_UT.cpp @@ -61,6 +61,18 @@ TEST(ProcessForceStopActionTest, ForceStopFailsReturnsError) EXPECT_EQ(result.error(), ExecErrc::kGeneralError); } +TEST(ProcessForceStopActionTest, ForceStopWithInvalidatedHandleDoesNotCallLauncher) +{ + StrictMock launcher; + const ProcessForceStopAction process_force_stop_action(launcher); + ProcessHandle handle = mock_handle; + handle.pid = -1; + + const Result result = process_force_stop_action.forceStop(cpp::stop_token{}, handle); + + EXPECT_TRUE(result.has_value()); +} + TEST(ProcessForceStopActionTest, ForceStopWithEmptyHandleAborts) { NiceMock launcher; diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/process_stop_action.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/process_stop_action.cpp index 5c3218f720..24b3c5109f 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/process_stop_action.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/process_stop_action.cpp @@ -33,6 +33,11 @@ Result ProcessStopAction::stop(cpp::stop_token stop_token, const Handle ha Result ProcessStopAction::visitStop(const ProcessHandle process) const { + if (process.pid == -1) + { + return {}; + } + const osal::OsalReturnType result = launcher_.requestTermination(process.pid); if (result == osal::OsalReturnType::kSuccess) diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/process_stop_action_UT.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/process_stop_action_UT.cpp index ce0a778adc..12cf4d2b9c 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/process_stop_action_UT.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/process_stop_action_UT.cpp @@ -61,6 +61,18 @@ TEST(ProcessStopActionTest, StopFailsReturnsError) EXPECT_EQ(result.error(), ExecErrc::kGeneralError); } +TEST(ProcessStopActionTest, StopWithInvalidatedHandleDoesNotCallLauncher) +{ + StrictMock launcher; + const ProcessStopAction process_stop_action(launcher); + ProcessHandle handle = mock_handle; + handle.pid = -1; + + const Result result = process_stop_action.stop(cpp::stop_token{}, handle); + + EXPECT_TRUE(result.has_value()); +} + TEST(ProcessStopActionTest, StopWithEmptyHandleAborts) { NiceMock launcher; From cda774771ab36839179a203643bf2afa2696acaf Mon Sep 17 00:00:00 2001 From: Daniel Thwaites Date: Fri, 9 Oct 2026 12:27:08 +0100 Subject: [PATCH 23/36] Allow stop actions to return either `kWaiting` or `kSuccess` For invalid handles, we should move to the terminated state immediately, rather than waiting for a termination event. --- .../details/component.cpp | 27 ++++--- .../details/component_UT.cpp | 75 +++++++++---------- .../details/force_stop_action/BUILD | 1 + .../empty_force_stop_action.cpp | 4 +- .../empty_force_stop_action.hpp | 2 +- .../empty_force_stop_action_UT.cpp | 5 +- .../force_stop_action/iforce_stop_action.hpp | 3 +- .../mock_force_stop_action.hpp | 2 +- .../process_force_stop_action.cpp | 11 +-- .../process_force_stop_action.hpp | 6 +- .../process_force_stop_action_UT.cpp | 36 ++++++--- .../details/stop_action/BUILD | 1 + .../details/stop_action/empty_stop_action.cpp | 4 +- .../details/stop_action/empty_stop_action.hpp | 2 +- .../stop_action/empty_stop_action_UT.cpp | 5 +- .../details/stop_action/istop_action.hpp | 3 +- .../details/stop_action/mock_stop_action.hpp | 2 +- .../stop_action/process_stop_action.cpp | 10 +-- .../stop_action/process_stop_action.hpp | 6 +- .../stop_action/process_stop_action_UT.cpp | 34 ++++++--- 20 files changed, 138 insertions(+), 101 deletions(-) diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/component.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/component.cpp index a26fbbd273..91d8c60467 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/component.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/component.cpp @@ -131,24 +131,31 @@ IComponent::RequestResult Component::StartingState::deactivate(Component& compon { static_cast(stop_activation_.request_stop()); - if (!component.stop_action_.stop(stop_token, handle_).has_value()) - { - return cpp::make_unexpected(ComponentError::kErrorAfterReady); - } - - component.setState(TerminatingState{handle_}); - return RequestState::kWaiting; + // The implementation from here onwards is the same as the ready state. + return ReadyState{handle_}.deactivate(component, stop_token); } IComponent::RequestResult Component::ReadyState::deactivate(Component& component, cpp::stop_token stop_token) { - if (!component.stop_action_.stop(stop_token, handle_).has_value()) + const Result result = component.stop_action_.stop(stop_token, handle_); + + if (!result.has_value()) { return cpp::make_unexpected(ComponentError::kErrorAfterReady); } - component.setState(TerminatingState{handle_}); - return RequestState::kWaiting; + switch (*result.value()) + { + case RequestState::kWaiting: + component.setState(TerminatingState{handle_}); + return RequestState::kWaiting; + + case RequestState::kSuccess: + component.setState(TerminatedState{}); + return RequestState::kSuccess; + } + + SCORE_LANGUAGE_FUTURECPP_UNREACHABLE_MESSAGE("Every possible RequestState should be handled"); } IComponent::RequestResult Component::TerminatingState::deactivate( diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/component_UT.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/component_UT.cpp index 562904df98..e62fedb8e8 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/component_UT.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/component_UT.cpp @@ -85,12 +85,46 @@ TEST(ComponentTest, StopActionCalled) ON_CALL(start_action, start(_)).WillByDefault(Return(Result{mock_handle})); EXPECT_CALL(stop_action, stop(_, _)) - .WillOnce(DoAll(WithArg<1>(Invoke(expect_mock_handle)), Return(Result{}))); + .WillOnce(DoAll(WithArg<1>(Invoke(expect_mock_handle)), Return(IComponent::RequestState::kSuccess))); static_cast(component.activate(cpp::stop_token{})); component.deactivate(mock_stop_token); } +TEST(ComponentTest, DeactivateReturnsWaitingWhenStopActionReturnsWaiting) +{ + MockStartAction start_action; + MockStopAction stop_action; + MockForceStopAction force_stop_action; + Component component(start_action, stop_action, force_stop_action); + + ON_CALL(start_action, start(_)).WillByDefault(Return(Result{mock_handle})); + ON_CALL(stop_action, stop(_, _)).WillByDefault(Return(IComponent::RequestState::kWaiting)); + + static_cast(component.activate(cpp::stop_token{})); + const auto result = component.deactivate(mock_stop_token); + + ASSERT_TRUE(result.has_value()); + ASSERT_EQ(result.value(), IComponent::RequestState::kWaiting); +} + +TEST(ComponentTest, DeactivateReturnsSuccessWhenStopActionReturnsSuccess) +{ + MockStartAction start_action; + MockStopAction stop_action; + MockForceStopAction force_stop_action; + Component component(start_action, stop_action, force_stop_action); + + ON_CALL(start_action, start(_)).WillByDefault(Return(Result{mock_handle})); + ON_CALL(stop_action, stop(_, _)).WillByDefault(Return(IComponent::RequestState::kSuccess)); + + static_cast(component.activate(cpp::stop_token{})); + const auto result = component.deactivate(mock_stop_token); + + ASSERT_TRUE(result.has_value()); + ASSERT_EQ(result.value(), IComponent::RequestState::kSuccess); +} + TEST(ComponentTest, ReadyConditionsCalled) { MockStartAction start_action; @@ -207,7 +241,7 @@ TEST(ComponentTest, CannotActivateInTerminatingState) Component component(start_action, stop_action, force_stop_action); ON_CALL(start_action, start(_)).WillByDefault(Return(Result{mock_handle})); - ON_CALL(stop_action, stop(_, _)).WillByDefault(Return(Result{})); + ON_CALL(stop_action, stop(_, _)).WillByDefault(Return(IComponent::RequestState::kWaiting)); static_cast(component.activate(cpp::stop_token{})); static_cast(component.deactivate(cpp::stop_token{})); @@ -224,7 +258,7 @@ TEST(ComponentTest, CannotDeactivateInTerminatingState) Component component(start_action, stop_action, force_stop_action); ON_CALL(start_action, start(_)).WillByDefault(Return(Result{mock_handle})); - ON_CALL(stop_action, stop(_, _)).WillByDefault(Return(Result{})); + ON_CALL(stop_action, stop(_, _)).WillByDefault(Return(IComponent::RequestState::kWaiting)); static_cast(component.activate(cpp::stop_token{})); static_cast(component.deactivate(cpp::stop_token{})); @@ -418,40 +452,5 @@ TEST(ComponentTest, SelfTerminatingExitNonZeroInStartingStateIsNotActive) EXPECT_FALSE(component.active()); } -TEST(ComponentTest, DeactivateAfterSelfTerminationReturnsSuccess) -{ - StrictMock start_action; - StrictMock stop_action; - MockForceStopAction force_stop_action; - cpp::span> ready_conditions; - Component component(start_action, stop_action, force_stop_action, ready_conditions, true); - - EXPECT_CALL(start_action, start(_)).WillOnce(Return(Result{mock_handle})); - - static_cast(component.activate(cpp::stop_token{})); - static_cast(component.tryHandleTermination(0)); - const auto result = component.deactivate(cpp::stop_token{}); - - ASSERT_TRUE(result.has_value()); - EXPECT_EQ(result.value(), IComponent::RequestState::kSuccess); -} - -TEST(ComponentTest, DeactivateWithRunningProcessReturnsWaiting) -{ - MockStartAction start_action; - MockStopAction stop_action; - MockForceStopAction force_stop_action; - Component component(start_action, stop_action, force_stop_action); - - ON_CALL(start_action, start(_)).WillByDefault(Return(Result{mock_handle})); - EXPECT_CALL(stop_action, stop(_, _)).WillOnce(Return(Result{})); - - static_cast(component.activate(cpp::stop_token{})); - const auto result = component.deactivate(cpp::stop_token{}); - - ASSERT_TRUE(result.has_value()); - EXPECT_EQ(result.value(), IComponent::RequestState::kWaiting); -} - } // namespace } // namespace score::mw::lifecycle::internal diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/BUILD b/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/BUILD index d9a88d1541..8d6e56e29a 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/BUILD +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/BUILD @@ -21,6 +21,7 @@ cc_library( visibility = ["//score/launch_manager/src/daemon/src/process_group_manager:__subpackages__"], deps = [ "//score/launch_manager/src/daemon/src/process_group_manager/details:handle", + "//score/launch_manager/src/daemon/src/process_group_manager/details:icomponent", "@score_baselibs//score/language/futurecpp", "@score_baselibs//score/result", ], diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/empty_force_stop_action.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/empty_force_stop_action.cpp index b997cbebbf..bca1980b29 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/empty_force_stop_action.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/empty_force_stop_action.cpp @@ -16,9 +16,9 @@ namespace score::mw::lifecycle::internal { -Result EmptyForceStopAction::forceStop(cpp::stop_token stop_token, const Handle) const +Result EmptyForceStopAction::forceStop(cpp::stop_token stop_token, const Handle) const { - return {}; + return IComponent::RequestState::kSuccess; } } // namespace score::mw::lifecycle::internal diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/empty_force_stop_action.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/empty_force_stop_action.hpp index e62c414c4a..6eb7851e91 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/empty_force_stop_action.hpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/empty_force_stop_action.hpp @@ -27,7 +27,7 @@ class EmptyForceStopAction final : public IForceStopAction /// @param stop_token Token which can be used to interrupt the action. /// @param handle The resource to act upon. /// @return Always successful. - Result forceStop(cpp::stop_token stop_token, const Handle) const override; + Result forceStop(cpp::stop_token stop_token, const Handle) const override; }; } // namespace score::mw::lifecycle::internal diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/empty_force_stop_action_UT.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/empty_force_stop_action_UT.cpp index 5e57524a6b..eb452789ab 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/empty_force_stop_action_UT.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/empty_force_stop_action_UT.cpp @@ -23,9 +23,10 @@ TEST(EmptyForceStopActionTest, ForceStopSucceeds) { EmptyForceStopAction empty_force_stop_action; - const auto result = empty_force_stop_action.forceStop(cpp::stop_token{}, EmptyHandle{}); + const Result result = empty_force_stop_action.forceStop(cpp::stop_token{}, EmptyHandle{}); - EXPECT_TRUE(result.has_value()); + ASSERT_TRUE(result.has_value()); + EXPECT_EQ(result.value(), IComponent::RequestState::kSuccess); } } // namespace diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/iforce_stop_action.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/iforce_stop_action.hpp index 8e03af0fcc..216505e287 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/iforce_stop_action.hpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/iforce_stop_action.hpp @@ -15,6 +15,7 @@ #define SCORE_LCM_IFORCE_STOP_ACTION_HPP_INCLUDED #include "score/mw/launch_manager/process_group_manager/details/handle.hpp" +#include "score/mw/launch_manager/process_group_manager/details/icomponent.hpp" #include "score/result/result.h" #include @@ -31,7 +32,7 @@ class IForceStopAction /// @param stop_token Token which can be used to interrupt the action. /// @param handle The resource to act upon. /// @return Whether the action was successful, or failed with an error. - virtual Result forceStop(cpp::stop_token stop_token, const Handle handle) const = 0; + virtual Result forceStop(cpp::stop_token stop_token, const Handle handle) const = 0; }; } // namespace score::mw::lifecycle::internal diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/mock_force_stop_action.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/mock_force_stop_action.hpp index dbe4890f8f..f950f28aba 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/mock_force_stop_action.hpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/mock_force_stop_action.hpp @@ -23,7 +23,7 @@ namespace score::mw::lifecycle::internal class MockForceStopAction : public IForceStopAction { public: - MOCK_METHOD(Result, forceStop, (cpp::stop_token, const Handle handle), (override, const)); + MOCK_METHOD(Result, forceStop, (cpp::stop_token, const Handle handle), (override, const)); }; } // namespace score::mw::lifecycle::internal diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/process_force_stop_action.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/process_force_stop_action.cpp index b474c76aee..7cc11e87c1 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/process_force_stop_action.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/process_force_stop_action.cpp @@ -22,7 +22,8 @@ ProcessForceStopAction::ProcessForceStopAction(osal::IProcess& launcher) : launc { } -Result ProcessForceStopAction::forceStop(cpp::stop_token stop_token, const Handle handle) const +Result ProcessForceStopAction::forceStop(cpp::stop_token stop_token, const Handle handle) + const { return std::visit( [this](auto&& handle) { @@ -31,24 +32,24 @@ Result ProcessForceStopAction::forceStop(cpp::stop_token stop_token, const handle); } -Result ProcessForceStopAction::visitForceStop(const ProcessHandle process) const +Result ProcessForceStopAction::visitForceStop(const ProcessHandle process) const { if (process.pid == -1) { - return {}; + return IComponent::RequestState::kSuccess; } const osal::OsalReturnType result = launcher_.forceTermination(process.pid); if (result == osal::OsalReturnType::kSuccess) { - return {}; + return IComponent::RequestState::kWaiting; } return MakeUnexpected(ExecErrc::kGeneralError); } -Result ProcessForceStopAction::visitForceStop(const Handle handle) const +Result ProcessForceStopAction::visitForceStop(const Handle handle) const { SCORE_LANGUAGE_FUTURECPP_UNREACHABLE_MESSAGE("ProcessForceStopAction should only be called with a ProcessHandle"); } diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/process_force_stop_action.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/process_force_stop_action.hpp index 1cd6668b2d..aa3aff4504 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/process_force_stop_action.hpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/process_force_stop_action.hpp @@ -35,14 +35,14 @@ class ProcessForceStopAction final : public IForceStopAction /// @param stop_token Token which can be used to interrupt the action. /// @param handle The resource to act upon. /// @return Whether the action was successful, or failed with an error. - Result forceStop(cpp::stop_token stop_token, const Handle handle) const override; + Result forceStop(cpp::stop_token stop_token, const Handle handle) const override; private: /// @brief Visitor method for process handles. - Result visitForceStop(const ProcessHandle process) const; + Result visitForceStop(const ProcessHandle process) const; /// @brief Visitor method for unsupported handles. - Result visitForceStop(const Handle handle) const; + Result visitForceStop(const Handle handle) const; /// @brief The process launcher used to stop the process. osal::IProcess& launcher_; diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/process_force_stop_action_UT.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/process_force_stop_action_UT.cpp index 45f402a4f6..0ae0d9a09a 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/process_force_stop_action_UT.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/process_force_stop_action_UT.cpp @@ -36,41 +36,53 @@ TEST(ProcessForceStopActionTest, ForceStopCallsForceTerminationWithPid) static_cast(process_force_stop_action.forceStop(cpp::stop_token{}, mock_handle)); } -TEST(ProcessForceStopActionTest, ForceStopSucceedsReturnsSuccess) +TEST(ProcessForceStopActionTest, ForceStopSucceedsReturnsWaiting) { NiceMock launcher; const ProcessForceStopAction process_force_stop_action(launcher); ON_CALL(launcher, forceTermination(_)).WillByDefault(Return(osal::OsalReturnType::kSuccess)); - const Result result = process_force_stop_action.forceStop(cpp::stop_token{}, mock_handle); + const Result result = process_force_stop_action.forceStop(cpp::stop_token{}, mock_handle); - EXPECT_TRUE(result.has_value()); + ASSERT_TRUE(result.has_value()); + EXPECT_EQ(result.value(), IComponent::RequestState::kWaiting); } -TEST(ProcessForceStopActionTest, ForceStopFailsReturnsError) +TEST(ProcessForceStopActionTest, ForceStopWithInvalidatedHandleReturnsSuccess) { NiceMock launcher; const ProcessForceStopAction process_force_stop_action(launcher); + ProcessHandle handle = mock_handle; + handle.pid = -1; - ON_CALL(launcher, forceTermination(_)).WillByDefault(Return(osal::OsalReturnType::kFail)); - - const Result result = process_force_stop_action.forceStop(cpp::stop_token{}, mock_handle); + const Result result = process_force_stop_action.forceStop(cpp::stop_token{}, handle); - EXPECT_FALSE(result.has_value()); - EXPECT_EQ(result.error(), ExecErrc::kGeneralError); + ASSERT_TRUE(result.has_value()); + EXPECT_EQ(result.value(), IComponent::RequestState::kSuccess); } -TEST(ProcessForceStopActionTest, ForceStopWithInvalidatedHandleDoesNotCallLauncher) +TEST(ProcessStopActionTest, ForceStopWithInvalidatedHandleDoesNotCallLauncher) { StrictMock launcher; const ProcessForceStopAction process_force_stop_action(launcher); ProcessHandle handle = mock_handle; handle.pid = -1; - const Result result = process_force_stop_action.forceStop(cpp::stop_token{}, handle); + static_cast(process_force_stop_action.forceStop(cpp::stop_token{}, handle)); +} + +TEST(ProcessForceStopActionTest, ForceStopFailsReturnsError) +{ + NiceMock launcher; + const ProcessForceStopAction process_force_stop_action(launcher); - EXPECT_TRUE(result.has_value()); + ON_CALL(launcher, forceTermination(_)).WillByDefault(Return(osal::OsalReturnType::kFail)); + + const Result result = process_force_stop_action.forceStop(cpp::stop_token{}, mock_handle); + + EXPECT_FALSE(result.has_value()); + EXPECT_EQ(result.error(), ExecErrc::kGeneralError); } TEST(ProcessForceStopActionTest, ForceStopWithEmptyHandleAborts) diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/BUILD b/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/BUILD index 051454f096..4e2911c814 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/BUILD +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/BUILD @@ -21,6 +21,7 @@ cc_library( visibility = ["//score/launch_manager/src/daemon/src/process_group_manager:__subpackages__"], deps = [ "//score/launch_manager/src/daemon/src/process_group_manager/details:handle", + "//score/launch_manager/src/daemon/src/process_group_manager/details:icomponent", "@score_baselibs//score/language/futurecpp", "@score_baselibs//score/result", ], diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/empty_stop_action.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/empty_stop_action.cpp index 76efcf8858..2a4c1b76ff 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/empty_stop_action.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/empty_stop_action.cpp @@ -16,9 +16,9 @@ namespace score::mw::lifecycle::internal { -Result EmptyStopAction::stop(cpp::stop_token stop_token, const Handle) const +Result EmptyStopAction::stop(cpp::stop_token stop_token, const Handle) const { - return {}; + return IComponent::RequestState::kSuccess; } } // namespace score::mw::lifecycle::internal diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/empty_stop_action.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/empty_stop_action.hpp index e957788b5a..cb517f34a9 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/empty_stop_action.hpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/empty_stop_action.hpp @@ -27,7 +27,7 @@ class EmptyStopAction final : public IStopAction /// @param stop_token Token which can be used to interrupt the action. /// @param handle The resource to act upon. /// @return Always successful. - Result stop(cpp::stop_token stop_token, const Handle) const override; + Result stop(cpp::stop_token stop_token, const Handle) const override; }; } // namespace score::mw::lifecycle::internal diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/empty_stop_action_UT.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/empty_stop_action_UT.cpp index ceddb9b131..120007ff57 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/empty_stop_action_UT.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/empty_stop_action_UT.cpp @@ -23,9 +23,10 @@ TEST(EmptyStopActionTest, StopSucceeds) { EmptyStopAction empty_stop_action; - const auto result = empty_stop_action.stop(cpp::stop_token{}, EmptyHandle{}); + const Result result = empty_stop_action.stop(cpp::stop_token{}, EmptyHandle{}); - EXPECT_TRUE(result.has_value()); + ASSERT_TRUE(result.has_value()); + EXPECT_EQ(result.value(), IComponent::RequestState::kSuccess); } } // namespace diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/istop_action.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/istop_action.hpp index 27426b3780..5ff0743fa5 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/istop_action.hpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/istop_action.hpp @@ -15,6 +15,7 @@ #define SCORE_LCM_ISTOP_ACTION_HPP_INCLUDED #include "score/mw/launch_manager/process_group_manager/details/handle.hpp" +#include "score/mw/launch_manager/process_group_manager/details/icomponent.hpp" #include "score/result/result.h" #include @@ -31,7 +32,7 @@ class IStopAction /// @param stop_token Token which can be used to interrupt the action. /// @param handle The resource to act upon. /// @return Whether the action was successful, or failed with an error. - virtual Result stop(cpp::stop_token stop_token, const Handle handle) const = 0; + virtual Result stop(cpp::stop_token stop_token, const Handle handle) const = 0; }; } // namespace score::mw::lifecycle::internal diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/mock_stop_action.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/mock_stop_action.hpp index ca4b3c2bd9..dc66d574c3 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/mock_stop_action.hpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/mock_stop_action.hpp @@ -23,7 +23,7 @@ namespace score::mw::lifecycle::internal class MockStopAction : public IStopAction { public: - MOCK_METHOD(Result, stop, (cpp::stop_token, const Handle handle), (override, const)); + MOCK_METHOD(Result, stop, (cpp::stop_token, const Handle handle), (override, const)); }; } // namespace score::mw::lifecycle::internal diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/process_stop_action.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/process_stop_action.cpp index 24b3c5109f..5380451c98 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/process_stop_action.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/process_stop_action.cpp @@ -22,7 +22,7 @@ ProcessStopAction::ProcessStopAction(osal::IProcess& launcher) : launcher_(launc { } -Result ProcessStopAction::stop(cpp::stop_token stop_token, const Handle handle) const +Result ProcessStopAction::stop(cpp::stop_token stop_token, const Handle handle) const { return std::visit( [this](auto&& handle) { @@ -31,24 +31,24 @@ Result ProcessStopAction::stop(cpp::stop_token stop_token, const Handle ha handle); } -Result ProcessStopAction::visitStop(const ProcessHandle process) const +Result ProcessStopAction::visitStop(const ProcessHandle process) const { if (process.pid == -1) { - return {}; + return IComponent::RequestState::kSuccess; } const osal::OsalReturnType result = launcher_.requestTermination(process.pid); if (result == osal::OsalReturnType::kSuccess) { - return {}; + return IComponent::RequestState::kWaiting; } return MakeUnexpected(ExecErrc::kGeneralError); } -Result ProcessStopAction::visitStop(const Handle handle) const +Result ProcessStopAction::visitStop(const Handle handle) const { SCORE_LANGUAGE_FUTURECPP_UNREACHABLE_MESSAGE("ProcessStopAction should only be called with a ProcessHandle"); } diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/process_stop_action.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/process_stop_action.hpp index c902a489aa..f8b0dca1a1 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/process_stop_action.hpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/process_stop_action.hpp @@ -35,14 +35,14 @@ class ProcessStopAction final : public IStopAction /// @param stop_token Token which can be used to interrupt the action. /// @param handle The resource to act upon. /// @return Whether the action was successful, or failed with an error. - Result stop(cpp::stop_token stop_token, const Handle handle) const override; + Result stop(cpp::stop_token stop_token, const Handle handle) const override; private: /// @brief Visitor method for process handles. - Result visitStop(const ProcessHandle process) const; + Result visitStop(const ProcessHandle process) const; /// @brief Visitor method for unsupported handles. - Result visitStop(const Handle handle) const; + Result visitStop(const Handle handle) const; /// @brief The process launcher used to stop the process. osal::IProcess& launcher_; diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/process_stop_action_UT.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/process_stop_action_UT.cpp index 12cf4d2b9c..0f2b27ac9b 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/process_stop_action_UT.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/process_stop_action_UT.cpp @@ -36,29 +36,30 @@ TEST(ProcessStopActionTest, StopCallsRequestTerminationWithPid) static_cast(process_stop_action.stop(cpp::stop_token{}, mock_handle)); } -TEST(ProcessStopActionTest, StopSucceedsReturnsSuccess) +TEST(ProcessStopActionTest, StopSucceedsReturnsWaiting) { NiceMock launcher; const ProcessStopAction process_stop_action(launcher); ON_CALL(launcher, requestTermination(_)).WillByDefault(Return(osal::OsalReturnType::kSuccess)); - const Result result = process_stop_action.stop(cpp::stop_token{}, mock_handle); + const Result result = process_stop_action.stop(cpp::stop_token{}, mock_handle); - EXPECT_TRUE(result.has_value()); + ASSERT_TRUE(result.has_value()); + EXPECT_EQ(result.value(), IComponent::RequestState::kWaiting); } -TEST(ProcessStopActionTest, StopFailsReturnsError) +TEST(ProcessStopActionTest, StopWithInvalidatedHandleReturnsSuccess) { NiceMock launcher; const ProcessStopAction process_stop_action(launcher); + ProcessHandle handle = mock_handle; + handle.pid = -1; - ON_CALL(launcher, requestTermination(_)).WillByDefault(Return(osal::OsalReturnType::kFail)); - - const Result result = process_stop_action.stop(cpp::stop_token{}, mock_handle); + const Result result = process_stop_action.stop(cpp::stop_token{}, handle); - EXPECT_FALSE(result.has_value()); - EXPECT_EQ(result.error(), ExecErrc::kGeneralError); + ASSERT_TRUE(result.has_value()); + EXPECT_EQ(result.value(), IComponent::RequestState::kSuccess); } TEST(ProcessStopActionTest, StopWithInvalidatedHandleDoesNotCallLauncher) @@ -68,9 +69,20 @@ TEST(ProcessStopActionTest, StopWithInvalidatedHandleDoesNotCallLauncher) ProcessHandle handle = mock_handle; handle.pid = -1; - const Result result = process_stop_action.stop(cpp::stop_token{}, handle); + static_cast(process_stop_action.stop(cpp::stop_token{}, handle)); +} + +TEST(ProcessStopActionTest, StopFailsReturnsError) +{ + NiceMock launcher; + const ProcessStopAction process_stop_action(launcher); - EXPECT_TRUE(result.has_value()); + ON_CALL(launcher, requestTermination(_)).WillByDefault(Return(osal::OsalReturnType::kFail)); + + const Result result = process_stop_action.stop(cpp::stop_token{}, mock_handle); + + EXPECT_FALSE(result.has_value()); + EXPECT_EQ(result.error(), ExecErrc::kGeneralError); } TEST(ProcessStopActionTest, StopWithEmptyHandleAborts) From 470ff82ff99665a7c4d78d41a9587ec8915d1316 Mon Sep 17 00:00:00 2001 From: Daniel Thwaites Date: Mon, 12 Oct 2026 09:42:26 +0100 Subject: [PATCH 24/36] Remove incorrect force-stop test This can be reintroduced once it is decided how a force stop is triggered. Link: https://github.com/eclipse-score/lifecycle/pull/705/changes#r4154237710 --- .../process_group_manager/details/component_UT.cpp | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/component_UT.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/component_UT.cpp index e62fedb8e8..26e5a40ba8 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/component_UT.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/component_UT.cpp @@ -193,19 +193,6 @@ TEST(ComponentTest, StopClearsActive) EXPECT_FALSE(component.active()); } -TEST(ComponentTest, ForceStopClearsActive) -{ - MockStartAction start_action; - MockStopAction stop_action; - MockForceStopAction force_stop_action; - Component component(start_action, stop_action, force_stop_action); - - static_cast(component.activate(cpp::stop_token{})); - component.deactivate(cpp::stop_token{}); - - EXPECT_FALSE(component.active()); -} - TEST(ComponentTest, CannotDeactivateInTerminatedState) { MockStartAction start_action; From b0016659cd0cc534e73320885b9f8cc9f690068c Mon Sep 17 00:00:00 2001 From: Daniel Thwaites Date: Wed, 14 Oct 2026 11:06:23 +0100 Subject: [PATCH 25/36] Simplify component methods Just switch using if statements rather than a separate implementation for each state. This makes it more obvious how things work with concurrency. --- .../details/component.cpp | 218 ++++++------------ .../details/component.hpp | 44 +--- .../details/component_UT.cpp | 13 +- 3 files changed, 83 insertions(+), 192 deletions(-) diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/component.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/component.cpp index 91d8c60467..005a0df4ba 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/component.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/component.cpp @@ -35,110 +35,72 @@ Component::Component( IComponent::RequestResult Component::activate(cpp::stop_token stop_token) { - return std::visit( - [&](auto state) { - return state.activate(*this, stop_token); - }, - getState()); -} + std::unique_lock lock{state_mutex_}; -IComponent::RequestResult Component::TerminatedState::activate(Component& component, cpp::stop_token stop_token) -{ - const Result start_result = component.start_action_.start(stop_token); + SCORE_LANGUAGE_FUTURECPP_ASSERT_MESSAGE( + std::holds_alternative(state_), "Cannot activate component in a running state"); + + const Result start_result = start_action_.start(stop_token); if (!start_result.has_value()) { return cpp::make_unexpected(ComponentError::kErrorBeforeReady); } - const Handle handle = start_result.value(); + const Handle& handle = start_result.value(); - // Create a child token, which can be stopped from tryHandleTermination - // in addition to forwarding requests from the parent. - cpp::stop_source stop_activation; + cpp::stop_source stop_activation{}; const cpp::stop_callback forward_stop{stop_token, [&stop_activation] { const bool stopped = stop_activation.request_stop(); SCORE_LANGUAGE_FUTURECPP_ASSERT(stopped); }}; - component.setState(StartingState{handle, stop_activation}); - for (const IReadyCondition& ready_condition : component.ready_conditions_) + state_ = StartingState{handle, stop_activation}; + + // Wait for ready conditions. The state is unlocked so that `deactivate` or + // `tryHandleTermination` are callable while we are waiting. + lock.unlock(); + for (const IReadyCondition& ready_condition : ready_conditions_) { if (!ready_condition.wait(stop_activation.get_token(), handle).has_value()) { return cpp::make_unexpected(ComponentError::kErrorBeforeReady); } } + lock.lock(); + // If `deactivate` or `tryHandleTermination` was called during the wait then + // activation fails. if (stop_activation.stop_requested()) { - // Startup was cancelled, either by the caller or by an unexpected termination. return cpp::make_unexpected(ComponentError::kErrorBeforeReady); } - // The handle may have been invalidated while starting, so don't reuse the local copy. - const std::lock_guard lock{component.state_mutex_}; - if (const StartingState* const starting = std::get_if(&component.state_)) + // The state could change while the lock was released, so we have to check. + if (const StartingState* const starting = std::get_if(&state_)) { - component.state_ = ReadyState{starting->handle_}; + state_ = ReadyState{starting->handle_}; + return RequestState::kSuccess; } - return RequestState::kSuccess; -} - -IComponent::RequestResult Component::StartingState::activate( - [[maybe_unused]] Component& component, - [[maybe_unused]] cpp::stop_token stop_token) -{ - SCORE_LANGUAGE_FUTURECPP_UNREACHABLE_MESSAGE("Cannot activate component in StartingState"); -} - -IComponent::RequestResult Component::ReadyState::activate( - [[maybe_unused]] Component& component, - [[maybe_unused]] cpp::stop_token stop_token) -{ - SCORE_LANGUAGE_FUTURECPP_UNREACHABLE_MESSAGE("Cannot activate component in ReadyState"); -} - -IComponent::RequestResult Component::TerminatingState::activate( - [[maybe_unused]] Component& component, - [[maybe_unused]] cpp::stop_token stop_token) -{ - SCORE_LANGUAGE_FUTURECPP_UNREACHABLE_MESSAGE("Cannot activate component in TerminatingState"); -} -IComponent::RequestResult Component::FaultState::activate( - [[maybe_unused]] Component& component, - [[maybe_unused]] cpp::stop_token stop_token) -{ - SCORE_LANGUAGE_FUTURECPP_UNREACHABLE_MESSAGE("Cannot activate component in FaultState"); + SCORE_LANGUAGE_FUTURECPP_UNREACHABLE_MESSAGE("If stop was not requested, then state must still be StartingState"); } IComponent::RequestResult Component::deactivate(cpp::stop_token stop_token) { - return std::visit( - [&](auto state) { - return state.deactivate(*this, stop_token); - }, - getState()); -} - -IComponent::RequestResult Component::TerminatedState::deactivate( - [[maybe_unused]] Component& component, - [[maybe_unused]] cpp::stop_token stop_token) -{ - SCORE_LANGUAGE_FUTURECPP_UNREACHABLE_MESSAGE("Cannot deactivate component in TerminatedState"); -} - -IComponent::RequestResult Component::StartingState::deactivate(Component& component, cpp::stop_token stop_token) -{ - static_cast(stop_activation_.request_stop()); + const std::lock_guard lock{state_mutex_}; - // The implementation from here onwards is the same as the ready state. - return ReadyState{handle_}.deactivate(component, stop_token); -} + StartingState* const starting = std::get_if(&state_); + const ReadyState* const ready = std::get_if(&state_); + SCORE_LANGUAGE_FUTURECPP_ASSERT_MESSAGE( + (starting != nullptr) || (ready != nullptr), "Cannot deactivate component in a non-running state"); + const Handle& handle = (starting != nullptr) ? starting->handle_ : ready->handle_; -IComponent::RequestResult Component::ReadyState::deactivate(Component& component, cpp::stop_token stop_token) -{ - const Result result = component.stop_action_.stop(stop_token, handle_); + // If the component is still waiting for ready conditions, cancel that. + if (starting != nullptr) + { + static_cast(starting->stop_activation_.request_stop()); + } + const Result result = stop_action_.stop(stop_token, handle); if (!result.has_value()) { return cpp::make_unexpected(ComponentError::kErrorAfterReady); @@ -146,113 +108,74 @@ IComponent::RequestResult Component::ReadyState::deactivate(Component& component switch (*result.value()) { + // Wait for `tryHandleTermination` to tell us the termination is done. case RequestState::kWaiting: - component.setState(TerminatingState{handle_}); + state_ = TerminatingState{handle}; return RequestState::kWaiting; + // Already terminated, jump straight to the final state. case RequestState::kSuccess: - component.setState(TerminatedState{}); + state_ = TerminatedState{}; return RequestState::kSuccess; } SCORE_LANGUAGE_FUTURECPP_UNREACHABLE_MESSAGE("Every possible RequestState should be handled"); } -IComponent::RequestResult Component::TerminatingState::deactivate( - [[maybe_unused]] Component& component, - [[maybe_unused]] cpp::stop_token stop_token) -{ - SCORE_LANGUAGE_FUTURECPP_UNREACHABLE_MESSAGE("Cannot deactivate component in TerminatingState"); -} - -IComponent::RequestResult Component::FaultState::deactivate( - [[maybe_unused]] Component& component, - [[maybe_unused]] cpp::stop_token stop_token) -{ - SCORE_LANGUAGE_FUTURECPP_UNREACHABLE_MESSAGE("Cannot deactivate component in FaultState"); -} - IComponent::RequestResult Component::tryHandleTermination(int32_t status) { - return std::visit( - [&](auto state) { - return state.tryHandleTermination(*this, status); - }, - getState()); -} + const std::lock_guard lock{state_mutex_}; -IComponent::RequestResult Component::TerminatedState::tryHandleTermination( - [[maybe_unused]] Component& component, - [[maybe_unused]] int32_t status) -{ - SCORE_LANGUAGE_FUTURECPP_UNREACHABLE_MESSAGE("Cannot handle termination for a component in TerminatedState"); -} + // If we are waiting for termination, mark it as done. We do not care + // about the exit code. + if (std::holds_alternative(state_)) + { + state_ = TerminatedState{}; + return RequestState::kSuccess; + } -IComponent::RequestResult Component::StartingState::tryHandleTermination(Component& component, int32_t status) -{ - if (ProcessHandle* handle = std::get_if(&handle_)) + StartingState* const starting = std::get_if(&state_); + ReadyState* const ready = std::get_if(&state_); + SCORE_LANGUAGE_FUTURECPP_ASSERT_MESSAGE( + (starting != nullptr) || (ready != nullptr), "Cannot handle termination in a non-running state"); + Handle& handle = (starting != nullptr) ? starting->handle_ : ready->handle_; + + // Invalidate the resource to prevent acting on something which is already gone. + // TODO: Component should not have code for specific handle types + if (ProcessHandle* const process_handle = std::get_if(&handle)) { - handle->pid = -1; + process_handle->pid = -1; } - for (const IReadyCondition& ready_condition : component.ready_conditions_) + // Certain ready conditions can accept terminations. + if (starting != nullptr) { - if (ready_condition.tryHandleTermination(status)) + for (const IReadyCondition& ready_condition : ready_conditions_) { - return RequestState::kSuccess; + if (ready_condition.tryHandleTermination(status)) + { + return RequestState::kSuccess; + } } } - if (component.self_terminating_ && status == 0) + // Self-terminating components can accept terminations. + if (self_terminating_ && status == 0) { - // Self-terminating means the component is logically still running - // even though the resource has stopped. - // Termination by itself does not make the component ready; if this - // is desired then a ready condition should handle it above. return RequestState::kSuccess; } - // Interrupt the worker thread waiting in activate(). - static_cast(stop_activation_.request_stop()); - - component.setState(FaultState{}); - return cpp::make_unexpected(ComponentError::kErrorAfterReady); -} - -IComponent::RequestResult Component::ReadyState::tryHandleTermination(Component& component, int32_t status) -{ - if (ProcessHandle* handle = std::get_if(&handle_)) - { - handle->pid = -1; - } - - if (component.self_terminating_ && status == 0) + // If none of the above cases apply, then termination is unexpected. + if (starting != nullptr) { - // Self-terminating means the component is logically still running - // even though the resource has stopped. - return RequestState::kSuccess; + // Interrupt the worker thread waiting in activate(). + static_cast(starting->stop_activation_.request_stop()); } - component.setState(FaultState{}); + state_ = FaultState{}; return cpp::make_unexpected(ComponentError::kErrorAfterReady); } -IComponent::RequestResult Component::TerminatingState::tryHandleTermination( - Component& component, - [[maybe_unused]] int32_t status) -{ - // Since we requested termination, we do not care about the exit code. - component.setState(TerminatedState{}); - return RequestState::kSuccess; -} - -IComponent::RequestResult Component::FaultState::tryHandleTermination( - [[maybe_unused]] Component& component, - [[maybe_unused]] int32_t status) -{ - SCORE_LANGUAGE_FUTURECPP_UNREACHABLE_MESSAGE("Cannot handle termination for a component in FaultState"); -} - IdentifierHash Component::getIdentifier() const { return identifier_; @@ -260,7 +183,8 @@ IdentifierHash Component::getIdentifier() const bool Component::active() const { - return std::holds_alternative(getState()); + const std::lock_guard lock{state_mutex_}; + return std::holds_alternative(state_); } } // namespace score::mw::lifecycle::internal diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/component.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/component.hpp index 14239a3cf3..ccfbf9fb0a 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/component.hpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/component.hpp @@ -93,49 +93,29 @@ class Component final : public IComponent /// @brief Name of the component. const IdentifierHash identifier_; - class TerminatedState final + struct TerminatedState final { - public: - RequestResult activate(Component& component, cpp::stop_token stop_token); - RequestResult deactivate(Component& component, cpp::stop_token stop_token); - RequestResult tryHandleTermination(Component& component, int32_t status); }; - class StartingState final + struct StartingState final { - public: Handle handle_; cpp::stop_source stop_activation_; - RequestResult activate(Component& component, cpp::stop_token stop_token); - RequestResult deactivate(Component& component, cpp::stop_token stop_token); - RequestResult tryHandleTermination(Component& component, int32_t status); }; - class ReadyState final + struct ReadyState final { - public: Handle handle_; - RequestResult activate(Component& component, cpp::stop_token stop_token); - RequestResult deactivate(Component& component, cpp::stop_token stop_token); - RequestResult tryHandleTermination(Component& component, int32_t status); }; - class TerminatingState final + struct TerminatingState final { - public: Handle handle_; - RequestResult activate(Component& component, cpp::stop_token stop_token); - RequestResult deactivate(Component& component, cpp::stop_token stop_token); - RequestResult tryHandleTermination(Component& component, int32_t status); }; - class FaultState final + struct FaultState final { - public: Handle handle_; - RequestResult activate(Component& component, cpp::stop_token stop_token); - RequestResult deactivate(Component& component, cpp::stop_token stop_token); - RequestResult tryHandleTermination(Component& component, int32_t status); }; using State = std::variant; @@ -145,20 +125,6 @@ class Component final : public IComponent /// @brief The current state of the component. State state_; - - /// @brief Set the current state. - void setState(State&& state) - { - const std::lock_guard lock{state_mutex_}; - state_ = state; - } - - /// @brief Get the current state. - [[nodiscard]] State getState() const - { - const std::lock_guard lock{state_mutex_}; - return state_; - } }; } // namespace score::mw::lifecycle::internal diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/component_UT.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/component_UT.cpp index 26e5a40ba8..bfee47901d 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/component_UT.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/component_UT.cpp @@ -202,7 +202,7 @@ TEST(ComponentTest, CannotDeactivateInTerminatedState) EXPECT_DEATH( { static_cast(component.deactivate(cpp::stop_token{})); }, - "Cannot deactivate component in TerminatedState"); + "Cannot deactivate component in a non-running state"); } TEST(ComponentTest, CannotActivateInReadyState) @@ -217,7 +217,7 @@ TEST(ComponentTest, CannotActivateInReadyState) static_cast(component.activate(cpp::stop_token{})); EXPECT_DEATH( - { static_cast(component.activate(cpp::stop_token{})); }, "Cannot activate component in ReadyState"); + { static_cast(component.activate(cpp::stop_token{})); }, "Cannot activate component in a running state"); } TEST(ComponentTest, CannotActivateInTerminatingState) @@ -234,7 +234,7 @@ TEST(ComponentTest, CannotActivateInTerminatingState) static_cast(component.deactivate(cpp::stop_token{})); EXPECT_DEATH( - { static_cast(component.activate(cpp::stop_token{})); }, "Cannot activate component in TerminatingState"); + { static_cast(component.activate(cpp::stop_token{})); }, "Cannot activate component in a running state"); } TEST(ComponentTest, CannotDeactivateInTerminatingState) @@ -252,7 +252,7 @@ TEST(ComponentTest, CannotDeactivateInTerminatingState) EXPECT_DEATH( { static_cast(component.deactivate(cpp::stop_token{})); }, - "Cannot deactivate component in TerminatingState"); + "Cannot deactivate component in a non-running state"); } TEST(ComponentTest, CannotActivateInFaultState) @@ -268,7 +268,7 @@ TEST(ComponentTest, CannotActivateInFaultState) component.tryHandleTermination(1); EXPECT_DEATH( - { static_cast(component.activate(cpp::stop_token{})); }, "Cannot activate component in FaultState"); + { static_cast(component.activate(cpp::stop_token{})); }, "Cannot activate component in a running state"); } TEST(ComponentTest, CannotDeactivateInFaultState) @@ -284,7 +284,8 @@ TEST(ComponentTest, CannotDeactivateInFaultState) component.tryHandleTermination(1); EXPECT_DEATH( - { static_cast(component.deactivate(cpp::stop_token{})); }, "Cannot deactivate component in FaultState"); + { static_cast(component.deactivate(cpp::stop_token{})); }, + "Cannot deactivate component in a non-running state"); } TEST(ComponentTest, SelfTerminatingExitZeroInReadyStateIsSuccess) From 676a9a76cb742d99593cd7d462695b6ff563564f Mon Sep 17 00:00:00 2001 From: Daniel Thwaites Date: Wed, 14 Oct 2026 11:59:08 +0100 Subject: [PATCH 26/36] Take `Handle&` rather than `Handle` --- .../details/force_stop_action/empty_force_stop_action.cpp | 2 +- .../details/force_stop_action/empty_force_stop_action.hpp | 2 +- .../details/force_stop_action/iforce_stop_action.hpp | 2 +- .../details/force_stop_action/mock_force_stop_action.hpp | 6 +++++- .../details/force_stop_action/process_force_stop_action.cpp | 6 +++--- .../details/force_stop_action/process_force_stop_action.hpp | 6 +++--- .../details/ready_condition/file_ready_condition.cpp | 2 +- .../details/ready_condition/file_ready_condition.hpp | 2 +- .../details/ready_condition/iready_condition.hpp | 2 +- .../details/ready_condition/mock_ready_condition.hpp | 2 +- .../ready_condition/report_running_ready_condition.cpp | 6 +++--- .../ready_condition/report_running_ready_condition.hpp | 6 +++--- .../details/stop_action/empty_stop_action.cpp | 2 +- .../details/stop_action/empty_stop_action.hpp | 2 +- .../details/stop_action/istop_action.hpp | 2 +- .../details/stop_action/mock_stop_action.hpp | 2 +- .../details/stop_action/process_stop_action.cpp | 6 +++--- .../details/stop_action/process_stop_action.hpp | 6 +++--- 18 files changed, 34 insertions(+), 30 deletions(-) diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/empty_force_stop_action.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/empty_force_stop_action.cpp index bca1980b29..ecb4d31b82 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/empty_force_stop_action.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/empty_force_stop_action.cpp @@ -16,7 +16,7 @@ namespace score::mw::lifecycle::internal { -Result EmptyForceStopAction::forceStop(cpp::stop_token stop_token, const Handle) const +Result EmptyForceStopAction::forceStop(cpp::stop_token stop_token, const Handle&) const { return IComponent::RequestState::kSuccess; } diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/empty_force_stop_action.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/empty_force_stop_action.hpp index 6eb7851e91..723c18e40c 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/empty_force_stop_action.hpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/empty_force_stop_action.hpp @@ -27,7 +27,7 @@ class EmptyForceStopAction final : public IForceStopAction /// @param stop_token Token which can be used to interrupt the action. /// @param handle The resource to act upon. /// @return Always successful. - Result forceStop(cpp::stop_token stop_token, const Handle) const override; + Result forceStop(cpp::stop_token stop_token, const Handle&) const override; }; } // namespace score::mw::lifecycle::internal diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/iforce_stop_action.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/iforce_stop_action.hpp index 216505e287..06d0397eee 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/iforce_stop_action.hpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/iforce_stop_action.hpp @@ -32,7 +32,7 @@ class IForceStopAction /// @param stop_token Token which can be used to interrupt the action. /// @param handle The resource to act upon. /// @return Whether the action was successful, or failed with an error. - virtual Result forceStop(cpp::stop_token stop_token, const Handle handle) const = 0; + virtual Result forceStop(cpp::stop_token stop_token, const Handle& handle) const = 0; }; } // namespace score::mw::lifecycle::internal diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/mock_force_stop_action.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/mock_force_stop_action.hpp index f950f28aba..744583543e 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/mock_force_stop_action.hpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/mock_force_stop_action.hpp @@ -23,7 +23,11 @@ namespace score::mw::lifecycle::internal class MockForceStopAction : public IForceStopAction { public: - MOCK_METHOD(Result, forceStop, (cpp::stop_token, const Handle handle), (override, const)); + MOCK_METHOD( + Result, + forceStop, + (cpp::stop_token, const Handle& handle), + (override, const)); }; } // namespace score::mw::lifecycle::internal diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/process_force_stop_action.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/process_force_stop_action.cpp index 7cc11e87c1..66b97aafce 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/process_force_stop_action.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/process_force_stop_action.cpp @@ -22,7 +22,7 @@ ProcessForceStopAction::ProcessForceStopAction(osal::IProcess& launcher) : launc { } -Result ProcessForceStopAction::forceStop(cpp::stop_token stop_token, const Handle handle) +Result ProcessForceStopAction::forceStop(cpp::stop_token stop_token, const Handle& handle) const { return std::visit( @@ -32,7 +32,7 @@ Result ProcessForceStopAction::forceStop(cpp::stop_tok handle); } -Result ProcessForceStopAction::visitForceStop(const ProcessHandle process) const +Result ProcessForceStopAction::visitForceStop(const ProcessHandle& process) const { if (process.pid == -1) { @@ -49,7 +49,7 @@ Result ProcessForceStopAction::visitForceStop(const Pr return MakeUnexpected(ExecErrc::kGeneralError); } -Result ProcessForceStopAction::visitForceStop(const Handle handle) const +Result ProcessForceStopAction::visitForceStop(const Handle& handle) const { SCORE_LANGUAGE_FUTURECPP_UNREACHABLE_MESSAGE("ProcessForceStopAction should only be called with a ProcessHandle"); } diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/process_force_stop_action.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/process_force_stop_action.hpp index aa3aff4504..03d37670ca 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/process_force_stop_action.hpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/process_force_stop_action.hpp @@ -35,14 +35,14 @@ class ProcessForceStopAction final : public IForceStopAction /// @param stop_token Token which can be used to interrupt the action. /// @param handle The resource to act upon. /// @return Whether the action was successful, or failed with an error. - Result forceStop(cpp::stop_token stop_token, const Handle handle) const override; + Result forceStop(cpp::stop_token stop_token, const Handle& handle) const override; private: /// @brief Visitor method for process handles. - Result visitForceStop(const ProcessHandle process) const; + Result visitForceStop(const ProcessHandle& process) const; /// @brief Visitor method for unsupported handles. - Result visitForceStop(const Handle handle) const; + Result visitForceStop(const Handle& handle) const; /// @brief The process launcher used to stop the process. osal::IProcess& launcher_; diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/file_ready_condition.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/file_ready_condition.cpp index 99c9944689..744433bb39 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/file_ready_condition.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/file_ready_condition.cpp @@ -28,7 +28,7 @@ FileReadyCondition::FileReadyCondition( { } -Result FileReadyCondition::wait(cpp::stop_token stop_token, [[maybe_unused]] const Handle) const +Result FileReadyCondition::wait(cpp::stop_token stop_token, [[maybe_unused]] const Handle&) const { const osal::OsalReturnType result = file_waiter_.waitForFile(file_path_, state_, timeout_, interval_, stop_token); diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/file_ready_condition.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/file_ready_condition.hpp index c53abafbec..89c8061623 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/file_ready_condition.hpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/file_ready_condition.hpp @@ -47,7 +47,7 @@ class FileReadyCondition final : public IReadyCondition /// @param stop_token Token which can be used to interrupt the wait. /// @param handle The resource to wait on. /// @return Whether the wait was successful, or failed with an error. - Result wait(cpp::stop_token stop_token, const Handle handle) const override; + Result wait(cpp::stop_token stop_token, const Handle& handle) const override; private: /// @brief The waiter used to check file state. diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/iready_condition.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/iready_condition.hpp index d68f857cba..43bf09fcae 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/iready_condition.hpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/iready_condition.hpp @@ -31,7 +31,7 @@ class IReadyCondition /// @param stop_token Token which can be used to interrupt the wait. /// @param handle The resource to wait on. /// @return Whether the wait was successful, or failed with an error. - virtual Result wait(cpp::stop_token stop_token, const Handle handle) const = 0; + virtual Result wait(cpp::stop_token stop_token, const Handle& handle) const = 0; /// @brief Notify the ready condition that a POSIX process has terminated. /// @param status Exit code of the process. diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/mock_ready_condition.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/mock_ready_condition.hpp index 93003c7072..231516d4ad 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/mock_ready_condition.hpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/mock_ready_condition.hpp @@ -23,7 +23,7 @@ namespace score::mw::lifecycle::internal class MockReadyCondition : public IReadyCondition { public: - MOCK_METHOD(Result, wait, (cpp::stop_token, const Handle handle), (override, const)); + MOCK_METHOD(Result, wait, (cpp::stop_token, const Handle& handle), (override, const)); }; } // namespace score::mw::lifecycle::internal diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/report_running_ready_condition.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/report_running_ready_condition.cpp index d9aae4354e..319e275caf 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/report_running_ready_condition.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/report_running_ready_condition.cpp @@ -25,7 +25,7 @@ ReportRunningReadyCondition::ReportRunningReadyCondition( { } -Result ReportRunningReadyCondition::wait(cpp::stop_token stop_token, const Handle handle) const +Result ReportRunningReadyCondition::wait(cpp::stop_token stop_token, const Handle& handle) const { return std::visit( [this](auto&& handle) { @@ -34,7 +34,7 @@ Result ReportRunningReadyCondition::wait(cpp::stop_token stop_token, const handle); } -Result ReportRunningReadyCondition::visitWait(const ProcessHandle process) const +Result ReportRunningReadyCondition::visitWait(const ProcessHandle& process) const { const osal::OsalReturnType result = launcher_.waitForkRunning(process.sync, timeout_); @@ -46,7 +46,7 @@ Result ReportRunningReadyCondition::visitWait(const ProcessHandle process) return MakeUnexpected(ExecErrc::kGeneralError); } -Result ReportRunningReadyCondition::visitWait(const Handle handle) const +Result ReportRunningReadyCondition::visitWait(const Handle& handle) const { SCORE_LANGUAGE_FUTURECPP_UNREACHABLE_MESSAGE( "ReportRunningReadyCondition should only be called with a ProcessHandle"); diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/report_running_ready_condition.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/report_running_ready_condition.hpp index 2b0062bc15..ccde39194a 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/report_running_ready_condition.hpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/report_running_ready_condition.hpp @@ -39,14 +39,14 @@ class ReportRunningReadyCondition final : public IReadyCondition /// @param stop_token Token which can be used to interrupt the wait. /// @param handle The resource to wait on. /// @return Whether the wait was successful, or failed with an error. - Result wait(cpp::stop_token stop_token, const Handle handle) const override; + Result wait(cpp::stop_token stop_token, const Handle& handle) const override; private: /// @brief Visitor method for process handles. - Result visitWait(const ProcessHandle process) const; + Result visitWait(const ProcessHandle& process) const; /// @brief Visitor method for unsupported handles. - Result visitWait(const Handle handle) const; + Result visitWait(const Handle& handle) const; /// @brief The process launcher used to wait for the process. osal::IProcess& launcher_; diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/empty_stop_action.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/empty_stop_action.cpp index 2a4c1b76ff..14cd03a53d 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/empty_stop_action.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/empty_stop_action.cpp @@ -16,7 +16,7 @@ namespace score::mw::lifecycle::internal { -Result EmptyStopAction::stop(cpp::stop_token stop_token, const Handle) const +Result EmptyStopAction::stop(cpp::stop_token stop_token, const Handle&) const { return IComponent::RequestState::kSuccess; } diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/empty_stop_action.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/empty_stop_action.hpp index cb517f34a9..38f3f1309d 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/empty_stop_action.hpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/empty_stop_action.hpp @@ -27,7 +27,7 @@ class EmptyStopAction final : public IStopAction /// @param stop_token Token which can be used to interrupt the action. /// @param handle The resource to act upon. /// @return Always successful. - Result stop(cpp::stop_token stop_token, const Handle) const override; + Result stop(cpp::stop_token stop_token, const Handle&) const override; }; } // namespace score::mw::lifecycle::internal diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/istop_action.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/istop_action.hpp index 5ff0743fa5..1e9e91bc73 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/istop_action.hpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/istop_action.hpp @@ -32,7 +32,7 @@ class IStopAction /// @param stop_token Token which can be used to interrupt the action. /// @param handle The resource to act upon. /// @return Whether the action was successful, or failed with an error. - virtual Result stop(cpp::stop_token stop_token, const Handle handle) const = 0; + virtual Result stop(cpp::stop_token stop_token, const Handle& handle) const = 0; }; } // namespace score::mw::lifecycle::internal diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/mock_stop_action.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/mock_stop_action.hpp index dc66d574c3..9a3488ac24 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/mock_stop_action.hpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/mock_stop_action.hpp @@ -23,7 +23,7 @@ namespace score::mw::lifecycle::internal class MockStopAction : public IStopAction { public: - MOCK_METHOD(Result, stop, (cpp::stop_token, const Handle handle), (override, const)); + MOCK_METHOD(Result, stop, (cpp::stop_token, const Handle& handle), (override, const)); }; } // namespace score::mw::lifecycle::internal diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/process_stop_action.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/process_stop_action.cpp index 5380451c98..2a60672581 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/process_stop_action.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/process_stop_action.cpp @@ -22,7 +22,7 @@ ProcessStopAction::ProcessStopAction(osal::IProcess& launcher) : launcher_(launc { } -Result ProcessStopAction::stop(cpp::stop_token stop_token, const Handle handle) const +Result ProcessStopAction::stop(cpp::stop_token stop_token, const Handle& handle) const { return std::visit( [this](auto&& handle) { @@ -31,7 +31,7 @@ Result ProcessStopAction::stop(cpp::stop_token stop_to handle); } -Result ProcessStopAction::visitStop(const ProcessHandle process) const +Result ProcessStopAction::visitStop(const ProcessHandle& process) const { if (process.pid == -1) { @@ -48,7 +48,7 @@ Result ProcessStopAction::visitStop(const ProcessHandl return MakeUnexpected(ExecErrc::kGeneralError); } -Result ProcessStopAction::visitStop(const Handle handle) const +Result ProcessStopAction::visitStop(const Handle& handle) const { SCORE_LANGUAGE_FUTURECPP_UNREACHABLE_MESSAGE("ProcessStopAction should only be called with a ProcessHandle"); } diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/process_stop_action.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/process_stop_action.hpp index f8b0dca1a1..c4b02c3e37 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/process_stop_action.hpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/process_stop_action.hpp @@ -35,14 +35,14 @@ class ProcessStopAction final : public IStopAction /// @param stop_token Token which can be used to interrupt the action. /// @param handle The resource to act upon. /// @return Whether the action was successful, or failed with an error. - Result stop(cpp::stop_token stop_token, const Handle handle) const override; + Result stop(cpp::stop_token stop_token, const Handle& handle) const override; private: /// @brief Visitor method for process handles. - Result visitStop(const ProcessHandle process) const; + Result visitStop(const ProcessHandle& process) const; /// @brief Visitor method for unsupported handles. - Result visitStop(const Handle handle) const; + Result visitStop(const Handle& handle) const; /// @brief The process launcher used to stop the process. osal::IProcess& launcher_; From 83b1314ef73b51978b8b6c5dad5b9d0c12672f24 Mon Sep 17 00:00:00 2001 From: Daniel Thwaites Date: Wed, 14 Oct 2026 12:17:29 +0100 Subject: [PATCH 27/36] Simplify actions by using `std::get_if` rather than `std::visit` --- .../process_force_stop_action.cpp | 20 +++++-------------- .../process_force_stop_action.hpp | 6 ------ .../process_force_stop_action_UT.cpp | 3 ++- .../report_running_ready_condition.cpp | 19 ++++-------------- .../report_running_ready_condition.hpp | 6 ------ .../report_running_ready_condition_UT.cpp | 3 ++- .../stop_action/process_stop_action.cpp | 20 +++++-------------- .../stop_action/process_stop_action.hpp | 6 ------ .../stop_action/process_stop_action_UT.cpp | 4 +++- 9 files changed, 21 insertions(+), 66 deletions(-) diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/process_force_stop_action.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/process_force_stop_action.cpp index 66b97aafce..a554899e7d 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/process_force_stop_action.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/process_force_stop_action.cpp @@ -25,21 +25,16 @@ ProcessForceStopAction::ProcessForceStopAction(osal::IProcess& launcher) : launc Result ProcessForceStopAction::forceStop(cpp::stop_token stop_token, const Handle& handle) const { - return std::visit( - [this](auto&& handle) { - return visitForceStop(handle); - }, - handle); -} + const ProcessHandle* const process = std::get_if(&handle); + SCORE_LANGUAGE_FUTURECPP_ASSERT_MESSAGE( + process != nullptr, "ProcessForceStopAction should only be called with a ProcessHandle"); -Result ProcessForceStopAction::visitForceStop(const ProcessHandle& process) const -{ - if (process.pid == -1) + if (process->pid == -1) { return IComponent::RequestState::kSuccess; } - const osal::OsalReturnType result = launcher_.forceTermination(process.pid); + const osal::OsalReturnType result = launcher_.forceTermination(process->pid); if (result == osal::OsalReturnType::kSuccess) { @@ -49,9 +44,4 @@ Result ProcessForceStopAction::visitForceStop(const Pr return MakeUnexpected(ExecErrc::kGeneralError); } -Result ProcessForceStopAction::visitForceStop(const Handle& handle) const -{ - SCORE_LANGUAGE_FUTURECPP_UNREACHABLE_MESSAGE("ProcessForceStopAction should only be called with a ProcessHandle"); -} - } // namespace score::mw::lifecycle::internal diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/process_force_stop_action.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/process_force_stop_action.hpp index 03d37670ca..d7a19f365e 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/process_force_stop_action.hpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/process_force_stop_action.hpp @@ -38,12 +38,6 @@ class ProcessForceStopAction final : public IForceStopAction Result forceStop(cpp::stop_token stop_token, const Handle& handle) const override; private: - /// @brief Visitor method for process handles. - Result visitForceStop(const ProcessHandle& process) const; - - /// @brief Visitor method for unsupported handles. - Result visitForceStop(const Handle& handle) const; - /// @brief The process launcher used to stop the process. osal::IProcess& launcher_; }; diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/process_force_stop_action_UT.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/process_force_stop_action_UT.cpp index 0ae0d9a09a..3de0ed5343 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/process_force_stop_action_UT.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/process_force_stop_action_UT.cpp @@ -91,7 +91,8 @@ TEST(ProcessForceStopActionTest, ForceStopWithEmptyHandleAborts) const ProcessForceStopAction process_force_stop_action(launcher); EXPECT_DEATH( - static_cast(process_force_stop_action.forceStop(cpp::stop_token{}, EmptyHandle{})), "Unreachable_Code"); + static_cast(process_force_stop_action.forceStop(cpp::stop_token{}, EmptyHandle{})), + "ProcessForceStopAction should only be called with a ProcessHandle"); } } // namespace diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/report_running_ready_condition.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/report_running_ready_condition.cpp index 319e275caf..cc842b9259 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/report_running_ready_condition.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/report_running_ready_condition.cpp @@ -27,16 +27,11 @@ ReportRunningReadyCondition::ReportRunningReadyCondition( Result ReportRunningReadyCondition::wait(cpp::stop_token stop_token, const Handle& handle) const { - return std::visit( - [this](auto&& handle) { - return visitWait(handle); - }, - handle); -} + const ProcessHandle* const process = std::get_if(&handle); + SCORE_LANGUAGE_FUTURECPP_ASSERT_MESSAGE( + process != nullptr, "ReportRunningReadyCondition should only be called with a ProcessHandle"); -Result ReportRunningReadyCondition::visitWait(const ProcessHandle& process) const -{ - const osal::OsalReturnType result = launcher_.waitForkRunning(process.sync, timeout_); + const osal::OsalReturnType result = launcher_.waitForkRunning(process->sync, timeout_); if (result == osal::OsalReturnType::kSuccess) { @@ -46,10 +41,4 @@ Result ReportRunningReadyCondition::visitWait(const ProcessHandle& process return MakeUnexpected(ExecErrc::kGeneralError); } -Result ReportRunningReadyCondition::visitWait(const Handle& handle) const -{ - SCORE_LANGUAGE_FUTURECPP_UNREACHABLE_MESSAGE( - "ReportRunningReadyCondition should only be called with a ProcessHandle"); -} - } // namespace score::mw::lifecycle::internal diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/report_running_ready_condition.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/report_running_ready_condition.hpp index ccde39194a..3b2adf718d 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/report_running_ready_condition.hpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/report_running_ready_condition.hpp @@ -42,12 +42,6 @@ class ReportRunningReadyCondition final : public IReadyCondition Result wait(cpp::stop_token stop_token, const Handle& handle) const override; private: - /// @brief Visitor method for process handles. - Result visitWait(const ProcessHandle& process) const; - - /// @brief Visitor method for unsupported handles. - Result visitWait(const Handle& handle) const; - /// @brief The process launcher used to wait for the process. osal::IProcess& launcher_; diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/report_running_ready_condition_UT.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/report_running_ready_condition_UT.cpp index eb0516d844..8e6fdcbab8 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/report_running_ready_condition_UT.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/report_running_ready_condition_UT.cpp @@ -69,7 +69,8 @@ TEST(ReportRunningReadyConditionTest, WaitWithEmptyHandleAborts) const ReportRunningReadyCondition report_running_ready_condition(launcher, std::nullopt); EXPECT_DEATH( - static_cast(report_running_ready_condition.wait(cpp::stop_token{}, EmptyHandle{})), "Unreachable_Code"); + static_cast(report_running_ready_condition.wait(cpp::stop_token{}, EmptyHandle{})), + "ReportRunningReadyCondition should only be called with a ProcessHandle"); } } // namespace diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/process_stop_action.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/process_stop_action.cpp index 2a60672581..680fd41250 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/process_stop_action.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/process_stop_action.cpp @@ -24,21 +24,16 @@ ProcessStopAction::ProcessStopAction(osal::IProcess& launcher) : launcher_(launc Result ProcessStopAction::stop(cpp::stop_token stop_token, const Handle& handle) const { - return std::visit( - [this](auto&& handle) { - return visitStop(handle); - }, - handle); -} + const ProcessHandle* const process = std::get_if(&handle); + SCORE_LANGUAGE_FUTURECPP_ASSERT_MESSAGE( + process != nullptr, "ProcessStopAction should only be called with a ProcessHandle"); -Result ProcessStopAction::visitStop(const ProcessHandle& process) const -{ - if (process.pid == -1) + if (process->pid == -1) { return IComponent::RequestState::kSuccess; } - const osal::OsalReturnType result = launcher_.requestTermination(process.pid); + const osal::OsalReturnType result = launcher_.requestTermination(process->pid); if (result == osal::OsalReturnType::kSuccess) { @@ -48,9 +43,4 @@ Result ProcessStopAction::visitStop(const ProcessHandl return MakeUnexpected(ExecErrc::kGeneralError); } -Result ProcessStopAction::visitStop(const Handle& handle) const -{ - SCORE_LANGUAGE_FUTURECPP_UNREACHABLE_MESSAGE("ProcessStopAction should only be called with a ProcessHandle"); -} - } // namespace score::mw::lifecycle::internal diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/process_stop_action.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/process_stop_action.hpp index c4b02c3e37..4e76ad72c1 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/process_stop_action.hpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/process_stop_action.hpp @@ -38,12 +38,6 @@ class ProcessStopAction final : public IStopAction Result stop(cpp::stop_token stop_token, const Handle& handle) const override; private: - /// @brief Visitor method for process handles. - Result visitStop(const ProcessHandle& process) const; - - /// @brief Visitor method for unsupported handles. - Result visitStop(const Handle& handle) const; - /// @brief The process launcher used to stop the process. osal::IProcess& launcher_; }; diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/process_stop_action_UT.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/process_stop_action_UT.cpp index 0f2b27ac9b..2a4bd66215 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/process_stop_action_UT.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/process_stop_action_UT.cpp @@ -90,7 +90,9 @@ TEST(ProcessStopActionTest, StopWithEmptyHandleAborts) NiceMock launcher; const ProcessStopAction process_stop_action(launcher); - EXPECT_DEATH(static_cast(process_stop_action.stop(cpp::stop_token{}, EmptyHandle{})), "Unreachable_Code"); + EXPECT_DEATH( + static_cast(process_stop_action.stop(cpp::stop_token{}, EmptyHandle{})), + "ProcessStopAction should only be called with a ProcessHandle"); } } // namespace From 9eff4b07565224be8899833973ae7ab9446b8958 Mon Sep 17 00:00:00 2001 From: Daniel Thwaites Date: Wed, 14 Oct 2026 13:30:27 +0100 Subject: [PATCH 28/36] Move process-specific code to an `invalidate` helper --- .../src/process_group_manager/details/component.cpp | 6 +----- .../src/process_group_manager/details/handle.hpp | 10 ++++++++++ 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/component.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/component.cpp index 005a0df4ba..0b00c16aeb 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/component.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/component.cpp @@ -141,11 +141,7 @@ IComponent::RequestResult Component::tryHandleTermination(int32_t status) Handle& handle = (starting != nullptr) ? starting->handle_ : ready->handle_; // Invalidate the resource to prevent acting on something which is already gone. - // TODO: Component should not have code for specific handle types - if (ProcessHandle* const process_handle = std::get_if(&handle)) - { - process_handle->pid = -1; - } + invalidate(handle); // Certain ready conditions can accept terminations. if (starting != nullptr) diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/handle.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/handle.hpp index b89f64ff0d..3236364635 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/handle.hpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/handle.hpp @@ -39,6 +39,16 @@ struct ProcessHandle /// @brief Represents some resource that we are managing. using Handle = std::variant; +/// @brief Changes a handle to indicate that the resource it pointed to is gone. +/// @param handle The handle to invalidate. +void invalidate(Handle& handle) +{ + if (ProcessHandle* const process_handle = std::get_if(&handle)) + { + process_handle->pid = -1; + } +} + } // namespace score::mw::lifecycle::internal #endif // SCORE_LCM_HANDLE_HPP_INCLUDED From 6cd4abd9f17302540b3cff1568382c9acc9d6895 Mon Sep 17 00:00:00 2001 From: Daniel Thwaites Date: Fri, 16 Oct 2026 12:53:17 +0100 Subject: [PATCH 29/36] Avoid creating duplicate success events Link: https://github.com/eclipse-score/lifecycle/pull/705#discussion_r4248281283 --- .../src/process_group_manager/details/component.cpp | 6 +++--- .../src/process_group_manager/details/component_UT.cpp | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/component.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/component.cpp index 0b00c16aeb..563ae55325 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/component.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/component.cpp @@ -131,7 +131,7 @@ IComponent::RequestResult Component::tryHandleTermination(int32_t status) if (std::holds_alternative(state_)) { state_ = TerminatedState{}; - return RequestState::kSuccess; + return RequestState::kWaiting; } StartingState* const starting = std::get_if(&state_); @@ -150,7 +150,7 @@ IComponent::RequestResult Component::tryHandleTermination(int32_t status) { if (ready_condition.tryHandleTermination(status)) { - return RequestState::kSuccess; + return RequestState::kWaiting; } } } @@ -158,7 +158,7 @@ IComponent::RequestResult Component::tryHandleTermination(int32_t status) // Self-terminating components can accept terminations. if (self_terminating_ && status == 0) { - return RequestState::kSuccess; + return RequestState::kWaiting; } // If none of the above cases apply, then termination is unexpected. diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/component_UT.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/component_UT.cpp index bfee47901d..06dfd88ac0 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/component_UT.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/component_UT.cpp @@ -288,7 +288,7 @@ TEST(ComponentTest, CannotDeactivateInFaultState) "Cannot deactivate component in a non-running state"); } -TEST(ComponentTest, SelfTerminatingExitZeroInReadyStateIsSuccess) +TEST(ComponentTest, SelfTerminatingExitZeroInReadyStateIsWaiting) { MockStartAction start_action; MockStopAction stop_action; @@ -302,7 +302,7 @@ TEST(ComponentTest, SelfTerminatingExitZeroInReadyStateIsSuccess) const auto result = component.tryHandleTermination(0); ASSERT_TRUE(result.has_value()); - EXPECT_EQ(result.value(), IComponent::RequestState::kSuccess); + EXPECT_EQ(result.value(), IComponent::RequestState::kWaiting); } TEST(ComponentTest, SelfTerminatingExitZeroInReadyStateIsActive) @@ -354,7 +354,7 @@ TEST(ComponentTest, SelfTerminatingExitNonZeroInReadyStateIsNotActive) EXPECT_FALSE(component.active()); } -TEST(ComponentTest, SelfTerminatingExitZeroInStartingStateIsSuccess) +TEST(ComponentTest, SelfTerminatingExitZeroInStartingStateIsWaiting) { MockStartAction start_action; MockStopAction stop_action; @@ -374,7 +374,7 @@ TEST(ComponentTest, SelfTerminatingExitZeroInStartingStateIsSuccess) static_cast(component.activate(cpp::stop_token{})); ASSERT_TRUE(result.has_value()); - EXPECT_EQ(result.value(), IComponent::RequestState::kSuccess); + EXPECT_EQ(result.value(), IComponent::RequestState::kWaiting); } TEST(ComponentTest, SelfTerminatingExitZeroInStartingStateIsActive) From 5563aa4e5b9c3aaa1dd4e08fc7dc6a13df4cb320 Mon Sep 17 00:00:00 2001 From: Daniel Thwaites Date: Fri, 16 Oct 2026 13:41:53 +0100 Subject: [PATCH 30/36] Unblock `ReportRunningReadyCondition` if the process crashes --- .../details/component.cpp | 2 +- .../ready_condition/iready_condition.hpp | 5 ++++- .../report_running_ready_condition.cpp | 18 +++++++++++++++++- .../report_running_ready_condition.hpp | 6 ++++++ 4 files changed, 28 insertions(+), 3 deletions(-) diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/component.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/component.cpp index 563ae55325..0cb5b984af 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/component.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/component.cpp @@ -148,7 +148,7 @@ IComponent::RequestResult Component::tryHandleTermination(int32_t status) { for (const IReadyCondition& ready_condition : ready_conditions_) { - if (ready_condition.tryHandleTermination(status)) + if (ready_condition.tryHandleTermination(handle, status)) { return RequestState::kWaiting; } diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/iready_condition.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/iready_condition.hpp index 43bf09fcae..f25877a557 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/iready_condition.hpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/iready_condition.hpp @@ -34,9 +34,12 @@ class IReadyCondition virtual Result wait(cpp::stop_token stop_token, const Handle& handle) const = 0; /// @brief Notify the ready condition that a POSIX process has terminated. + /// @param handle The process that terminated. /// @param status Exit code of the process. /// @return Whether this ready condition accepts the event. - bool tryHandleTermination(int32_t status) const + // FIXME: This should be a more generic event which makes sense for any + // handle type. + bool tryHandleTermination(const Handle& handle, int32_t status) const { return false; } diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/report_running_ready_condition.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/report_running_ready_condition.cpp index cc842b9259..e842b1be79 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/report_running_ready_condition.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/report_running_ready_condition.cpp @@ -33,7 +33,9 @@ Result ReportRunningReadyCondition::wait(cpp::stop_token stop_token, const const osal::OsalReturnType result = launcher_.waitForkRunning(process->sync, timeout_); - if (result == osal::OsalReturnType::kSuccess) + // If the PID is invalid, the process died and the notification was sent + // from tryHandleTermination rather than the process itself. + if (result == osal::OsalReturnType::kSuccess && process->pid > 0) { return {}; } @@ -41,4 +43,18 @@ Result ReportRunningReadyCondition::wait(cpp::stop_token stop_token, const return MakeUnexpected(ExecErrc::kGeneralError); } +bool ReportRunningReadyCondition::tryHandleTermination(const Handle& handle, int32_t status) const +{ + const ProcessHandle* const process = std::get_if(&handle); + SCORE_LANGUAGE_FUTURECPP_ASSERT_MESSAGE( + process != nullptr, "ReportRunningReadyCondition should only be called with a ProcessHandle"); + + // FIXME: This static_cast was copied from the old code at + // https://github.com/eclipse-score/lifecycle/blob/876c5df184ee0008017334303c12f1c2ee75e42e/score/launch_manager/src/daemon/src/process_group_manager/details/process_info_node.cpp#L171 + // Should errors be handled here? + static_cast(process->sync->send_sync_.post()); + + return true; +} + } // namespace score::mw::lifecycle::internal diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/report_running_ready_condition.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/report_running_ready_condition.hpp index 3b2adf718d..ca97fde593 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/report_running_ready_condition.hpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/report_running_ready_condition.hpp @@ -41,6 +41,12 @@ class ReportRunningReadyCondition final : public IReadyCondition /// @return Whether the wait was successful, or failed with an error. Result wait(cpp::stop_token stop_token, const Handle& handle) const override; + /// @brief Notify the ready condition that a POSIX process has terminated. + /// @param handle The process that terminated. + /// @param status Exit code of the process. + /// @return Whether this ready condition accepts the event. + bool tryHandleTermination(const Handle& handle, int32_t status) const; + private: /// @brief The process launcher used to wait for the process. osal::IProcess& launcher_; From 07ecedaae548cada0ef4271842e3735360993002 Mon Sep 17 00:00:00 2001 From: Daniel Thwaites Date: Fri, 16 Oct 2026 15:58:09 +0100 Subject: [PATCH 31/36] Fix duplicate definition of `invalidate` --- .../src/process_group_manager/details/BUILD | 1 + .../process_group_manager/details/handle.cpp | 27 +++++++++++++++++++ .../process_group_manager/details/handle.hpp | 8 +----- 3 files changed, 29 insertions(+), 7 deletions(-) create mode 100644 score/launch_manager/src/daemon/src/process_group_manager/details/handle.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 ce3a2fff42..ac11fef575 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 @@ -82,6 +82,7 @@ cc_library( cc_library( name = "handle", + srcs = ["handle.cpp"], hdrs = ["handle.hpp"], include_prefix = "score/mw/launch_manager/process_group_manager/details", strip_include_prefix = "/score/launch_manager/src/daemon/src/process_group_manager/details", diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/handle.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/handle.cpp new file mode 100644 index 0000000000..6428c86213 --- /dev/null +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/handle.cpp @@ -0,0 +1,27 @@ +/******************************************************************************** + * 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/handle.hpp" + +namespace score::mw::lifecycle::internal +{ + +void invalidate(Handle& handle) +{ + if (ProcessHandle* const process_handle = std::get_if(&handle)) + { + process_handle->pid = -1; + } +} + +} // namespace score::mw::lifecycle::internal diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/handle.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/handle.hpp index 3236364635..a6a1643cd1 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/handle.hpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/handle.hpp @@ -41,13 +41,7 @@ using Handle = std::variant; /// @brief Changes a handle to indicate that the resource it pointed to is gone. /// @param handle The handle to invalidate. -void invalidate(Handle& handle) -{ - if (ProcessHandle* const process_handle = std::get_if(&handle)) - { - process_handle->pid = -1; - } -} +void invalidate(Handle& handle); } // namespace score::mw::lifecycle::internal From df1d279b28324687eacfa0a025b003717f38b7ba Mon Sep 17 00:00:00 2001 From: Daniel Thwaites Date: Tue, 20 Oct 2026 10:33:09 +0100 Subject: [PATCH 32/36] Make `IReadyCondition::tryHandleTermination` virtual Link: https://github.com/eclipse-score/lifecycle/pull/705#discussion_r4312256649 --- .../details/ready_condition/iready_condition.hpp | 2 +- .../details/ready_condition/report_running_ready_condition.hpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/iready_condition.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/iready_condition.hpp index f25877a557..590befebfd 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/iready_condition.hpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/iready_condition.hpp @@ -39,7 +39,7 @@ class IReadyCondition /// @return Whether this ready condition accepts the event. // FIXME: This should be a more generic event which makes sense for any // handle type. - bool tryHandleTermination(const Handle& handle, int32_t status) const + virtual bool tryHandleTermination(const Handle& handle, int32_t status) const { return false; } diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/report_running_ready_condition.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/report_running_ready_condition.hpp index ca97fde593..996ac1ec12 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/report_running_ready_condition.hpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/report_running_ready_condition.hpp @@ -45,7 +45,7 @@ class ReportRunningReadyCondition final : public IReadyCondition /// @param handle The process that terminated. /// @param status Exit code of the process. /// @return Whether this ready condition accepts the event. - bool tryHandleTermination(const Handle& handle, int32_t status) const; + bool tryHandleTermination(const Handle& handle, int32_t status) const override; private: /// @brief The process launcher used to wait for the process. From fe7aa3254e51065ca6dda36d191c8021798f73fd Mon Sep 17 00:00:00 2001 From: Daniel Thwaites Date: Tue, 20 Oct 2026 10:50:10 +0100 Subject: [PATCH 33/36] Only accept one ready condition per component Link: https://github.com/eclipse-score/lifecycle/pull/705#discussion_r4312346036 --- .../details/component.cpp | 23 ++--- .../details/component.hpp | 10 +-- .../details/component_UT.cpp | 84 ++++++++++--------- 3 files changed, 53 insertions(+), 64 deletions(-) diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/component.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/component.cpp index 0cb5b984af..6078c1d043 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/component.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/component.cpp @@ -20,13 +20,13 @@ Component::Component( const IStartAction& start_action, const IStopAction& stop_action, const IForceStopAction& force_stop_action, - const cpp::span> ready_conditions, + const IReadyCondition& ready_condition, bool self_terminating, IdentifierHash identifier) : start_action_(start_action), stop_action_(stop_action), force_stop_action_(force_stop_action), - ready_conditions_(ready_conditions), + ready_condition_(ready_condition), self_terminating_(self_terminating), identifier_(identifier), state_(TerminatedState{}) @@ -55,15 +55,12 @@ IComponent::RequestResult Component::activate(cpp::stop_token stop_token) state_ = StartingState{handle, stop_activation}; - // Wait for ready conditions. The state is unlocked so that `deactivate` or + // Wait for the ready condition. The state is unlocked so that `deactivate` or // `tryHandleTermination` are callable while we are waiting. lock.unlock(); - for (const IReadyCondition& ready_condition : ready_conditions_) + if (!ready_condition_.wait(stop_activation.get_token(), handle).has_value()) { - if (!ready_condition.wait(stop_activation.get_token(), handle).has_value()) - { - return cpp::make_unexpected(ComponentError::kErrorBeforeReady); - } + return cpp::make_unexpected(ComponentError::kErrorBeforeReady); } lock.lock(); @@ -144,15 +141,9 @@ IComponent::RequestResult Component::tryHandleTermination(int32_t status) invalidate(handle); // Certain ready conditions can accept terminations. - if (starting != nullptr) + if (starting != nullptr && ready_condition_.tryHandleTermination(handle, status)) { - for (const IReadyCondition& ready_condition : ready_conditions_) - { - if (ready_condition.tryHandleTermination(handle, status)) - { - return RequestState::kWaiting; - } - } + return RequestState::kWaiting; } // Self-terminating components can accept terminations. diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/component.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/component.hpp index ccfbf9fb0a..9f503fa644 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/component.hpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/component.hpp @@ -19,11 +19,8 @@ #include "score/mw/launch_manager/process_group_manager/details/ready_condition/iready_condition.hpp" #include "score/mw/launch_manager/process_group_manager/details/start_action/istart_action.hpp" #include "score/mw/launch_manager/process_group_manager/details/stop_action/istop_action.hpp" -#include -#include #include #include -#include namespace score::mw::lifecycle::internal { @@ -36,15 +33,14 @@ class Component final : public IComponent /// @param start_action How to start the resource. /// @param stop_action How to gracefully stop the resource. /// @param force_stop_action How to forcefully stop the resource. - /// @param ready_conditions How to decide when the resource has finished its startup. + /// @param ready_condition How to decide when the resource has finished its startup. /// @param self_terminating Whether the resource is allowed to terminate on its own. /// @param identifier Name of the component. Component( const IStartAction& start_action, const IStopAction& stop_action, const IForceStopAction& force_stop_action, - cpp::span> ready_conditions = - cpp::span>{}, + const IReadyCondition& ready_condition, bool self_terminating = false, IdentifierHash identifier = IdentifierHash{}); @@ -85,7 +81,7 @@ class Component final : public IComponent const IForceStopAction& force_stop_action_; /// @brief How to decide when the resource has finished its startup. - const cpp::span> ready_conditions_; + const IReadyCondition& ready_condition_; /// @brief Whether the resource is allowed to terminate on its own. const bool self_terminating_; diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/component_UT.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/component_UT.cpp index 06dfd88ac0..994e04e0cb 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/component_UT.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/component_UT.cpp @@ -19,7 +19,6 @@ #include #include #include -#include #include namespace score::mw::lifecycle::internal @@ -57,7 +56,8 @@ TEST(ComponentTest, StartSucceeds) MockStartAction start_action; MockStopAction stop_action; MockForceStopAction force_stop_action; - Component component(start_action, stop_action, force_stop_action); + MockReadyCondition ready_condition; + Component component(start_action, stop_action, force_stop_action, ready_condition); const auto result = component.activate(cpp::stop_token{}); @@ -69,7 +69,8 @@ TEST(ComponentTest, StartActionCalled) MockStartAction start_action; MockStopAction stop_action; MockForceStopAction force_stop_action; - Component component(start_action, stop_action, force_stop_action); + MockReadyCondition ready_condition; + Component component(start_action, stop_action, force_stop_action, ready_condition); EXPECT_CALL(start_action, start(_)).WillOnce(Return(Result{mock_handle})); @@ -81,7 +82,8 @@ TEST(ComponentTest, StopActionCalled) MockStartAction start_action; MockStopAction stop_action; MockForceStopAction force_stop_action; - Component component(start_action, stop_action, force_stop_action); + MockReadyCondition ready_condition; + Component component(start_action, stop_action, force_stop_action, ready_condition); ON_CALL(start_action, start(_)).WillByDefault(Return(Result{mock_handle})); EXPECT_CALL(stop_action, stop(_, _)) @@ -96,7 +98,8 @@ TEST(ComponentTest, DeactivateReturnsWaitingWhenStopActionReturnsWaiting) MockStartAction start_action; MockStopAction stop_action; MockForceStopAction force_stop_action; - Component component(start_action, stop_action, force_stop_action); + MockReadyCondition ready_condition; + Component component(start_action, stop_action, force_stop_action, ready_condition); ON_CALL(start_action, start(_)).WillByDefault(Return(Result{mock_handle})); ON_CALL(stop_action, stop(_, _)).WillByDefault(Return(IComponent::RequestState::kWaiting)); @@ -113,7 +116,8 @@ TEST(ComponentTest, DeactivateReturnsSuccessWhenStopActionReturnsSuccess) MockStartAction start_action; MockStopAction stop_action; MockForceStopAction force_stop_action; - Component component(start_action, stop_action, force_stop_action); + MockReadyCondition ready_condition; + Component component(start_action, stop_action, force_stop_action, ready_condition); ON_CALL(start_action, start(_)).WillByDefault(Return(Result{mock_handle})); ON_CALL(stop_action, stop(_, _)).WillByDefault(Return(IComponent::RequestState::kSuccess)); @@ -125,21 +129,16 @@ TEST(ComponentTest, DeactivateReturnsSuccessWhenStopActionReturnsSuccess) ASSERT_EQ(result.value(), IComponent::RequestState::kSuccess); } -TEST(ComponentTest, ReadyConditionsCalled) +TEST(ComponentTest, ReadyConditionCalled) { MockStartAction start_action; MockStopAction stop_action; MockForceStopAction force_stop_action; - MockReadyCondition ready_condition_1; - MockReadyCondition ready_condition_2; - std::array, 2> ready_conditions{ready_condition_1, ready_condition_2}; - Component component(start_action, stop_action, force_stop_action, ready_conditions); + MockReadyCondition ready_condition; + Component component(start_action, stop_action, force_stop_action, ready_condition); - InSequence sequence; ON_CALL(start_action, start(_)).WillByDefault(Return(Result{mock_handle})); - EXPECT_CALL(ready_condition_1, wait(_, _)) - .WillOnce(DoAll(WithArg<1>(Invoke(expect_mock_handle)), Return(Result{}))); - EXPECT_CALL(ready_condition_2, wait(_, _)) + EXPECT_CALL(ready_condition, wait(_, _)) .WillOnce(DoAll(WithArg<1>(Invoke(expect_mock_handle)), Return(Result{}))); static_cast(component.activate(mock_stop_token)); @@ -151,8 +150,7 @@ TEST(ComponentTest, TerminationDuringStartupInterruptsActivation) MockStopAction stop_action; MockForceStopAction force_stop_action; MockReadyCondition ready_condition; - std::array, 1> ready_conditions{ready_condition}; - Component component(start_action, stop_action, force_stop_action, ready_conditions); + Component component(start_action, stop_action, force_stop_action, ready_condition); ON_CALL(start_action, start(_)).WillByDefault(Return(Result{mock_handle})); EXPECT_CALL(ready_condition, wait(_, _)).WillOnce(Invoke([&component](cpp::stop_token token, const Handle) { @@ -173,7 +171,8 @@ TEST(ComponentTest, StartSetsActive) MockStartAction start_action; MockStopAction stop_action; MockForceStopAction force_stop_action; - Component component(start_action, stop_action, force_stop_action); + MockReadyCondition ready_condition; + Component component(start_action, stop_action, force_stop_action, ready_condition); static_cast(component.activate(cpp::stop_token{})); @@ -185,7 +184,8 @@ TEST(ComponentTest, StopClearsActive) MockStartAction start_action; MockStopAction stop_action; MockForceStopAction force_stop_action; - Component component(start_action, stop_action, force_stop_action); + MockReadyCondition ready_condition; + Component component(start_action, stop_action, force_stop_action, ready_condition); static_cast(component.activate(cpp::stop_token{})); component.deactivate(cpp::stop_token{}); @@ -198,7 +198,8 @@ TEST(ComponentTest, CannotDeactivateInTerminatedState) MockStartAction start_action; MockStopAction stop_action; MockForceStopAction force_stop_action; - Component component(start_action, stop_action, force_stop_action); + MockReadyCondition ready_condition; + Component component(start_action, stop_action, force_stop_action, ready_condition); EXPECT_DEATH( { static_cast(component.deactivate(cpp::stop_token{})); }, @@ -210,7 +211,8 @@ TEST(ComponentTest, CannotActivateInReadyState) MockStartAction start_action; MockStopAction stop_action; MockForceStopAction force_stop_action; - Component component(start_action, stop_action, force_stop_action); + MockReadyCondition ready_condition; + Component component(start_action, stop_action, force_stop_action, ready_condition); ON_CALL(start_action, start(_)).WillByDefault(Return(Result{mock_handle})); @@ -225,7 +227,8 @@ TEST(ComponentTest, CannotActivateInTerminatingState) MockStartAction start_action; MockStopAction stop_action; MockForceStopAction force_stop_action; - Component component(start_action, stop_action, force_stop_action); + MockReadyCondition ready_condition; + Component component(start_action, stop_action, force_stop_action, ready_condition); ON_CALL(start_action, start(_)).WillByDefault(Return(Result{mock_handle})); ON_CALL(stop_action, stop(_, _)).WillByDefault(Return(IComponent::RequestState::kWaiting)); @@ -242,7 +245,8 @@ TEST(ComponentTest, CannotDeactivateInTerminatingState) MockStartAction start_action; MockStopAction stop_action; MockForceStopAction force_stop_action; - Component component(start_action, stop_action, force_stop_action); + MockReadyCondition ready_condition; + Component component(start_action, stop_action, force_stop_action, ready_condition); ON_CALL(start_action, start(_)).WillByDefault(Return(Result{mock_handle})); ON_CALL(stop_action, stop(_, _)).WillByDefault(Return(IComponent::RequestState::kWaiting)); @@ -260,7 +264,8 @@ TEST(ComponentTest, CannotActivateInFaultState) MockStartAction start_action; MockStopAction stop_action; MockForceStopAction force_stop_action; - Component component(start_action, stop_action, force_stop_action); + MockReadyCondition ready_condition; + Component component(start_action, stop_action, force_stop_action, ready_condition); ON_CALL(start_action, start(_)).WillByDefault(Return(Result{mock_handle})); @@ -276,7 +281,8 @@ TEST(ComponentTest, CannotDeactivateInFaultState) MockStartAction start_action; MockStopAction stop_action; MockForceStopAction force_stop_action; - Component component(start_action, stop_action, force_stop_action); + MockReadyCondition ready_condition; + Component component(start_action, stop_action, force_stop_action, ready_condition); ON_CALL(start_action, start(_)).WillByDefault(Return(Result{mock_handle})); @@ -293,8 +299,8 @@ TEST(ComponentTest, SelfTerminatingExitZeroInReadyStateIsWaiting) MockStartAction start_action; MockStopAction stop_action; MockForceStopAction force_stop_action; - cpp::span> ready_conditions; - Component component(start_action, stop_action, force_stop_action, ready_conditions, true); + MockReadyCondition ready_condition; + Component component(start_action, stop_action, force_stop_action, ready_condition, true); ON_CALL(start_action, start(_)).WillByDefault(Return(Result{mock_handle})); @@ -310,8 +316,8 @@ TEST(ComponentTest, SelfTerminatingExitZeroInReadyStateIsActive) MockStartAction start_action; MockStopAction stop_action; MockForceStopAction force_stop_action; - cpp::span> ready_conditions; - Component component(start_action, stop_action, force_stop_action, ready_conditions, true); + MockReadyCondition ready_condition; + Component component(start_action, stop_action, force_stop_action, ready_condition, true); ON_CALL(start_action, start(_)).WillByDefault(Return(Result{mock_handle})); @@ -326,8 +332,8 @@ TEST(ComponentTest, SelfTerminatingExitNonZeroInReadyStateIsError) MockStartAction start_action; MockStopAction stop_action; MockForceStopAction force_stop_action; - cpp::span> ready_conditions; - Component component(start_action, stop_action, force_stop_action, ready_conditions, true); + MockReadyCondition ready_condition; + Component component(start_action, stop_action, force_stop_action, ready_condition, true); ON_CALL(start_action, start(_)).WillByDefault(Return(Result{mock_handle})); @@ -343,8 +349,8 @@ TEST(ComponentTest, SelfTerminatingExitNonZeroInReadyStateIsNotActive) MockStartAction start_action; MockStopAction stop_action; MockForceStopAction force_stop_action; - cpp::span> ready_conditions; - Component component(start_action, stop_action, force_stop_action, ready_conditions, true); + MockReadyCondition ready_condition; + Component component(start_action, stop_action, force_stop_action, ready_condition, true); ON_CALL(start_action, start(_)).WillByDefault(Return(Result{mock_handle})); @@ -360,8 +366,7 @@ TEST(ComponentTest, SelfTerminatingExitZeroInStartingStateIsWaiting) MockStopAction stop_action; MockForceStopAction force_stop_action; MockReadyCondition ready_condition; - std::array, 1> ready_conditions{ready_condition}; - Component component(start_action, stop_action, force_stop_action, ready_conditions, true); + Component component(start_action, stop_action, force_stop_action, ready_condition, true); ON_CALL(start_action, start(_)).WillByDefault(Return(Result{mock_handle})); @@ -383,8 +388,7 @@ TEST(ComponentTest, SelfTerminatingExitZeroInStartingStateIsActive) MockStopAction stop_action; MockForceStopAction force_stop_action; MockReadyCondition ready_condition; - std::array, 1> ready_conditions{ready_condition}; - Component component(start_action, stop_action, force_stop_action, ready_conditions, true); + Component component(start_action, stop_action, force_stop_action, ready_condition, true); ON_CALL(start_action, start(_)).WillByDefault(Return(Result{mock_handle})); EXPECT_CALL(ready_condition, wait(_, _)).WillOnce(Invoke([&](cpp::stop_token, const Handle) { @@ -403,8 +407,7 @@ TEST(ComponentTest, SelfTerminatingExitNonZeroInStartingStateIsError) MockStopAction stop_action; MockForceStopAction force_stop_action; MockReadyCondition ready_condition; - std::array, 1> ready_conditions{ready_condition}; - Component component(start_action, stop_action, force_stop_action, ready_conditions, true); + Component component(start_action, stop_action, force_stop_action, ready_condition, true); ON_CALL(start_action, start(_)).WillByDefault(Return(Result{mock_handle})); @@ -426,8 +429,7 @@ TEST(ComponentTest, SelfTerminatingExitNonZeroInStartingStateIsNotActive) MockStopAction stop_action; MockForceStopAction force_stop_action; MockReadyCondition ready_condition; - std::array, 1> ready_conditions{ready_condition}; - Component component(start_action, stop_action, force_stop_action, ready_conditions, true); + Component component(start_action, stop_action, force_stop_action, ready_condition, true); ON_CALL(start_action, start(_)).WillByDefault(Return(Result{mock_handle})); EXPECT_CALL(ready_condition, wait(_, _)).WillOnce(Invoke([&](cpp::stop_token, const Handle) { From c8564a965e9515fb0b06f15bd6e6c381f7106599 Mon Sep 17 00:00:00 2001 From: Daniel Thwaites Date: Tue, 20 Oct 2026 13:58:49 +0100 Subject: [PATCH 34/36] Assert that `sync` is not null --- .../report_running_ready_condition.cpp | 6 +++ .../report_running_ready_condition_UT.cpp | 38 ++++++++++++++++++- 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/report_running_ready_condition.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/report_running_ready_condition.cpp index e842b1be79..fe3eb18afd 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/report_running_ready_condition.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/report_running_ready_condition.cpp @@ -30,6 +30,9 @@ Result ReportRunningReadyCondition::wait(cpp::stop_token stop_token, const const ProcessHandle* const process = std::get_if(&handle); SCORE_LANGUAGE_FUTURECPP_ASSERT_MESSAGE( process != nullptr, "ReportRunningReadyCondition should only be called with a ProcessHandle"); + SCORE_LANGUAGE_FUTURECPP_ASSERT_MESSAGE( + process->sync != nullptr, + "ReportRunningReadyCondition should only be called with a ProcessHandle containing IPCCommsSync"); const osal::OsalReturnType result = launcher_.waitForkRunning(process->sync, timeout_); @@ -48,6 +51,9 @@ bool ReportRunningReadyCondition::tryHandleTermination(const Handle& handle, int const ProcessHandle* const process = std::get_if(&handle); SCORE_LANGUAGE_FUTURECPP_ASSERT_MESSAGE( process != nullptr, "ReportRunningReadyCondition should only be called with a ProcessHandle"); + SCORE_LANGUAGE_FUTURECPP_ASSERT_MESSAGE( + process->sync != nullptr, + "ReportRunningReadyCondition should only be called with a ProcessHandle containing IPCCommsSync"); // FIXME: This static_cast was copied from the old code at // https://github.com/eclipse-score/lifecycle/blob/876c5df184ee0008017334303c12f1c2ee75e42e/score/launch_manager/src/daemon/src/process_group_manager/details/process_info_node.cpp#L171 diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/report_running_ready_condition_UT.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/report_running_ready_condition_UT.cpp index 8e6fdcbab8..5f72fd7a3a 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/report_running_ready_condition_UT.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/report_running_ready_condition_UT.cpp @@ -25,7 +25,13 @@ namespace using namespace ::testing; using namespace std::chrono_literals; -const ProcessHandle mock_handle = ProcessHandle{42}; +alignas(osal::IpcCommsSync) std::array sync_buffer; + +// The semaphores are never touched, since the launcher is mocked. +const ProcessHandle mock_handle = ProcessHandle{ + 42, + osal::IpcCommsP{reinterpret_cast(sync_buffer.data()), [](osal::IpcCommsSync*) { + }}}; TEST(ReportRunningReadyConditionTest, WaitCallsWaitForkRunningWithSyncAndTimeout) { @@ -73,5 +79,35 @@ TEST(ReportRunningReadyConditionTest, WaitWithEmptyHandleAborts) "ReportRunningReadyCondition should only be called with a ProcessHandle"); } +TEST(ReportRunningReadyConditionTest, WaitWithNullSyncAborts) +{ + NiceMock launcher; + const ReportRunningReadyCondition report_running_ready_condition(launcher, std::nullopt); + + EXPECT_DEATH( + static_cast(report_running_ready_condition.wait(cpp::stop_token{}, ProcessHandle{42, nullptr})), + "ReportRunningReadyCondition should only be called with a ProcessHandle containing IPCCommsSync"); +} + +TEST(ReportRunningReadyConditionTest, TryHandleTerminationWithEmptyHandleAborts) +{ + NiceMock launcher; + const ReportRunningReadyCondition report_running_ready_condition(launcher, std::nullopt); + + EXPECT_DEATH( + static_cast(report_running_ready_condition.tryHandleTermination(EmptyHandle{}, 0)), + "ReportRunningReadyCondition should only be called with a ProcessHandle"); +} + +TEST(ReportRunningReadyConditionTest, TryHandleTerminationWithNullSyncAborts) +{ + NiceMock launcher; + const ReportRunningReadyCondition report_running_ready_condition(launcher, std::nullopt); + + EXPECT_DEATH( + static_cast(report_running_ready_condition.tryHandleTermination(ProcessHandle{42, nullptr}, 0)), + "ReportRunningReadyCondition should only be called with a ProcessHandle containing IPCCommsSync"); +} + } // namespace } // namespace score::mw::lifecycle::internal From f6b4540a557855791967d17d461d8f30fcf160ff Mon Sep 17 00:00:00 2001 From: Daniel Thwaites Date: Tue, 20 Oct 2026 15:53:36 +0100 Subject: [PATCH 35/36] Fix crash when activation is cancelled from both sources Link: https://github.com/eclipse-score/lifecycle/pull/705#discussion_r4312406021 --- .../details/component.cpp | 4 +-- .../details/component_UT.cpp | 25 +++++++++++++++++++ 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/component.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/component.cpp index 6078c1d043..6572edce0c 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/component.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/component.cpp @@ -49,8 +49,8 @@ IComponent::RequestResult Component::activate(cpp::stop_token stop_token) cpp::stop_source stop_activation{}; const cpp::stop_callback forward_stop{stop_token, [&stop_activation] { - const bool stopped = stop_activation.request_stop(); - SCORE_LANGUAGE_FUTURECPP_ASSERT(stopped); + // This can fail if stop is requested from both places. + static_cast(stop_activation.request_stop()); }}; state_ = StartingState{handle, stop_activation}; diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/component_UT.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/component_UT.cpp index 994e04e0cb..bc43a0c760 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/component_UT.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/component_UT.cpp @@ -442,5 +442,30 @@ TEST(ComponentTest, SelfTerminatingExitNonZeroInStartingStateIsNotActive) EXPECT_FALSE(component.active()); } +TEST(ComponentTest, SettingBothStopTokens) +{ + MockStartAction start_action; + MockStopAction stop_action; + MockForceStopAction force_stop_action; + MockReadyCondition ready_condition; + Component component(start_action, stop_action, force_stop_action, ready_condition); + cpp::stop_source stop_source{}; + + ON_CALL(start_action, start(_)).WillByDefault(Return(Result{mock_handle})); + EXPECT_CALL(ready_condition, wait(_, _)).WillOnce(Invoke([&](cpp::stop_token token, const Handle) { + static_cast(terminateFromOtherThread(component, 1)); + EXPECT_TRUE(token.stop_requested()); + + static_cast(stop_source.request_stop()); + EXPECT_TRUE(token.stop_requested()); + + return Result{}; + })); + + const auto result = component.activate(stop_source.get_token()); + + EXPECT_FALSE(result.has_value()); +} + } // namespace } // namespace score::mw::lifecycle::internal From cd30debaf0141255413c9465afb70cde89e9fe8d Mon Sep 17 00:00:00 2001 From: Daniel Thwaites Date: Tue, 20 Oct 2026 16:32:15 +0100 Subject: [PATCH 36/36] Enter fault state if a start/stop action fails --- .../src/daemon/src/process_group_manager/details/component.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/component.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/component.cpp index 6572edce0c..6c692fcddc 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/component.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/component.cpp @@ -43,6 +43,7 @@ IComponent::RequestResult Component::activate(cpp::stop_token stop_token) const Result start_result = start_action_.start(stop_token); if (!start_result.has_value()) { + state_ = FaultState{}; return cpp::make_unexpected(ComponentError::kErrorBeforeReady); } const Handle& handle = start_result.value(); @@ -100,6 +101,7 @@ IComponent::RequestResult Component::deactivate(cpp::stop_token stop_token) const Result result = stop_action_.stop(stop_token, handle); if (!result.has_value()) { + state_ = FaultState{handle}; return cpp::make_unexpected(ComponentError::kErrorAfterReady); }