Actor Framework Discussions

cancel
Showing results for 
Search instead for 
Did you mean: 

AF query

Solved!
Go to solution

Good afternoon, all!

 

I haven't checked the forums in a while and was curious if there have been any new advancements in Actor Framework? 

 

Specifically, the core framework as well as any really good add on toolkits?

 

I have had an idea for a while about a topology of actors that I would possibly use in an application.

I would have a central "controller" actor that would be the traffic cop for the application and it would launch and then store the enqueuer names as strings with a key value pair in memory.

 

Then if an actor wanted to communicate with another actor it wouldn't need the enqueuer name, it would send the message to the controller with the name of the actor it wanted to talk to.

The controller would then route the message to the actor. 

 

I have never implemented it but often wonder if that would make a good architecture, or is that a bad idea?

 

I like the AF Forwarding Msg utility and want to give that a try soon. Also was looking at the Composed Systems Stream toolkit, that one seems a bit more complicated. 

 

 

Steven Howell
Controls and Instrumentation Engineer
Jacobs Technologies
NASA Johnson Space Center
0 Kudos
Message 1 of 16
(2,028 Views)

@StevenHowell wrote:

I like the AF Forwarding Msg utility and want to give that a try soon. 


Thanks! I'm not sure which version you were looking at, but if it wasn't the latest (3.0) I highly recommend that over the previous versions as it's both easier to use and addresses more use cases.

CLA CLED AF Guild
0 Kudos
Message 2 of 16
(2,014 Views)

Casey, 

 

I recently downloaded the 3.0 version. 

Steven Howell
Controls and Instrumentation Engineer
Jacobs Technologies
NASA Johnson Space Center
0 Kudos
Message 3 of 16
(2,003 Views)
Solution
Accepted by topic author StevenHowell

Great! Regarding some of your other questions...

 


Specifically, the core framework as well as any really good add on toolkits?


"Uninit" was added 2022 Q3 in order to clean-up any actions even if there's an error produced in "Pre Launch Init". What other sorts of add on toolkits interest you?

 


I have had an idea for a while about a topology of actors that I would possibly use in an application.

I would have a central "controller" actor that would be the traffic cop for the application and it would launch and then store the enqueuer names as strings with a key value pair in memory.

 

Then if an actor wanted to communicate with another actor it wouldn't need the enqueuer name, it would send the message to the controller with the name of the actor it wanted to talk to.

The controller would then route the message to the actor. 

 

I have never implemented it but often wonder if that would make a good architecture, or is that a bad idea?

The idea of having one central "controller" for Msgs seems antithetical to the typical recommendation to use a "Tree" for actors (not just in NI's AF) because it increases coupling and the potential for race conditions. Another benefit of the tree structure is that it's a lot easier to test sub-trees of your system in isolation from the rest. If you commonly find yourself wanting to communicate with other actors across the actor tree directly, I'd say that is more likely a code smell and maybe there are other steps you can take to reduce the friction of messaging.

CLA CLED AF Guild
Message 4 of 16
(1,984 Views)
Solution
Accepted by topic author StevenHowell

@CaseyM wrote:

 

The idea of having one central "controller" for Msgs seems antithetical to the typical recommendation to use a "Tree" for actors (not just in NI's AF) because it increases coupling and the potential for race conditions.


To the original poster, let me expand on this just a bit.  One of the basic tenets of the framework - and the model from which it is derived - is that an actor should exist as its own entity, with minimal knowledge the other actors in the system.  This makes actors easily testable and potentially reusable.  What you are suggesting requires that you build knowledge of the structure of your overall system - the topology - into its individual components.  So now your actors depend on other, specific, actors in your system to do their jobs.  You can get away with this for a while, but you'll come to regret it as your system scales.  And it doesn't really buy you anything that you can't get by investing a bit in the design of your topology.

Message 5 of 16
(1,954 Views)

Thanks Casey,

 

As I said I was just "kicking it around" I have never done it. 

 

A lot of times I will have an actor that is an "as needed basis". It will be launched as a nested from some other actor, or possibly even as a root actor (error handler) and when it does what it was asked to do, it stops. 

 

The biggest hurdle I have when designing an AF architecture is, which actor should be the root that launches the others and starts the chain. 

 

Case in point, my current project. I have an actor that is a "DAQ Actor". It connects with some instruments over Modbus and reads data from them, that is the only thing it does.

The DAQ actor launches another actor that "totalizes" the upper and lower byte registers to get the total reading. 
The Totalizer launches the averaging actor that puts the totalized values into an accumulator and adds those values read from the DAQ once a second for 60 seconds and then provides an average over that 60 seconds.

 

The Average actor launches the Datalogger which writes the data to an SQLite Database.

 

Also launched at startup, and before the DAQ actor is another root actor, the error handler actor. I give the DAQ actor the error handlers queue and then the DAQ actor passes it down the chain and so on so that all nested actors and the root DAQ actor have the queue reference.

I tried to have the error handler as an as needed actor but was having some difficulties with the database as SQLite doesnt allow concurrent writes. (to my knowledge) so I was seeing some weird behavior.

I thought launching the error handler as a separate actor and then queueing up messages from errors from the actors would be the better option.

 

In the Handle Error override of each actor in the system I read the error queue from an accessor and then enqueue the error message.

 

This seemed like a good overall architecture but I sometimes question my madness. 

Steven Howell
Controls and Instrumentation Engineer
Jacobs Technologies
NASA Johnson Space Center
0 Kudos
Message 6 of 16
(1,944 Views)

Thanks Allen for your reply as well. 

 

Everything you posted is correct and I remember that from the AF class. You taught me well!

 

I typically stay in a tree format but sometimes wonder in systems that might need a central controller, what would be the best option? That might not be a good case for AF?

Steven Howell
Controls and Instrumentation Engineer
Jacobs Technologies
NASA Johnson Space Center
0 Kudos
Message 7 of 16
(1,943 Views)

I often just have one Root actor and it's only job is to launch other actors (and maybe route messages between them). So I completely sidestep the question of, "Is my GUI the root of the application and should every other actor be nested under that or should it be some other functional actor like the DAQ actor?"

 

If you're sometimes launching multiple "root" actors, what's the top-level module/actor that's doing that? Maybe consider making that your Root actor.

 

Just in general, when it comes to deciding how to structure your tree, if you think about things in the terms justACS talked bout - easily testable and potentially reusable - I find that the tree design often becomes more clear. i.e. If you wanted to test a small portion of your application, can you do that without bringing in dependencies of other unnecessary actors? If you do that, I think you might find less and less of a need for a "central controller" actor.

CLA CLED AF Guild
0 Kudos
Message 8 of 16
(1,927 Views)

There's nothing wrong with having a "main controller" actor. In fact, you SHOULD have one of those in a system like yours. The issue is with sub-actors knowing about all of the other actors in the tree. [Edit: CaseyM posted while I was typing, and he recommended to avoid having a "main controller". Terminology aside, there should be one actor whos job is to launch the other actors. Perhaps a better name would be "business logic" actor, but I typed this out calling it "Controller" so pretend I said "business logic actor" instead ;)]

 

I'd also say that your topology seems like a code smell, and IMO violates the "Single Responsibility Principle". Basically, each actor needs to be responsible for just one thing.

 

There's a couple issues I see with your system. The first is the error handler. Error handling is already built into the Actor Framework, so going around it like you're doing doesn't seem like a great idea. I've always had good experience just sending errors up the chain so they can be dealt with by the caller of the actor. I'm happy to learn more about your system though.

 

The other issue is that you have a very "narrow and deep" tree, whereas I'd suggest you flatten it out or eliminate many actors completely. For example, why does DAQ need to know about the Totalizer, Averager, or SQL database? It should only know "DAQ stuff".

 

Allow me to propose an alternate topology: you have a top level "Controller" actor. This actor knows it needs to acquire DAQ data, so it launches the DAQ actor. That DAQ actor acquires data and sends it to its caller, and that's it (only one responsibility). This means that actor can now be reused anywhere you need DAQ data. If it provides a "Handle Data" interface, then it will be zero-coupled to its callers.

 

Now Controller gets Data, it also knows that the raw data coming back from the DAQ actor (in this case) needs to be totalized a specific way. I question if this needs to be an actor at all as it just seems like one function, and it doesn't need to behave asynchronously. Just make it its own object, or even just a single function. [Aside: if your DAQ actor ALWAYS needs to totalize like this, just put the totalizer function in the DAQ actor since you never need it to do anything else. If it's a "sometimes" thing, then definitely keep it OUT of the DAQ actor.]

 

So now in your Controller actor, it handles the message sent to it by the DAQ actor and adds the bytes together. No need for another Actor. You now need to average this over 60 seconds. I'd again question if this even needs to be an Actor, since a single function could do this (e.g., PtbyPt Average). Even assuming you DO need an Actor for this, let your Controller actor launch it. The DAQ actor doesn't need to know about the Totalizer OR the Averager actor, and the Totalizer and Averager don't need to know about each other. It's best if they don't, because Averager should just Average some data, regardless of whether it came from a Totalizer or straight from a DAQ actor or whatever.

 

So, I'd suggest your Averager be launched from Controller as well (if it has to be an Actor at all). When Controller gets the message from DAQ, it processes it (Totalizes) then sends it along as a new message to Averager. While Averager processes its data, it sends the Average message back to the Controller. Last, the Controller again knows to send the data to the Datalogger function, which can then write the data to the database.

 

IMO the only one of those that might need to be an Actor is the SQL actor, but I've done that with regular ol' objects with regular ol' dataflow before.

 

You might be able to get rid of several of your Actors.

 

The key to consider is that yes, the "same" data goes into and out of Controller several times, but each time it goes in and out it has been recontextualized. The first time the data enters, it's multiple unscaled bytes. When it leaves, it's actual scaled data. The bytes may be the same, but the information contained there is new. The next time it leaves it's going to the averager, then it comes back as a smoothed value. Again, recontextualized as "Single point data leaving" and "60-second rolling average data" returning. Those are certainly very different things!

 

Going back to the Error handling, I've never had an extra Actor to handle errors. Each Actor "handles" errors as they show up. For example, if the DAQ actor sees a communication error, perhaps it can try to reconnect. If it can, great- nobody upstream needs to know or care about that error. If it's an unrecoverable error, then the DAQ actor (whose ONLY job is to read data) doesn't know what to DO about the error. It then sends that error upstream. (In my experience, fatal errors will shut down the Actor, and the caller will need to relaunch it. Much simpler than handling states within the Actor itself.)

 

The Controller actor then sees "Ah- the DAQ actor failed. That means I have to stop this test." In that case, the test halts, but the Controller still carries on handling GUI "stuff". The error stops at the Controller level.

 

The only solid case I've seen for an "across the tree" Actor was for simple unidirectional logging. Say the Logging actor just took string inputs, then wrote that string and some details to a file, then in that case you could argue that each sub-Actor can write to it independently. That doesn't seem too bad to me, but the Message Forwarding Utility you found earlier might be a simpler way to do it. Yes, messages get passed through the tree, but now you don't have to sling the "Logger" reference around all over the place.

 

Sorry for the wall of text. Hopefully that makes sense, and if it's chock full of bad ideas then perhaps one of the gurus on here can show me the error of my ways. This system works well for me and achieved REALLY high throughput on analog voltage sampling, and that actor can be reused in all sorts of cases that need data acquisition.

Message 9 of 16
(1,924 Views)

Thanks Bert for your reply, it gave me a lot of insight. I need to go through it again and really digest what you have said but it makes more sense overall. 

 

The way I have it designed, the modules are not fully reusable, you are exactly correct. Not sure why I didn't see that before.

 

I thought I was following the Single Responsibility Principle by having the DAQ actor just read the data from the instrument and then it hands off the raw data to the totalizer which has one responsibility, then the totalizer hands its data off to the averager which has one responsibility and so on.

 

They do have single responsibilities but they are still "linked" to another actor.

 

My confusion lies in this:

 

If the "controller" launches the DAQ, Averager, Totalizer, Datalogger, etc etc. wont the controller have to know the nested actors it launched queues to be able to send them messages? You would have to write the queue of each nested actor into the memory of the controller so that it can send messages "down the tree". 

 

In my mind that makes the controller depend on all the other nested actors to be able to provide scaled averaged data. Am I missing something?

 

Hope I haven't thoroughly confused everyone.

Steven Howell
Controls and Instrumentation Engineer
Jacobs Technologies
NASA Johnson Space Center
0 Kudos
Message 10 of 16
(1,913 Views)