|
Boost : |
From: Andreas Huber (ah2003_at_[hidden])
Date: 2004-06-02 16:12:58
Darryl Green wrote:
> I have been trying to formulate a reasonable model that is a pure
> extension to UML statechart semantics to deal with this - I think I
> have. It also shouldn't break any code that currently uses boost:fsm.
> My proposal is to allow a transition action's context to be *any*
> outer state of the source state and to change the state transition
> processing to:
>
> 1) Exit and destruct out to (but not including) the transition
> action's context or the ICOS, whichever comes first.
>
> 2) Execute transition action.
>
> 3) Exit and destruct any remaining states out to the ICOS.
>
> 4) Enter destination states.
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.
> This doesn't allow access to the state being exited in the transition
> action, but it does allow the action of a transition that exits an
> outer state to access that state's context "on the way out".
I don't understand. With your proposal above the transition action can
access any state that is exited as the result of the transition, even the
innermost state. Simply make the action a member of the state you want to
access and you're done, right?
> I am
> finding this to be more frequent/useful than access to an individual
> simple state's context anyway. Where the requirement is really to
> access an individual state the cost is a split into an inner and
> outer state.
Still confused...
> 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.
>> 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.
>
> Intuitive to you ;-)
I'm not sure. I'd expect that an FSM expert would immediately know what's
going on when I say "in-state reaction immediately followed by a
transition".
> I don't think it is too bad in simple cases, but
> it gets ugly when what I really wanted was more like what is
> described in my proposal above (access to an outer state's context),
> which I've found my usage evolving into, more often than not.
If you only need to access the outer states' data members, why not give this
outer state an in-state reaction, as I have written in my second followup to
your message? I'll quote here for clarity:
Andreas Huber wrote:
> One more and probably the strongest argument pro your pre-exit trick
> is that it also works nicely with a transition originating at an
> outer state. E.g.
> in the StopWatch state chart you want to do something special when a
> EvReset event is received and the machine is currently in the Stopped
> state. You'd then add a Stopped in-state reaction, which does what it
> has to do with whatever Stopped data members and them simply calls
> forward_event(). The event will then trigger the transition
> originating at the Active state.
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.
>> 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.
>
> I agree with this. I overstated my case somewhat. I think you will
> see in my later description of exception handling that I do conceed
> that exit is critical in some ways. I'm having trouble clearly
> delineating the features of exit that need special handling, and felt
> that this case was one that didn't. I think this is just a very
> slight difference of opinion on how to decompose the features of
> exit, and not of any great importance overall.
>
Agreed.
>>> 3) I think this is a red herring
>>
>> Do you mean a logical fallacy?
>
> I think it is in the sense that you can't infer that because some
> aspect of A is the same as a corresponding aspect of B that A is B.
Ok.
>> I think this *is* quite important. I believe not sticking to UML
>> would lower the acceptance of the library a lot.
>
> I didn't mean to say that the library shouldn't stick to the UML spec
> where it is applicable. I don't think that it is applicable in the
> area of object lifetime management because in UML states are not
> objects.
True. Perhaps a better way to put it: One should be able to model UML state
charts with boost::fsm, with as few detours/workarounds/surprises as
possible.
In any case, UML statecharts (or Harel statecharts) aren't
> the only/ultimate/most rigourous definitions of FSM semantics. It
> isn't hard to find a plethora of possibly more rigorous FSM models
> (I'm not making any claims for them, I have just seen a lot of
> citations - not read all the papers). One thing that is not so easy
> to find is anything regarding integrating FSM as an aspect of a
> multi-paradigm programming language like C++
Agreed.
> (yuk - reads like an add
> - lucky I didn't
> say "rich API" as well...).
:-)
>> 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.
>
> Precisely. I don't mean that it can't be done, only that the C++ view
> that what a ctor does a dtor "undoes" doesn't always hold in other
> contexts, why in this one? I suggest that users have to provide an
> exit() to implement an exit action. If you think it will help Java
> programmers, I guess you could call it dispose() -)
LOL
>>> 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.
>
> No I didn't I said innermost outer state, (IOS) not ICOS.
Sorry, I guess it was a little late.
> A better
> term might have been immediate outer state (of the state that had
> exit fail). I'm going to write IOS from now on, and hope that the
> definition is clear enough.
It certainly is. BTW, in the docs I always use "direct outer state", I think
that is even clearer (at least for my Swiss German-thinking brain).
> I reached my proposed solution after discounting the approach of
> entering inner states to do recovery, because it won't work for an
> innermost state with an exit action that can fail (because the
> recovery state would then be the innermost state, making the original
> state unstable). I guess some
> ephemeral "recovery state" concept could deal with this and only
> construct/enter an inner recovery state when needed, otherwise
> treating the recovery state's IOS as the innermost state. I haven't
> checked to see how boost::fsm behaves if you don't specify the
> initial inner state (illegal in UML afaik).
In boost::fsm, if you don't specify an inner initial state for state A then
A is automatically an innermost state and can therefore not have inner
states. That is, trying to specify A as the Context of another state will
lead to a compile time error.
>>> 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?
>
> That was my first preference, but it created the problem of ephemeral
> inner recovery states.
It wouldn't if we required that exit() failures must always be handled in
the state the caused the failure, see below.
> So instead, I proposed considering exit to be "done"
> but "failed". This seems reasonable enough - analogous to exception
> handling where you aren't in the block that threw any more, you are
> in the catch block. This means that after the exit action runs and
> fails, the state's destructor must run (and succeed - don't throw in
> the dtor or we are doomed obviously).
>
>> How can we then enter a sibling (in StopWatch e.g. Running
>> and Stopped are siblings)?
>
> I think the above explained it. If we were in Running, and its exit
> action failed, this would have to be reacted to by Active, which
> could then make a transition to stopped (or more likely, a 3rd
> sibling that dealt with recovery from failing to exit Running).
Hmmm, all this is very problematic because in the transition to the recovery
state the IOS state is exited although its inner state has never been
successfully exited. I'd very much prefer to stay in the state that we just
failed to exit, recover and then retry to exit the state. Everything else
seems very questionable to me. Moreover, how many times do we retry? Does it
even make sense to retry? The more I think about this the more I believe
that pretty much the only sensible thing to do is to destruct the remaining
state objects and propagate the exception to the state machine client...
Regards,
Andreas
Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk