[MSM] state transitions and exceptions

Everyone, I did just get around to give the MSM a first shot and I have say I am impressed at the neat design, the ease of use, "understandability", tutorial ... exceptional work. Really great and it helped me a great deal so far. So thanks a bunch for this teriffic addition to boost. That's the kind of stuff I love boost for. There is one question however that puzzles me. How can / do I prevent state transistions when a transition function throws? Most transition functions are defined like that: void so_transition(event::Cause const &e) { methodCausingAnException(); } The tutorial only mentions this at the very end of the back-end docs and this is not clear to me. I have stuff inside my transitions that may throw and the example text mentiones other valid examples (like connect() logic). So what am I supposed to do here? This? void so_transition(event::Cause const &e) { try { methodCausingAnException(); } catch (const std::exception &sex) { return ??? } } The type is void and I can't return anything that would prevent the transition. Or should I just let the exception pass? What is the correct way? Suggestions are appreciated, Stephan

On 29.3.2012. 10:55, Stephan Menzel wrote:
Everyone,
I did just get around to give the MSM a first shot and I have say I am impressed at the neat design, the ease of use, "understandability", tutorial ... exceptional work. Really great and it helped me a great deal so far. So thanks a bunch for this teriffic addition to boost. That's the kind of stuff I love boost for.
I know how you feel. MSM is an awesome library. Unfortunately compilers tend to disagree :)
There is one question however that puzzles me. How can / do I prevent state transistions when a transition function throws?
MSM has two kinds of transition functions, actions and guards. What you are describing above is an action, and it is not designed to prevent the transition. You should use a guard. Guards have a boolean return value. For an example see good_disk_format() function in the SimpleTutorial.cpp in the MSM docs. HTH

Jurai,
On Thu, Mar 29, 2012 at 7:07 PM, Juraj Ivančić
I know how you feel. MSM is an awesome library. Unfortunately compilers tend to disagree :)
Indeed. I am having massive trouble boost.binding the process_event() function, which I call from within a threaded pool like thingy. It was so bad, I had to macro wrapper functions around it. Not even the usual static_cast trick helped. But this is something I can live with, considering all the benefits.
MSM has two kinds of transition functions, actions and guards. What you are describing above is an action, and it is not designed to prevent the transition. You should use a guard. Guards have a boolean return value. For an example see good_disk_format() function in the SimpleTutorial.cpp in the MSM docs.
I know about this difference. But it doesn't cover what I mean. Exceptions as in 'exceptional situations'. Consider the socket example. You would probably like to use a MSM to implement a protocol. Not when you do a transition from let's say state 'Idle' to state 'Connected' you would be able to make sure everything is ready for connection in your guard. Like checking if you have your target endpoint and it is correct and whatever else you need. But still, your connect() that you would inevitably have to do in the transition may fail. Or anything else like that. In my implementation I have a lot of guards now checking whatever could go wrong but there's no way eliminating all possible exception courses. I have to assume process_event() will pass the exception to the caller and the transition will not occur but the docs are not specific about this. Plus since I called process event indirectly via Q and the wrapper will not know how to deal with this case. Let alone guarantee RAII in the transition. I believe it would be good to be able to bind an exception handler to each transition much like the guards, and to be able to return true or false from that handler, depending on what this function thinks if the exception should prevent transition or not. But this is only an idea. Cheers, Stephan

Jurai,
On Thu, Mar 29, 2012 at 7:07 PM, Juraj Ivančić
wrote: I know how you feel. MSM is an awesome library. Unfortunately compilers tend to disagree :)
I'd say compilers should be blamed for this ;-) (I'm still wondering how it can be that compilers make such a poor use of my 6 cores with hyperthreading when compile a single object file...) Typically, boost forces compiler writers to improve. But yes, that's the deal. MSM handles a lot for you at compile time to get features, the best speed and ease of use at the cost of longer compile time.
Indeed. I am having massive trouble boost.binding the process_event() function, which I call from within a threaded pool like thingy. It was so bad, I had to macro wrapper functions around it. Not even the usual static_cast trick helped. But this is something I can live with, considering all the benefits.
process_event is a template function, which means boost.bind requires you to use a function pointer. If what you mean is generating an event from an asio handler, I usually use the following (simplified) solution (which I plan to add to a coming paper with msm + asio) (from memory, did not try to compile): template <class Fsm> struct handle_write { void operator()(const boost::system::error_code& error) const { if (error) fsm_->process_event(WriteError()); else fsm_->process_event(WriteDone()); } Fsm* fsm_; }; HTH, Christophe

