
Darryl Green wrote:
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,
Right. A very nice way to solve the problem, BTW.
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.
It isn't bizare, it's only surprising because: 1. It allows that states are accessed when their exit() function has already run. People putting their exit code into exit() might not expect that the transition action might later again access the state. They could assume that it's the other way round. 2. It makes entry/exit asymmetrical. A programmer changing a state having only an entry action (i.e. a ctor) could very quickly come to the conclusion that he needs to add a destructor for an exit action. Both make the library a bit less intuitive to use. However, this is not the biggest problem. I'm much more concerned about the fact that your proposal breaks existing client code (currently, there are at least 2 independent projects using boost::fsm) to achieve something that can already be done. In FSM terms your "pre-exit" trick is an in-state reaction immediately followed by a transition and I think it is much more intuitive to do it this way than the way you propose. Plus, it doesn't even require more code. Don't get me wrong, I'm not strictly against breaking existing client code, it just doesn't seem justified in this case.
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.
Sorry, this has become a bit of a reflex lately.
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.
Right.
2) Exit actions must run because their side effects :-) can be important - don't forget to put those rods back in the reactor....
Right.
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".
Right.
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.
Exit actions are more critical than others in the current implementation because the state machine can be terminated pre-maturely at any time (state_machine<>::terminate()). UML clearly defines what termination means. Now, you could rightly argue that state_machine<> should not have a terminate() function as this can easily be implemented with a reaction in an outermost state, which is triggered by a user-defined event. I'm not saying this is a problem at all, I just want to show the implications of making exit actions equal citizens. One more thing that makes exit actions different is that an entry action failure or transition action failure often leads to states being exited before the problem can be handled.
3) I think this is a red herring
Do you mean a logical fallacy?
- 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
I think this *is* quite important. I believe not sticking to UML would lower the acceptance of the library a lot.
and use a rigidly defined language mechanism to implement it is very inflexible.
I agree that using destructors makes it slightly less flexible.
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.
In such languages RAII is achieved with a separate Dispose() function. If I was to port the library I'd have users provide Dispose() to implement an exit action.
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.
Ok so far, this is certainly doable.
Restrict the allowable transition to be to an inner state only (once again, avoids any further exit actions).
I don't understand this. You said that the reaction should be searched in the ICOS. If you make a transition from the ICOS to a state that is an inner state of the one whose exit action failed then the ICOS and all its inner states must be left (again this behavior is required by UML), right?
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 think it *is* genuinely different. A failing exit action prevents the calling of the exit actions of outer states. Once an exit action has failed you can never leave the state again, *unless* you find a way to somehow recover and then retry exiting the state. If you cannot handle the problem, the only thing you can do is to enter inner states or abort everything and rethrow the exception. A failure of the other actions never brings you in a situation where you are *forced* to either retry the previously failed action or bail out completely.
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? How can we then enter a sibling (in StopWatch e.g. Running and Stopped are siblings)?
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.
Hmm, I don't remember why I wrote "see above". I guess it wasn't that important. Anyway, you are right with this observation. However as I've explained above ;-), I'd rather have this handled with your pre-exit trick. [example snipped] Ok, I see (finally ;-)) that there might be a use case for exit(), namely the one that you don't want to run certain actions on destruction, but you do want to run them on termination. exit() also doesn't break any existing code, as destructors keep acting as they do now. We have to define more thoroughly what happens when exit() throws. Regards, Andreas