
Andreas Huber <ah2003 <at> gmx.net> writes:
Darryl Green wrote:
A state is considered left (and hence is destructed) only after exit and transition actions have run. Well it buys access to the state being left from the transition action for a start.
Right, but I don't think it is justified to complicate the interface and the implementation (we'd need to add entry()/exit()) for that. As I've explained in my answer to your other post you can always move such a variables into an outer context that exists during a transition.
You seem to be missing my point. The very feature that is (afaik) unique to your fsm library is made far less useable by this design decision. The feature in question is the "state local storage". Moving it up a level is all very well but it doesn't allow RAII based on state because it must be in the ICOS or higher. I want the state local storage gone in the new state. I can either write a "pre-exit" action in a react() function, or run it in the exit (destructor), which is only useful for those actions that do not depend on the event. I would like to be able to define transitions in the state that will make them, and refer to that state's context in the action. I really don't see what is so bizarre about that. Note that I'm quite happy to leave entry() == constructor (see below).
A state-specific recovery function would make error handling much more difficult, as you have no idea what you need to do to bring the state machine back into a stable state. If you e.g. make a transition from such a state, it is not guaranteed that the machine is stable afterwards (see Exception handling in the tutorial).
You keep saying this. I have seen the exception handling in the tutorial. As far as I know your arguments re exit actions not failing are: 1) All exit actions must run. This is because every subsequent action (be it an outer exit action, the transition action, or an entry action in going to the new state) may reasonably depend on the successful execution of all prior exit actions. 2) Exit actions must run because their side effects :-) can be important - don't forget to put those rods back in the reactor.... 3) Exit actions are often be logically paired with entry actions in a way similar to C++ ctor/dtor in order to implement RAII like idioms. The resource may be something physical like "the valve" and acquisition may mean "turn on" and release may mean "turn off". My view (fwiw) on these is: 1) This is very important. See below. 2) This is a usage decision best left to the user - any action, not just exit actions, can be critical - or not. 3) I think this is a red herring - you represent state activation by constructing an object, so you have c'tors and d'tors as ideal places to put those "actions" that do map precisely to these concepts, regardless of whether you also provide exit actions. Trying to stick rigidly to the UML spec and use a rigidly defined language mechanism to implement it is very inflexible. Note I haven't mentioned anything about c'tor/entry action mapping. This is because I don't think there is any real distinction between entry actions and c'tors. There are plenty of languages (java etc) with c'tors to perform initialisation, which is similar to the purpose of an entry action, but that don't provide object destruction/destructors (at least not in a RAII compatible way), so I don't see anything evil about the use of one and not the other. I propose the following handling of failing exit actions, which as far as I can see addresses the important item (1) above. Use a mechanism essentially the same as what you have now for failure handling. If exit() of a state fails the innermost outer state is checked for a handler for the failure. If there is one, the transition is made. If there isn't, it is an irrecoverable failure (can't run any more exit actions). At this point the fsm is simply destructed (without running exit actions) and the exception rethrown. Restrict the allowable transition to be to an inner state only (once again, avoids any further exit actions). When writing a reaction for exit handle failure, be aware that as an inner exit handler has failed, certain preconditions which would exist for other transitions, don't. This is not any different to any other action failing afaiks. 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). The recovery state can attempt to perform recovery (or wait for something external to performed it) before making a transition back to the state that previously failed exit. I would be inclined to decompose a state with a possibly failing exit into an outer state with the context (state local storage) that was previously in the orignal state, and a pair of inner states, one implementing the behaviour of the orignal state, and the other used for recovery. Most likely, it wouldn't be a single state anyway (It might be only one with an exit that can fail, but it would already be part of a larger outer state) so there isn't much overhead/difficulty in doing this.
My specific concern is the use of the extended functionality to produce states which have/build significant context that (should) only exist until a transition action deals with it. I don't see that using this state context after the exit action has run is a problem. The exit action may well have added to or modified it to make the context complete, not damaged (and in particular not destructed) it.
That's true, see above.
See what above? That I could move the context out a level? See above :-) for why this doesn't address the problem as I see it.
I'm also concerned that the current fsm destruction/exit action implementation model results in an environment where I should avoid using exit actions that have any significant external effects because I don't want/expect them to be run when/if the fsm is destructed.
Concern noted. This is actually the central disagreement between me and Dave Abrahams. If you have any real-world examples, I'd be *very* interested to see them.
Well, Dave's example pretty much mirrors a real world case. The case in question doesn't use disk, but it does use persistent storage of sorts. In this case, I have a state machine which doesn't consider shutdown to be something that can be reacted to, because by design the system in question is able to be shutdown in the following ways: 1) By having the power removed at any time. 2) A watchdog may kill (with varying degrees of prejudice) the process (or anything up to a full system restart). The reaction to this should be to do as little as possible before terminating. Similarly (this isn't implemented as part of the FSM) an orderly shutdown doesn't try to complete in progress "transactions" - it abandons them, leaving the system in a slightly old, but stable, state. 3) At a finer grained level, under various circumstances, enough context external to a particular FSM instance has (possibly physically) gone before the FSM knows about it, that trying to run exit actions to update external state is just silly (and hard to handle). When this happens the FSM just gets destructed. The destructor doesn't do anything externally observeable. The required reaction to each of these shutdown conditions is identical. The fsm doesn't "know" about the shutdown. It is never notified of them (obviously, in case 1 :-). It uses exit actions only to maintain the state exit invariants that make sense in the absence of shutdown. Obviously, to allow resource cleanup and orderly shutdown to occur in 2 (sometimes) and 3 (always) destructors do run. hth Darryl.