Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ IComponent::RequestResult Component::activate(cpp::stop_token stop_token)
if (!start_result.has_value())
{
state_ = FaultState{};
return cpp::make_unexpected(ComponentError::kErrorBeforeReady);
return RequestResult::kFailure;
}
const Handle& handle = start_result.value();

Expand All @@ -61,22 +61,22 @@ IComponent::RequestResult Component::activate(cpp::stop_token stop_token)
lock.unlock();
if (!ready_condition_.wait(stop_activation.get_token(), handle).has_value())
{
return cpp::make_unexpected(ComponentError::kErrorBeforeReady);
return RequestResult::kFailure;
}
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);
return RequestResult::kFailure;
}

// 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;
return RequestResult::kSuccess;
}

SCORE_LANGUAGE_FUTURECPP_UNREACHABLE_MESSAGE("If stop was not requested, then state must still be StartingState");
Expand All @@ -99,29 +99,18 @@ IComponent::RequestResult Component::deactivate(cpp::stop_token stop_token)
}

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

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");
// `tryHandleTermination` tells us when the termination is done.
state_ = TerminatingState{handle};
return RequestResult::kSuccess;
}

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

Expand All @@ -130,7 +119,7 @@ IComponent::RequestResult Component::tryHandleTermination(int32_t status)
if (std::holds_alternative<TerminatingState>(state_))
{
state_ = TerminatedState{};
return RequestState::kWaiting;
return TerminationResult::kIgnore;
}

StartingState* const starting = std::get_if<StartingState>(&state_);
Expand All @@ -145,13 +134,13 @@ IComponent::RequestResult Component::tryHandleTermination(int32_t status)
// Certain ready conditions can accept terminations.
if (starting != nullptr && ready_condition_.tryHandleTermination(handle, status))
{
return RequestState::kWaiting;
return TerminationResult::kIgnore;
}

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

// If none of the above cases apply, then termination is unexpected.
Expand All @@ -162,7 +151,7 @@ IComponent::RequestResult Component::tryHandleTermination(int32_t status)
}

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

IdentifierHash Component::getIdentifier() const
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,21 +46,18 @@ class Component final : public IComponent

/// @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.
/// @return Whether the component activation succeeded or failed.
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.
/// @return Whether the component deactivation succeeded or failed.
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;
/// @return Whether the termination is ignored or means the component failed.
TerminationResult tryHandleTermination(int32_t status) override;

/// @brief Return the name of the component.
/// @return The name of the component.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,12 @@ void expect_mock_handle(const Handle handle)
EXPECT_EQ(std::get<ProcessHandle>(handle).pid, mock_handle.pid);
}

IComponent::RequestResult terminateFromOtherThread(Component& component, const int32_t status)
IComponent::TerminationResult 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;
IComponent::TerminationResult result;
std::thread termination_thread([&component, &result, status]() {
result = component.tryHandleTermination(status);
});
Expand All @@ -61,7 +61,7 @@ TEST(ComponentTest, StartSucceeds)

const auto result = component.activate(cpp::stop_token{});

EXPECT_TRUE(result.has_value());
EXPECT_EQ(result, IComponent::RequestResult::kSuccess);
}

TEST(ComponentTest, StartActionCalled)
Expand All @@ -87,13 +87,13 @@ TEST(ComponentTest, StopActionCalled)

ON_CALL(start_action, start(_)).WillByDefault(Return(Result<Handle>{mock_handle}));
EXPECT_CALL(stop_action, stop(_, _))
.WillOnce(DoAll(WithArg<1>(Invoke(expect_mock_handle)), Return(IComponent::RequestState::kSuccess)));
.WillOnce(DoAll(WithArg<1>(Invoke(expect_mock_handle)), Return(IComponent::RequestResult::kSuccess)));

static_cast<void>(component.activate(cpp::stop_token{}));
component.deactivate(mock_stop_token);
static_cast<void>(component.deactivate(mock_stop_token));
}

TEST(ComponentTest, DeactivateReturnsWaitingWhenStopActionReturnsWaiting)
TEST(ComponentTest, DeactivateReturnsSuccessWhenStopActionReturnsSuccess)
{
MockStartAction start_action;
MockStopAction stop_action;
Expand All @@ -102,16 +102,15 @@ TEST(ComponentTest, DeactivateReturnsWaitingWhenStopActionReturnsWaiting)
Component component(start_action, stop_action, force_stop_action, ready_condition);

ON_CALL(start_action, start(_)).WillByDefault(Return(Result<Handle>{mock_handle}));
ON_CALL(stop_action, stop(_, _)).WillByDefault(Return(IComponent::RequestState::kWaiting));
ON_CALL(stop_action, stop(_, _)).WillByDefault(Return(IComponent::RequestResult::kSuccess));

static_cast<void>(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);
ASSERT_EQ(result, IComponent::RequestResult::kSuccess);
}

TEST(ComponentTest, DeactivateReturnsSuccessWhenStopActionReturnsSuccess)
TEST(ComponentTest, DeactivateReturnsFailureWhenStopActionFails)
{
MockStartAction start_action;
MockStopAction stop_action;
Expand All @@ -120,13 +119,12 @@ TEST(ComponentTest, DeactivateReturnsSuccessWhenStopActionReturnsSuccess)
Component component(start_action, stop_action, force_stop_action, ready_condition);

ON_CALL(start_action, start(_)).WillByDefault(Return(Result<Handle>{mock_handle}));
ON_CALL(stop_action, stop(_, _)).WillByDefault(Return(IComponent::RequestState::kSuccess));
ON_CALL(stop_action, stop(_, _)).WillByDefault(Return(IComponent::RequestResult::kFailure));

static_cast<void>(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);
ASSERT_EQ(result, IComponent::RequestResult::kFailure);
}

TEST(ComponentTest, ReadyConditionCalled)
Expand Down Expand Up @@ -163,7 +161,7 @@ TEST(ComponentTest, TerminationDuringStartupInterruptsActivation)

const auto result = component.activate(cpp::stop_token{});

EXPECT_FALSE(result.has_value());
EXPECT_EQ(result, IComponent::RequestResult::kFailure);
}

TEST(ComponentTest, StartSetsActive)
Expand All @@ -188,7 +186,7 @@ TEST(ComponentTest, StopClearsActive)
Component component(start_action, stop_action, force_stop_action, ready_condition);

static_cast<void>(component.activate(cpp::stop_token{}));
component.deactivate(cpp::stop_token{});
static_cast<void>(component.deactivate(cpp::stop_token{}));

EXPECT_FALSE(component.active());
}
Expand Down Expand Up @@ -231,7 +229,7 @@ TEST(ComponentTest, CannotActivateInTerminatingState)
Component component(start_action, stop_action, force_stop_action, ready_condition);

ON_CALL(start_action, start(_)).WillByDefault(Return(Result<Handle>{mock_handle}));
ON_CALL(stop_action, stop(_, _)).WillByDefault(Return(IComponent::RequestState::kWaiting));
ON_CALL(stop_action, stop(_, _)).WillByDefault(Return(IComponent::RequestResult::kSuccess));

static_cast<void>(component.activate(cpp::stop_token{}));
static_cast<void>(component.deactivate(cpp::stop_token{}));
Expand All @@ -249,7 +247,7 @@ TEST(ComponentTest, CannotDeactivateInTerminatingState)
Component component(start_action, stop_action, force_stop_action, ready_condition);

ON_CALL(start_action, start(_)).WillByDefault(Return(Result<Handle>{mock_handle}));
ON_CALL(stop_action, stop(_, _)).WillByDefault(Return(IComponent::RequestState::kWaiting));
ON_CALL(stop_action, stop(_, _)).WillByDefault(Return(IComponent::RequestResult::kSuccess));

static_cast<void>(component.activate(cpp::stop_token{}));
static_cast<void>(component.deactivate(cpp::stop_token{}));
Expand All @@ -270,7 +268,7 @@ TEST(ComponentTest, CannotActivateInFaultState)
ON_CALL(start_action, start(_)).WillByDefault(Return(Result<Handle>{mock_handle}));

static_cast<void>(component.activate(cpp::stop_token{}));
component.tryHandleTermination(1);
static_cast<void>(component.tryHandleTermination(1));

EXPECT_DEATH(
{ static_cast<void>(component.activate(cpp::stop_token{})); }, "Cannot activate component in a running state");
Expand All @@ -287,14 +285,14 @@ TEST(ComponentTest, CannotDeactivateInFaultState)
ON_CALL(start_action, start(_)).WillByDefault(Return(Result<Handle>{mock_handle}));

static_cast<void>(component.activate(cpp::stop_token{}));
component.tryHandleTermination(1);
static_cast<void>(component.tryHandleTermination(1));

EXPECT_DEATH(
{ static_cast<void>(component.deactivate(cpp::stop_token{})); },
"Cannot deactivate component in a non-running state");
}

TEST(ComponentTest, SelfTerminatingExitZeroInReadyStateIsWaiting)
TEST(ComponentTest, SelfTerminatingExitZeroInReadyStateIsIgnored)
{
MockStartAction start_action;
MockStopAction stop_action;
Expand All @@ -307,8 +305,7 @@ TEST(ComponentTest, SelfTerminatingExitZeroInReadyStateIsWaiting)
static_cast<void>(component.activate(cpp::stop_token{}));
const auto result = component.tryHandleTermination(0);

ASSERT_TRUE(result.has_value());
EXPECT_EQ(result.value(), IComponent::RequestState::kWaiting);
EXPECT_EQ(result, IComponent::TerminationResult::kIgnore);
}

