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 @@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -54,14 +54,14 @@ TEST_F(ComponentEventQueueTest, GetNextEventReturnsPushedEventWithPayloadIntact)
{
RecordProperty("Description", "Verify a pushed event is returned by getNextEvent with its payload preserved.");
const IdentifierHash process_identifier{"payload"};
EXPECT_TRUE(queue_.push(ActivationFailed{process_identifier, IComponent::ComponentError::kErrorBeforeReady}));
EXPECT_TRUE(queue_.push(ActivationFailed{process_identifier, IComponent::RequestResult::kFailure}));

auto event = queue_.getNextEvent();
ASSERT_TRUE(event.has_value());
ASSERT_TRUE(std::holds_alternative<ActivationFailed>(*event));
const auto& failed = std::get<ActivationFailed>(*event);
EXPECT_EQ(failed.node_identifier, process_identifier);
EXPECT_EQ(failed.reason, IComponent::ComponentError::kErrorBeforeReady);
EXPECT_EQ(failed.reason, IComponent::RequestResult::kFailure);
}

TEST_F(ComponentEventQueueTest, GetNextEventReturnsSupervisionFailureWithPayloadIntact)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -175,11 +175,13 @@ void Graph::updateRunTargetInPlace(RunTarget& run_target, ComponentTaskType task
// RunTargets are updated in place, no need to queue them on the thread pool.
if (task_type == ComponentTaskType::kActivate)
{
run_target.activate(stop_source_.get_token());
const IComponent::RequestResult result = run_target.activate(stop_source_.get_token());
SCORE_LANGUAGE_FUTURECPP_ASSERT(result == IComponent::RequestResult::kSuccess);

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.

Hmm I think it's right now safe to assert here because the run_target.activate just sets variable but not sure if we should assume that will be the case forever. I would rather log an error I think?

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.

Originally we just ignored the return value entirely. I think logging an error could have the same consequences (errors going unnoticed) - the log message might not be noticed if tests are passing.

I would prefer keeping the assert and adding proper error handling if/when the need arises

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 the assert is good here because other changes might be needed if this did change, needs an assert message though to clear things up (e.g. "Run target activation cannot fail").

Also, is SCORE_LANGUAGE_FUTURECPP_ASSERT debug or production? Probably doesn't matter here but this seems ambiguous. Or did the API change?

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.

Also, is SCORE_LANGUAGE_FUTURECPP_ASSERT debug or production?

Neither...

Compilation flag
Default Debug Production
Assertion type
Default Yes Yes No
Debug No Yes No
Production Yes Yes Yes

}
else
{
run_target.deactivate(stop_source_.get_token());
const IComponent::RequestResult result = run_target.deactivate(stop_source_.get_token());
SCORE_LANGUAGE_FUTURECPP_ASSERT(result == IComponent::RequestResult::kSuccess);
}
current_transition_->onNodeFinished(run_target.getIdentifier());
}
Expand Down Expand Up @@ -347,11 +349,11 @@ void Graph::handleComponentEvent(const ComponentEvent& event)
<< (std::is_same_v<T, ActivationSuccessful> ? std::string_view("activation")
: std::string_view("deactivation"))
<< "successfully";
nodeExecuted(data.node_identifier, {});
nodeExecuted(data.node_identifier, IComponent::RequestResult::kSuccess);
}
else if constexpr (std::is_same_v<T, ActivationFailed>)
{
nodeExecuted(data.node_identifier, score::cpp::make_unexpected(data.reason));
nodeExecuted(data.node_identifier, data.reason);
}
else if constexpr (std::is_same_v<T, UnexpectedTermination>)
{
Expand All @@ -368,17 +370,17 @@ void Graph::handleComponentEvent(const ComponentEvent& event)
}
else if constexpr (std::is_same_v<T, JobSkipped>)
{
nodeExecuted(data.node_identifier, {});
nodeExecuted(data.node_identifier, IComponent::RequestResult::kSuccess);
}
},
event);
}

void Graph::nodeExecuted(IdentifierHash node, score::cpp::expected_blank<IComponent::ComponentError> error)
void Graph::nodeExecuted(IdentifierHash node, IComponent::RequestResult result)
{
bool was_last_in_queue = --jobs_in_progress_ == 0;

if (!error.has_value())
if (result != IComponent::RequestResult::kSuccess)
{
setState(GraphState::kAborting);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -279,7 +279,7 @@ class Graph final
private:
/// @brief Reports that a node has finished executing, enqueuing successors or updating the graph state if a
/// transition has finished.
void nodeExecuted(IdentifierHash node, score::cpp::expected_blank<IComponent::ComponentError> error);
void nodeExecuted(IdentifierHash node, IComponent::RequestResult result);

/// @brief Sets the current state of the graph.
/// @param new_state The new state to set for the graph.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -140,16 +140,15 @@ class GraphTest : public ::testing::Test
res = job.component.get().deactivate(job.stop_token);
}

ASSERT_TRUE(res.has_value());
ASSERT_EQ(res.value(), IComponent::RequestState::kSuccess);
ASSERT_EQ(res, IComponent::RequestResult::kSuccess);
}

void failActivationJob(const ComponentTask& job)
{
EXPECT_CALL(process_interface_, startProcess).WillOnce(Return(osal::OsalReturnType::kFail));
const auto res = job.component.get().activate(job.stop_token);

ASSERT_FALSE(res.has_value());
ASSERT_EQ(res, IComponent::RequestResult::kFailure);
}

/// @brief Execute a Run Target activation that activates or deactivates a single node
Expand Down Expand Up @@ -345,7 +344,7 @@ TEST_F(GraphInitialTransitionTest, jobFailure)
const auto job = job_queue_->pop()->value();
failActivationJob(job);
graph_->handleComponentEvent(
ActivationFailed{IdentifierHash{process_name(0)}, IComponent::ComponentError::kErrorBeforeReady});
ActivationFailed{IdentifierHash{process_name(0)}, IComponent::RequestResult::kFailure});

EXPECT_EQ(graph_->getState(), GraphState::kUndefinedState);
}
Expand Down Expand Up @@ -548,8 +547,7 @@ TEST_F(GraphHandleComponentEventTest, failedFirstDuringTransition)
// Fail the first job
const auto first_job = job_queue_->pop();
graph_->handleComponentEvent(
ActivationFailed{
first_job->value().component.get().getIdentifier(), IComponent::ComponentError::kErrorBeforeReady});
ActivationFailed{first_job->value().component.get().getIdentifier(), IComponent::RequestResult::kFailure});

const auto second_job = job_queue_->pop();

Expand All @@ -567,8 +565,7 @@ TEST_F(GraphHandleComponentEventTest, failureFollowedBySuccessFails)
// Fail the first job
const auto first_job = job_queue_->pop();
graph_->handleComponentEvent(
ActivationFailed{
first_job->value().component.get().getIdentifier(), IComponent::ComponentError::kErrorBeforeReady});
ActivationFailed{first_job->value().component.get().getIdentifier(), IComponent::RequestResult::kFailure});

const auto second_job = job_queue_->pop();
executeJobSuccessfully(second_job->value());
Expand All @@ -589,8 +586,7 @@ TEST_F(GraphHandleComponentEventTest, cancelAfterFailureStaysAborting)
// Fail the first job
const auto first_job = job_queue_->pop();
graph_->handleComponentEvent(
ActivationFailed{
first_job->value().component.get().getIdentifier(), IComponent::ComponentError::kErrorBeforeReady});
ActivationFailed{first_job->value().component.get().getIdentifier(), IComponent::RequestResult::kFailure});

EXPECT_EQ(graph_->getState(), GraphState::kAborting);

Expand Down Expand Up @@ -623,8 +619,7 @@ TEST_F(GraphHandleComponentEventTest, unexpectedTerminationDuringSuccess)
}),
Return(osal::OsalReturnType::kSuccess)));

graph_->handleComponentEvent(
UnexpectedTermination{component->getIdentifier(), IComponent::ComponentError::kErrorAfterReady});
graph_->handleComponentEvent(UnexpectedTermination{component->getIdentifier()});

EXPECT_EQ(graph_->getState(), GraphState::kUndefinedState);
}
Expand Down Expand Up @@ -652,7 +647,7 @@ TEST_F(GraphHandleComponentEventTest, unexpectedTerminationDuringTransition)
Return(osal::OsalReturnType::kSuccess)));

// The active component then crashes
graph_->handleComponentEvent(UnexpectedTermination{component_index, IComponent::ComponentError::kErrorAfterReady});
graph_->handleComponentEvent(UnexpectedTermination{component_index});

const auto second_job = job_queue_->pop();
executeJobSuccessfully(second_job->value());
Expand All @@ -679,7 +674,7 @@ TEST_F(GraphTransitionFailuresTest, UnusualOrderOfFailures)
.WillOnce(Return(osal::OsalReturnType::kSuccess)); // Process is already gone, semaphore will time out
EXPECT_CALL(process_interface_, forceTermination).WillOnce(Return(osal::OsalReturnType::kFail));

graph_->handleComponentEvent(UnexpectedTermination{component_id, IComponent::ComponentError::kErrorAfterReady});
graph_->handleComponentEvent(UnexpectedTermination{component_id});
graph_->handleComponentEvent(ActivationSuccessful{component_id});

EXPECT_EQ(graph_->getState(), GraphState::kUndefinedState) << "Graph should be in a final state";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,42 +31,37 @@ namespace score::mw::lifecycle::internal
class IComponent
{
public:
enum class ComponentError : uint8_t
enum class [[nodiscard]] RequestResult : std::uint8_t
{
/// @brief An error occurred during startup, before the component was considered ready.
kErrorBeforeReady,
/// @brief An error occurred any time after the component was considered ready.
kErrorAfterReady,
/// @brief An error occurred during startup. Specifically, a timeout was reached.
kActivationTimedOut,
/// @brief The operation succeeded.
kSuccess,

/// @brief The operation timed out.
kTimeout,

/// @brief The operation failed.
kFailure
};

enum class RequestState : uint8_t
enum class [[nodiscard]] TerminationResult : std::uint8_t
{
/// @brief Activation was successful and the component is now ready.
kSuccess,
/// @brief Activation is waiting on a notification from another thread. The component may not be ready.
kWaiting
};
/// @brief The termination does not affect the state of the component.
kIgnore,

using RequestResult = score::cpp::expected<RequestState, ComponentError>;
/// @brief The component failed.
kFailure
};

/// @brief Begin activation of the component.
/// @p stop_token Token that can be stopped to exit the activation early.
/// @returns kSuccess if the component is now ready, kWaiting if the component is waiting for a notification, or an
/// error.
[[nodiscard]] virtual RequestResult activate(score::cpp::stop_token stop_token) = 0;
virtual RequestResult activate(score::cpp::stop_token stop_token) = 0;

/// @brief Begin deactivation of the component.
/// @p stop_token Token that can be stopped to exit the deactivation early.
/// @returns kSuccess if the component is now ready, kWaiting if the component is waiting for a notification, or an
/// error.
[[nodiscard]] virtual RequestResult deactivate(score::cpp::stop_token stop_token) = 0;
virtual RequestResult deactivate(score::cpp::stop_token stop_token) = 0;

/// @brief Notify the component that it has terminated with status @p status
/// @returns kSuccess if the component is now ready, kWaiting if the component is waiting for a notification, or an
/// error if the termination was not expected.
[[nodiscard]] virtual RequestResult tryHandleTermination(int32_t status) = 0;
virtual TerminationResult tryHandleTermination(int32_t status) = 0;

/// @returns the index of the component in the graph.
[[nodiscard]] virtual IdentifierHash getIdentifier() const = 0;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ class MockComponent : public IComponent
public:
MOCK_METHOD(RequestResult, activate, (score::cpp::stop_token stop_token), (override));
MOCK_METHOD(RequestResult, deactivate, (score::cpp::stop_token stop_token), (override));
MOCK_METHOD(RequestResult, tryHandleTermination, (int32_t status), (override));
MOCK_METHOD(TerminationResult, tryHandleTermination, (int32_t status), (override));
MOCK_METHOD(IdentifierHash, getIdentifier, (), (override, const));
MOCK_METHOD(bool, active, (), (override, const));
};
Expand Down
Loading
Loading