sml icon indicating copy to clipboard operation
sml copied to clipboard

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

Open cmorganBE opened this issue 2 years ago • 13 comments

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:

  • The reason for the event going up to Manager is so we can lock around stateMachine so multiple threads aren't calling stateMachine.process_event() at the same time.
  • I thought about putting in place an EventTick10ms that the StateMachine could use to decrement a counter and use as a timeout trigger. This would let us get away without extra threads which simplifies things a ton.

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

cmorganBE avatar Nov 14 '21 15:11 cmorganBE

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.

uyha avatar Nov 14 '21 15:11 uyha

@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.

cmorganBE avatar Nov 14 '21 19:11 cmorganBE

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.

ladislas avatar Dec 17 '21 13:12 ladislas

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

tralamazza avatar Dec 17 '21 14:12 tralamazza

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?

ladislas avatar Dec 17 '21 14:12 ladislas

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

tralamazza avatar Dec 17 '21 15:12 tralamazza

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

ladislas avatar Jan 04 '22 14:01 ladislas

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.

tralamazza avatar Jan 04 '22 16:01 tralamazza

Synchronous APIs are more problematic.

@tralamazza why is that?

ladislas avatar Jan 04 '22 21:01 ladislas

@tralamazza why is that?

Because you can create loops and stack overflows

tralamazza avatar Jan 04 '22 21:01 tralamazza

@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.

cppden avatar May 22 '22 12:05 cppden

@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).

tralamazza avatar May 23 '22 07:05 tralamazza

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.

cmorganBE avatar May 23 '22 12:05 cmorganBE