Boost logo

Boost :

From: Darryl Green (darryl.green_at_[hidden])
Date: 2004-05-31 07:07:45


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.


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