What
Due to the asynchronous design, the new control API introduced in #478 is more difficult to use compared to the old one.
For example, a version of the smoke test:
Before
void main()
{
score::mw::lifecycle::ControlClient client{};
TEST_STEP("Control daemon report running")
{
// report running
score::mw::lifecycle::report_running();
}
TEST_STEP("Activate RunTarget Running")
{
score::cpp::stop_token stop_token;
auto result = client.ActivateRunTarget("Running").Get(stop_token);
EXPECT_TRUE(result.has_value()) << "Activating target Running failed: " << result.error().Message();
}
TEST_STEP("Activate RunTarget Startup")
{
score::cpp::stop_token stop_token;
auto result = client.ActivateRunTarget("Startup").Get(stop_token);
EXPECT_TRUE(result.has_value());
}
TEST_STEP("Activate RunTarget Off")
{
client.ActivateRunTarget("Off");
}
}
After
bool event_received = false;
RunTargetActivationSource event_source;
RunTargetName event_target;
std::mutex event_mutex;
std::condition_variable event_condition;
void push_event(RunTargetActivationSource source, RunTargetName target)
{
{
std::unique_lock event_lock(event_mutex);
event_condition.wait(event_lock, [&] {
return !event_received;
});
event_received = true;
event_source = source;
event_target = target;
}
event_condition.notify_one();
};
void pop_event(std::function<void(RunTargetActivationSource source, RunTargetName target)> callback)
{
{
std::unique_lock event_lock(event_mutex);
event_condition.wait(event_lock, [&] {
return event_received;
});
callback(event_source, event_target);
event_received = false;
}
event_condition.notify_one();
};
void main()
{
std::unique_ptr<ILmControl> client;
TEST_STEP("Create client")
{
auto client_result = ILmControl::Create("StateManager/LaunchManager/Instance");
ASSERT_TRUE(client_result.has_value()) << client_result.error().Message();
client = std::move(client_result).value();
}
TEST_STEP("Register callback")
{
const auto result = client->register_run_target_activation_callback(push_event);
ASSERT_TRUE(result.has_value());
}
TEST_STEP("Report running")
{
report_running();
}
pop_event([](RunTargetActivationSource source, RunTargetName target) {
TEST_STEP("Callback for RunTarget Startup")
{
EXPECT_EQ(source, RunTargetActivationSource::kInitialActivation);
EXPECT_EQ(target, "Startup");
}
});
TEST_STEP("Activate RunTarget Running")
{
const auto result = client->activate_run_target("Running", true);
EXPECT_TRUE(result.has_value()) << result.error().Message();
}
pop_event([](RunTargetActivationSource source, RunTargetName target) {
TEST_STEP("Callback for RunTarget Running")
{
EXPECT_EQ(source, RunTargetActivationSource::kStateManagerRequest);
EXPECT_EQ(target, "Running");
}
});
TEST_STEP("Activate RunTarget Startup")
{
const auto result = client->activate_run_target("Startup", true);
EXPECT_TRUE(result.has_value()) << result.error().Message();
}
pop_event([](RunTargetActivationSource source, RunTargetName target) {
TEST_STEP("Callback for RunTarget Startup")
{
EXPECT_EQ(source, RunTargetActivationSource::kStateManagerRequest);
EXPECT_EQ(target, "Startup");
}
});
TEST_STEP("Activate RunTarget Off")
{
const auto result = client->activate_run_target("Off", true);
EXPECT_TRUE(result.has_value()) << result.error().Message();
}
}
In the new API, it is not possible to call activate_run_target while the activation_callback is still running. So, the user has to write multithreaded code.
Acceptance Criteria (DoD)
- Control API provides methods that do not require external synchronisation
How
#489 (comment)
What
Due to the asynchronous design, the new control API introduced in #478 is more difficult to use compared to the old one.
For example, a version of the smoke test:
Before
After
In the new API, it is not possible to call
activate_run_targetwhile theactivation_callbackis still running. So, the user has to write multithreaded code.Acceptance Criteria (DoD)
How
#489 (comment)