Jump to content

Dealing with State in Message Handlers


Recommended Posts

Hi guys, I'd love to get your input on this problem:

 

What is the best way to handle state dependance in a message handler, that is, when the way your message handler behaves for any given message must change depending on state? I've attached a mock up of the two ways that I've come up with to deal with, which is "better"? Or does this point to some deeper flaw in my understanding of how I should be conceptualizing these systems?

StateInMessageHandlers.png
 
Thanks in advance for your insights guys!

Cheers,
Alex

P.S. Whoops, just noticed I forgot to shift-register the state in the second example, but you get the idea!

 

post-16778-0-03765600-1371425180_thumb.p

Link to comment

I don't have a functional reason, but, from a style viewpoint, I would rather dequeue the message and process based on the state (first image) as that method reads better to me. I'm suspicious there is a reason to prefer that method beyond style, but it's not coming to me this early on a Monday.

Link to comment

The Actor Framework dispatches on message type first and then in the message handler evaluates the current state to decide what to do. This choice was forced by other aspects of the system, not something that was deliberately chosen. It has the advantage that adding/removing messages is very easy. Adding/removing states is thus commensurately harder (you have to visit every handler). You might want to decide based on which you are more likely to add/remove -- states or messages.

 

Now, my suspicion is that *most* of your messages behave the same in every state, and you'll have less duplicated code if you make the decision based on message first and then on state. Even when the code isn't exactly the same, deciding on message first gives you a way to put common code in for a message either before or after you do the state-specific work. Of course, the counterargument also holds that a given state might do work before/after every message... so the only real decider here would be the duplicated code if most messages do the same thing regardless of state.

 

Thus my lean would be toward message first, but I don't see anything fundamentally wrong with the other.

Link to comment

Thanks for the answers guys. I've built up this hodge-podge of code that is built on a good foundation, but has had so many inconsistent "need this feature now" decisions made that it's very hard to read. I'm looking for a consistent scheme of state management to settle on, good to get you guys' insights.

Link to comment

I almost always use the second example; that is, I prefer to have the message handlers inside the state rather than the state inside the message handlers.  It more naturally reflects how I think about the systems and makes it easier to inspect the code.

 

It has the added bonus that it's dirt simple to add Entry and Exit actions for any state.  I've found using Entry/Exit actions greatly simplifies my state machines.

Link to comment

Well. In context of my comments on the other thread. The message handler would always be the first example and all the state info and logic would be pushed down in the hierarchy.into subvis/classes (in your examples just "create subvifrom selection" and add the while loop memory for the state to it).

 

A few of the arguments for #1 that I would put forward (not any particular order)

1. it is cleaner and easier to read.(subjective I know, but the frame represents messages only rather than a  message and a state)

2. You can group messages in the frames (like you can with events) independently of state.

3. You can guarantee that a "dequeue" happens for every message regardless of state.

4. Less code replication.

5. Separation of responsibilities (bugs in the statemachine are separate from bugs in the message handler).

Link to comment

I usually go with something like #2. I can see some drawbacks. For instance, new messages won't be handled until states queued up due to a previous message are finished. But, currently I have found this to be best *for me* (without the inner while loop). The drawback I mentioned can be somewhat mitigated by the following: I use the state (job) queue to handle routines (startup, shutdown, reconfigure) etc; routines that you usually don't want new messages to be handled while they are happening anyways. Then I try to handle all other incoming messages in the inner case structure, right where I get them; I get the message, I do what I want with it right there. This also keeps me from having to make message data, state data, which can clutter things up fast. Adding things to your shift register just so it can be processed in a different case seems dirty.

If it is any sort of long processing state, or state that could backup the incoming messages, I dish it off to another loop and continue on. 

 

Edit: If the same processing needs to be done for multiple messages, you could have a state and just queue up that state. But I prefer to have a subVI and put it in multiple places. This keeps things clear as to "I got this message, this is what I do with it" and doesn't send other developers inherting your code on a rabbit chase, following state transition to state transition. Yes, it may cause some code duplication, but In this case I'm find with it. Again, my opinion

Edited by for(imstuck)
Link to comment
A few of the arguments for #1 that I would put forward (not any particular order)

1. it is cleaner and easier to read.(subjective I know, but the frame represents messages only rather than a  message and a state)

2. You can group messages in the frames (like you can with events) independently of state.

3. You can guarantee that a "dequeue" happens for every message regardless of state.

4. Less code replication.

5. Separation of responsibilities (bugs in the statemachine are separate from bugs in the message handler).

 

Funny.  I could use every one of those arguments as reasons why I prefer message handlers inside states.  (Perhaps with the exception of #2, 'cause I'm not sure what you mean.)

 

Earlier AQ said he suspects most messages will behave the same in all states.  Oddly, I don't find that to be the case at all.  Usually each message is only recognized in 1 or 2 states.  I suspect the difference has something to do with how I think about and create state machines. 

 

 

For instance, new messages won't be handled until states queued up due to a previous message are finished.

 

Noooooooo......  *cry*  Say it ain't so, Greg.  ("When a man is in despair, it means that he still believes in something.")

 

State machines don't have the concept of state queues.  In fact, you'll notice Alex's state enum isn't on a queue.  :P

 

 

But I prefer to have a subVI and put it in multiple places. This keeps things clear as to "I got this message, this is what I do with it" and doesn't send other developers inherting your code on a rabbit chase, following state transition to state transition. Yes, it may cause some code duplication, but In this case I'm find with it.

 

Sub VIs are one way to deal with a multiple states handling the same message in the same way.  Another way that works in some situations is to create a vi containing a message handling case structure for all your common messages and drop that in the default case of the message handling case structures of your states that have a few extra unique message handlers.  If the message isn't one of the unique ones, the common handler will process it.

 

Functionally doing that is the same as creating a MessageHandler class and subclassing it for each state.  I've done that too, but there's not much advantage to using a single-method class for each state's message handler.

  • Like 1
Link to comment
Funny.  I could use every one of those arguments as reasons why I prefer message handlers inside states.  (Perhaps with the exception of #2, 'cause I'm not sure what you mean.)
Really? Hmmm. So as an example, you would use my #5 argument to suggest that requiring to be in a certain state to be able to dequeue a message at all, means that messaging and state have been separated and are not dependent?

#2 is just adding multiple strings to a frame so it behaves similarly to adding multiple control events to a single event structure frame (same code, different messages).

Link to comment

Hmm food for thought.

 

With #2 (different message handler per state), what do you do with a message that arrives that you don't want to handle in your current state, but you do want to handle it when you're next ready to? You could:

1) Ack and request resend with some sort of delay (potentially blocking somewhere as you wait)

2) Store in some sort of buffer to be re-enqueued as part of a state transition (perhaps potential for messages to get "LOST IN SPACE")

3) Preview queue (effectively re-enqueing, likely to be CPU intensive unless you introduce some sort of blocking wait, which will in turn affect messages you DO want to handle).

Link to comment

First off, let me say that I believe using state/message or message/state isn't that important a decision.  Functionally they both do the exact same thing, though, as AQ pointed out, the decision affects how much code you need to change to add new functionality.

 

Second, I'm surprised so many people favor message/state.  State diagrams have a natural state/message relationship so I don't understand why people would want to go through the mental gymnastics to convert the state/message state model into message/state code. 

 

 

Really? Hmmm. So as an example, you would use my #5 argument to suggest that requiring to be in a certain state to be able to dequeue a message at all, means that messaging and state have been separated and are not dependent?

 

With rare exceptions, in my state machines all states have messages handlers, so there is no requirement to be in a certain state to dequeue messages.  Messages and states are inherently independent regardless of which paradigm is chosen--both of them choose a specific message handler based on the current state and the message received.

 

 

#2 is just adding multiple strings to a frame so it behaves similarly to adding multiple control events to a single event structure frame (same code, different messages).

 

Ahh... so if a message is handled the same way across multiple states you combine several states into a single frame.  I can see how that would be easier when messages do the exact same thing in many states.  As I said earlier, I don't run into that very often, and when I do there are other ways of dealing with it.  (As a matter of style, I tend to not group multiple case structure input values into a single frame.  I don't think Labview does a very good job displaying them.)

 

 

Hmm food for thought.

 

With #2 (different message handler per state), what do you do with a message that arrives that you don't want to handle in your current state, but you do want to handle it when you're next ready to? You could:

1) Ack and request resend with some sort of delay (potentially blocking somewhere as you wait)

2) Store in some sort of buffer to be re-enqueued as part of a state transition (perhaps potential for messages to get "LOST IN SPACE")

3) Preview queue (effectively re-enqueing, likely to be CPU intensive unless you introduce some sort of blocking wait, which will in turn affect messages you DO want to handle).

 

I design my state machines so messages that aren't handled in the current state are discarded.*  Often my state machines will broadcast state changes so any callers are aware of what state it is in and can react accordingly.  I dislike actors sending messages to themselves, so I wouldn't use 1 or 2.  Previewing queues leads to all sorts of trouble, so don't do 3 either.

 

(*Whenever a message is discarded I generate an "Unhandled message in <actor loop>:<message name>" debug message.  This helps me identify errors during development.)

 

In fact, asking about messages "I don't want to handle in the current state but do want to handle when I'm ready" doesn't make any sense in the way my state machines work.  Doing anything with a message--other than discarding it--is handling it.  It's not unusual for my state machines to handle messages differently depending on what state I'm in.

 

To answer the intent of your question, if the state machine were to receive a message that indicates it should execute some action in the future, I'd probably set an internal flag to trigger the action at the appropriate time rather than mess around with resending messages.  In general I try to avoid 'do something in the future' behavior.

 

------------------

 

Here's a copy of my message handling loop template.  Essentially I drop this entire loop in each case frame that defines a state.  The default case is for unhandled messages.

 

post-7603-0-31107100-1371652720_thumb.pn

MessageHandler.vit

Link to comment
Ahh... so if a message is handled the same way across multiple states you combine several states into a single frame.

 

