From: Andreas Huber (ah2003_at_[hidden])
Date: 2004-05-28 17:25:43
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
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.
F1. UML standard 184.108.40.206 (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 (220.127.116.11) and how states are exited when a transition is made
(18.104.22.168). 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 22.214.171.124 (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."
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 (126.96.36.199) 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.
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
Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk