From Friday, April 19th (11:00 PM CDT) through Saturday, April 20th (2:00 PM CDT), 2024, ni.com will undergo system upgrades that may result in temporary service interruption.

We appreciate your patience as we improve our online experience.

Actor Framework Discussions

cancel
Showing results for 
Search instead for 
Did you mean: 

Need some guidance on AF project

So I played around with the AF way back in LV2012 and ended up doing a small project with it.  It worked well.  I see a lot has changed now in LV2015.  It seems to be all for the better.  It seems much easier to use to kudos to AQ and the rest of the team for that.

So here is my general question.  It regards error handling and logging.

Generally in non-AF programs I have a seperate error handling and logging modules.  Each one has a parallel loop and is queue driven.  So converting them into actors should be very simple.  I'm not worried about that part.  What I am worried about is how best to communicate with them in the sense of where they should fit in the actor tree model.

In my traditional non-AF programs both the error handler and logger have an API with a vi name "Handle Errors" or "Log info" and I just drop those wherever I want to collect data.  One thought I had was to just create the error handler and logger as actors and wrap each in its own library.  Make the actor and all messages private and just provide a few API vis, such as Launch Error Handler, which would launch the error handler actor and store the enqueuer in a functional global and then I could have a "Handler Error" vi which would pull the enqueuer out of the FGV and send the message.  It would basically look like my current error handling and logging libraries except that behind the scenes i would replace the "guts" with the Actor Framework.  For calling the error handler and logger from non-AF code that would be perfect. 

However in a fully AF program it seems like there might be a better way to do it.  But how best to integrate everything with the "Tree" model where each actor only communicates with its nested actors or its caller.  Would it make sense to place the error handler or logger somewhere on the tree and bubble all messages up the tree? (and maybe  back down if necessary)?  - seems like a lot of message handling.  Another idea I had seen was to launch the error handler and logger first and then pass their enqueuers to each subactor - what are the downsides to this approach?  What about debugging?  I know one of the touted advantages of the tree model is that it is easier to debug because it is easier to track the messages.  How would this change?

What I am thinking of doing is creating a class that inherits from the generic actor and in its private data has a logger enqueuer and a error enqueuer.  Then I could write one override for the "Handle Error" vi , which would take care of most of the error handling. 

Another related question I have is with the idea of singleton classes.  I know when classes came out originally there were a variety of designs to have a singleton class.  How Does the idea of singletons integrate with the AF?  Are there any issues there?  I would really like to only have 1 error handler and 1 logger at any time.  This would also apply to a database module I would need to add to my project as well.  It seems to me that if you only ever launch an actor once on startup and then pass around the enqueuer it is basically a singleton class.  In the sense that every time you fork the wire you are just basically making a copy of the queue ref.  So everything would point back to the single orginial instance.  So that shouldn't present any problems, as long as you don't launch 2 copies of the actor somehow.  thoughts?

Hopefully I've explained my questions well enough.  I would welcome anyone who has some experience and can point me in the right direction.

Sam Taggart
CLA, CPI, CTD, LabVIEW Champion
DQMH Trusted Advisor
Read about my thoughts on Software Development at sasworkshops.com/blog
GCentral
Message 1 of 40
(7,975 Views)

Sam,

Your problem is not an uncommon one when starting with the AF.  I have seen several similar questions recently concerning this.

The way that I have handled this is to make the loggers apart from the AF as you suggest with the singleton idea.  Taking this one step farther, I would suggest that you use inversion of control.  This is a design principle where the framework will call into the code rather than the code be an expression of the framework.  In this case, your logger class would receive the flow of control and make decisions appropriate to the class itself.  For instance, you may want to drop this in a bunch of different actors but only want to initialize or close once - in this case, you would keep track of all instance open and whether the object has been initialized (for the former case) or whether all instances of the object are requesting a close (for the latter case).  This principle is actually a good design principle for any reference that might be shared across actors and fits very well (IMO) with the AF.  If well implemented, it bypasses all of the boiler plate messaging that would be associated with a logger implemented as an actor without creating race conditions.  A good example of a LV implementation of this principle is the extensible session framework (ESF)

Hope this helps.

Cheers, cirrus

Message 2 of 40
(6,671 Views)

I have played around with the ESF once.  I liked it, but never really used it for anything.

So to make I understand you correctly, you are suggesting that I keep the logger seperate from the normal AF tree and that I wrap the API in the ESF?

Sam Taggart
CLA, CPI, CTD, LabVIEW Champion
DQMH Trusted Advisor
Read about my thoughts on Software Development at sasworkshops.com/blog
GCentral
Message 3 of 40
(6,671 Views)

I am suggesting that - you can create your own loggers as actors but this seems to intensive for me.  Everyone will want to talk to the loggers, so you will either 1) break your rule for using the AF by allowing access to these actors across boundaries rather than up or down the actor tree or 2) create a potential nest of messages for passing information up and down the tree. 

I will try to package up my implementation and post it somewhere (likely GitHub) and you can check out how I do it if you so desire.

Message 4 of 40
(6,671 Views)

Taggart wrote:

What I am thinking of doing is creating a class that inherits from the generic actor and in its private data has a logger enqueuer and a error enqueuer.  Then I could write one override for the "Handle Error" vi , which would take care of most of the error handling. 

Do this.

Have your top level actor launch your logger first, and then pass its enqueuer on to the other actors that need it.  It is a trivial matter to pass the logger enqueuer to any children of those top-tier actors when you launch them.

The tree hierarchy is strongly encouraged, but not strictly required.  Exceptions can and should be made where appropriate.  Lots of folks like routing their errors to a central logger, and this is probably the best way to do it in AF.

An FGV would be a bad choice here, because it does exactly the same thing (creating a peer-to-peer connection between actors), but it does so in a very non-AF manner, which will complicate debugging.

I'm actually not very fond of ESF, except possibly for its original intended use case - an extensible smart reference for instrumentation.  Anything that you can do with ESF you can do with an actor, and actors are data flow safe.  ESF, like most reference-controlled resources, simply is not.

A final note on error handling:  understand that your central error handler will likely not be very effective at actually handling errors.  It can log them for you, and it can shut your system down (though I note that you can do the latter with the stock AF error handling), but that's about it.  Unless you are willing to compromise the encapsulation and modularity of your system, you can't really write a centralized response to errors.  Your best bet is to handle them as close to their source as possible, which means handling them within the actor's methods, or within individual Handle Error overrides.

Message 5 of 40
(6,671 Views)

Taggart wrote:

Another related question I have is with the idea of singleton classes.  I know when classes came out originally there were a variety of designs to have a singleton class.  How Does the idea of singletons integrate with the AF?  Are there any issues there?  I would really like to only have 1 error handler and 1 logger at any time.  This would also apply to a database module I would need to add to my project as well.  It seems to me that if you only ever launch an actor once on startup and then pass around the enqueuer it is basically a singleton class.  In the sense that every time you fork the wire you are just basically making a copy of the queue ref.  So everything would point back to the single orginial instance.  So that shouldn't present any problems, as long as you don't launch 2 copies of the actor somehow.  thoughts?

Your understanding is correct.  AF doesn't have singletons, per se.  If you pass around an actor's queue, the actor is effectively a singleton.  If you launch the actor a second time, you get a second instance.

Message 6 of 40
(6,671 Views)

Good points on the error handler.  I like that idea.  I'm thinking I'll just log them all centrally and then handle them locally or maybe bubble them up a layer and handle them there if need be.

Other somewhat related questions:

The project I'm working on has several processes that (at least the way I'm thinking about them now) are probably best represented as state machines.  I've done plenty of state machines the old fashioned way (just using an enum), but never using LVOOP and the state pattern.  What is the learning curve like on that?  and how does it fit into the AF model?  Would I be better having a state machine as a helper loop and just using some mechanism to pass messages from AF to the helper loop?  or is it better to have everything inside the main Actor Core loop?  Also some of these processes depend on each other.  There is some timing involved.  I was thinking though that could be handled by the next level up in the tree (ie. some sort of controller actor).

Shared resources:  If 2 actors share a resource (say perhaps a pump), what is the best way to handle that?  I'm thinking maybe create a pump class and use some sort of delegation.  Give each actor a reference to the pump.  Then in the pump class have some sort of semaphore or way of locking it so only 1 process can use it at a time?

thoughts?

Can I make this work in the AF? or am I just using a really good hammer to pound in a screw?

Edit: 

Actually let me rephrase my last question.  I'm sure I can probably make the AF work, the question I have is more:  is the AF a good choice for this type of problem?.  I don't want to be trying to put a square peg in a round hole.

Sam Taggart
CLA, CPI, CTD, LabVIEW Champion
DQMH Trusted Advisor
Read about my thoughts on Software Development at sasworkshops.com/blog
GCentral
Message 7 of 40
(6,671 Views)

Taggart wrote:

The project I'm working on has several processes that (at least the way I'm thinking about them now) are probably best represented as state machines.  I've done plenty of state machines the old fashioned way (just using an enum), but never using LVOOP and the state pattern.  What is the learning curve like on that?  and how does it fit into the AF model?  Would I be better having a state machine as a helper loop and just using some mechanism to pass messages from AF to the helper loop?  or is it better to have everything inside the main Actor Core loop?  Also some of these processes depend on each other.  There is some timing involved.  I was thinking though that could be handled by the next level up in the tree (ie. some sort of controller actor).

Everything that follows assumes you are referring to behavioral state, meaning that your actor will respond differently to any given message based on its current state.  For example, a TCP/IP actor that can receive a Connect message will connect if it is currently not connected, and return an error if it is.

If you are talking about behavioral state, I would advise against putting that code in a helper loop.  Actors manage behavioral state very well.

If you just have a couple of elements in your state enum, and only one or two places where you check that state, you can get by with what you've been doing.  In the TCP example, the enum would have Connected and Disconnected elements, and the case structure associated with the Connect operation would contain connection code or error code,

But that doesn't scale well.

If you need more states, and more places where states matter, consder replacing the enum and case structures with a class and dynamic dispatch VIs.  Then you creat children of that class that put state-specific code in the appropriate VIs.  In our TCP example, you would have a state class with a dynamic dispatch VI called Connect.  Then you would have two child classes:  Connected and Disconnected.  Disconnected.lvclass:Connect.vi would run your connect code.  Connected.lvclass:Connect.vi would return an "already connected" error.

This is called State Pattern.  (The enum solution is a non-OO implementation of state pattern.)  The Wikipedia article on it is OK, but it links to other articles that are quite good.

There is a State Pattern implementation for AF that is appropriate in many (but not all) cases.  You can find it here:  Implementing the State Pattern in Actor Framework.

Now, be careful here.  A lot of people use LabVIEW state machines to implement a sequence, rather than behavioral state.  That looks more like "Do step A, and use the results to determine whether you will next do step B or step C."  That isn't state pattern, it's sequencing, and sequencing *is* often best left to a helper loop.

How can you differentiate between these?

1.  If you want the actor to respond differently to its messages depending on its state, use the state pattern.

2.  If you want the actor to step through a series of steps, and you don't want those steps interrupted, offload the sequence into a helper loop.

3. Otherwise, just rely on your basic actor message handler.

These are guidelines, of course.  It is perfectly acceptable, for example, to have your actor send itself a message as part of the response to some other message.  That's no problem, provided that you can tolerate some other message from your caller or nested actor getting executed first.

Message 8 of 40
(6,671 Views)

An FGV would be a bad choice here, because it does exactly the same thing (creating a peer-to-peer connection between actors), but it does so in a very non-AF manner, which will complicate debugging.

And now you have built an error logger that is usable only in the context of the Actor Framework.  Error loggers are used in many applications; unless you are intending to only create AF projects, you have just increased your work load (which is good I guess if you bill by the hour).

And, I am not clear on how this makes a peer-to-peer connection - your actor depends on the FGV, not the other way around, so the use of the FGV does not effect coupling between actors. 

And with regards to complicating debugging - how exactly do FGVs effect the complexity?  In this case (a logger), you have a fairly simple execution instance that has to do one thing and one thing only - maintain a list of messages generated by the system. 

I'm actually not very fond of ESF, except possibly for its original intended use case - an extensible smart reference for instrumentation.  Anything that you can do with ESF you can do with an actor, and actors are data flow safe.  ESF, like most reference-controlled resources, simply is not.

I will disagree with this - my opinion is that, regardless of whether you use the ESF or some other method of implementing IOC, the AF is just the place where you would want to use this.  Anywhere you share a reference amongst multiple application instances (actors) I think that this principle excels.  If you choose to go with an actor, you will have to be very careful about how you launch and close your system otherwise you will end up with a case where the system is trying access a resource that is no longer is available.  And if you want to talk about difficult to debug, race conditions in the AF are the definition given the rentrancy of all core AF VIs. 

This is the root of the problem with implementing an AF structure that is not tree-like in nature - who is going to be in charge of the actor that is responsible for logging?  The launching actor will be.  But why?  Why is the launchng actor coupled to the logger?  Why is the logger not waiting for everyone to exit (including the controller) so that nothing is left dangling?  This problem is solved by IOC.

Message 9 of 40
(6,671 Views)

Taggart wrote:

Shared resources:  If 2 actors share a resource (say perhaps a pump), what is the best way to handle that?  I'm thinking maybe create a pump class and use some sort of delegation.  Give each actor a reference to the pump.  Then in the pump class have some sort of semaphore or way of locking it so only 1 process can use it at a time?

Why do two actors need to access the pump?  That seems like an issue with how you are assigning responsibility.  Can you rearrange your algorithm so there is only one logical entity that needs access to that hardware?

Again, references in AF are usually *bad*, precisely because you need things like semaphores.  If you have a single actor controlling access to the pump, then the actor itself provides all the semaphoring you need.

Now, I would not have an actor that just controls a single pump.  Between them, the two actors in your example define some kind of system that should probably not be spread over two actors.  Figure out what that system is, and write one actor for it.  Whatever is left over in your two original actors can probably be reassigned as well.

Message 10 of 40
(6,671 Views)