
David Abrahams wrote:
Andreas Huber <ah2003@gmx.net> writes:
If anything, I'd want to prohibit exceptions in entry actions, since those can lead to the FSM being in a state from which it's impossible to continue.
Interesting, it seems "failure-proof" entry actions would be an inevitable consequence when you allow failing exit actions.
Not AFAICT. Even if you don't allow exit actions to fail, how will you recover from a failed entry action?
Please have a look at the state chart at http://tinyurl.com/yrbee Let us assume that the lower orthogonal region is entered first (in boost::fsm this would be region 0). When we initiate this state machine, The states are entered in the following sequence: A, B, (exception is thrown while trying to enter C). When that exception is thrown then, as you rightly point out, we have a state machine that is in an invalid state. To bring this machine back into a stable state, boost::fsm now does the following: 1. The outermost unstable state (see definitions.html) is determined. In this example this is A, because the state machine never had the chance to enter the second orthogonal region. 2. An exception_thrown event is allocated on the stack 3. The state machine searches for a reaction for the exception_thrown event. It starts by examining the outermost unstable state. If that state does not have an appropriate reaction, it works its way outward. 4. Since A does have a reaction for the exception_thrown event, that reaction is executed. 5. The execution of said reaction leads to the exit of B, the exit of A and finally to the entry of E. Do we agree that the state machine is now in a valid (stable) state (assuming that the entry action of E did not throw)? If the upper orthogonal region is entered first, the entry sequence is as follows: A, D, B, (exception is thrown while trying to enter C). Now B is the outermost unstable state and consequently B is exited and F entered. Again, we're stable, right? In both cases if the state machine hadn't found a suitable reaction then it would have been terminated (by exiting all remaining states) and the original exception would have been propagated to the state machine client. In both cases if another exception had been thrown during transition to either E or F, the state machine would have been terminated and the new exception would have been propagated to the state machine client. I think it is obvious that this algorithm will ensure under *all* circumstances that the state machine becomes stable again after having encountered an exception (a terminated state machine is stable too). This is only possible when exit actions cannot throw. If you allow exit actions to throw, even if you prohibit throwing entry actions, this is possible only if you accept that a call to an entry action might not be matched with a corresponding call to an exit action.
I am assuming here that when going A->B, you exit A before you enter B.
Right.
At least with exit actions, you can stay where you are and keep all your FSM's abstract invariants intact.
As I have already pointed out: Only if your machines are flat and non- orthogonal, right?
I don't understand what you mean. Certainly w.r.t. "flat", only one exit action in a stack of nested states can be allowed to fail.
Please have a look at the state chart again. We now assume that nothing fails upon initiating the state machine (i.e. the C entry action does not exist). Let us further assume that the state machine is handed a normal event, EvX and the transition to E has been selected (i.e. the transition trigger is EvX and not exception_thrown). Before we can enter E, we first need to exit C, B, D and A in this sequence. If exit actions are allowed to throw and if either the B, D, or A exit action throws, then the state machine is now in an invalid state and there's no way how we can bring it back into a stable state again, right?
My approach is almost symmetrical to yours, isn't it? Since I allow failing entry actions I am forced to disallow failing exit actions.
I don't understand that. You can't neccessarily re-enter a valid state if you've already exited and entry throws an exception.
As I have pointed out above, if the entry action of either E or F also throws then, yes, all is lost and we need to terminate the machine an propagate the exception to the state machine client. However, one would certainly try to make sure that the entry actions of E and F will not throw, right?
With your approach exit actions can fail what forces you to disallow failing entry actions. In both approaches allowing the failure-proof action to suddenly fail can result in an FSM having an invalid state from where it is impossible to recover.
However, I consider my approach superior because I can guarantee that for every successfully completed entry action there will always be a matching call to the exit action. You cannot give such a guarantee.
If this is needed only rarely I'd rather have users work around the problem by setting a boolean and posting an error event in a failing exit action and then testing that boolean in all following exit actions. Eeeeew, that's gross. Sorry, but that just smells like an awful hack.
I very much agree. But don't we have to do the same when we want to signal a destructor failure?
Circular argument again ;-)
Sorry, I don't get it. Why exactly is that circular? I'm not talking about state destructors, I'm talking about C++ destructors in general. If you happen to be in the situation of somehow needing to signal a destructor failure to the outside world you are in the same situation as someone that needs to signal a exit action failure in a FSM framework that does not allow to throw from exit actions (whether the FSM framework maps exit actions to destructors or exit() functions is irrelevant). My argument was that in C++ you can already find yourself in a situation where signaling a failure is, well, quite difficult. So, it isn't all that surprising that as a user of a library you might find yourself in more (or other) such situations. While I do see that one does not exactly follow from the other (it was never my intention to suggest that) I really don't see why this reasoning is circular. Regards, Andreas