Actor Framework Discussions

cancel
Showing results for 
Search instead for 
Did you mean: 

Auto-stopping dynamically launched nested actors

It is NOT A REFERENCE.  It's the actual object.

There is nothing wrong with Actor Framework.  It works correctly, and as expected.

You coded your loop incorrectly.  An actor object wire is not a reference to an object.  It is the object.

The fact that you use (ultimately) a queue to communicate with a running actor does not mean that the actor object is not by-value.  It still is by-value.  And AF programming requires that you treat its wire as by-value.  If you do, you get expected results -- as you do with any other object.

When you say, "Both wires contain the exact same reference to the exact same resource", you are incorrect.  Both wires contain the same cluster data types (as defined in the object's class type definition), but that data is copied.  It's DATA, it's not a reference to anything.

0 Kudos
Message 11 of 34
(2,526 Views)

It is exactly as Allen tried to explain to you:

"Actors are by-value objects.  If you fork the wire, as you have done here, you make a copy of the original actor object; the new copy is not running, and has nothing to do with your instance of the actor (the one with the running Actor Core.)  When you call Launch Nested Actor you will be successful, but the nested actor's enqueuer will be stored in the unlaunched copy, not in the running version where you need it."

You cannot treat an actor object wire as though it is a reference to the actual actor.  It doesn't work that way.

0 Kudos
Message 12 of 34
(2,526 Views)

The actor object CONTAINS a reference.

And you are missing the entire point of my post. The point is that using the actor framework provided VI, a property is set on an actor instead of sending a message to itself. The entire point of the actor framework is to update the state of an actor through messaging once launched. The Launch Nested Actor.vi is NOT doing that.

0 Kudos
Message 13 of 34
(2,526 Views)

Sorry for the confusion.  I'm late to the party...

Yes, you can insert a copy of an actor's enqueuer object (which you can treat like a reference, although it's not..) in an actor object.  But that copy of that enqueuer will only exist in that particular copy of the actor object. 

Once you launch the actor, that ONE actor object wire (and its contents) is the actor.  All other copies are irrelevant as far as the actor is concerned.  (You could launch the forked copy, however -- you'll get a second actor of the same class as the first.  They're still independent.)

Once launched, the actor is the instance of the actor's class (an object) that exists within an independent process -- and there is no direct access to it any longer.  Methods that are invoked on the actor by passing it a message will get the actor's object (not a copy of it!), and when they access the data in that object, they're accessing the actor's data.

Other VIs in other processes, if they have a copy of the actor's object, will only be accessing and operating on data in that copy of the actor object.  And that has no relation to the actor object in the running actor.

So if you fork the actor object wire, you're creating an irrelevant dead-end.  Nothing you do with or to that forked copy will have anything to do with the actor.  It is not a reference to the running actor.

So you CANNOT launch an actor and then put a copy of another actor's enqueuer in a COPY of the actor's object.  It will never be seen by the running actor.

This is correct behavior...

0 Kudos
Message 14 of 34
(2,526 Views)

Brainstorms wrote:

Yes, you can insert a copy of an actor's enqueuer object (which you can treat like a reference, although it's not..) in an actor object.  But that copy of that enqueuer will only exist in that particular copy of the actor object. 

Once you launch the actor, that ONE actor object wire (and its contents) is the actor.  All other copies are irrelevant as far as the actor is concerned.  (You could launch the forked copy, however -- you'll get a second actor of the same class as the first.  They're still independent.)

Once launched, the actor is the instance of the actor's class (an object) that exists within an independent process -- and there is no direct access to it any longer.  Methods that are invoked on the actor by passing it a message will get the actor's object (not a copy of it!), and when they access the data in that object, they're accessing the actor's data.

Other VIs in other processes, if they have a copy of the actor's object, will only be accessing and operating on data in that copy of the actor object.  And that has no relation to the actor object in the running actor.

So if you fork the actor object wire, you're creating an irrelevant dead-end.  Nothing you do with or to that forked copy will have anything to do with the actor.  It is not a reference to the running actor.

So you CANNOT launch an actor and then put a copy of another actor's enqueuer in a COPY of the actor's object.  It will never be seen by the running actor.

This is correct behavior...

Please look at my code, as this contains of a lot of misleading statements that is still misunderstanding my point. The problem context is within the root actor's overridden Actor Core.vi. Therefore, the actor passed into the control of this VI is an already launched actor with a valid messaging reference contained within its value.

When I branch the wire up to my user event loop, I know that I have just copied the object EXCEPT for its messaging capability. Sending a message to that actor will send the message to the exact same parent call of Actore Core.vi that is running. This is the entire point of the actor framework! It's that you can send messages to already launched actors through their messaging reference.

If I would have pulled the reference out of the wire before passing the wire to the user event loop, we would not be having the discussions we're having as that is exactly what is done in the actor framework template project and is a valid intended use case. If you see my response with the picture, what we are discussing has nothing to do with my original post. It has to do with a semantical argument because I decided to not pull the messaging (enqueuer) reference out of the object prior to my while loop. If you look at the screenshot I think should be the behavior, I do indeed pull out only the messaging reference in order to send a message to the object. If you look inside the Launch Nested Actor.vi, it is setting a data on an actor wire rather than sending a message to said actor.

0 Kudos
Message 15 of 34
(2,526 Views)

I haven't had a chance to look through your code yet, but why are you expecting the "Launch Nested Actor" function to also send a message to the actor (which I assume is the newly-launched nested actor)? "Launch Nested Actor" simply sets up the new actor as an asynchronously running process (or whatever the LabVIEW equivalent is, clumps or whatever). Are you expecting some other automatic message to also be sent, before you start sending your own defined messages?

0 Kudos
Message 16 of 34
(2,526 Views)

Okay, I downloaded and am looking at your code...

You are correct that "the actor [object] passed into the control of this VI is an already launched actor [object] with a valid messaging reference [object] contained within its [private data cluster]."

Your following sentence, by the way, is not correct; when you branch the wire, you still have the contained enqueuer ("messaging capability") in the copy.  Which you could use to send messages to the actor or the actor's caller.  But that's not where your problem lies...

In your third paragraph, you say that "we would not be having the discussions we're having" -- except that yes, we still would...  Because this is not the core of the problem.

I think I see where the conceptual problem lies:

There is a difference between Class Attributes and Instance Attributes in object-oriented programming.  You may be misled (in a way that's understandable if you're new to LVOOP) into thinking that the Private Data Cluster ".ctl" typedef that's defined by the object's class is a class attribute.

Further, the way AF presents actors as examples would re-inforce this perception, because actors objects are always shown as though they were singletons of an actor class...  As though this were necessary, as though their PDC data were class attributes.

But they're not.  The data held by an actor object is instance attributes, not class attributes.  I.e., every actor object, which are instances of the actor's class, have separate, independent storage allocated for the cluster that's defined by the LVOOP class definition.  This is not a single allocation that is accessed by every instance of the class (which would make it a class attribute).

IF the actor object's PDC were a class attribute, then your root actor override of Actor Core would work as you reason it should, and the enqueuer of the launched nested actor would end up in the PDC of the root actor and the launched nested actor would be shut down as you expect when the root actor shuts down the other actors using a Stop Message to its nested actors...

But the PDC data of actor objects are instance data.  When you launch the nested actor using a COPY of the running actor's object, you are creating a NEW INSTANCE when you fork the wire (which, as you correctly pointed out, has a COPY of the running actor's pair of enqueuers, self & caller). 

Here's the flub: When the launched nested actor has its enqueuer added to the branched wire of the running actor (in your "Default behavior" case of your Diagram Disable), that enqueuer goes into the INSTANCE data of the branch copy of the actor object.  It does NOT end up in CLASS data that any instance of the actor class (i.e., any branch of the wire) would access.  Because LVOOP does not (directly) provide class attributes.

(You can, however, fairly easily implement class attributees in LVOOP; I'm running an app I built with AF that does so...)

So the nested actor's enqueuer ends up in a branch copy of the root actor's object, not in the root actor's object.  And therefore not in the root actor's PDC to be referenced later when it's time to stop.  And what happens to it?  When the "Launch Nested Actor" in your DD "Default behavior" case runs and launches the nested actor, the root actor's "actor object copy" wire that receives the resulting enqueuer is not wired to the output of "Launch Nested Actor.vi"...  So the object copy containing goes nowhere... and LabVIEW's garbage collector reaps it and it's gone.

And once it's gone, it's gone, and that means "There's no way to talk to the nested actor any longer."  I.e., you can't stop it.  (Or do anything else useful with it.)

Does this now make sense?

0 Kudos
Message 17 of 34
(2,526 Views)

Thank you for your question. To clarify, I am dynamically launching nested actors from inside my root actor's Actore Core.vi in a process that runs parallel to the parent Actore Core.vi call (valid and required for such situations). Thus, the message I am expecting to be sent by the Launch Nested Actor.vi and the message I do send manually in my corrected code (the second picture) is from the root actor to itself, not the newly launched nested actor. This message does two things: (1) it actually launches the nested actor, (2) it registers the nested actors enqueuer reference with my root actor in its Nested Actors property. If you look at Actor.lvclass:Stop Core.vi, you will see that this VI iterates through all the Nested Actors saved in the class and sends them a stop message. This is how the auto-stop behavior works. So if this Nested Actors property isn't populated correctly when a calling actor launches a nested actor, the calling actor won't have knowledge of the nested actors when it runs its Stop Core.vi method. For a dynamically launched nested actor, the Launch Nested Actor.vi only does (1).

So this expectation stems from the expectation of the auto-stop behavior to work. I'm going to repeat myself here, but it's good for iteration. For a dynamically launched nested actor, which is a valid use case, the Launch Nested Actor.vi does not properly register the launched actor as a nested actor in the calling actor. This is because it adds the launched actors Message Enqueuer.lvclass to the Nested Actors property by a simple value set if it sees a true for the auto-stop boolean. It works in the static case, i.e. launching known nested actors prior to calling "Call Parent Node" in your Actore Core.vi, because the actor class is subsequently passed from the Launch Nested Actor.vi indicator into the control of the parent Actor Core.vi. So in that case, the added enqueuer makes it into the parent Actor Core.vi handling all of the messages, including the stop message. View this in the default actor framework template project. In the dynamic case, the parent node of Actor Core.vi has already been called, so this simple value set never makes it back to the actor that is already running in the parent Actore Core.vi. Therefore, the only way to tell a caller actor to properly launch a nested actor is to have it send itself the below mentioned Send Launch Nested Actor Msg.lvlcass message. This is the proper way because at this point it has been launched and its Actore Core.vi is running. Even the actor framework documentation points to the fact that messages should be used to update the state of your running actors. In this case it's an actor sending itself a message since it's the one taking the action of launching nested actors.

What I found out is that this expected behavior is actually already recognized, because the VI (Actor.lvclass:Send Launch Nested Actor Msg.vi) and the message class (Launch Nested Actor Msg.lvclass) already exist and are in the Actor Framework.lvlib. My point is that I feel this message passing should be the default behavior for launching the nested actor so that the auto-stop behavior works as expected for both the static and dynamic launch use cases right off the bat. There has been no reason presented why this shouldn't be the case. In fact, if you just replaced the code of "Launch Nested Actor.vi" with the code in my second picture, no one launching static actors would know and it would work as expected for the dynamic case.

Let me know if I'm all over the place here, as I am afraid I am in this post.

All of this will make sense if you look at the following VIs:

  • RootActor.lvclass:Actor Core.vi
  • Actor.lvclass:Launch Nested Actor.vi (pay attention to the auto-stop boolean)
  • Actor.lvclass:Stop Core.vi (notice the iteration over the Nested Actors, which should be an array of the launched nested actors' Messaging Enqueuer.lvclass)
  • Actor.lvclass:Send Launch Nested Actor Msg.vi (this is hanging out in the Advanced palette and properly launches a nested actor for an actor whose parent Actore Core.vi is already running)
0 Kudos
Message 18 of 34
(2,526 Views)

I read through your post, but you are still talking about things that aren't relevant. It's also somewhat condescending to make an assumption that I am new to OOP.

Brainstorms wrote:

When the "Launch Nested Actor" in your DD "Default behavior" case runs and launches the nested actor, the root actor's "actor object copy" wire that receives the resulting enqueuer is not wired to the output of "Launch Nested Actor.vi"...  So the object copy containing goes nowhere... and LabVIEW's garbage collector reaps it and it's gone.

And once it's gone, it's gone, and that means "There's no way to talk to the nested actor any longer."  I.e., you can't stop it.  (Or do anything else useful with it.)

That is entirely my point and an explanation of the pathological behavior in the Launch Nested Actor.vi. This is the behavior I do not like. It shouldn't be doing this. Instead the root actor should be sending the Launch Nested Actor Msg.lvclass message to itself, which works because you have passed it the correct message enqueuer to itself! Go to this message class in the Actor Framework.lvlib and follow the Do.vi method. Or you could just simply follow the code in the expected behavior case of the diagram disabled structure. I think my point will become clear if you do this.

Just focus on this question: How should a launched root actor properly launch dynamic nested actors such that the auto-stop behavior works?

That's all I care about getting clairfication on in addition to fixing the actor framework API if need be. And to be clear, I have it working as one would expect with no unlaunched "orphaned" actors, no errors, and no actors just running off by themselves with no knowledge that their caller actor closed. It doesn't work if I use the default Launch Nested Actor.vi as used in examples. So if someone says I'm wrong in my solution (which is cleary attached to this post), then they need to provide another solution and quit explaning non-relevant things to me.

0 Kudos
Message 19 of 34
(2,526 Views)

I can't give you a good answer to your question either, but some comments:

1. I've never used the "Send Launch Nested Actor Msg" VI, but I think you have found a correct use case for it. I've added a snippet below of the Root Actor to show how to use it better in your case i.e. no actor branching. (I know that's not what you're asking for, but anyway... ).

ActorFWSubPanelTest_no_branching.jpg

2. If you look at the Do.vi for the "Send Launch Nested Actor Msg" class, you'll see that it ultimately uses the "Launch Nested Actor" function anyway. Again though, not sure that's relevant to your questions, but it may make them moot.

3. I think you're taking the concept of actor messaging to too low a level in expecting that under-the-hood messaging should be used to set properties of a newly launched actor or the caller actor. No other Actor sytem I know does this e.g. Akka or Scala, or even early languages like Act. I'm not saying I know much about those other systems, but they don't do any under-the-hood messaging like you're expecting, as far as I'm aware.

Anyway, 4am here and too late to think straight, so that's all I have. 

Edit to add: Here's the stop case of the code I modified:

ActorFWSubPanelTest_no_branching_StopCase.jpg

0 Kudos
Message 20 of 34
(2,526 Views)