Actor Framework Discussions

cancel
Showing results for 
Search instead for 
Did you mean: 

Actor Principles, Complexity and Class Count

Daklu wrote:

Just as an FYI, I'm calling it "Agile Actors" now.  Presumptuous?  Perhaps.

Or AA for short?  Maybe your next presentation should be 'Agile Actors, a 12 step programing method'. 

-John
------------------------
Certified LabVIEW Architect
Message 11 of 47
(1,594 Views)

Jed394 wrote:

There seems to be a debate about giving any actor(no mater what framework) the ability to message itself and put commands on its own queue. 

I don't know if "debate" is the right word.  I advise against it because I don't think it scales well in practice, but in theroy it can work fine.  In small doses it can be simpler than the alternatives.  The danger is in not refactoring away from it soon enough.

(And to clarify, it's really about having a message handling loop send messages to itself.  Helper loops sending messages to the MHL can be considered "an actor sending messages to itself," and that is fine.)

0 Kudos
Message 12 of 47
(1,594 Views)

Daklu wrote:

Jed394 wrote:

There seems to be a debate about giving any actor(no mater what framework) the ability to message itself and put commands on its own queue. 

I don't know if "debate" is the right word.  I advise against it because I don't think it scales well in practice, but in theroy it can work fine.  In small doses it can be simpler than the alternatives.  The danger is in not refactoring away from it soon enough.

(And to clarify, it's really about having a message handling loop send messages to itself.  Helper loops sending messages to the MHL can be considered "an actor sending messages to itself," and that is fine.)

Hmmmm, this sounds very problematic.  What if you need to have messages that propagate additional actions within an actor at the same time those actions could be performed atomically?  As my example demonstrates, you might want to repeat one step later without repeating all of them.  And you might want to make the actor available to respond to incoming messages between these steps.  If we were to 'lock-up' the actor while it performed all the steps in order then we could cause messages to stack up.  Isn't it better to have the actor hand off the work to the helper and then wait for messages while the work is done?  And then when the helper messages completion, we take the next steps to hand off work again without embedding the business logic within the completion message from the helper.

Perhaps the solution would be a generic sequencer message.  You could pass it a sequence to perform, it would call the helper to do each step and the helper would call it at completion.  The sequencer would then decide what to do next and call the helper again or stop if the sequence is complete.  Again, I am searching for a best practice generic solution that can be applied to many simular situations.

-John
------------------------
Certified LabVIEW Architect
0 Kudos
Message 13 of 47
(1,594 Views)

jlokanis wrote:

It seems to me that if the Action Complete message that S sends to M needs to read the flag and then decide to execute the next step in the init sequence or not, that is ebedding the business logic across each Action Complete message.

It is, but that's not the same as embedding business logic in each action, which is much more problematic.  The Action Complete message handler defines the actor's behavior when that message is received.  That's where the code should be implemented.

Since you're using command-pattern messaging your business logic is distributed across a bunch of different classes instead of on a single block diagram.  (Reason #419 why I prefer name-data messaging.)  If you want to centralize that logic, I'd probably create a sub vi rather than implement everything in the Init message.  In my mind, receiving the Init message is the event that starts the process.  Controlling the initialization process is someone else's responsibility.

0 Kudos
Message 14 of 47
(1,594 Views)

jlokanis wrote:

Hmmmm, this sounds very problematic.  What if you need to have messages that propagate additional actions within an actor at the same time those actions could be performed atomically? 

I usually design my APIs so there is no overlapping functionality.  I don't like exposing messages (or methods, for non-actors) that do the same things as other publicly exposed messages.  That creates multiple 'levels' of your API and causes a lot of confusion.  I think an API is far easier to understand when there is only a single level and each call is orthogonal.  If I'm going to have multiple levels in my API--such as a high level "easy" API and a more detailed "advanced" API--then I design it with that in mind.  But I still make sure there is no overlapping functionality in any single level.

For me, messages that want to enqueue other messages is a code smell.  It's telling me that message shouldn't be exposed as part of the actor's API.  You have M's Init message executing A, B, and C, but also want to be able to call each of them individually.  So now you're trying to figure out how to manage the initialization process.  Why not just expose A, B, and C and make the calling actor responsible for invoking that sequence of messages?  Or invoke A, B, and C when you create the actor prior to spinning up the main message handling loop?  (FWIW, my actors almost never expose public "Init" messages.)

0 Kudos
Message 15 of 47
(1,594 Views)

jlokanis wrote:

One area that I am having the most trouble with is the 'rule' that an actor should never be blocked.

...

Lets take the example of an actor that has an initialization process.  This process consists of 3 steps. 

Initialization is not a good example.  Initialization is putting oneself in an initial state; the initial valid, consistent state.  One should never handle an external message when in an inconsistent or invalid state, and "partially initialized" is such a state, by definition.  You should either block during initialization, or redefine "initialized" to be an earlier, quicker operation, with later steps being automatically-sequenced operations.  Personally, I'm happy to block during initialization.  Let the User watch a Busy Cursor or a Progress Bar or something.

0 Kudos
Message 16 of 47
(1,594 Views)

jlokanis wrote:

Perhaps the solution would be a generic sequencer message.  You could pass it a sequence to perform, it would call the helper to do each step and the helper would call it at completion.  The sequencer would then decide what to do next and call the helper again or stop if the sequence is complete.  Again, I am searching for a best practice generic solution that can be applied to many simular situations.

I suppose you could implement it that way, but it strikes me as more complicated than necessary most of the time.  When you have a sequence of actions that need to be executed but you want to maintain interruptability, you have two basic options, both requiring two loops, which I'll refer to as the calling loop (controls when the sequence starts) and the execution loop (executes each step in the sequence.)  For the purposes of this example it doesn't matter if the loops are actor message handling loops or helper loops.

The first option is to place the sequencing logic in the execution loop.  This follows the traditional QSM implementation patterns with the execution loop adding states to the queue according to its internal logic.  This *can* work okay as long as the following conditions are met:

  1. There is only one caller
  2. The caller always waits until it receives a "Ready" message from the execution loop before sending any non-interrupt messages.
  3. There is only one interrupt/priority message available--Exit--and it is enqueued at the front of the queue.

The calling loop sends a "StartSequence" message and the execution loop goes through the entire sequence of actions, returning a "Ready" message to the caller when it can start a sequence again.  (As much as I've blasted QSMs in the past, they can be used successfully as helper loops managed by a MHL--and I still advise a high degree of caution if you're going to use one.)

The second option is to place the sequencing logic in the calling loop.  In this implementation the execution loop does not add any states to its queue and it has no knowledge of sequences.  The caller sends a message to the execution loop requesting an action.  The execution loop returns a message to the caller when the action is complete.  The caller takes that information, decides what the next step will be, and sends the appropriate request to the execution loop.  Rinse and repeat until the sequence is complete.  This is essentially what you have shown in the diagram in your original post.

These are the only options I am aware of for safely executing a sequence of actions while maintaining interruptability and scalability in Labview.  Everything else is a derivative.

The 'Generic Sequencer Message' you have described is a derivative of the second option.  The difference is instead of embedding the sequencing logic directly in the calling loop as would be done with name-data messaging, you've abstracted it behind a new kind of message class.  That may provide the benefit of centralizing the sequence logic in command-pattern messaging system, but it also might just push everything down an abstraction layer without providing any benefit.  It's hard to say without actually implementing it.

One thing you didn't specify is who is responsible for defining the sequence logic, the caller or the caller's caller (the one sending the sequence message to the caller.)  If the caller defines the sequence, then to the caller's caller a sequence message looks exactly like any other message, and I'm left wondering why bother with an extra layer of indirection.  If the caller's caller can define arbitrary sequences, then I'm wondering what role the caller plays in all this--why not just have the caller's caller interact with the execution loop directly?

I think there may be specific situations where a generic sequence message could make sense, but I'm not at the point where I would consider it a best practice or general solution.

0 Kudos
Message 17 of 47
(1,594 Views)

Why not just expose A, B, and C and make the calling actor responsible for invoking that sequence of messages?  Or invoke A, B, and C when you create the actor prior to spinning up the main message handling loop?  (FWIW, my actors almost never expose public "Init" messages.)

Well, for one reason, I do not want the calling actor to need to know how to init this actor.  But I have on occasion used the concept of having the actor init itself before starting the message handler.  The problem with that is error handling.  I have my message handler deal with errors.  Each message can simply return an error if needed and the message handler then calls that Actor's error handler to decide what to do with the error.  If I init outside of the handler and get an error on init, I need to add some code to deal with the error.

Sometimes I have had the actor send itself the init message before starting the handler so it is the first message the actor gets.  But that is not certain since the actor can receive messages the instant it starts to exist, before its handier is running.

So, if your init of an actor requires you to read a config file as part of the process and then while your actor is running, the user could take an action that requires re-reading that config file, how do you accomplish that without code redundancy?  You need a message that says: 'read config file' and you need a message that says 'do a bunch of stuff including reading the config file'.  Oh, and the config file reading is in a sub-actor since you don't want to block the main actor while that is being done (let's assume for this example that reading the config file can take an unknown amount of time and can result in errors).  So, you cannot inline the config file read, you need to message the sub-actor and then later when it messages you back, you need to continue the init process.  Without the message handler running, there is no way to have the sub-actor do A, B and C and tell you when they are done.

I think another difference is I don't think of an actor as an API.  I think of it as a processing engine.  It can receive messages from the outside to do things but it also needs to do things internally and since a message is the only way to 'Do' a thing, all operations need to have a message.

-John
------------------------
Certified LabVIEW Architect
0 Kudos
Message 18 of 47
(1,594 Views)

drjdpowell wrote:

jlokanis wrote:

One area that I am having the most trouble with is the 'rule' that an actor should never be blocked.

...

Lets take the example of an actor that has an initialization process.  This process consists of 3 steps. 

Initialization is not a good example.  Initialization is putting oneself in an initial state; the initial valid, consistent state.  One should never handle an external message when in an inconsistent or invalid state, and "partially initialized" is such a state, by definition.  You should either block during initialization, or redefine "initialized" to be an earlier, quicker operation, with later steps being automatically-sequenced operations.  Personally, I'm happy to block during initialization.  Let the User watch a Busy Cursor or a Progress Bar or something.

So, what if you want to respond to a cancel or exit message while the initialization process is running?  If you have blocked the actor, it cannot receive those messages.  And if you decide to enable the receiving of messages, then you must break your init process into sections and check for cancel between those.  You can either have the actor message handler perform those steps as separate messages, allowing for the cancel to occur between steps or you can pass the entire process to a helper and let the actor wait for either the completed message or a cancel.  If canceled, you need to have a means of stopping the helper mid-process.  Finally, if you want to re-use any of the init steps you need a message to tell the helper to do one of those steps and a message for it to tell you it is done.

-John
------------------------
Certified LabVIEW Architect
0 Kudos
Message 19 of 47
(1,594 Views)

Daklu wrote:

This is essentially what you have shown in the diagram in your original post.

I was really hoping that my original solution was not a best-practice.  When I diagrammed it (with the real messages and actors, not the simplified example in this original post) I could barely follow it and had a hard time explaining it to someone else.  When things get that complex, they tend to have more bugs in them.

I guess my requirements:

  • Perform a multi-step process with the ability to interrupt
  • Allow each step in the process to be re-used at a later time by the actor.

Cannot be implemented without a high degree of complexity.

-John
------------------------
Certified LabVIEW Architect
0 Kudos
Message 20 of 47
(1,594 Views)