Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
3d8c515
Implement new component class
danth Sep 24, 2026
125fa2e
Move start action, stop action, ready condition to subfolders
danth Sep 25, 2026
6964d4a
Add force stop actions
danth Sep 25, 2026
e5cb0c0
Add empty actions
danth Sep 28, 2026
963c385
Allow actions to return an error code
danth Sep 29, 2026
5a78414
Add process actions
danth Sep 30, 2026
300256f
Add unit tests for new actions
danth Sep 30, 2026
42668b5
Add report running ready condition
danth Sep 30, 2026
61e9dbb
Pass through `stop_token` from the component to the actions
danth Oct 1, 2026
9bbe70b
Use references rather than pointers in `Component`
danth Oct 1, 2026
3e34721
Use `cpp::span` rather than `std::vector` for ready conditions
danth Oct 1, 2026
9150542
Add docstrings for new classes
danth Oct 2, 2026
4601d01
Add file ready condition
danth Oct 2, 2026
0bf8cd6
Rename `force_stop` to `forceStop`
danth Oct 2, 2026
df2e12c
Replace `kNotImplemented` returns with panics
danth Oct 2, 2026
307bd2f
Add component state machine
danth Oct 5, 2026
e8fe4eb
Make component identifier `const`
danth Oct 5, 2026
74dbc00
Implement `tryHandleTermination` on `Component`
danth Oct 7, 2026
1f65477
Interrupt ready conditions when the process terminates
danth Oct 8, 2026
2ed0e8d
Encapsulate visitor methods within actions
danth Oct 8, 2026
649598b
Improve handling of self-terminating components
danth Oct 8, 2026
c10319d
Invalidate process handle when the process terminates
danth Oct 9, 2026
cda7747
Allow stop actions to return either `kWaiting` or `kSuccess`
danth Oct 9, 2026
470ff82
Remove incorrect force-stop test
danth Oct 12, 2026
b001665
Simplify component methods
danth Oct 14, 2026
676a9a7
Take `Handle&` rather than `Handle`
danth Oct 14, 2026
83b1314
Simplify actions by using `std::get_if` rather than `std::visit`
danth Oct 14, 2026
9eff4b0
Move process-specific code to an `invalidate` helper
danth Oct 14, 2026
6cd4abd
Avoid creating duplicate success events
danth Oct 16, 2026
5563aa4
Unblock `ReportRunningReadyCondition` if the process crashes
danth Oct 16, 2026
07eceda
Fix duplicate definition of `invalidate`
danth Oct 16, 2026
df1d279
Make `IReadyCondition::tryHandleTermination` virtual
danth Oct 20, 2026
fe7aa32
Only accept one ready condition per component
danth Oct 20, 2026
c8564a9
Assert that `sync` is not null
danth Oct 20, 2026
f6b4540
Fix crash when activation is cancelled from both sources
danth Oct 20, 2026
cd30deb
Enter fault state if a start/stop action fails
danth Oct 20, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,37 @@
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",
"//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",
"@score_baselibs//score/language/futurecpp",
],
)

lm_cc_test(
name = "component_UT",
srcs = ["component_UT.cpp"],
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",
"@googletest//:gtest_main",
],
)

cc_library(
name = "component_task",
hdrs = ["component_task.hpp"],
Expand Down Expand Up @@ -49,6 +80,16 @@ 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",
visibility = ["//score/launch_manager/src/daemon/src/process_group_manager:__subpackages__"],
deps = ["//score/launch_manager/src/daemon/src/osal:ipc_comms"],
)

