[statechart] Getting the current state from an async machine
Hi, I need a way to get the current state from an asynchronous state machine for testing purposes - something analogous to state_cast. I'll only be using this in my unit tests, so I don't mind if it's a bit cumbersome to use. Thread-safety also isn't an issue. Is there any way of getting a reference to the actual Processor instance? Thanks / Johan
Hi Johan
I need a way to get the current state from an asynchronous state machine for testing purposes - something analogous to state_cast.
I'll only be using this in my unit tests, so I don't mind if it's a bit cumbersome to use. Thread-safety also isn't an issue. Is there any way of getting a reference to the actual Processor instance?
No, there isn't. This is because any call to a member function of asynchronous_state_machine or simple_state from a different thread than the worker thread will *very* likely lead to a race condition (or a dead lock, if you try to use a mutex besides the one in fifo_scheduler). Here's one way but it is not pretty: 1. Derive a new event, e.g. EvQueryState. As members, add a member stateType of type const std::type_info & and a boost::function object, accepting a bool as the only parameter. 2. Add a global handler for unconsumed events to your FSM http://boost-consulting.com/boost/libs/statechart/doc/reference.html#unconsu... 3. In the handler check whether the unconsumed event is EvQueryState. If so, call the embedded function object with typeid(*(this->state_begin())) == stateType. This of course assumes that the machine has exactly one innermost state at all times. If you also need to detect if the machine is terminated or has multiple innermost states you'd need to pass a collection of const std::type_info *. 4. Build a utility function that accepts a fifo_scheduler, a processor_handle object and a const type_info &. The function queues a EvQueryState, waits for the callback and then returns the bool that came with the callback. Please let me know whether that works for you. HTH, -- Andreas Huber When replying by private email, please remove the words spam and trap from the address shown in the header.
Andreas, Andreas Huber wrote:
Hi Johan
I need a way to get the current state from an asynchronous state machine for testing purposes - something analogous to state_cast.
I'll only be using this in my unit tests, so I don't mind if it's a bit cumbersome to use. Thread-safety also isn't an issue. Is there any way of getting a reference to the actual Processor instance?
No, there isn't. This is because any call to a member function of asynchronous_state_machine or simple_state from a different thread than the worker thread will *very* likely lead to a race condition (or a dead lock, if you try to use a mutex besides the one in fifo_scheduler).
I'm very well aware of that, and can understand why you didn't provide a way for this from the beginning as it is generally unsafe. I'd just be using this under very specific circumstances, as I mentioned above.
Here's one way but it is not pretty: 1. Derive a new event, e.g. EvQueryState. As members, add a member stateType of type const std::type_info & and a boost::function object, accepting a bool as the only parameter. 2. Add a global handler for unconsumed events to your FSM http://boost-consulting.com/boost/libs/statechart/doc/reference.html#unconsu... 3. In the handler check whether the unconsumed event is EvQueryState. If so, call the embedded function object with typeid(*(this->state_begin())) == stateType. This of course assumes that the machine has exactly one innermost state at all times. If you also need to detect if the machine is terminated or has multiple innermost states you'd need to pass a collection of const std::type_info *. 4. Build a utility function that accepts a fifo_scheduler, a processor_handle object and a const type_info &. The function queues a EvQueryState, waits for the callback and then returns the bool that came with the callback.
Thank you! It works perfectly. The only (minor) annoyance is that I'll have to provide the unconsumed_event inside the class for testing purposes only, but that could be conditionally enabled using preprocessor directives. / Johan
Johan Nilsson wrote: [snip]
I'm very well aware of that, and can understand why you didn't provide a way for this from the beginning as it is generally unsafe.
Sorry, I figured that you already know that. I've developed a habit of overexplaining things in public forums. [snip]
Thank you! It works perfectly.
I forgot to mention that this only works portably if you are compiling with BOOST_STATECHART_USE_NATIVE_RTTI defined. It works always with GCC as state_base has a virtual dtor to avoid compiler warnings. Here's the habit again ;-)...
The only (minor) annoyance is that I'll have to provide the unconsumed_event inside the class for testing purposes only, but that could be conditionally enabled using preprocessor directives.
Right, I've wondered before whether I should provide an appropriate fifo_scheduler method for this but have so far rejected it because it would mean to add something asynchronous_state_machine-specific to the otherwise generic interface of fifo_scheduler. I'll think about this some more, ideas welcome... Regards, -- Andreas Huber When replying by private email, please remove the words spam and trap from the address shown in the header.
Andreas Huber wrote:
Johan Nilsson wrote:
[snip]
Thank you! It works perfectly.
I forgot to mention that this only works portably if you are compiling with BOOST_STATECHART_USE_NATIVE_RTTI defined. It works always with GCC as state_base has a virtual dtor to avoid compiler warnings. Here's the habit again ;-)...
Oops, I didn't define that and the code works under VC8 and GCC 4. Did I just get lucky, or is the macro defined by default? / Johan
Johan Nilsson wrote:
I forgot to mention that this only works portably if you are compiling with BOOST_STATECHART_USE_NATIVE_RTTI defined. It works always with GCC as state_base has a virtual dtor to avoid compiler warnings. Here's the habit again ;-)...
Oops, I didn't define that and the code works under VC8 and GCC 4. Did I just get lucky, or is the macro defined by default?
Ok, what version are you using? Current CVS or an earlier one? -- Andreas Huber When replying by private email, please remove the words spam and trap from the address shown in the header.
Andreas Huber wrote:
Johan Nilsson wrote:
I forgot to mention that this only works portably if you are compiling with BOOST_STATECHART_USE_NATIVE_RTTI defined. It works always with GCC as state_base has a virtual dtor to avoid compiler warnings. Here's the habit again ;-)...
Oops, I didn't define that and the code works under VC8 and GCC 4. Did I just get lucky, or is the macro defined by default?
Ok, what version are you using? Current CVS or an earlier one?
RC_1_34_0 as per 9th of August. // Johan
Johan Nilsson wrote:
Oops, I didn't define that and the code works under VC8 and GCC 4. Did I just get lucky, or is the macro defined by default?
Ok, what version are you using? Current CVS or an earlier one?
RC_1_34_0 as per 9th of August.
// Johan
Ok, investigating ... -- Andreas Huber When replying by private email, please remove the words spam and trap from the address shown in the header.
Andreas Huber wrote:
Ok, investigating ...
... it turns out this is a documentation error. state_base has virtual functions no matter whether the #define is there or not. So, typeid on a state_base object will always return the most-derived type and the method I described earlier will always work. Sorry for the noise! I'll fix the docs ASAP. Regards, -- Andreas Huber When replying by private email, please remove the words spam and trap from the address shown in the header.
Andreas Huber wrote:
Andreas Huber wrote:
Ok, investigating ...
... it turns out this is a documentation error. state_base has virtual functions no matter whether the #define is there or not. So, typeid on a state_base object will always return the most-derived type and the method I described earlier will always work. Sorry for the noise! I'll fix the docs ASAP.
Thanks! // Johan
participants (2)
-
Andreas Huber
-
Johan Nilsson