feat: Improve ChangeState API

* Add `[[nodiscard]]` to `bool Device::ChangeState()`
* Introduce throwing variant `void Device::ChangeStateOrThrow()`

resolves #441
This commit is contained in:
Dennis Klein 2023-02-28 12:12:54 +01:00 committed by Dennis Klein
parent 5ef17fddbb
commit 435d07eaf9
4 changed files with 75 additions and 24 deletions

View File

@ -164,30 +164,30 @@ void Device::TransitionTo(State s)
while (s != currentState) { while (s != currentState) {
switch (currentState) { switch (currentState) {
case State::Idle: case State::Idle:
if (s == State::Exiting) { ChangeState(Transition::End); } if (s == State::Exiting) { ChangeStateOrThrow(Transition::End); }
else { ChangeState(Transition::InitDevice); } else { ChangeStateOrThrow(Transition::InitDevice); }
break; break;
case State::InitializingDevice: case State::InitializingDevice:
ChangeState(Transition::CompleteInit); ChangeStateOrThrow(Transition::CompleteInit);
break; break;
case State::Initialized: case State::Initialized:
if (s == State::Exiting || s == State::Idle) { ChangeState(Transition::ResetDevice); } if (s == State::Exiting || s == State::Idle) { ChangeStateOrThrow(Transition::ResetDevice); }
else { ChangeState(Transition::Bind); } else { ChangeStateOrThrow(Transition::Bind); }
break; break;
case State::Bound: case State::Bound:
if (s == State::DeviceReady || s == State::Ready || s == State::Running) { ChangeState(Transition::Connect); } if (s == State::DeviceReady || s == State::Ready || s == State::Running) { ChangeStateOrThrow(Transition::Connect); }
else { ChangeState(Transition::ResetDevice); } else { ChangeStateOrThrow(Transition::ResetDevice); }
break; break;
case State::DeviceReady: case State::DeviceReady:
if (s == State::Running || s == State::Ready) { ChangeState(Transition::InitTask); } if (s == State::Running || s == State::Ready) { ChangeStateOrThrow(Transition::InitTask); }
else { ChangeState(Transition::ResetDevice); } else { ChangeStateOrThrow(Transition::ResetDevice); }
break; break;
case State::Ready: case State::Ready:
if (s == State::Running) { ChangeState(Transition::Run); } if (s == State::Running) { ChangeStateOrThrow(Transition::Run); }
else { ChangeState(Transition::ResetTask); } else { ChangeStateOrThrow(Transition::ResetTask); }
break; break;
case State::Running: case State::Running:
ChangeState(Transition::Stop); ChangeStateOrThrow(Transition::Stop);
break; break;
case State::Binding: case State::Binding:
case State::Connecting: case State::Connecting:
@ -281,7 +281,7 @@ void Device::InitWrapper()
} }
} }
// ChangeState(Transition::Auto); // ChangeStateOrThrow(Transition::Auto);
} }
void Device::BindWrapper() void Device::BindWrapper()
@ -298,7 +298,7 @@ void Device::BindWrapper()
Bind(); Bind();
if (!NewStatePending()) { if (!NewStatePending()) {
ChangeState(Transition::Auto); ChangeStateOrThrow(Transition::Auto);
} }
} }
@ -341,7 +341,7 @@ void Device::ConnectWrapper()
Connect(); Connect();
if (!NewStatePending()) { if (!NewStatePending()) {
ChangeState(Transition::Auto); ChangeStateOrThrow(Transition::Auto);
} }
} }
@ -443,7 +443,7 @@ void Device::InitTaskWrapper()
InitTask(); InitTask();
if (!NewStatePending()) { if (!NewStatePending()) {
ChangeState(Transition::Auto); ChangeStateOrThrow(Transition::Auto);
} }
} }
@ -466,7 +466,7 @@ void Device::RunWrapper()
// change to Error state in case of an exception, to release LogSocketRates // change to Error state in case of an exception, to release LogSocketRates
tools::CallOnDestruction cod([&](){ tools::CallOnDestruction cod([&](){
ChangeState(Transition::ErrorFound); ChangeStateOrThrow(Transition::ErrorFound);
}); });
PreRun(); PreRun();
@ -493,7 +493,7 @@ void Device::RunWrapper()
// if Run() exited and the state is still RUNNING, transition to READY. // if Run() exited and the state is still RUNNING, transition to READY.
if (!NewStatePending()) { if (!NewStatePending()) {
ChangeState(Transition::Stop); ChangeStateOrThrow(Transition::Stop);
} }
PostRun(); PostRun();
@ -794,7 +794,7 @@ void Device::ResetTaskWrapper()
ResetTask(); ResetTask();
if (!NewStatePending()) { if (!NewStatePending()) {
ChangeState(Transition::Auto); ChangeStateOrThrow(Transition::Auto);
} }
} }
@ -813,7 +813,7 @@ void Device::ResetWrapper()
GetChannels().clear(); GetChannels().clear();
fTransportFactory.reset(); fTransportFactory.reset();
if (!NewStatePending()) { if (!NewStatePending()) {
ChangeState(Transition::Auto); ChangeStateOrThrow(Transition::Auto);
} }
} }

View File

@ -11,6 +11,7 @@
// FairMQ // FairMQ
#include <fairmq/Channel.h> #include <fairmq/Channel.h>
#include <fairmq/Error.h>
#include <fairmq/Message.h> #include <fairmq/Message.h>
#include <fairmq/Parts.h> #include <fairmq/Parts.h>
#include <fairmq/ProgOptions.h> #include <fairmq/ProgOptions.h>
@ -498,21 +499,49 @@ class Device
public: public:
/// @brief Request a device state transition /// @brief Request a device state transition
/// @param transition state transition /// @param transition state transition
/// @return whether the transition was successfully scheduled
/// ///
/// The state transition may not happen immediately, but when the current state evaluates the /// The state transition may not happen immediately, but when the current state evaluates the
/// pending transition event and terminates. In other words, the device states are scheduled /// pending transition event and terminates. In other words, the device states are scheduled
/// cooperatively. /// cooperatively.
bool ChangeState(const Transition transition) { return fStateMachine.ChangeState(transition); } [[nodiscard]] bool ChangeState(const Transition transition)
{
return fStateMachine.ChangeState(transition);
}
/// @brief Request a device state transition /// @brief Request a device state transition
/// @param transition state transition /// @param transition state transition
/// @return whether the transition was successfully scheduled
/// ///
/// The state transition may not happen immediately, but when the current state evaluates the /// The state transition may not happen immediately, but when the current state evaluates the
/// pending transition event and terminates. In other words, the device states are scheduled /// pending transition event and terminates. In other words, the device states are scheduled
/// cooperatively. /// cooperatively.
bool ChangeState(const std::string& transition) [[nodiscard]] bool ChangeState(const std::string& transition)
{ {
return fStateMachine.ChangeState(GetTransition(transition)); return fStateMachine.ChangeState(GetTransition(transition));
} }
/// @brief Request a device state transition
/// @param transition state transition
/// @throws when the transition could not have been scheduled
///
/// Throwing version of Device::ChangeState().
void ChangeStateOrThrow(Transition transition)
{
if(!ChangeState(transition)) {
auto const err = MakeErrorCode(ErrorCode::DeviceChangeStateFailed);
throw std::system_error(err.value(),
err.category(),
tools::ToString("Invalid transition: ", transition));
}
}
/// @brief Request a device state transition
/// @param transition state transition
/// @throws when the transition could not have been scheduled
///
/// Throwing version of Device::ChangeState().
void ChangeStateOrThrow(std::string const& transition)
{
ChangeStateOrThrow(GetTransition(transition));
}
/// @brief waits for the next state (any) to occur /// @brief waits for the next state (any) to occur
State WaitForNextState() { return fStateQueue.WaitForNext(); } State WaitForNextState() { return fStateQueue.WaitForNext(); }

View File

@ -152,7 +152,7 @@ auto DeviceRunner::Run() -> int
fDevice->RegisterChannelEndpoints(); fDevice->RegisterChannelEndpoints();
if (fConfig.Count("print-channels")) { if (fConfig.Count("print-channels")) {
fDevice->PrintRegisteredChannels(); fDevice->PrintRegisteredChannels();
fDevice->ChangeState(fair::mq::Transition::End); fDevice->ChangeStateOrThrow(fair::mq::Transition::End);
return 0; return 0;
} }
@ -160,7 +160,7 @@ auto DeviceRunner::Run() -> int
if (fConfig.Count("version")) { if (fConfig.Count("version")) {
LOGV(info, verylow) << "FairMQ version: " << FAIRMQ_GIT_VERSION; LOGV(info, verylow) << "FairMQ version: " << FAIRMQ_GIT_VERSION;
LOGV(info, verylow) << "User device version: " << fDevice->GetVersion(); LOGV(info, verylow) << "User device version: " << fDevice->GetVersion();
fDevice->ChangeState(fair::mq::Transition::End); fDevice->ChangeStateOrThrow(fair::mq::Transition::End);
return 0; return 0;
} }

View File

@ -97,4 +97,26 @@ TEST(Transitions, ConcurrentTransitionTos)
} }
} }
TEST(Transitions, InvalidChangeState)
{
Device device;
thread t([&] { device.RunStateMachine(); });
ASSERT_FALSE(device.ChangeState(Transition::Connect));
ASSERT_TRUE(device.ChangeState(Transition::End));
if (t.joinable()) { t.join(); }
}
TEST(Transitions, InvalidChangeStateOrThrow)
{
Device device;
thread t([&] { device.RunStateMachine(); });
ASSERT_THROW(device.ChangeStateOrThrow(Transition::Connect), std::system_error);
ASSERT_NO_THROW(device.ChangeStateOrThrow(Transition::End));
if (t.joinable()) { t.join(); }
}
} // namespace } // namespace