TEST(ComponentTest, SelfTerminatingExitZeroInReadyStateIsActive)
Expand All @@ -327,7 +324,7 @@ TEST(ComponentTest, SelfTerminatingExitZeroInReadyStateIsActive)
EXPECT_TRUE(component.active());
}

TEST(ComponentTest, SelfTerminatingExitNonZeroInReadyStateIsError)
TEST(ComponentTest, SelfTerminatingExitNonZeroInReadyStateIsFailure)
{
MockStartAction start_action;
MockStopAction stop_action;
Expand All @@ -340,8 +337,7 @@ TEST(ComponentTest, SelfTerminatingExitNonZeroInReadyStateIsError)
static_cast<void>(component.activate(cpp::stop_token{}));
const auto result = component.tryHandleTermination(1);

ASSERT_FALSE(result.has_value());
EXPECT_EQ(result.error(), IComponent::ComponentError::kErrorAfterReady);
EXPECT_EQ(result, IComponent::TerminationResult::kFailure);
}

TEST(ComponentTest, SelfTerminatingExitNonZeroInReadyStateIsNotActive)
Expand All @@ -360,7 +356,7 @@ TEST(ComponentTest, SelfTerminatingExitNonZeroInReadyStateIsNotActive)
EXPECT_FALSE(component.active());
}

TEST(ComponentTest, SelfTerminatingExitZeroInStartingStateIsWaiting)
TEST(ComponentTest, SelfTerminatingExitZeroInStartingStateIsIgnored)
{
MockStartAction start_action;
MockStopAction stop_action;
Expand All @@ -370,16 +366,15 @@ TEST(ComponentTest, SelfTerminatingExitZeroInStartingStateIsWaiting)

ON_CALL(start_action, start(_)).WillByDefault(Return(Result<Handle>{mock_handle}));

IComponent::RequestResult result;
IComponent::TerminationResult result;
EXPECT_CALL(ready_condition, wait(_, _)).WillOnce(Invoke([&](cpp::stop_token, const Handle) {
result = terminateFromOtherThread(component, 0);
return Result<void>{};
}));

static_cast<void>(component.activate(cpp::stop_token{}));

ASSERT_TRUE(result.has_value());
EXPECT_EQ(result.value(), IComponent::RequestState::kWaiting);
EXPECT_EQ(result, IComponent::TerminationResult::kIgnore);
}

TEST(ComponentTest, SelfTerminatingExitZeroInStartingStateIsActive)
Expand All @@ -401,7 +396,7 @@ TEST(ComponentTest, SelfTerminatingExitZeroInStartingStateIsActive)
EXPECT_TRUE(component.active());
}

TEST(ComponentTest, SelfTerminatingExitNonZeroInStartingStateIsError)
TEST(ComponentTest, SelfTerminatingExitNonZeroInStartingStateIsFailure)
{
MockStartAction start_action;
MockStopAction stop_action;
Expand All @@ -411,16 +406,15 @@ TEST(ComponentTest, SelfTerminatingExitNonZeroInStartingStateIsError)

ON_CALL(start_action, start(_)).WillByDefault(Return(Result<Handle>{mock_handle}));

IComponent::RequestResult result;
IComponent::TerminationResult result;
EXPECT_CALL(ready_condition, wait(_, _)).WillOnce(Invoke([&](cpp::stop_token, const Handle) {
result = terminateFromOtherThread(component, 1);
return Result<void>{};
}));

static_cast<void>(component.activate(cpp::stop_token{}));

ASSERT_FALSE(result.has_value());
EXPECT_EQ(result.error(), IComponent::ComponentError::kErrorAfterReady);
EXPECT_EQ(result, IComponent::TerminationResult::kFailure);
}

TEST(ComponentTest, SelfTerminatingExitNonZeroInStartingStateIsNotActive)
Expand Down Expand Up @@ -464,7 +458,7 @@ TEST(ComponentTest, SettingBothStopTokens)

const auto result = component.activate(stop_source.get_token());

EXPECT_FALSE(result.has_value());
EXPECT_EQ(result, IComponent::RequestResult::kFailure);
}

} // namespace
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@ struct [[nodiscard]] ActivationFailed
{
/// @brief The identifier of the node that failed.
IdentifierHash node_identifier;
/// @brief Description of the error.
IComponent::ComponentError reason;
/// @brief Why the activation failed, either kFailure or kTimeout.

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.

I think RequestResult should still be an expected (expected_blank I think) with an error code. Something that can be kSuccess being an error code seems wrong to me.

IComponent::RequestResult reason;
};

/// @brief A node finished deactivating.
Expand All @@ -53,8 +53,6 @@ struct [[nodiscard]] UnexpectedTermination
{
/// @brief The identifier of the node that terminated.
IdentifierHash node_identifier;
/// @brief Description of the error.
IComponent::ComponentError reason;
};

/// @brief A job was queued but cancelled by the time it was processed
Expand Down
Loading
Loading