cc_library(
name = "mock_component",
testonly = True,
Expand Down Expand Up @@ -393,7 +434,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",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
/********************************************************************************
* 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 IForceStopAction& force_stop_action,
const IReadyCondition& ready_condition,
bool self_terminating,
IdentifierHash identifier)
: start_action_(start_action),
stop_action_(stop_action),
force_stop_action_(force_stop_action),
ready_condition_(ready_condition),
self_terminating_(self_terminating),
identifier_(identifier),
state_(TerminatedState{})
{
}

IComponent::RequestResult Component::activate(cpp::stop_token stop_token)
{
std::unique_lock<std::mutex> lock{state_mutex_};

SCORE_LANGUAGE_FUTURECPP_ASSERT_MESSAGE(
std::holds_alternative<TerminatedState>(state_), "Cannot activate component in a running state");

const Result<Handle> 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();

cpp::stop_source stop_activation{};
const cpp::stop_callback forward_stop{stop_token, [&stop_activation] {
// This can fail if stop is requested from both places.
static_cast<void>(stop_activation.request_stop());
}};

state_ = StartingState{handle, stop_activation};

// Wait for the ready condition. The state is unlocked so that `deactivate` or
// `tryHandleTermination` are callable while we are waiting.
lock.unlock();
if (!ready_condition_.wait(stop_activation.get_token(), handle).has_value())
{
return cpp::make_unexpected(ComponentError::kErrorBeforeReady);
}
lock.lock();

Comment thread
danth marked this conversation as resolved.
// If `deactivate` or `tryHandleTermination` was called during the wait then
// activation fails.
if (stop_activation.stop_requested())
{
return cpp::make_unexpected(ComponentError::kErrorBeforeReady);
}

// The state could change while the lock was released, so we have to check.
if (const StartingState* const starting = std::get_if<StartingState>(&state_))
{
state_ = ReadyState{starting->handle_};
return RequestState::kSuccess;
}

SCORE_LANGUAGE_FUTURECPP_UNREACHABLE_MESSAGE("If stop was not requested, then state must still be StartingState");

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this actually reachable for self-terminating processes where no stop was requested in tryHandleTermination?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When no stop is requested, the state is not changed. So, if the activate method is still running, then the state must still be starting. If the process terminates after it enters the ready state, activate has already returned so the assertion is not reached

}

IComponent::RequestResult Component::deactivate(cpp::stop_token stop_token)
{
const std::lock_guard<std::mutex> lock{state_mutex_};

StartingState* const starting = std::get_if<StartingState>(&state_);
const ReadyState* const ready = std::get_if<ReadyState>(&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_;

// If the component is still waiting for ready conditions, cancel that.
if (starting != nullptr)
{
static_cast<void>(starting->stop_activation_.request_stop());
}

const Result<RequestResult> result = stop_action_.stop(stop_token, handle);
if (!result.has_value())
{
state_ = FaultState{handle};
return cpp::make_unexpected(ComponentError::kErrorAfterReady);
}

switch (*result.value())
{
// Wait for `tryHandleTermination` to tell us the termination is done.
case RequestState::kWaiting:
state_ = TerminatingState{handle};
return RequestState::kWaiting;

// Already terminated, jump straight to the final state.
case RequestState::kSuccess:
state_ = TerminatedState{};
return RequestState::kSuccess;
}

SCORE_LANGUAGE_FUTURECPP_UNREACHABLE_MESSAGE("Every possible RequestState should be handled");
}

IComponent::RequestResult Component::tryHandleTermination(int32_t status)
{
const std::lock_guard<std::mutex> lock{state_mutex_};

// If we are waiting for termination, mark it as done. We do not care
// about the exit code.
if (std::holds_alternative<TerminatingState>(state_))
{
state_ = TerminatedState{};
return RequestState::kWaiting;
}

StartingState* const starting = std::get_if<StartingState>(&state_);
ReadyState* const ready = std::get_if<ReadyState>(&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.
invalidate(handle);

// Certain ready conditions can accept terminations.
if (starting != nullptr && ready_condition_.tryHandleTermination(handle, status))
{
return RequestState::kWaiting;
}

// Self-terminating components can accept terminations.
if (self_terminating_ && status == 0)
{
return RequestState::kWaiting;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How do we move from Starting->Ready for a self-terminating process that has ready condition ProcessState:Terminated?
Is there a ReadyCondition currently missing for this case?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, the ready condition is missing because we were waiting for #738. As a separate pull request we can adapt that code to implement the ready condition interface


// If none of the above cases apply, then termination is unexpected.
if (starting != nullptr)
{
// Interrupt the worker thread waiting in activate().
static_cast<void>(starting->stop_activation_.request_stop());
}

state_ = FaultState{};
return cpp::make_unexpected(ComponentError::kErrorAfterReady);
}

IdentifierHash Component::getIdentifier() const
{
return identifier_;
}

bool Component::active() const
{
const std::lock_guard<std::mutex> lock{state_mutex_};
return std::holds_alternative<ReadyState>(state_);
}

} // namespace score::mw::lifecycle::internal
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
/********************************************************************************
* Copyright (c) 2026 Contributors to the Eclipse Foundation
*
* See the NOTICE file(s) distributed with this work for additional
* information regarding copyright ownership.
*
* This program and the accompanying materials are made available under the
* terms of the Apache License Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0
*
* SPDX-License-Identifier: Apache-2.0
********************************************************************************/

#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"
#include "score/mw/launch_manager/process_group_manager/details/stop_action/istop_action.hpp"
#include <mutex>
#include <variant>

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_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,
const IReadyCondition& ready_condition,
bool self_terminating = false,
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 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 IReadyCondition& ready_condition_;

/// @brief Whether the resource is allowed to terminate on its own.
const bool self_terminating_;

/// @brief Name of the component.
const IdentifierHash identifier_;

struct TerminatedState final
{
};

struct StartingState final
{
Handle handle_;
cpp::stop_source stop_activation_;
};

struct ReadyState final
{
Handle handle_;
};

struct TerminatingState final
{
Handle handle_;
};

struct FaultState final
{
Handle handle_;
};

using State = std::variant<TerminatedState, StartingState, ReadyState, TerminatingState, FaultState>;

/// @brief Protects the state from concurrent modifications.
mutable std::mutex state_mutex_;

/// @brief The current state of the component.
State state_;
};

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

#endif // SCORE_LCM_COMPONENT_HPP_INCLUDED
Loading
Loading