Boost logo

Boost :

From: Andreas Huber (ah2003_at_[hidden])
Date: 2004-05-25 18:24:23


David Abrahams wrote:
> Andreas Huber <ah2003_at_[hidden]> 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


Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk