Reentrancy and Stateful VI's
Whether you’re new to LabVIEW or have been around the block the topic of VI reentrancy is an important one especially when dealing with stateful VI’s. In this post we’re going to briefly define the different reentrancy settings and then talk about the dangers of stateful VI’s who have the wrong reentrancy settings or are used in the wrong context. We define a stateful VI as one that stores data within itself from one call to the next. This is done in LabVIEW using shift registers and feedback nodes.
Every VI has execution settings that can be found under the Execution dropdown on the VI Properties dialog. Notice there are three reentrancy settings on the left hand side. Let’s talk about these options.
Non-reentrant execution is the default selection for a new VI in LabVIEW. There is only once instance of this VI allowed in memory meaning if the VI is called in two different locations at the same time, one will have to wait to use the VI while the other is executing.
Shared Clone Reentrant
If a VI is set to shared clone reentrant execution, the compiler will determine how many instances of the VI are needed and these instances can be used interchangeably to satisfy callers. Shared clone reentrancy is excellent for non stateful VI’s that are called in multiple locations. This optimizes the execution of your code by allowing multiple instances of the VI to run simultaneously in parallel.
Preallocated Clone Reentrant
If a VI is set to preallocated clone reentrant, the compiler will allocate memory for each instance of the VI on the block diagram and any data stored in that VI will only apply to that instance. Preallocated clone reentrancy is the execution setting of choice when a stateful VI is called in multiple locations and has locally scoped data.
Sounds simple enough right? Armed with this new information you start building new applications and VI’s making intentional use of reentrancy in your stateful VI’s. But you will likely run into a few issues. Here a couple important things to keep in mind.
Be careful with feedback nodes and shift registers
Stateful VI’s are powerful and convenient but can quickly bite you if your reentrancy settings are not set correctly for the usage case. Be careful placing feedback nodes and shift registers in your code. Whether you realize it or not you are creating a stateful VI. Here’s some guidelines:
- If a VI is stateful and is being used as a functional global variable, it must be set to non-reentrant so the same instance is called and used everywhere.
- If a VI is stateful and is being used in a local capacity, it must be preallocated to function correctly. For example the following VI outputs the elapsed time since it was called and the input value was changed. This VI could be useful for monitoring a value and then delaying an action when a value changes. If this VI were accidently set to non-reentrant or shared clone it would not function correctly unless it appeared in a single location in the code.
- If a VI is not stateful and is used in multiple locations, shared clone reentrancy is generally sufficient.
Careful with class inheritance and stateful VI’s
Class inheritance is a powerful tool. It provides a convenient way to reuse code, extend functionality, compartmentalize functions and data based on hierarchy, and much more. But extreme caution should be taken when using inheritance and stateful VI’s. Dynamic Dispatch VI’s can only be set to non-reentrant or shared clone. So if you’re trying to use a preallocated VI in a dynamic method, you will likely get unexpected behavior. This sounds confusing so let’s look at an example.
Let’s say you build a small class based test application that runs UUT A and UUT B (child classes) through an array of test steps. The program uses a control algorithm to monitor the current from the UUT which varies as the program varies the voltage to the UUT. The application is designed to test two UUT A products and two UUT B products all at the same time. The polling loop below for each UUT runs the latest step determined by the scheduling loop.
Let’s take a look inside the Polling Loop.vi which appears four times to control both UUT A‘s and both UUT B‘s. The parent Polling Loop.vi reads from hardware, performs some state logic, writes outputs to hardware, then logs the latest state to file.
Let’s say each child (UUT A and UUT B) have their own control algorithm which resides in their own overridden State.vi method. UUT A‘s State.vi reads the current and uses PID to determine the output voltage for a given step setpoint. Take a look at the following VI and see if you can see a problem with this?
The LabVIEW built-in PID.vi method used above is a stateful method that stores its data internally in shift registers. While it is correctly set to preallocated clone reentrant, in this context it is being called within a dynamic dispatch method State.vi which is shared clone reentrant. This means you don’t know which instance of State.vi will be called in each of the two UUT A polling loops. This is a major problem. Your code will not function as intended. To solve this you would either need to write your own PID algorithm and store the data between calls in the child class data or would need to rework the architecture and avoid dynamic dispatch.
A shared clone reentrant VI will essentially cause any preallocated VI’s called within it to behave like a shared clone. It’s generally best to not have stateful VI’s that are locally scoped when using dynamic dispatch methods. It is best to store the data using another mechanism such as the class private data.
- VI’s containing shift registers or feedback nodes are called stateful VI’s because they store the state of some data each time they are called.
- Stateful VI’s used as a functional global variable must be non-reentrant to function properly as a global variable.
- Stateful VI’s used to store state in a locally scoped VI must be set to preallocated to function correctly in their local capacity. Extreme caution should be used when using these VI’s inside other shared clone VI’s as they themselves will then function as a shared clone.
- Avoid combining stateful VI’s with shared clone dynamic dispatch methods. Store the data between calls using another mechanism such as the class private data itself.