
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 transition".
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 exited/destroyed.
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. Regards Darryl.