boost-ext / sml

C++14 State Machine library
https://boost-ext.github.io/sml
Boost Software License 1.0
1.15k stars 178 forks source link

Suggestions for how to best handle timeouts inside of a boost::sml state machine? #479

Open cmorganBE opened 2 years ago

cmorganBE commented 2 years ago

I've got some state machines where I'd like to call some other class's method to initiate a physical operation, transition state, and then timeout if the subsequent event isn't received.

Thoughts on how to best handle these timeouts? Right now I'm inclined to do something like:

Pseudocode below:

class Manager;
class StateMachine;
class PhysicalMachine;

StateMachine::on_enter_xxx() {
   machine->do_something();
   startThread([]() { manager->dispatchEvent(StateMachineTimeoutXXXEvent); }
}

transition to 'waiting for completion of xxx'

then we get something like:

timeout thread -> Manager::dispatchEvent() -> stateMachine.process_event(StateMachineTimeoutXXXEvent)

and the StateMachine can use StateMachineTimeoutXXXEvent to initiate a retry, or enter a fault state

Some thoughts:

Other thoughts on the approach or how to best handle things?

uyha commented 2 years ago

I've implemented something similar, but I design my state machine to have an event to progress all the time. I call this Process, and everytime the state machine is in some waiting state, this event will be emitted all the time, and inside the state machine, there's a guard to check if it is time-out.

cmorganBE commented 2 years ago

@uyha does Process occur at some time interval where you subtract each time you see that event and guard returns true when you reach zero? I'm looking to house the logic for the timeout inside of the state machine for localization and I have a handful of different timeout intervals depending on the present state.

ladislas commented 2 years ago

Asking myself the same questions. It's not clear to me how to best integrate sml inside and embedded project more complex than just a button and a led.

tralamazza commented 2 years ago

Avoid (or ban altogether) internal events. I solve this by creating interfaces and injecting them dependencies. Act on external events.

ladislas commented 2 years ago

Avoid (or ban altogether) internal events. I solve this by creating interfaces and injecting them dependencies. Act on external events.

Thanks @tralamazza! Do you have, by any chance, a project or example that you can share?

tralamazza commented 2 years ago

Avoid (or ban altogether) internal events. I solve this by creating interfaces and injecting them dependencies. Act on external events.

Thanks @tralamazza! Do you have, by any chance, a project or example that you can share?

https://cpp.godbolt.org/z/o8n9jYqjW

ladislas commented 2 years ago

Thank you @tralamazza, I've finally had time to study your code and it helped a lot!

tralamazza commented 2 years ago

Glad I could help. This is the method we use in our project for dependencies w/ asynchronous calls, like your thread sleep/timeout example. Synchronous APIs are more problematic.

ladislas commented 2 years ago

Synchronous APIs are more problematic.

@tralamazza why is that?

tralamazza commented 2 years ago

@tralamazza why is that?

Because you can create loops and stack overflows

cppden commented 2 years ago

@tralamazza note the SML has bug when an event is pushed from action (as you do in your example) - see #427 and the link to an ugly fix for it.

tralamazza commented 2 years ago

@cppden thanks I know the queue trick :) but I do not suggest adding queues for the same reason (as internal events), easy to produce loops and hidden flows (we just trade the call stack for an explicit queue).

cmorganBE commented 2 years ago

Our approach was to use a thread to dequeue events and call sml::process(). This meant we could have the actions queue events onto this same queue to avoid the call chaining. It would be helpful if sml reported an error to warn the user (maybe it does and I don't remember?) when processing from an action.