on Thu Mar 29 2012, "Christophe Henry"
Jurai,
On Thu, Mar 29, 2012 at 7:07 PM, Juraj Ivančić
wrote: I know how you feel. MSM is an awesome library. Unfortunately compilers tend to disagree :)
I'd say compilers should be blamed for this ;-) (I'm still wondering how it can be that compilers make such a poor use of my 6 cores with hyperthreading when compile a single object file...)
There isn't much inherent parallelism to exploit in C++ compilation of a single translation unit. -- Dave Abrahams BoostPro Computing http://www.boostpro.com

on Thu Mar 29 2012, "Christophe Henry"
wrote: Jurai,
On Thu, Mar 29, 2012 at 7:07 PM, Juraj Ivančić
wrote: I know how you feel. MSM is an awesome library. Unfortunately compilers tend to disagree :)
I'd say compilers should be blamed for this ;-) (I'm still wondering how it can be that compilers make such a poor use of my 6 cores with hyperthreading when compile a single object file...)
There isn't much inherent parallelism to exploit in C++ compilation of a single translation unit.
-- Dave Abrahams BoostPro Computing http://www.boostpro.com
I'm not an expert in this field, but this surprises me a bit. I understand that there is not much parallelism in parsing a file, but after? Taking msm as example. Most of the compile time comes from template instanciations of the function process_event for every event type. Adding support for a new event adds roughly the same compilation time. Every process_event has little in common with other process_event instances. What prevents the compiler from handling each at the same time? Christophe

On 01.04.2012, at 22:13, Christophe Henry wrote:
on Thu Mar 29 2012, "Christophe Henry"
wrote: Jurai,
On Thu, Mar 29, 2012 at 7:07 PM, Juraj Ivančić
wrote: I know how you feel. MSM is an awesome library. Unfortunately compilers tend to disagree :)
I'd say compilers should be blamed for this ;-) (I'm still wondering how it can be that compilers make such a poor use of my 6 cores with hyperthreading when compile a single object file...)
There isn't much inherent parallelism to exploit in C++ compilation of a single translation unit.
-- Dave Abrahams BoostPro Computing http://www.boostpro.com
I'm not an expert in this field, but this surprises me a bit. I understand that there is not much parallelism in parsing a file, but after? Taking msm as example. Most of the compile time comes from template instanciations of the function process_event for every event type. Adding support for a new event adds roughly the same compilation time. Every process_event has little in common with other process_event instances. What prevents the compiler from handling each at the same time?
The locking mechanism for ASTs would probably be very complicated if you want to do template instantiation in parallel. It probably could be done, but going that route isn't very attractive because the cores are much better used compiling 6 files in parallel. Sebastian

On 01.04.2012, at 22:13, Christophe Henry wrote:
on Thu Mar 29 2012, "Christophe Henry"
wrote: Jurai,
On Thu, Mar 29, 2012 at 7:07 PM, Juraj Ivančić
wrote: I know how you feel. MSM is an awesome library. Unfortunately compilers tend to disagree :)
I'd say compilers should be blamed for this ;-) (I'm still wondering how it can be that compilers make such a poor use of my 6 cores with hyperthreading when compile a single object file...)
There isn't much inherent parallelism to exploit in C++ compilation of a single translation unit.
-- Dave Abrahams BoostPro Computing http://www.boostpro.com
I'm not an expert in this field, but this surprises me a bit. I understand that there is not much parallelism in parsing a file, but after? Taking msm as example. Most of the compile time comes from template instanciations of the function process_event for every event type. Adding support for a new event adds roughly the same compilation time. Every process_event has little in common with other process_event instances. What prevents the compiler from handling each at the same time?
The locking mechanism for ASTs would probably be very complicated if you want to do template instantiation in parallel. It probably could be done, but going that route isn't very attractive because the cores are much better used compiling 6 files in parallel.
Sebastian
Sure but it's still very annoying because in a well-designed application with little dependency, I work very very often on a single cpp file where my state machine (or other boost-heavy code) is used and have to wait up to 2mn that this file is compiled with 1 thread while my other 11 are bored. Then I wait until linking is done, on 1 thread. This doesn't look like a very good usage of my cores either ;-) I hereby make the promise to switch all my applications to the first standard-conform compiler which compiles this one file on 12 threads thus saving me hours per year :) Christophe

