Repository navigation
Introduce new Component class
#705
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
3d8c515
125fa2e
6964d4a
e5cb0c0
963c385
5a78414
300256f
42668b5
61e9dbb
9bbe70b
3e34721
9150542
4601d01
0bf8cd6
df2e12c
307bd2f
e8fe4eb
74dbc00
1f65477
2ed0e8d
649598b
c10319d
cda7747
470ff82
b001665
676a9a7
83b1314
9eff4b0
6cd4abd
5563aa4
07eceda
df1d279
fe7aa32
c8564a9
f6b4540
cd30deb
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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(); | ||
|
|
||
| // 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"); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
||
| } | ||
|
|
||
| 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; | ||
| } | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
Uh oh!
There was an error while loading. Please reload this page.