
David Abrahams wrote:
It seems to me that part of the problem lies in how you define "recover". You seem to think that if an *entry* action fails after exiting a state there is a sensible meaning of "recover" that can always be achieved, while if a (2nd) exit action fails in the same circumstances no sensible recovery is possible. I don't understand how that can be, but as I've said many times, I'm probably missing something.
I give up. I will never convince you this way. My reasoning obviously too often assumes that the reader agrees with things that I believe are well-established in the FSM domain. Please forget everything I've said so far and consider only what follows. As the subject says, I'm only trying to argue that exit actions in state machines must never fail (i.e. that it is a bad idea to add the possibility of failure of exit actions to the behavior defined in the UML standard). Neither am I trying to justify my exception handling system nor do I assume any kind of implementation (especially not that exit actions are implemented with destructors). I do assume that the hypothetical FSM library is written in standard C++. What follows somewhat repeats arguments in another post of mine but I think it is beneficial to do so for maximum clarity. Facts: F1. UML standard 2.12.4.2 (State entry and exit): "Whenever a state is entered, it executes its entry action before any other action is executed. Conversely, whenever a state is exited, it executes its exit action as the final step prior to leaving the state." F2. The UML standard defines that all currently active states are left when a state machine is terminated. I do not quote the text here as in the UML standard this is not a single definition but consists of what a final state is (2.12.2.5) and how states are exited when a transition is made (2.12.4.6). What is called "terminating the state machine" in this post (and in the boost::fsm documentation) is called "making a transition to the final state" in UML. F3. UML standard 2.12.4.3 (Exiting non-concurrent state): "When exiting from a composite state, the active substate is exited recursively. This means that the exit actions are executed in sequence starting with the innermost active state in the current state configuration." Assumptions: A1. When a state machine object is destructed, the modeled state machine must also be terminated (i.e. the destructor of the state machine unconditionally terminates the state machine before returning to the client). Actually, the UML standard in one place (2.12.4.4) hints in this direction but it is far from clear whether this assumption is covered by the UML standard (and could thus be put in the hard facts section). A2. If an exit action fails, the state associated with the exit action has not been left. If exit actions were allowed to fail, then the following problems/inconsistencies/loopholes would arise: P1. Since C++ destructors must not throw and cannot return a value, an exit action failure cannot be propagated up the call chain to the caller of the state machine destructor. P2. A state machine termination can involve the calling of multiple exit actions (F2). If one of these exit actions fails, the state associated with the action has not yet been left (A2) and the termination process cannot continue because F3 defines the order in which the states must be left. Neither can it abort the termination and return to the client normally as that would violate A1. Conclusion: Since P1 & P2 are very directly caused by failing exit actions, I conclude that state exit actions must not fail. I realize that I have only provided reasoning for when a state machine is destructed and not for its normal operation. So one could argue that only those exit actions that can possibly be called from the state machine destructor must not throw. However, sometimes state machines need to be destructed pre-maturely (e.g. a human initiated a system shutdown) and the caller of the destructor has no means to bring the state machine into the "right" state before destruction. So, pretty much no exit action should be allowed to fail. I realize that the above reasoning is far from water-tight, as it is based on two assumptions. However, I believe that most people proficient in the FSM domain will agree that it is safe to assume A1 & A2. Moreover, one could also question whether the UML standard should have any bearing for an FSM library that implements behavior that is not covered in the standard (i.e. exit action failures). I see failing exit actions as an addition or enhancement to the behavior defined in the standard. If such an enhancement breaks the behavior defined in the standard, I think the enhancement should be rejected. The above is the best reasoning I can currently give to show that failing exit actions do not make any sense. For me, this is more than enough to not ever consider them again (at least for an FSM library implemented in C++). I will answer questions arising from this post and I am interested in feedback whether this convinces people but I will not try to reason any further as I have run out of arguments and I think I have already invested too much time into this. Regards, Andreas