Actor Framework Discussions

cancel
Showing results for 
Search instead for 
Did you mean: 

How do I get an actor to wait for its nested actors to stop running before stopping itself? (x-post from LabVIEW forum)

I'm developing a series of projects that are based on the Actor Framework and my actor dependency hierarchy is starting to get some depth. One of the issues I'm facing now is making sure that each actor will only stop running (and signal this via a Last Ack to a higher-level actor or via a notification to non-AF code that launches it) once all of its nested actors have stopped running.

For instance, say I have a type of actor that handles communication with a microcontroller over USB - a USB Controller Actor. I might want to have an application-specific actor that launches USB Controller Actor to issue commands to the microcontroller. When shutting down, I want this top-level actor to send a Stop Msg to USB Controller Actor and then wait to receive a Last Ack back before sending a notification within a provided notifier to the non-AF application code, which can then finish shutting down completely.

I'm sure that having actors wait for all nested actors to shutdown before shutting down themselves is an extremely common requirement and I'm confident National Instruments have made it possible to handle that in a simple, elegant manner. I'm just struggling to figure out what that is.

The approaches I've experimented with are:

  • Creating a pseudo "Stop" message for an actor that won't actually stop it running straight away, but instruct it to stop all nested actors, wait for their Last Acks and then shut itself down by sending Stop Msg to itself. This isn't elegant because it means the client will be forced to fire off these pseudo stop messages instead of Stop Msg to certain actors.
  • Instantiating an internally-used notifier and overriding Stop Core.vi and Handle Last Ack Core.vi. The idea is that within Stop Core.vi, I send Stop Msg to each of the nested actors and then make it wait indefinitely on the internal notifier. Within Handle Last Ack Core.vi, I make it send a notification using this notifier which allows Stop Core.vi to continue and perform deinitialisation for the top-level actor itself. Figures 1 & 2 below show this approach. I wasn't confident that this would work since it assumed that it was possible for Stop Core.vi to execute and then Handle Last Ack Core.vi to concurrently execute some time after. These assumptions didn't hold and it didn't work. It would have been messy even if it had.
  • Overriding Stop Core.vi, making it send Stop Msg to each nested actor and then waiting for an arbitrarily long period of time (100ms, 200ms, etc.). What if a nested actor takes only 10ms to shut down? What if takes 400ms?

The figures below show how I implemented the second approach. Ignore the broken object wires - they only appear that way in the snippets.

Figure 1 - Stop Core.vi from the second approach

StopCore.png

Figure 2 - Handle Last Ack Core.vi from the second approach

HandleLastAckCore.png

Edit: The original post can be found here http://forums.ni.com/t5/LabVIEW/How-do-I-get-an-actor-to-wait-for-its-nested-actors-to-stop/td-p/314...

Message 1 of 4
(4,633 Views)

I would recommend sticking with AF messages as your communication method, so cross out the notifier option.

I would not do a wait of a length of time as in your last example. How do you know what the right amount of time is? You don't want to be hung in the Stop Core. So cross out the waiting option.

The recommended way of handling this it to override Handle Last Ack Core.vi in your actor that launched the nested actor. You will also need to add some data to the launcher actor to know about stopping state.

So, if you want to stop your USB controller you send it a "Finish Your Job" message and set a boolean that says the USB is "Expected to Stop". The USB controller will know that when it receives that Finsh Your Job message to do some things and untimately to send itself a Stop msg. Then the USB controller will send a Last Ack with the normal stop code to the launcher. The launcher will indentify that the USB is "Expected to Stop" and not then stop itself, but maybe change that boolean back to false and go back to doing its regular business. Note, if the USB controller sends a Last Ack when it was not "expected to stop" you may handle that differently.

You would probably also want to know that the USB is now disconnected and that the system shouldn't send messages to the USB Controller when it isn't running (1556 error for not a valid enqueuer reference).

There is no "canned" version of doing what I said in the last two paragraphs. This is because it is different for each project. I believe that almost everyone should do something here, because the default is to send the last ack up the tree and down each branch such that the entire system ends up stopping. You should implement your solution for your system. Write down your system states and sequences and then build it. Refactor when you realize you didn't have all of the requirement right.

Casey

Casey Lamers


Phoenix, LLC


casey.lamers@phoenixwi.com


CLA, LabVIEW Champion


Check Out the Software Engineering Processes, Architecture, and Design track at NIWeek. 2018 I guarantee you will learn things you can use daily! I will be presenting!

0 Kudos
Message 2 of 4
(3,701 Views)

> I would recommend sticking with AF messages as your communication method, so cross out the notifier option.

The notifier would really be intended just to say "I'm done shutting down" to the top-level non-AF code that launches the top-level actor in the actor hierarchy. Non-AF code can't receive AF messages so another communication method is necessary here.

> I would not do a wait of a length of time as in your last example. How do you know what the right amount of time is? You don't want to be hung in the Stop Core. So cross out the waiting option.

This is what I was trying to get at as well in my post. I don't consider it a good solution either.

> So, if you want to stop your USB controller you send it a "Finish Your Job" message and set a boolean that says the USB is "Expected to Stop". The USB controller will know that when it receives that Finsh Your Job message to do some things and untimately to send itself a Stop msg. Then the USB controller will send a Last Ack with the normal stop code to the launcher. The launcher will indentify that the USB is "Expected to Stop" and not then stop itself, but maybe change that boolean back to false and go back to doing its regular business. Note, if the USB controller sends a Last Ack when it was not "expected to stop" you may handle that differently.

This is similar to the first solution I listed - sending a pseudo "stop" message (in this case "Finish Your Job"). I suppose if this really is the best option at the table, I could roll my own subclass of Actor that provides a standard "Verified stop" message and corresponding VI in which it recursively dispatches "Verified stop" messages to all nested actors and initialises a numeric control equal to the number of Last Ack messages it expects back. Then Handle Last Ack Core.vi can be overridden to decrement this counter when a Last Ack message is received if the counter is still positive, and send a (normal) stop message to its own actor when the counter hits zero.

Does this seem like a reasonable approach?

0 Kudos
Message 3 of 4
(3,701 Views)

Yes, I am saying you should use your first option.

You need a subclass of Actor. Every actor you have is a subclass of actor. So, I don't think of this as anything special. Perhaps not every actor in your system needs to inherit from something with knowledge that it owns nested actors that it needs to stop without itself stopping. Reuse is great, so thinking of a generic way and having a common parent is great. However, sometimes you need to program for the specific implementation. Identifying which approach to take is the job.

I have used a increment/decrement approach. You can also use an array of enqueuers (that you construct when you launch nested actors) and look at the enqueuer for the actor that stopped and remove it from the array and look at the array size.

If the solution covers your requirements it is not wrong. If requirements change you may need to change your solution, so some upfront thinking about different cases is time well spent. Just make sure you don't spend too much time building something you don't need.

Casey

Casey Lamers


Phoenix, LLC


casey.lamers@phoenixwi.com


CLA, LabVIEW Champion


Check Out the Software Engineering Processes, Architecture, and Design track at NIWeek. 2018 I guarantee you will learn things you can use daily! I will be presenting!

0 Kudos
Message 4 of 4
(3,701 Views)