on Tue Apr 03 2012, Sebastian Redl
On 01.04.2012, at 22:13, Christophe Henry wrote:
I'm not an expert in this field, but this surprises me a bit. I understand that there is not much parallelism in parsing a file, but after? Taking msm as example. Most of the compile time comes from template instanciations of the function process_event for every event type. Adding support for a new event adds roughly the same compilation time. Every process_event has little in common with other process_event instances. What prevents the compiler from handling each at the same time?
The locking mechanism for ASTs would probably be very complicated if you want to do template instantiation in parallel. It probably could be done, but going that route isn't very attractive because the cores are much better used compiling 6 files in parallel.
Plus, the way the language is defined, ordering is very important. There's a point of definition and various points of instantiation... particular lookups are done in those locations, and the symbol table has to be right in each case, at least with respect to the symbols used in the template. I doubt it's easy to unwind those dependencies. -- Dave Abrahams BoostPro Computing http://www.boostpro.com

AMDG On 04/04/2012 06:07 PM, Dave Abrahams wrote:
Plus, the way the language is defined, ordering is very important. There's a point of definition and various points of instantiation... particular lookups are done in those locations, and the symbol table has to be right in each case, at least with respect to the symbols used in the template. I doubt it's easy to unwind those dependencies.
But... "If the function name is an unqualified id and the call ... would find a better match had the lookup within the associated namespaces considered all the function declarations with external linkage introduced within those namespaces in all translation units, not just considering those declarations found in the template declaration and template instantiation contexts, then the program has undefined behavior." (14.6.4.2) "If a template, a member template, or a member of a class template is explicitly specialized, then the specialization shall be declared before the first use of that specialization that would cause an implicit instantiation to take place... no diagnostic is required" (14.7.3) (There is a similar clause for partial specialization) In other words, it seems to me that in most cases where delaying the point of instantiation would change the behavior it is either undefined behavior or ill-formed, no diagnostic required. In Christ, Steven Watanabe

on Thu Apr 05 2012, Steven Watanabe
AMDG
On 04/04/2012 06:07 PM, Dave Abrahams wrote:
Plus, the way the language is defined, ordering is very important. There's a point of definition and various points of instantiation... particular lookups are done in those locations, and the symbol table has to be right in each case, at least with respect to the symbols used in the template. I doubt it's easy to unwind those dependencies.
But...
"If the function name is an unqualified id and the call ... would find a better match had the lookup within the associated namespaces considered all the function declarations with external linkage introduced within those namespaces in all translation units, not just considering those declarations found in the template declaration and template instantiation contexts, then the program has undefined behavior." (14.6.4.2)
"If a template, a member template, or a member of a class template is explicitly specialized, then the specialization shall be declared before the first use of that specialization that would cause an implicit instantiation to take place... no diagnostic is required" (14.7.3) (There is a similar clause for partial specialization)
In other words, it seems to me that in most cases where delaying the point of instantiation would change the behavior it is either undefined behavior or ill-formed, no diagnostic required.
But "most cases" is not enough. If you can show that's true in *all* cases, there may be a case for parallel template instantiation. -- Dave Abrahams BoostPro Computing http://www.boostpro.com

on Thu Mar 29 2012, "Christophe Henry"
If what you mean is generating an event from an asio handler, I usually use the following (simplified) solution (which I plan to add to a coming paper with msm + asio)
That sounds really cool! Please let us know when you publish! -- Dave Abrahams BoostPro Computing http://www.boostpro.com

on Thu Mar 29 2012, "Christophe Henry"
wrote: If what you mean is generating an event from an asio handler, I usually use the following (simplified) solution (which I plan to add to a coming paper with msm + asio)
That sounds really cool! Please let us know when you publish!
-- Dave Abrahams BoostPro Computing http://www.boostpro.com
You can count on it! Christophe

Everyone,
I did just get around to give the MSM a first shot and I have say I am impressed at the neat design, the ease of use, "understandability", tutorial ... exceptional work. Really great and it helped me a great deal so far. So thanks a bunch for this teriffic addition to boost. That's the kind of stuff I love boost for.
Thanks :) Happy you like it.
There is one question however that puzzles me. How can / do I prevent state transistions when a transition function throws?
Most transition functions are defined like that:
void so_transition(event::Cause const &e) {
methodCausingAnException(); }
The tutorial only mentions this at the very end of the back-end docs and this is not clear to me. I have stuff inside my transitions that may throw and the example text mentiones other valid examples (like connect() logic). So what am I supposed to do here?
This?
void so_transition(event::Cause const &e) { try { methodCausingAnException(); } catch (const std::exception &sex) { return ??? } }
The type is void and I can't return anything that would prevent the transition. Or should I just let the exception pass? What is the correct way?
Suggestions are appreciated,
First of all, I have to give a word of caution. The UML Standard does not
foresee exceptions and they don't mix well with the run-to-completion
algorithm on which state machines rely. Try to avoid them if you can.
This being said, exceptions happen in C++ ;-)
By default, MSM catches them, then calls the exception_caught handler, which
by default is:
template
Stephan
HTH, Christophe

Christophe,
On Thu, Mar 29, 2012 at 9:46 PM, Christophe Henry
First of all, I have to give a word of caution. The UML Standard does not foresee exceptions and they don't mix well with the run-to-completion algorithm on which state machines rely. Try to avoid them if you can.
Yes, mum. ;-) I know, it's quite a pain and I have to deal with that quite often. Doing UML interface descriptions in common tools expose this particular problem all the time. In my opinion however, it is a problem the UML Standard has, not me. *duck*
Like the no_transition handler, you can overwrite it with your own. When this is called, the transition is interrupted where it was and ceases processing. This leaves you in a not very desirable state because you have been interrupted somewhere in the guard/exit/action/entry chain. When this happens, I advise you to process to yourself an error event to handle this gracefully. For example:
template
void exception_caught (Event const&,FSM& fsm,std::exception& ) { fsm.process_event(ErrorConnection()); }
Yes, that can work. I wasn't aware I can post events from within the state machine. But they are actually queued, as I see. Nice. It's getting even better. This should be able to deal with the problem. Only downside I can see is that I'd need an entry in the transition table for each state. I already had to adjust those MPL settings. But this will work, thanks.
You can prevent MSM from catching the exception by activating in your front end a switch:
typedef int no_exception_thrown;
Then, you get the exception thrown from your process_event call.
I think your first solution ist better because it allows me better to reset internal data to reasonable values.
In any case, the transition where the exception occurs is terminated and you don't need to terminate yourself.
OK, thanks. This should be explicitly stated like that in the tutorial. Or maybe it is and I just couldn't find it. Maybe there's room for one of those little chapters titled "exceptions". On a side note, there also could be room for a few words about how do I position such a thing in an existing (or new) architecture. Like best or common practice. When I started to work with it I was entirely unsure about it. I am here in a MVC arch with basically a GUI and a backend and threaded multiplexing controller (part of the GUI) -> backend functions and demultiplexing data signals -> GUI changes. As a first try, I understood the state machine as part of the controller logic belonging to one functionality and essentially one widget. Which meant that the SM was posting events to the backend functions in a threaded Q. This proved no good as the result of this was that a state could easily be switched without the backend having reacted yet, creating races and the possibility of acting twice. So I have moved it back behind the multiplexing, not posting async and so far I think I'm far better off. Another issue were interactions with other parts of the applications that send signals about, interfering with things the SM is now concerned with. Effectively creating need for far more reach the SM needs to have. Stuff like that. Christophe, if you are interested, I may be able to churn out a little article and about that if you are interested. Cheers, Stephan

Hi Stephan,
On Thu, Mar 29, 2012 at 9:46 PM, Christophe Henry
wrote: First of all, I have to give a word of caution. The UML Standard does not foresee exceptions and they don't mix well with the run-to-completion algorithm on which state machines rely. Try to avoid them if you can.
Yes, mum. ;-) I know, it's quite a pain and I have to deal with that quite often. Doing UML interface descriptions in common tools expose this particular problem all the time. In my opinion however, it is a problem the UML Standard has, not me. *duck*
I agree. Such a big Standard and so many holes...
Like the no_transition handler, you can overwrite it with your own. When this is called, the transition is interrupted where it was and ceases processing. This leaves you in a not very desirable state because you have been interrupted somewhere in the guard/exit/action/entry chain. When this happens, I advise you to process to yourself an error event to handle this gracefully. For example:
template
void exception_caught (Event const&,FSM& fsm,std::exception& ) { fsm.process_event(ErrorConnection()); } Yes, that can work. I wasn't aware I can post events from within the state machine. But they are actually queued, as I see. Nice. It's getting even better. This should be able to deal with the problem. Only downside I can see is that I'd need an entry in the transition table for each state. I already had to adjust those MPL settings. But this will work, thanks.
Or you can use a second region NoError --> Error. This costs you only 1 transition and it is in the spirit of the Standard. With a msm flag marking Error, you know when your fsm is in error mode.
You can prevent MSM from catching the exception by activating in your front end a switch:
typedef int no_exception_thrown;
Then, you get the exception thrown from your process_event call.
I think your first solution ist better because it allows me better to reset internal data to reasonable values.
In any case, the transition where the exception occurs is terminated and you don't need to terminate yourself.
OK, thanks. This should be explicitly stated like that in the tutorial. Or maybe it is and I just couldn't find it. Maybe there's room for one of those little chapters titled "exceptions".
Should be possible, yes ;-)
On a side note, there also could be room for a few words about how do I position such a thing in an existing (or new) architecture. Like best or common practice. When I started to work with it I was entirely unsure about it. I am here in a MVC arch with basically a GUI and a backend and threaded multiplexing controller (part of the GUI) -> backend functions and demultiplexing data signals -> GUI changes. As a first try, I understood the state machine as part of the controller logic belonging to one functionality and essentially one widget. Which meant that the SM was posting events to the backend functions in a threaded Q. This proved no good as the result of this was that a state could easily be switched without the backend having reacted yet, creating races and the possibility of acting twice. So I have moved it back behind the multiplexing, not posting async and so far I think I'm far better off. Another issue were interactions with other parts of the applications that send signals about, interfering with things the SM is now concerned with. Effectively creating need for far more reach the SM needs to have. Stuff like that. Christophe, if you are interested, I may be able to churn out a little article and about that if you are interested.
I surely am interested as I plan a few papers or tutorials on this matter. msm used with asio, or as the Controller part of a MVC. Another point of view cannot hurt. Cheers, Christophe

template
void exception_caught (Event const&,FSM& fsm,std::exception& ) { fsm.process_event(ErrorConnection()); }
In a telecom application we are doing something similar, but we have
several submachines, and we can have exceptions during the processing
of the error event.
In each submachnie we throw the exception further to the container machine.
// Base template class for submachines
template <typename T>
class BaseStateMachine: public msm::front::state_machine_def<T>
{
public:
// ...
// Exception Handling
template

<snip>
When there is an error in the outermost statemachine we propagate the exception out from it. At this point, the client code (which calls the process_event function) shall restart the state machine therefore resetting it to the initial state. But we cannot simply restart the machine because after we have thrown the exception out from it, it becomes nonresponsive, i.e. it does not react to the fsm->stop() and fsm->start() sequence.
So we came up with the following:
void Client::reCreateBackEnd() { if(backEnd){ // backEnd is a smart pointer backEnd->stop(); backEnd.reset(); }
// Constructing the BackEnd backEnd.reset(new ...); backEnd->set_states( ... ); backEnd->start(); }
It is too drastic to recreate the back end, I think. Christophe, is there any more elegant solution to do this? Why is it that the state machine becomes not restartable if an exception is thrown out from it?
BR, Gabor
Which version are you using? Until 1.49, it was not possible to call start() a second time. I think your machine probably stayed in the state it was before the exception. If I understood your problem, it should be gone with 1.49. HTH, Christophe
participants (7)
-
Christophe Henry
-
Dave Abrahams
-
Gábor Márton
-
Juraj Ivančić
-
Sebastian Redl
-
Stephan Menzel
-
Steven Watanabe