04-06-2020 07:46 AM - edited 04-06-2020 07:47 AM
Hello, I am interested in switching from traditional QMH to Actor Framework for SW implementation for my customers. One of the biggest motivations is that we often receive repeat order from my customers (especially FPGA-related) and have to focus more on modularity and scalability. (Actor Framework seems often introduced as better replacemnt of traditional QMH. ) I repeated first 2 days of AF training course materials, and did some researches on internet to find similar case-study, but I could not find any direct answers, so, really hope to get some advice from true experts of Actor Framework.
As a starting point of applying Actor Framework, I picked up configuration sequence in a simple and on-going application development for my customer. In the project, there is one acquisition device (reconfigurable oscilloscope such as 5164), and data logging is required. During configuration, an operator configures such as vertical range, etc on UI and hits "configure" button. Once Caller receives configuration from UI, state is switched to "configuring" and UI is disabled. Then, Acq module is configured first, before the configuration for Log module. When Acq module finishes its configuration, it notifies its actual configuration value and binary-voltage scaling factor. Once caller receives the info from Acq module, configure message is sent to Log module, and log file is given the actual configuration values and binary-voltage scaling factors as logging property (such as TDMS properties).
Below is sequence diagram of configuration sequence.
(for simplicity, case of configuration errors are ignored)
To implment this sequence by Actor Framework, I got couple following questions.
1. Course materials and many people say reply message should be avoided as much as possible to prevent any possibilities of deadlock. I can half-understand the advice, but I could not find anyone gives the advice with a real case-study. In my case with the configuration sequence above, I decided not to use "Send Message and Wait for Reponse" (Lesson 12). In stead, send configuration message from Caller to Acq module, start self-timer inside caller, and wait for "configured" reply from Acq module (via Abstract Message). In this circumstance, I do not know if this implementation actually follows the advice and could prevent (or reduce at least) the risk of deadlock. Is there any golden template to implement this sequence by Actor Framework?
2. Does anybody know whether there is an existing example of applying Actor Framework to traditional LabVIEW Sample Project, such as Waveform Measurement and Logging, or cRIO Waveform Acquisition and Logging, etc?
CLA / CLED / LV FPGA Experts (IDL, VST, FlexRIO, other SDIs)
Solved! Go to Solution.
04-06-2020 10:23 AM
Just a note from a non-AF person, but the messaging pattern the OP is using here is an asynchronous version of a Request-Reply, with an alternate action on timeout. Don't know how much effort those "self timers" are, but this is something that could added to the AF more formally.
04-06-2020 10:39 AM
There is no problem with sending a message (e.g. Caller sends Configure to Acq), then receiving a message back (e.g. Acq sends Configured to Caller). This method is asynchronous, which means that Caller can still receive other messages while it waits for Acq to Configure. For example, maybe the UI wants to send Shutdown to the Caller in the middle of Acq Configure.
The reason to avoid Send Message and Wait for Response is because that method is synchronous. For example, if Caller sends Configure via Send Message and Wait for Response, then Caller Actor will be 100% blocked until Acq Configure returns a value. If UI sent a Shutdown message to Caller, it's impossible for Caller to handle the message until Acq finishes.
For your self-timer, try looking at Send Time-delayed Message. You can tell it to send a Shutdown message in 5 seconds (your timeout value). Then you can cancel the Shutdown message if you receive Configured message before expiration.
Good luck! There's definitely a steep learning curve to the Actor Framework.
04-06-2020 05:12 PM
I am FAR from being a professional, but I have some thoughts. Basically, your problem boils down to "I need Acq and Log to start up and return a "successful" message before I do something else".
IMHO, you are massively overcomplicating things by going this route. I have to ask, why are your Acq and Log actors "alive" at the top of the diagram? If they're just waiting for instructions, they're not really helpful, are they? Acq and Log both need to confirm they were able to start up, so Caller does (in some ways) need to wait on both of them to move on. It doesn't need to *block*, necessarily, but it can if the blocking is guaranteed to be quick. For example- create a file, if it succeeds, move on, if not, throw an error. A quick decision like that can be blocking. (If it needs a long startup sequence, do not block).
I would recommend spawning and killing the actors as-needed. Your "Caller" actor would launch Acq in some method. Perform Acq's configuring in its Pre-launch Init function. If it fails, that function returns an error immediately to Caller, and the Actor is not launched. If it succeeds, launch Log, who will grab the file name in its own Pre-launch Init. If it can't generate that file name, it fails, the actor isn't launched, and you can process the error there.
Doing it this way IS synchronous, but that is one of the things Pre-launch Init is good at. You also lose the requirement to have your timeouts and state changes altogether, which massively simplifies all of your actors.
I have used this very successfully to make a data acquisition actor in the past. Initially, I wanted to start up the actor, then send messages to make it transition states as-needed. This meant that I needed an internal state handler and the ability to accept or reject messages based on states. For example, if I sent a Stop Acquisition message while it was already not acquiring, I need to send some reply back to the caller that says "Command incompatible with current mode".
Then I realized... why do I need to have an actor living in a state machine if it's not acquiring? So I revamped it. In Pre-launch Init, it does a Reserve on the hardware needed. If it succeeds, great- it moves on to Actor Core, where it starts sampling the data and sending it to the desired location (using abstract, zero-coupling messages). If it fails to acquire the hardware, Pre-launch Init fails, the actor isn't created, and my Main program never goes into this limbo state of "waiting to initialize" where it just sits and waits for a message.
Caveat to this: I do not know what kind of hardware you're using. I would say that if you're using something that takes a while to start up (say some particular device needs to warm up for 5 minutes before being "ready") then I would do it the way you're doing it, with asynchronous start/started OK messages, and I'd use Time Delayed Send Message (oh what an incredible tool that is!)
Basically it boils down to this: If your actor doesn't need to reasonably exist without doing its configuration setup (like a logger getting access to the log file), AND it *always* determines if config succeeded or failed *quickly*, then do it as part of Pre-launch Init. If your initialization functions take a while to process, put it in Actor Core but before it calls "Call Parent Node". Putting it there will make it non-blocking to your original caller, so you will need to implement a timeout/wait/etc in the caller.
Check out this wonderful post by AristosQueue, the inventor of the Actor Framework, for some more info about initializing actors. There is no one-size-fits-all approach. I hope that if my suggestion is bad I'll be corrected 🙂
04-06-2020 08:53 PM
I like it when stuff I have taught appears in the forums so I don't have to type it again. BertMcMahan has exactly the solution... Caller launches Acq, initializing it with timeout value. No need to even send a first message. Acq runs and checks its own time. It will always and unconditionally reply back with either "timed out", "configured", or "last ack" (if an error occurred and it shut down). When caller receives one of those messages, it takes the appropriate action (tell UI to quit, tell log to record configuration, or handle the error somehow).
If you do have to have Acq already launched, that's still fine... send it a "do configuration" message that has the timeout in the payload. Now Acq knows how long it has to run.
04-07-2020 11:28 AM
I really appreciate all the advice from the fantastic four falks. Thank you for all for the time and wisdom.
Let me first summarise the advice for myself looking back this thread in future.
-self-timer may require some amount of efforts (codes) to implement.
(agree! and I have always chosen to implement it on QMH and hated it. )
-fantastic explanations to understand what is synchronous vs. asynchronous.
-Time-delayed message may be nice to implement timer on AF.
(thank you so much for completely answering my Q1 + fantastic advice!)
-an Actor should have minimum responsibility of parallel task. (not all the time, but almost maybe?)
-configuration sequence is not necessarilly parallelized (I could rephrase it as "too coupled" ?)
-take advantage of Pre-launch Init for unparallelized conifugration sequence if it is quickly done.
(Your advice is incredible. You have saved me from a deep pitfall I have been in for years. )
-confirm the advice of BertMcMahan as a inventor.
(Thank you for the invention!)
04-07-2020 11:39 AM
Hello BertMcMahan, AristosQueue,
Thanks again for the time to read through and write the great advice. Please let me better understand your explanations.
BertMcMahan told that time-consuming initialization (or configuration) could be placed inside of Acq's Actor Core (in front of Call Parent Node). Then, Caller-side can judge timeout by itself WITHOUT receiving asynchronous reply message from Acq.
AristosQueue told that Acq runs initialization and checks its own time. So, time-consuming initialization should run in parallel with Call Parent Node.
To me, it seems slightly different between the two advice. Forgive me if I am understanding anything wrong.
04-07-2020 12:46 PM
You're correct- they are two slightly different ways to accomplish your goal. I'd say that if Actor Core *needs* the initialization finished to handle messages, put it upstream of Call Parent. That way your initialization is guaranteed to finish before the first message can be handled (messages will be able to queue up during initialization, but they won't be executed).
If you put it in parallel with Call Parent, then the Actor can start responding to messages right away *while initialization is still happening*. Which case will depend on your specific setup.
My preference for an Acquisition and Logging actor would be to put it in front of Call Parent. My acquisition actors tend to not have many incoming messages (other than "Stop"), so it wouldn't matter either way to me. I'd definitely put it in front of Call Parent for Log, because initialization is where you'll get your file path. Receiving a message to "log something" and trying to handle it before you have completed initialization doesn't make much sense. Then again, I'd put that in Pre-launch Init anyway.
04-07-2020 12:53 PM
(Sorry to double post)
Oh, and to expound a bit on what AQ said: with an actor with a fairly long startup time, launching it with its own timeout value is a great way to do it. Off the cuff, I think I'd use an abstract message class to represent "Startup result", bundling that message and a timeout value together as inputs when you launch the actor. Your caller class will give your child class a specific message to respond with. The Startup result class would just have an enumerated type input of "Successful", "Failed", "Timed out", etc.
The abstract message class only contains "Send Message", not the actual contents of the message. The callee actor can then use that abstract class in its code without being coupled to the caller actor. When the caller actor launches the callee actor, it sends along a concrete version of Startup Result, which contains a method *for the caller class* that handles what happens when the result is returned. Basically, it says "Send this message back to me when you are finished initializing".
This way your child class is totally decoupled from your parent and can be used in any number of applications. Anytime you need to call Logger, you create a method for the new Caller actor and use that in the concrete version of Logger's abstract message.
Hope that helps... it's a little hard to explain via text.
04-08-2020 08:01 AM - edited 04-08-2020 08:07 AM
Thanks much for the continuous support. Following your advice, now the configuration sequence has been modified like below. (Describe normal operations without exceptions. )
Would you give any additional comments about this design?