LabVIEW

cancel
Showing results for 
Search instead for 
Did you mean: 

Obtain Notifer

I am looking for feedback on a pattern I am considering for named notifiers in a large LabVIEW application.

 

I recently tracked down what appears to be a long-runtime memory/resource problem caused by calling Obtain Notifier inside a loop. In my case, the Obtain Notifier call was not obvious on the top-level diagram. It was buried several layers down in subVIs, and one of those subVIs was being called repeatedly by a long-running loop. 

 

The NI documentation for Obtain Notifier specifically warns about this pattern. For a named notifier, LabVIEW does not simply hand back the exact same reference each time. It returns a new reference to the existing named notifier. If this is done inside a loop, LabVIEW creates a new reference each iteration. NI notes that each new reference consumes additional memory, and in a tight or long-running loop this can look like a memory leak because memory use continues to grow until the VI stops running.  I eventually got 'out of memory error' despite having tons of free RAM.

 

NI documentation page:
https://www.ni.com/docs/en-US/bundle/labview-api-ref/page/functions/obtain-notifier.html

also

https://forums.ni.com/t5/LabVIEW/Re-obtaining-Notifiers-to-Send-Notification-rather-than-wiring/m-p/...

 

The documentation also says Obtain Notifier can return Error 2. I do not know that there is a single fixed “maximum number of notifier handles” that applies in all cases.  If so, I'm curious to what the magic number actually is.

 

My proposed fix is to stop calling Obtain Notifier directly in code. Instead, I am considering wrapping it in a small per-call-site cache VI.

 

The idea is:

  • Configure the helper VI as inline/reentrant so each call site has its own cached reference.
  • Use a feedback node or shift-register style storage.
  • On first call, call Obtain Notifier and cache the returned reference.
  • On later calls from that same call site, return the cached reference.
  • If a prior obtain failed, clear the error and retry obtaining the notifier a limited number of times.
  • Do not call Obtain Notifier on every loop iteration.
  • Do not release/destroy the notifier every iteration.
  • Let final cleanup/shutdown handle releasing references in the proper order.

 

wldgoose_0-1782218884154.png

 

 

 

wldgoose_1-1782218892825.png

 

 

I am thinking this per-call-site cache may be safer than a true global FGV in this application because I cannot always guarantee how the helper will be called across a large codebase. A non-reentrant FGV would create one shared cached reference for the entire application. That may be fine in some designs, but in this case I am more interested in preventing accidental repeated obtains from inside loops while still allowing each call site to own its own cached reference.

 

This may be overkill, especially the retry logic, but my motivation is defensive programming. In a large application, it is easy for a low-level utility VI containing Obtain Notifier to eventually get called from inside a loop without that being obvious at the top level.

 

Yea yea, I know that notifers are basically globals and easy to abuse..  but heck, all data is global if you try hard enough!

 

What are your thoughts?  Anyone doing something similar?

 

-josh

0 Kudos
Message 1 of 10
(169 Views)

To answer one of your questions, the magic number is 2^20. For the most part, each by-reference LabVIEW type can have that many references open.

 

https://knowledge.ni.com/KnowledgeArticleDetails?id=kA00Z0000019KhWSAU&l=en-US

Message 2 of 10
(149 Views)

@Jacobson wrote:

To answer one of your questions, the magic number is 2^20. For the most part, each by-reference LabVIEW type can have that many references open.

 

https://knowledge.ni.com/KnowledgeArticleDetails?id=kA00Z0000019KhWSAU&l=en-US


 

 

"The number of simultaneously opened references is therefore limited to 1048575 for each type of reference. "

 

 

That seems about right for the loop iteration time and how long it took to get the Error 2.

 

Cool.

Message 3 of 10
(144 Views)

@wldgoose  a écrit :

My proposed fix is to stop calling Obtain Notifier directly in code. Instead, I am considering wrapping it in a small per-call-site cache VI.

 

The idea is:

  • Configure the helper VI as inline/reentrant so each call site has its own cached reference.
  • Use a feedback node or shift-register style storage.
  • On first call, call Obtain Notifier and cache the returned reference.
  • On later calls from that same call site, return the cached reference.

 


Hello,

 

Just a quick though on it : does each call site will have the exact same Notifier Name for all the application lifetime ?

 

If yes: why not executing Obtain Notifier outside the Loop and passing the reference inside the loop.

If not: then you will have a problem, your subVI may return the reference of another Notifier (the first call will be kept even if the Notifier Name has changed).

0 Kudos
Message 4 of 10
(137 Views)

There are several ways to avoid it. Send the Notifier to the subvi's, do one named Obtain outide the loop and reuse the reference inside the loop, or as you did, use a FGV.

G# - Award winning reference based OOP for LV, for free! - Qestit VIPM GitHub

Qestit Systems
Certified-LabVIEW-Developer
0 Kudos
Message 5 of 10
(135 Views)

@PinguX wrote:

@wldgoose  a écrit :

My proposed fix is to stop calling Obtain Notifier directly in code. Instead, I am considering wrapping it in a small per-call-site cache VI.

 

The idea is:

  • Configure the helper VI as inline/reentrant so each call site has its own cached reference.
  • Use a feedback node or shift-register style storage.
  • On first call, call Obtain Notifier and cache the returned reference.
  • On later calls from that same call site, return the cached reference.

 


Hello,

 

Just a quick though on it : does each call site will have the exact same Notifier Name for all the application lifetime ?

 

If yes: why not executing Obtain Notifier outside the Loop and passing the reference inside the loop.

If not: then you will have a problem, your subVI may return the reference of another Notifier (the first call will be kept even if the Notifier Name has changed).


 

I was going to make it generic to take any name for the type boolean..

 

Wouldn't the first call only trigger for each instance of the calling VI, as it's in-line'ed?  I always envisioned in-line as just copy and paste this code into the calling block diagram.  so each instance should have its own first call state?

0 Kudos
Message 6 of 10
(95 Views)

Hi Josh,

 

Why not call "Release Notifier" when you are done with your reference? It releases the reference, and only destroys the actual notifier object if no other reference to the same notifier is open somewhere else in your program (or if you set "force destroy?" to True).

If you ensure each obtained reference is wired to a "Release Notifier", there is no reason to have a memory leak.

 

By caching the reference in a preallocated clone reentrant subVI, you are still creating a reference for each call site (which can be at multiple places in your loop), while all you probably need is a single reference.

 

My usual strategy:

- Obtain the reference once during some initialization phase of the application.

- Wire the reference to all loops/tasks that need it.

- Release the reference once during some finalization phase, when all loops/tasks that used it are stopped.

- Use unnamed references only. In my opinion, the concept of naming references is a bad idea since it requires to ensure name uniqueness across your application. When obtaining an unnamed reference, you are sure to always create a new object.

 

Regards,

Raphaël.

Message 7 of 10
(76 Views)

I don't think the code you're showing does what you want or expect it to do.

 

1. If there's an upstream error that passes it's error into this VI then you end up obtaining a new reference regardless of whether the one in the shift register is still valid.

2. Let's say you run this VI for the first time (a new, valid, reference is created) and then someone closes that reference. Now, sometime later, you call into this VI and since there's no incoming error and it's not the first call you'll end up passing out the reference in the shift register which has already been closed.

 

I think you can probably just change the case structure selection logic to testing whether the notifier reference is valid. There's still a possible race condition with that logic but I would say it's less likely.

0 Kudos
Message 8 of 10
(64 Views)

@raphschru wrote:

Hi Josh,

 

Why not call "Release Notifier" when you are done with your reference? It releases the reference, and only destroys the actual notifier object if no other reference to the same notifier is open somewhere else in your program (or if you set "force destroy?" to True).

If you ensure each obtained reference is wired to a "Release Notifier", there is no reason to have a memory leak.

 

By caching the reference in a preallocated clone reentrant subVI, you are still creating a reference for each call site (which can be at multiple places in your loop), while all you probably need is a single reference.

 

My usual strategy:

- Obtain the reference once during some initialization phase of the application.

- Wire the reference to all loops/tasks that need it.

- Release the reference once during some finalization phase, when all loops/tasks that used it are stopped.

- Use unnamed references only. In my opinion, the concept of naming references is a bad idea since it requires to ensure name uniqueness across your application. When obtaining an unnamed reference, you are sure to always create a new object.

 

Regards,

Raphaël.


Close to my normal strategy.  I typically create any notifer/que in an init method of a class and store them in private memory.  Accessors take care of the rest.  When done, I destroy them via a cleanup method.  IMHO, a downstream programmer shouldn't need to worry about such things as notifiers/ques.  Abstract it away.

 

You were being kind.. I'll go even further than you did.. in this case this named notifer was acting a lot more like a global than I really like... IMHO, when I see a named notifer, I try to take a step back and ask myself if I'm trying to make a global.  If so, I need to think about things..

 

This was a rare care that I had both a named notifier and it didn't live in private memory of a class in the code base...

 

My original thought was to just do this and patch my original problem w/o having to rewrite a bunch of code.. (lazy, i know) but then I got to thinking, this thing would work outside a loop, and it might be an even safer way to deal with notifiers, in-case one gets by me again...  also easy to do a find/replace on..  I can look for all "obtain notifers" with a simple find all, and make sure they are in such a structure...  worst case, it'd make all my memory leaks into slight drizzles 🙂

 

0 Kudos
Message 9 of 10
(53 Views)

@Jacobson wrote:

I don't think the code you're showing does what you want or expect it to do.

 

1. If there's an upstream error that passes it's error into this VI then you end up obtaining a new reference regardless of whether the one in the shift register is still valid.

2. Let's say you run this VI for the first time (a new, valid, reference is created) and then someone closes that reference. Now, sometime later, you call into this VI and since there's no incoming error and it's not the first call you'll end up passing out the reference in the shift register which has already been closed.

 

I think you can probably just change the case structure selection logic to testing whether the notifier reference is valid. There's still a possible race condition with that logic but I would say it's less likely.


"I think you can probably just change the case structure selection logic to testing whether the notifier reference is valid. " 

 

I like this.  It's more straight forward too.  I'm thinking this is what you envisioned:

 

wldgoose_0-1782235697708.png

 


 

wldgoose_1-1782235702967.png

 

 

 

 



I think the answer to 1 and 2 here is what you suggested.  Test to see if the notifer is still active.  If it's not, it'll try to obtain it, and if it don't exist (assuming create is false), it'll throw an error, which is what it would be expected to do. 

  • On the first call, i'll throw an error, and pick it up on the 2nd time of the retry loop.  A few cycles wasted on the first go-around, but no big deal.
  • If the notifer is good and previously init'ed, it'll just pass what's on the shift register
  • If the notifer isn't init'ed and the "create notifier" bool is true, it'll throw an error on the first iteration of the loop, and then create the notifer on the 2nd.
  • If someone closed it, and "create notifer" is false, the notifier status will throw an error, and the obtain notifer will throw an error.  It'll rety a few times before throwing and error and pass that error to error out..

 

As raphschru said, the best soution is not to end up in a situation where this matters..  I guess my bigger question still is, is it even worth doing things like this, trying to make the language make it harder for me to do stupid things? or is the answer just be careful?   This one got by me, and it kind'a freaked me out..

 

 

0 Kudos
Message 10 of 10
(39 Views)