Actor Framework Discussions

cancel
Showing results for 
Search instead for 
Did you mean: 

ArT_Actors: A Step beyond Actor Framework 3.0?

DavidStaab wrote:

Then make him buy breakfast every day until he fixes the build.

Ooooo... I like that one.  Broken builds become something to look forward to...

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

For the record, it would have been a metaphorical smack so it would only qualify for a metaphorical aggravated assault.  Of course, I'm a metaphorical repeat offender so it would be my metaphorical 3rd strike, landing me in a metaphorical prison for the rest of my metaphorical life.  Metaphorically speaking, that is.

On the other hand, I probably would strongly consider having senior programmers review his code before being allowed to check it in just for the safety of the project.  And I wouldn't really fault the junior programmer for having done the things you described.  As a matter of fact I'd be encouraged by his creativity.  Mistakes are part of the learning process.  First we discover what we *can* do, then we discover what we *should* do.

Did I say smack him?  I meant give him a raise...

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

@AQ

Please, please, please, if you do implement up messages and down messages, make them an optional extension of the framework rather than something everybody needs deal with.  I understand the usefulness of differentiating between messages an actor receives from external sources and those it receives from subordinate actors, but not everyone wants or needs the framework to enforce that.

0 Kudos
Message 51 of 62
(1,402 Views)

AristosQueue wrote:

And one other block:

The Actor Tree

Actors form a tree. Most module communications frameworks have more of a listener/observer pattern, where a module runs and publishes information and any other module may subscribe to hear about updates, forming a cross-connected graph of listeners.

......

When a caller launches an actor, the queue to talk to that actor is returned to the caller. No other code module has any way of requesting or looking that queue up by name. Thus the actor tree is preserved by default. When one actor wants to talk to a sibling actor, it passes a message up to the common caller, and the common caller passes a message down to the other actor. In general, actors are constructed to not even know about siblings -- it is the caller that knows about the siblings and makes decisions about which messages need to be passed along. You can bypass the actor tree by creating messages that pass one actor's queue to another actor. You may choose to do this when those two sibling actors have such a strong interaction that you want them to talk directly rather than passing messages through the common caller actor. The caller actor is still in charge of lifetime issues. Short circuiting the actor is something you should do rarely and consciously. Creating any sort of general repository of names mapping to queue references is ill-advised as it reintroduces the issues described above.

Sure, having both pieces early in the learning curve should help. But I can't say for sure without seeing the entire document - it's also about how you introduce concepts (sequence) and how you emphasize what is truly important and what is secondary. Anyway, I am too contaminated with AF knowledge at this point - you may want to get feedback from a bunch of 'unspoiled souls' - LAVAg.org seems to be a perfect place for that.

I also found what you refer to as 'Template Document' (C:\Program Files\National Instruments\LabVIEW 2012\ProjectTemplates\Source\Core\Actor Framework\documentation\Actor Framework.html). I did not see it before as it comes with LV2012 (which I did not have). I would suggest uploading it to /Community/Actor Framework/Documents for folks using prior versions of LV. And adding a link to it in your White Paper. I think its helpful. I got a little confused by Message Queues section (too many details for intended audience?).

Now the important part - the choice of Caller - Actor terms:

  1. I am OK with 'Caller-to-Actor' - it properly reflects the purpose of this Queue - whoever got hands on it can send messages to that Actor
  2. 'Actor-to-Caller' is a different story:
    • The name implies it being a feedback channel to 'a caller' (as there may be multiple callers in AF) - which it is not.
    • Launch Actor.vi suggests that it is a feedback channel to the Launcher ( an Actor that has launched this Actor) - which may or may not be the case
    • It is used to deliver LastAck message from the Actor - to caller (?) or is it to Launcher (?) or maybe to Owner (?) ...
    • I got an impression (possibly a wrong one) that AF encourages using Actor-to-Caller communication channel to deliver information related to Actor life cycle to the recipient. Which makes that recipient The Owner. So why not call it an Actor-to-Owner Queue ?
  3. Enqueuer- did you, guys, ever consider how hard it is for a non-English speaker to pronounce this (let alone spell it correctly) and for others to get what that guy just have said?

I recall AQ's post on LAVAg a couple months ago saying: "It is one of our primary jobs as the authors of software to name things well. "Well" means distinctly, precisely, accurately, simply and unambiguously. "

There was a nice list of name pairs in that post. A name pair, IMO, has to reflect the central role of Task Tree (see Template Document) in Actor Framework. Here are several pairs to consider instead of Caller - Actor:

  1. Parent - Child ? No, too coupled to OO concepts
  2. Source - Target ? Good in a message-centric approach as in Message Source/Message Target, but we are Actor-centric in AF
  3. Controller - Puppet ?
  4. Director - Actor ? I do like this one
  5. Body - Organ ?

The other ones are:

  • Owner - Actor
  • Actor - Subactor (as in System-Subsystem or Tree-Subtree) - certainly better than Nested Actor - Nested+Tree Concept immediately brings an image of egrets nesting on a pine tree to my mind
  • Host - Actor (works for both, composition and aggregation)
  • Launcher - Actor ?
  • Luncher - Lunchee ?
  • Creator - Actor (you can even coin a Cractor (Creator Actor), making it into Cractor - Actor)

Any other pairs I've missed here ?

0 Kudos
Message 52 of 62
(1,402 Views)

> Enqueuer- did you, guys, ever consider how hard it is for a

> non-English speaker to pronounce this (let alone spell it

> correctly) and for others to get what that guy just have said?

Oh, we talked about lots of names. The "-er" and "-ator" suffixes are pretty standard for OO design in most OO languages written by English speakers. Even outside of OO, the ability to add those suffixes to any verb to form "one who does that verb" is relatively common.

The name has to be a noun. It has to be distinct from the Dequeuer, but the names have to be related. It has to in some way incorporate the syllable "queue" to highlight its relationship to the Message Queue.

They were originally "Send Queue" and "Receive Queue", but the primary objection is that they *aren't* the actual queues. They are proxies for the queues. That lead to "Queue Send Proxy" and "Queue Receive Proxy" and many variations thereof. At one point I was using "That Which Enqueues" and "That Which Dequeues". Tried "Enqueue Reference" and "Message Dequeue Reference", but among other problems that meant that the main queue class should be "Message Queue Reference". But that lead us to just shorten it down to "Message Enqueuer" and "Message Dequeuer".

As for the "caller" term...

We have had just as much discussion there. It pretty much came down to "caller" or "launcher".  Remember that the caller might not be an actor, so saying "actor and subactor" was out.  The term "launcher" had a lot of support because of "Launch Actor.vi". But whenever we would talk about one actor spinning up another one, inevitably people would talk about "calling another actor." The relationship often feels like a long, drawn out, negotiated subroutine call. "I'm your nested actor. I'm doing something for you. I send you periodic updates. You change your mind about what I should be doing. I scream about you changing your mind all the time. You say it's your perogative as the caller. I sulk. You send pings demanding my attention. Finally you send me a stop command and I shut down, but not without giving you a piece of my mind (wrapped in a Last Ack message)."

Yes, dramatization was necesary. These things happen late in the day.

Anyway, the names have been debated for multiple years, and there was a fairly high amount of support behind the ones I finally went with, and they're better than what was there before by a long shot, and this was the last chance I had to change the names before they were solidified into the LV release, with all the attendant mutation problems if we're going to change them again (I have to admit, having the ability to just shatter backward compatibility every couple months was kind of nice... being a proper library has its pros and its cons).

0 Kudos
Message 53 of 62
(1,402 Views)

Dmitry wrote:

  1. 'Actor-to-Caller' is a different story:
    • The name implies it being a feedback channel to 'a caller' (as there may be multiple callers in AF) - which it is not.

There is only one caller for an actor -- the VI or other actor that called Launch Actor.vi. The queue that is passed into Launch Actor is the queue that the actor will use to communicate with its caller. If the caller actor shares the nested actor's queue with other actors, those other actors are not callers. Senders, messagers, brothers-at-arms, flamingos... you can use whatever term you want for those other actors except "caller" or "nested".

Dmitry wrote:

    • Launch Actor.vi suggests that it is a feedback channel to the Launcher ( an Actor that has launched this Actor) - which may or may not be the case

Honestly, until a couple months ago, I never even thought about an actor using *another actor's* queue to pass into Launch Actor.vi. Then someone wanted to do it and I though, "Holy cow. What an insane idea." It is completely an abusive hack to even think about doing that. Don't. Nothing we have said about the positive benefits of the actor tree holds true if one actor go launching actors on behalf of other actors. Total mess.

Launcher = caller = the enqueuer that goes to the actor that called Launch Actor.vi

And God help you if the above is ever not true, because I won't. 

0 Kudos
Message 54 of 62
(1,402 Views)

AristosQueue wrote:


There is only one caller for an actor -- the VI or other actor that called Launch Actor.vi. The queue that is passed into Launch Actor is the queue that the actor will use to communicate with its caller. If the caller actor shares the nested actor's queue with other actors, those other actors are not callers. Senders, messagers, brothers-at-arms, flamingos... you can use whatever term you want for those other actors except "caller" or "nested".

OK. I respect your design and name choices. One more question: from what I've read and heard I think AF encourages keeping Caller-to-Actor Enqueuer private, but does not prohibit sharing it as an exception. Correct ? If so - it may make sense to note somewhere in the White Paper that AF strongly encourages using Composition over Aggregation, but does not prohibit Aggregation as a design choice.

0 Kudos
Message 55 of 62
(1,402 Views)

AristosQueue wrote:

If I wanted to design a system where a batch message could be sent (thus guaranteeing it is contiguous) but still be pre-empted by higher priorities, I would do the following:

  1. Create 1 message at normal priority that says "these are the tasks to perform".
  2. When that message is received, pass that list of tasks over to *another loop* to perform the tasks.
  3. Have my main message-receive loop continue waiting for more messages. If the next message is another batch, add it to the other loop's queue.
  4. If the next message is a high priority message, signal the other loop to pause (pick your favorite technique for doing this... the easiest might be using the Enqueue At Opposite End primitive, since all we need is a simple "pause" signal). Handle the message, then signal the other loop to proceed. 

Good. This looks like the 'job queue' James mentioned when discussing Pause/Resume/Cancel at the start of this thread. It seems there is a consensus that if you need to execute a long action and still be able to Stop Actor or Pause/Resume/Cancel current action you need a second loop. Several related questions:

  1. Are there general guidelines for AF developers on when a single Message Loop is OK and when one needs to add one or more extra loops to Actor Core ?
  2. Is adding such loops to Actor Core considered a good practice or there are other recommended ways of adding extra loops ?
  3. Actor object can live in one loop only. Any recommendations on choosing which loop should have the Actor object? Always the message loop ?
  4. It is quite clear that a 'job queue' loop needs a subset of data residing in Actor object. Recommendations on how to split Actor data in pieces and how to sync this data back with 'Actor Object' loop?

My prior experience with QDSM Actors is that you have to have a really good reason to have two or more loops - splitting/synching Actor data adds complexity and overhead to Actor code.

  5.   What kind of functions an Actor should support in it's main Event Loop while executing a lengthy task (a 10 min scan) in the Job Queue

    • Stop/Pause/Resume/Cancel
    • Get messages
    • Any meaningful functions that can run safely while the scan is under way (if any) ?

Well, I can put this another way - is it, in general, considered OK to only support Stop/Pause/Resume/Cancel in the main Message Loop when  Job Queue loop executes a lengthy job in case the Actor provides all status updates (say via pub/sub or Self-Addressed Messages) from within Job Queue or other auxiliary loops ? I am thinking about an interesting 'productivity' feature that may be easy to add if the answer is "Yes, in general, this is OK" ...

0 Kudos
Message 56 of 62
(1,401 Views)

Dmitry wrote:

  1. Are there general guidelines for AF developers on when a single Message Loop is OK and when one needs to add one or more extra loops to Actor Core ?
  2. Is adding such loops to Actor Core considered a good practice or there are other recommended ways of adding extra loops ?
  3. Actor object can live in one loop only. Any recommendations on choosing which loop should have the Actor object? Always the message loop ?
  4. It is quite clear that a 'job queue' loop needs a subset of data residing in Actor object. Recommendations on how to split Actor data in pieces and how to sync this data back with 'Actor Object' loop?

1. There are, and they're on the forums kind of spaced out, but they aren't in the whitepaper. The whitepaper just says you can do it but not when you would want to do so. I'll look into adding something about that.

2. Very much considered good practice. It's the third way of code reuse promoted by AF: Override methods, extend with new message handling and append additional action loops.

3. Always the message loop. You don't really have any option about that short of forking the entire actor into two loops, and then you quickly have weird state issues since the two objects are by-value and thus share nothing. In practice, most people see the actor wire having to go into the Call Parent Node and it becomes pretty clear who gets the actor object.

4. Yes. The communication between the additional loops in Actor Core and the message loop are discussed in the whitepaper.

Dmitry wrote:

My prior experience with QDSM Actors is that you have to have a really good reason to have two or more loops - splitting/synching Actor data adds complexity and overhead to Actor code.

You're not really splitting the actor... the actor goes to the Message handling loop and you have some other queue to communicate to the subsequent Actor Core loops (or events or control refnums or pick-your-favorite-mechanism). Those core loops likely maintain their own shift-registered data or they're just event handling/event spawning loops, which need no state.

Dmitry wrote:

  5.   What kind of functions an Actor should support in it's main Event Loop while executing a lengthy task (a 10 min scan) in the Job Queue

    • Stop/Pause/Resume/Cancel
    • Get messages
    • Any meaningful functions that can run safely while the scan is under way (if any) ?

Well, I can put this another way - is it, in general, considered OK to only support Stop/Pause/Resume/Cancel in the main Message Loop when  Job Queue loop executes a lengthy job in case the Actor provides all status updates (say via pub/sub or Self-Addressed Messages) from within Job Queue or other auxiliary loops ? I am thinking about an interesting 'productivity' feature that may be easy to add if the answer is "Yes, in general, this is OK" ...

There are so many designs for Stop/Pause/Resume/Cancel behaviors for long operations that it is impossible to give a general categorization of "the right way to do it." My preferred way is to send a message to do the first part of the operation and at the end of the operation, have the operation post a message to itself for the next step, thus allowing the system to go back and check the message queue for any change in instructions. That keeps me from having to manage another job loop. But sometimes I do manage another job loop, and when I do, what you describe sounds reasonable. I'm a bit unclear on where the Self-Addressed Messages come into play.

0 Kudos
Message 57 of 62
(1,401 Views)

AristosQueue wrote:

Dmitry wrote:

My prior experience with QDSM Actors is that you have to have a really good reason to have two or more loops - splitting/synching Actor data adds complexity and overhead to Actor code.

You're not really splitting the actor... the actor goes to the Message handling loop and you have some other queue to communicate to the subsequent Actor Core loops (or events or control refnums or pick-your-favorite-mechanism). Those core loops likely maintain their own shift-registered data or they're just event handling/event spawning loops, which need no state.

Sure. What I've meant is - a second or third loop (with their own shift registers) may need to be passed [persistent] data from Actor Message Loop to perform their functions. They may also cause changes to such [persistent] data , which makes it necessary communicating these changes back to Actor Message Loop. Both require extra code (i.e. development overhead). So, unless there is a good reason to add extra loops, I try to contain everything in the Actor Message Loop.

AristosQueue wrote:

I'm a bit unclear on where the Self-Addressed Messages come into play.

Well, I shouldn't have used this AF term. There is a simple way to register 'private' events (without introducing a Topic) with ArT_Actors which resembles AF Self-Addressed messages.

AristosQueue wrote: (Message #28)

Now, a separate reply on the "separation of hierarchies" question.

I've read Dmitry's follow up post. I agree with the concern about "inheritance for two reasons". I even agree that his solution works and is sometimes useful technique. But it is not the *only* technique, and there's one that I would consider better that does not require any change of definition of the entities of the framework.

Yes, you can extend a class for UI, but that is not the only way to add UI. You can also have the UI running independently in a separate hierarchy (as is done in the 2012 evap cooler shipping example). Thus they become completely separate when they need to be... but both are actors. The actors themselves do not separate into a functional core and a transmission core. Rather the operation of the hardware and the UI to the hardware separates into two actors. In this setup you are free to add multiple data extensions to the hardware actor (as we do with the HAL layer in the example) and free to add multiple extensions to the UI hierarchy (the user-monitored UI and the free-running program UI). One nice functional benefit of this method of separation -- as opposed to the separation that continues to allow UI by inheritance -- is that the funcitonal actor can continue executing even if the UI shuts down, allowing for headless operation and/or different UIs being attached. Contrariwise, the same UI can keep going and change out which hardware actor is being messaged.

I agree. Implementing GUIs as separate Actor hierarchy scales well and ensures a good level of controller/GUI decoupling. Originally I had two major reasons for separating Messaging and Functional aspects of ArT_Actor into a ArT_Actor_Component and ArT_Actor_Class:

  • Keeping messaging code and functional code separate from the start - so that, if Iater on, there is a a compelling reason to keep it that way, I already have the separation in place (and if not - just have ArT_Actor_Class class inheriting from ArT_Actor_Component class)
  • Avoiding "inheritance for two reasons" specifically for the GUI use case. I saw the AF3.0 Cooler example as a direct endorsement for using such technique and thought it is important for the framework to prevent such design practice (enforcement vs. educating developers)

And there may be other auxiliary loops (like a Job Queue) a developer may want adding to Actor Core at some point. Based on the above I would take the following approach:

  1. Do not introduce extra loops to Actor Core without a good reason (i.e. if Actor Message Loop may properly handle new functionality)
  2. If you do add an extra loop - be prepared to spin it off as a subactor later on (recommended way to scale Actor Core)
  3. Do not fragment your Actors into smaller pieces in anticipation of future requirements. Refactor an Actor only when new requirements justify that (avoid Needless Complexity smell)

I think this kind of guidance is very important for developers switching from QDSM designs to AF and that having it readily available in one location (AF Best Practices ?) would save a lot time and pain down the road

Message 58 of 62
(1,401 Views)

Dmitry wrote:
So, unless there is a good reason to add extra loops, I try to contain everything in the Actor Message Loop...

And there may be other auxiliary loops (like a Job Queue) a developer may want adding to Actor Core at some point. Based on the above I would take the following approach:

  1. Do not introduce extra loops to Actor Core without a good reason (i.e. if Actor Message Loop may properly handle new functionality)
  2. If you do add an extra loop - be prepared to spin it off as a subactor later on (recommended way to scale Actor Core)
  3. Do not fragment your Actors into smaller pieces in anticipation of future requirements. Refactor an Actor only when new requirements justify that (avoid Needless Complexity smell)

I think this kind of guidance is very important for developers switching from QDSM designs to AF and that having it readily available in one location (AF Best Practices ?) would save a lot time and pain down the road

I absolutely agree with points 1-3.  The one potential disagreement may be over what we each consider a "good reason" to add additional loops.  My actors very frequently contain additional loops beyond the message handling loop.  State machines, metronomes, sequencers, continuous processes, etc. are all implemented in separate loops.  My message handling loops are typically limited to setting/getting values and/or dispatching messages to the other loops.

I also agree this kind of guidance is helpful for devs.  However, they are not specific to devs switching from QDSM to actor-oriented programming.  They are good guidelines for any multi-threaded application in Labview.

  1. Do not introduce extra loops to the execution engine without a good reason
  2. If you do add an extra loop - be prepared to implement the loop with correct behavior
  3. Do not fragment your execution engine into smaller pieces in anticipation of future requirements

I've ranted about typical QDSM implementations many times, so I'll try to restrain myself here.    Suffice it to say I've met several devs who learned bad habits by using the QDSM.  Based on what I've seen, I believe those habits are caused by a fundamental lack of knowledge of how to combine parallel loops into a single coherent and well-behaved component.  That lack of knowledge leads devs to shove more and more functionality into the QDSM message handling loop, which in turn causes the kinds of problems Norm illustrated in his "Rebirth of the State Machine" presentation.

Well written multi-threaded code requires knowing how to combine independent loops into a functional whole.  I've been developing a theory (tentatively called "foundational loops") based on the idea that there are a small number of loop "types" that when combined are able to implement any aribitrary functionality.  For example, the loops I listed above are each a unique type of loop with specific rules for implementation.  (There are probably more types too, but currently the QDSM isn't among them.)  When you start with a small, well-defined set of loops it is much easier to learn how to combine them without unknowingly introducing bugs than it is when a bunch of arbitrary functionality is built into a loop.

My sense is many developers will need to unlearn QDSM principles to really appreciate and take advantage of the AF and actor-oriented programming.

(Foundational Loops are closely related to actor-oriented programming.  However, actors are a higher level abstraction than foundational loops.  i.e. While every actor consists of at least a foundation message handling loop, not every foundation loop is an actor.  Actors are composed by combining one or more foundation loops.)

0 Kudos
Message 59 of 62
(1,401 Views)

Hello everyone.  I had to drop out of this conversation due to going on holiday sans internet.  Some late comments:

AristosQueue wrote:

The Actor Tree

Actors form a tree. Most module communications frameworks have more of a listener/observer pattern, where a module runs and publishes information and any other module may subscribe to hear about updates, forming a cross-connected graph of listeners.

I don't think the issue is the Observer Pattern here (at least as I understand it), the issue is a system of global addressing, where every module can send messages to any other module.  Similar to the issue of named queues.  This could easily lead to lots of cross connects and even circles of communication (the "echo chamber").  But this is regardless of whether a listener/observer pattern is also used.  Note that global addressing is inconsistent with the Actor Model (Actors can only send messages to other Actors that they have created or whose address was received in a previous message).

I use the Observer Pattern to allow one Actor to send a registration message to another Actor that it rightfully has the address of.  No global addressing and minimal cross-connects.  In fact, using the Observer pattern allows me to limit the possible interaction among actors, since I never have to have two Actors that explicitly have the addresses of each other, and I seldom have any need to share (bare, unprotected) addresses.  Instead, the addressed are passed wrapped in registration messages, where they can only be used to send the specified message that they register for.

  1. A module would hear a message about some system change and would start acting differently, but a module that is listening to it might not have heard about the system change yet, and until the system change message had echoed through the entire graph, the behavior of the modules was undefined.
  2. A module would announce some message. Another module would hear that message and repeat it. And then others would repeat it. Eventually the module would hear its own message repeated, but it had no idea if that was its own message or an original message from someone else, so it would repeat it again.

These problems, again, are more of the "many cross-connected modules" design.  A strict tree is going to solve it, but may be a problem when you start using the Actor Framework in a distributed setting with any meaningful amount of network delay between Actors.

When a caller launches an actor, the queue to talk to that actor is returned to the caller. No other code module has any way of requesting or looking that queue up by name. Thus the actor tree is preserved by default. When one actor wants to talk to a sibling actor, it passes a message up to the common caller, and the common caller passes a message down to the other actor. In general, actors are constructed to not even know about siblings -- it is the caller that knows about the siblings and makes decisions about which messages need to be passed along. You can bypass the actor tree by creating messages that pass one actor's queue to another actor. You may choose to do this when those two sibling actors have such a strong interaction that you want them to talk directly rather than passing messages through the common caller actor. The caller actor is still in charge of lifetime issues. Short circuiting the actor is something you should do rarely and consciously. Creating any sort of general repository of names mapping to queue references is ill-advised as it reintroduces the issues described above.

All very good stuff. 

-- James

0 Kudos
Message 60 of 62
(1,402 Views)