
Andreas Huber <ah2003 <at> gmx.net> writes:
Darryl Green <darryl.green <at> unitab.com.au> writes:
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
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:
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;
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
run the
in-state reaction and before the transition action. IUC, then you wouldn't normally need the last two parameters, right?
Right. This works too. I don't really understand your objection to the transition semantics I proposed. I personally find that proposal perfectly clear (well I would), and the complication of another type of transition unnecessary, but it is of course up to you. I'm done on this one, but I would like to hear other comments - I have to agree with Rob Stewart that it is a bit too quiet around here (though I'm not convinced that we need fsm "experts" - I'm sure there is plenty to be offered by those untainted by too much previous fsm experience).
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 don't think it is really the same as throwing while unwinding - or are you saying that the implementation would have to be such that it genuinely was exactly that? I envisioned that the transition exception would be caught and converted to an event. The event would be (must be) queued (in a queue of depth 1) while doing the exit action. If the exit threw, this would be handled immediately (in line with the exit exception proposal outlined previously), but the transition exception would *not* be lost. Only when/if the exit "recovery" completed sucessfully would the fsm be considered to be in the correct context/state to handle the original transition action failure event. Only if recovery failed would we be in real trouble - at that point the fsm would have to be "abandoned" (ie. terminated without exit).
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*!
Yes, exit failure is "special". However, I think it can be dealt with as I outlined above and in the previous post. That is, exit exception event handling must not fail.
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 not claiming any particular expertise or experience here - my first reaction, which seems pretty common, is to be very keen to avoid mixing fsms and exceptions at all. I certainly want at least the option of the simple behaviour of just abandoning the fsm and propagating the exception. If anyone else is still interested in giving serious consideration to other options, I'd be interested in continuing to try to work something out, otherwise, I'm done on this one too. I don't have any immediate need for anything more elaborate, and I'd be pretty happy if I never did.
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.
It does. Regards Darryl.