No. And I think this is where we view things fundamentally differently perhaps. In my world, messages have nothing to do with states. Well. That's not strictly true since there may be one or two out of 100 that you could identify as having a particular state. Perhaps it's better to say there is no inherent link between them. Messages are just commands and the handler is just the API interface to the state-machine - the contoller. There is no different messaging strategy "just" for state-machines where messages map to discrete states. States are handled by a separate process (the state-machine). In this world, the rest of the software doesn't care about all the internal states of the module let alone be responsible for driving them. Many of the messages I send to a state-machine are things like INIT, START, STOP, PAUSE, CONTINUE etc

But if you change your terminology and say......

"Ahh... so if a message is handled the same way across multiple messages, you combine several messages into a single frame.

Then yes. That was what I was stating in comparison to events.

Link to comment

For the record, there's a truly fascinating design that I've seen implemented.

 

1. Make class Message Handler as a *by value* object. In other words, it has methods for "Handle Message X" and "Handle Message Y", and all of its data is within itself.

2. Put that class in a shift register of a message handling loop. Dequeue a message, figure out the type of the message, call the right method (how you figure out the message type -- strings, enums, dynamic dispatching, variants, finger in the wind -- doesn't matter).

3. Some messages tell the message handler to "change state". Let's say message X is such a message. In the "Handle Message X" method, instead of changing some internal variable, output an entirely new Message Handler object of a completely different Message Handler child class. That now goes around on the shift register. All future messages will dispatch to the new set of methods. Any relevant data from the old object must be copied/swapped into the new object.

 

This eliminates the case structure in the message handling of "if I am this state, then do this action" entirely.

 

Because we made the output terminal NOT be a dynamic dispatch output, you can implement this in the Actor Framework and switch out which Actor is in your loop while the actor is running.

 

This is known in CS circles as the State Pattern.

  • Like 1
Link to comment
For the record, there's a truly fascinating design that I've seen implemented.

 

1. Make class Message Handler as a *by value* object. In other words, it has methods for "Handle Message X" and "Handle Message Y", and all of its data is within itself.

2. Put that class in a shift register of a message handling loop. Dequeue a message, figure out the type of the message, call the right method (how you figure out the message type -- strings, enums, dynamic dispatching, variants, finger in the wind -- doesn't matter).

3. Some messages tell the message handler to "change state". Let's say message X is such a message. In the "Handle Message X" method, instead of changing some internal variable, output an entirely new Message Handler object of a completely different Message Handler child class. That now goes around on the shift register. All future messages will dispatch to the new set of methods. Any relevant data from the old object must be copied/swapped into the new object.

 

This eliminates the case structure in the message handling of "if I am this state, then do this action" entirely.

 

Because we made the output terminal NOT be a dynamic dispatch output, you can implement this in the Actor Framework and switch out which Actor is in your loop while the actor is running.

 

This is known in CS circles as the State Pattern.

 

Elimination of the case structure isn't that important. The encapsulation of the state-machine is the important bit which is what you have achieved but It is only the equivalent of "create Sub VI" on the case. From your description, you are still "driving" the machine with external, module specific messages which causes you to require the application state-machine (execution engine or sequencer) to know what module specific messages to send and in which order. That's a dependency that I don't like.

 

So. Keep the class and keep the case structure and hard-code the "Message X" and "Message Y" in a couple of frames (probably multiple hard-coded messages in one frame)  and we are  back to the API. The execution engine only has to worry about application stuff and not what language the state-machines speaks (I just feel that if the messages are hard-coded, then there isn't any point to them).

 

At that point, you can rationalise the interfaces to the modules (same messages) and swap entire modules in and out with the same execution engine (same as switching out the actor, I suppose) OR swap the execution engine with another. The end result of breaking message interdependence is is that you get swappable modules, swappable execution engines and swappable user interfaces and whole sections of a project become reusable (which is why I'd love to see the project manager cope with nested projects).

  • Like 1
Link to comment
  • 4 weeks later...
Noooooooo......  *cry*  Say it ain't so, Greg.  ("When a man is in despair, it means that he still believes in something.")

 

State machines don't have the concept of state queues.  In fact, you'll notice Alex's state enum isn't on a queue.  :P

 

Part one of that statement is purely separating the job queue from the incoming queue, but I understand your concern.

 

Part two I noticed after I posted *facepalm*

 

I'm back looking for the silver bullet, has anyone found it yet?

Link to comment
I'm back looking for the silver bullet, has anyone found it yet?

 

If the silver bullet you're looking for is the "one implementation pattern to rule them all," then no, to the best of my knowledge nobody has found one.  I don't believe one exists.  AOD comes the closest for me, though it is much larger in scope than an implementation pattern. 

 

For your customers, you are the silver bullet.  If there was an implementation silver bullet, there'd be no need for architects.

Link to comment
If the silver bullet you're looking for is the "one implementation pattern to rule them all," then no, to the best of my knowledge nobody has found one.  I don't believe one exists.  AOD comes the closest for me, though it is much larger in scope than an implementation pattern. 

 

For your customers, you are the silver bullet.  If there was an implementation silver bullet, there'd be no need for architects.

 

I was being facetious  :P

Link to comment

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...

Important Information

By using this site, you agree to our Terms of Use.