From: Darryl Green (darryl.green_at_[hidden])
Date: 2004-06-03 01:17:24
Andreas Huber <ah2003 <at> gmx.net> writes:
> Darryl Green wrote:
> > My proposal is to allow a transition action's context to be *any*
> > outer state of the source state and to change the state transition
> > processing to:
> > 1) Exit and destruct out to (but not including) the transition
> > action's context or the ICOS, whichever comes first.
> > 2) Execute transition action.
> > 3) Exit and destruct any remaining states out to the ICOS.
> > 4) Enter destination states.
> Ok, this doesn't break any existing client code. But I think it is also
> surprising for FSM folks because UML clearly defines that all exit actions
> are called before the transition action is called. But I'm not strictly
> against such a change, I just have to think about this some more.
The actual invariants implied by the use of composite states and entry/exit
actions are not violated by this modification. Repositioning the transition
action within the same overall sequence doesn't break anything, except
possibly for the transition action itelf. I say this because the exit actions
should only depend on all inner exits having run. A transition action can
normally depend on all exit actions having been run. However, you only get
what you ask for. fsm::transition< event, destination, action_context, action
> clearly specifies the context as part of the transition action. As the
action is a member of that context/state object, it can hardly expect (in fact
it requires that this not be the case) that state's exit actions to have run
when the action runs. It all seems logically consistent to me.
> > This doesn't allow access to the state being exited in the transition
> > action, but it does allow the action of a transition that exits an
> > outer state to access that state's context "on the way out".
> I don't understand.
Sorry. Because I came up with this while trying to think of a model that was
consistent with "exit before transition action" I didn't think about it
allowing the case where nothing was exited before the action. You are right,
and I was confused.
> > There is afaiks no impact on exception handling (I don't think it
> > changes the docs, but it my change the implementation - I haven't
> > checked). If the transition action fails, the exit actions up to the
> > ICOS should run, then the failure reaction is searched for from ICOS
> > out.
> I'll have to change the implementation to run exit() and destructors when an
> exception is propagated from the transition action.
> Also when this happens we have a major problem if one of the called exit()
> functions throws as this would inevitably abort the program.
I think you could allow exit() failure, but no failures allowed in the "exit
failure handler". This is probably a reasonable rule in any case (see below).
> I'm not sure. I'd expect that an FSM expert would immediately know what's
> going on when I say "in-state reaction immediately followed by a
Well, I'd wonder how you made an in-state reaction be "immediately" followed
by another transition. But that might be my hardware background showing...
> If you only need to access the outer states' data members, why not give this
> outer state an in-state reaction, as I have written in my second followup to
> your message?
Ok. That would work. It makes the statechart "look funny" (transforms it in a
way that I would never do if not for this "feature").
> I'm also unsure why you think that your pre-exit trick leads to ugly code. I
> guess an example would make this a much clearer.
It just leads to a state machine that is less readable than it would be if
everything was in nice neat transition declarations. The custom reaction isn't
ugly - just obfuscating because its semantics aren't visible.
> > A better
> > term might have been immediate outer state (of the state that had
> > exit fail). I'm going to write IOS from now on, and hope that the
> > definition is clear enough.
> It certainly is. BTW, in the docs I always use "direct outer state", I think
> that is even clearer (at least for my Swiss German-thinking brain).
Direct outer state it is then (but I'll keep using IOS in this thread).
> >>> I would envisage using such a mechanism by making the transition to
> >>> a recovery state used only for that purpose. The recovery state, as
> >>> a sibling of the failed state has access to all the context the
> >>> failed state had (except for the now destructed failed states own).
> >> We have never left the state whose exit action has thrown, right?
> > That was my first preference, but it created the problem of ephemeral
> > inner recovery states.
> It wouldn't if we required that exit() failures must always be handled in
> the state the caused the failure, see below.
> > So instead, I proposed considering exit to be "done"
> > but "failed". This seems reasonable enough - analogous to exception
> > handling where you aren't in the block that threw any more, you are
> > in the catch block. This means that after the exit action runs and
> > fails, the state's destructor must run (and succeed - don't throw in
> > the dtor or we are doomed obviously).
> >> How can we then enter a sibling (in StopWatch e.g. Running
> >> and Stopped are siblings)?
> > I think the above explained it. If we were in Running, and its exit
> > action failed, this would have to be reacted to by Active, which
> > could then make a transition to stopped (or more likely, a 3rd
> > sibling that dealt with recovery from failing to exit Running).
> Hmmm, all this is very problematic because in the transition to the recovery
> state the IOS state is exited
No it isn't. The failed_exit reaction needs to redirect a partially completed
transition, not start a new one. I didn't explain this properly. Active is the
IOS. We try to exit Running, it fails, so we destruct running, but not Active.
Active handles the failure by entering Stopped. Active is never
> although its inner state has never been
> successfully exited. I'd very much prefer to stay in the state that we just
> failed to exit, recover and then retry to exit the state. Everything else
> seems very questionable to me. Moreover, how many times do we retry? Does it
> even make sense to retry? The more I think about this the more I believe
> that pretty much the only sensible thing to do is to destruct the remaining
> state objects and propagate the exception to the state machine client...
Consider state S which has a direct outer state O. A accumulates some data to
be written to disk. It stores it in a buffer owned by O. On exit from S, the
exit action should write the buffer to disk. However, the disk is full, so the
exit action fails. We make a transition to the recovery state R, which does
something (prompts user, starts deleting stuff, whatever) and exits when some
criteria is met (user clicks on try again, some timer expires?). At this point
R, which has an exit action exactly the same as S, may itself fail exit.
Potentially this could go on forever (R repeatedly handling its own failure).
Perhaps it is at this point (when the failed_exit handler state itself fails
exit) that the state machine should be destructed etc.
Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk