Boost logo

Boost :

From: Andreas Huber (ah2003_at_[hidden])
Date: 2004-06-03 11:31:54


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


Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk