
Darryl Green <darryl.green <at> unitab.com.au> writes:
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.
I sort of agree, but I think that it's a bad idea to change the behavior of fsm::transition and simple_state<>::transit<>(). What do you say about the following: namespace boost { namespace fsm { template< class Event, class Destination, class InStateReactionContext, class void ( InStateReactionContext::*pInStateReaction )( const Event & ) class TransitionContext = unspecified, void ( TransitionContext::*pTransitionAction )( const Event & ) = unspecified > struct compound_transition { // implementation-defined }; } } This is slightly different from your proposal but I think it is clearer and therefore more easily understood. All exit actions are always called after the in-state reaction and before the transition action. IUC, then you wouldn't normally need the last two parameters, right?
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,
Definitely not in this particular situation. This would be the same as allowing a destructor to throw. I know I said this before and people rightly argued that I'm wrong. But here we really have the same situation because a second exception is thrown (from exit()) while we are *unwinding* from a first exception (thrown from the transition action). Now you suddenly have two exceptions. Which one are you going to handle? Since you can only catch one of them the other one is inevitably lost. I think throwing exit actions make sense only when you propagate *all* exceptions to the state machine client. As soon as you want to be able to handle exceptions in the state machine itself you very quickly run into logical problems similar to the one above, *if* you allow exit actions to throw. For example, suppose that in StopWatch there is an additional transition from Active to a new state Error. If the entry action of Stopped throws, then the resulting exception_thrown event leads to a transition to Error. So far so good. What if the exit action of Active throws? We have one exception pending (since Error has never been reached we never had the chance to actually handle the exception) and now an additional exception is thrown. What do we do now? We can bail out at this point an propagate the second exception to the state machine client but we still have lost the first exception. *Bad* *idea*! Note that we don't have this problem if we never attempt to handle the exception in the state machine but propagate it out to the client instead because we wouldn't call any exit actions in this case (the library currently does but it wouldn't if we had exit()). Since exit() actions are not called when the state machine is later destructed we won't run into the same problem. Thoughts?
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.
Ok, I think compound_transition<> should solve this problem rather nicely. Regards, Andreas