Jump to content

Error Handling Thoughts


Recommended Posts

Recently in this thread http://lavag.org/top...nentizing-code/ Daklu discussed his approach to error handling. This is toward the end of page 1.

Those familiar with the standard templates for creating VIs for Static and Dynamic Dispatch and Data Member Access are already too aware of the big green box you are presented with in the block diagram wit the ominous command: "Wire the error cluster across even in the "No Error" case so that warnings get propagated." But is wrapping all of your code inside this block necessary? Daklu provided some thoughts that I thought deserved a thread of their own.

Taken directly from one of Daklu's posts:

First, I've ditched the rule of wrapping all sub-vi code in an error case structure. (Blasphemy! Burn me at the stake!) About 9 months ago AQ and I had a discussion about this. He termed it, "hyper-reactionary error handling" and talked about the impact it has on the cpu's resources. He said,

Quote

Since the common case is "no error", all that extraneous error checking *has* to hurt performance... I mean, in many VIs, several registers must be permanently allocated to the error cluster because it is constantly in your "most recently used" cache.

These days I almost never wrap an entire code block in an error case. Many of my sub vis don't have an error case at all. The questions I ask myself when deciding about an error case are:

1. Do I need the sub vi outputs to be different if there is an error on the input terminal? If so, then I'll use a switch prim or case structure somewhere on the block diagram, though it may not wrap the entire code block.

2. Will the sub vi exit significantly faster by skipping the code on the block diagram when an error is on the input terminal? Yes? Then I'll wrap at least that section of code in an error case. The code I'm looking at here are things like For Loops containing primitive operations that don't have error terminals. String operations, array operations, cpu intesive math, etc. If a sub vi or prim has an error input terminal I don't worry about it's processing time. It will respond to the error input terminal appropriately and I assume (for simplicity) that its execution time = 0 if there is an error in.

index.php?app=core&module=attach&section=attach&attach_rel_module=post&attach_id=4025

In the diagram above I'm doing a lot of data compilation to gather the information necessary to create a GutStep object. However, the only primitive operations on this bd are Search Array and Index Array. Is the time saved in those rare instances where there is an error in worth the cost of error checking the other 99.9% of the time when there isn't? Nope, not in my opinion. In fact, this entire block diagram and all the sub vis execute exactly the same code regardless of the error in condition. (I know the sub vis ignore the error in terminal because of the green triangle in the lower left corner.)

But low level performance isn't the main reason I've ditched it. I found it adds a lot of complexity and causes more problems than it solves, like the issue you've run into. You've wrapped your accessor method code in an error case because that's what we're "supposed" to do. Why does the accessor method care about the error in terminal? Is unbundling a cluster so expensive that it should be skipped? Nope. Is it the accessor method's responsibility to say, "I saw an error so I'm not returning a real queue refnum because some of you downstream vis might not handle it correctly?" Nope. Each sub vi is responsible for it's own actions when there is an error on its input terminal.

In pratical terms that means my accessor methods never have an error case. They set/get what's been requested, each and every time. My object "creator" methods almost never have an error case, also passing the error wire through. Most of my "processing" method don't have an error case either. In the diagram I have 3 different "Lookup" methods. Each of those encapsulates some processing based on the inputs and returns an appropriate output. None of them change their behavior based on the error in terminal's state.

However, each of the three Lookup methods are capable of raising an error on their own (I know this because the lower right corner doesn't have a colored triangle,) which would ulitmately result in an invalid GutStep object being sent out. Is it the factory method's responsibility to try and prevent downstream code from accidentally using an invalid GutStep object? Nope. It's the factory method's responsibility to create GutStep object to the best of it's ability. If an error occurs in this method or upstream of this method that might cause the data used to create the GutStep object to be something other than expected, this method lets the calling vi know via the error wire. It's the responsiblity of the calling code to check the error wire and respond according to it's own specific requirements.

That's a little bit more about my general approach to error handling. It's more geared around responsibilities--who should be making the decision? (Responsibility-based thinking permeates all my dev work.) I've found that, in general, much of the time I don't really care if there's an error on the wire or not. Many of the primitive functions and structures already know how to handle errors. What do I gain by blindly adding more layers on top of it? Complexity in the form of more possible execution paths, each of which needs to be tested or inspected to verify it is working correctly. Thanks, but I'll pass. ;)

============================================================= END OF EXCERPT=============================================================

All, I can say is 'Damn I wish I had thought about this' and then thought about the importance of this information buried in a thread about Componentizing code. So, with his permission I decided to generate this thread. Your comments are always appreciated.

-kugr

Link to comment

I have always wondered about that. I mean if I am just unbundling something what can go wrong and why do I need to skip that on error in? It never really made a lot of sense to me but I blindly followed the advice to wrap all the content of my subvis in error cases. Now I will only be doing that if I actually will be putting code in the error case or if I really think I need to skip the code in the no error case on error in.

Does anyone know how to modify the templates used to create accessor vis?

Uhm.. This has nothing to do with the thread but it is killing me. What the heck is a GutStep?

Link to comment

I have a few things to add but I'm keen to see others opinions on error checking.

What the heck is a GutStep?

The "Step" class is part of a Sequencer library that I use for sequencing tests. GutStep is a child class written to execute a test on a specific product. Either that or it's a Nazi parade march. Can't remember which...

Link to comment

Steve, you don't have to modify the templates as AQ added this feature in 2009.

Static and Dynamic 'templates' are generated in C not G. Accessor methods are G. You can find them and replace them (this has been posted before) or you can use the Custom Hooks to alter them (introduced in 2009).

Cheers

-JG

Link to comment

I have a few things to add but I'm keen to see others opinions on error checking.

I just threw away my old dogma. So still in the process of establishing a new paradigm on error handling.

So for the Getter's, it is clear to me that I shouldn't do error checking here. The error checking will be done by the code that receives the objects properties. Furthermore, the error isn't indicating that anything is wrong with that data.

But for setters, it's not so simple. An error might indicate that something is 'bad' about the incoming data, so I wouldn't want to overwrite 'good' data (from a previous call or the default value) with that, kind of corrupting the objects data.

Still some more thoughts (or much better, just do some coding) on my side are required for the even bigger picture of parallel loops, communicating the errors and my simple case of a pure data queue (as opposed to a message queue which can transmit an error message).

Felix

Link to comment

My general rule is VIs don't modify in-place data if an error is supplied: if a wire goes straight through my VI with an in-out style, unless stated otherwise, that VI will NOT modify any data on that wire if presented with an error. Every one of my VIs which has a set of error terminals (most of them) has a statement in the documentation that either says "This VI provides standard error I/O functionality", or it will explicitly state its error handling logic if it deviates from that behavior. I admit to being lazy though for getter/setter methods.

Will the sub vi exit significantly faster by skipping the code on the block diagram when an error is on the input terminal?

This is another big consideration for me, but I never violate my first rule. Even if I save next to nothing processing time wise by checking for an error, if I modify in-out data, I wrap it up. In an error case structure that is...

I just threw away my old dogma. So still in the process of establishing a new paradigm on error handling.

So for the Getter's, it is clear to me that I shouldn't do error checking here.

I often omit checking in getters too (unless there's an expensive look-up time), but I should be clear I still propagate the error in these cases.

I should clarify the bit about getters though. I honestly almost never modify the default get/set properties for objects out of laziness. If it's a simple primitive, I figure the overhead in calling the VI, boxing and unboxing the actual Object etc is way more expensive than a simple case structure check. The opposite is if I have a potentially big buffer allocation due to a getter, I will always want to keep that wrapped up.The getter-style methods I often omit checking from are derived methods that usually simply return a primitive type. Maybe something that returns the size of an array, or does a trivial calculation. Mostly because in these cases I actually need to think about the code.

Link to comment

Somewhere, on some website, maybe even this forum, there was listed a collection of native LV functions that operated even when the ErrorCluster indicated an error. This was not a bad thing as I think one of the VIs was perhaps the Close reference. Did anyone else remember reading that and where it was? I am remembering a collection of about 7 or so VIs were listed.

Link to comment

Somewhere, on some website, maybe even this forum, there was listed a collection of native LV functions that operated even when the ErrorCluster indicated an error. This was not a bad thing as I think one of the VIs was perhaps the Close reference. Did anyone else remember reading that and where it was? I am remembering a collection of about 7 or so VIs were listed.

Nugget by Darren on the NI forum.

Destroy queue (and equivalents) are on that list as well. This is why I didn't expect the error hitting my in this case, as without error-handled getters for the queue, it works.

Felix

Link to comment

Nugget by Darren on the NI forum.

Destroy queue (and equivalents) are on that list as well. This is why I didn't expect the error hitting my in this case, as without error-handled getters for the queue, it works.

Felix

Awesome, thanks Felix!

Here is the link: http://forums.ni.com...010/m-p/1143364

(Sorry no light saber included ;) but many thanks to Darren!)

-kugr

Link to comment

Be careful with the Error cluster.

About a year ago I was asked to refactor some LabVIEW 5.0 originated low-level dialogs used extensively throughout our code.

These dialogs used a straight through in-out style. Some of the refactoring included using new NI functions and library VIs.

This caused much of our upper level code to need a recompile because the LV compiler now detected that the previous static state of the error cluster could now change. I had to change the error handling of the dialogs to discard the errors. We use TestStand and LabVIEW with Debug licenses on out ATEs.

Whenever an engineer sees a 'dirty dot' upon opening a VI, the person responsible for the lower level change gets a finger wagging nono.gif and DEFCON 1 is declared.

(Lucky or maybe unlucky for me, I'm just recently down to a group of one...)

I posted the example of my problem last year in this thread: Recompiled VIs + SCC

Link to comment

...

Has anyone actually measured the cost of wrapping everything in error cases?

Nothing in comparison to dynamic dispatch.shifty.gif

Well, yes I am keen to know too. I aside from the DD comment above, you are have overhead calling the subVI on a static method anyways.

Does the error case really matter?

Or is it a big overhead?

I would like to know the answer to that in the Dev Environment and in the Run Time Environment (if it is different).

As for me, in terms of Classes, right now I can't be bothered not including Error in/out terminals - except for Private and some Protected VIs.

The last thing I want is to update a Public method and find I don't have Error in/out terminals - that would cause a change in the Public API and I would want to go through my code and relink, I would rather just do that in the first place.

And I have never found an application has bogged down because of it.

Plus if I am chaining methods then I can keep them in a straight line for Class in/out and Error in/out and each VI in the API matches.

But... is there a worthwhile reason to change?

Link to comment

Benchmarked in 7.1.:

1 million iterations

straight wire through 186 ms

error case structure 306 ms

so the case structure costs about 120 ns.

Hey, just checked without any wire (so a clear error vi).

424 ms!

So this is most likely slower due to the buffer reuse in above cases.

Test in newer version: takes more than 500ms and the difference between straight wire and error case struct is almost gone.

Felix

Edited by Black Pearl
  • Like 1
Link to comment

Benchmarked in 7.1.:

1 million iterations

straight wire through 186 ms

error case structure 306 ms

so the case structure costs about 120 ns.

Hey, just checked without any wire (so a clear error vi).

424 ms!

So this is most likely slower due to the buffer reuse in above cases.

Test in newer version: takes more than 500ms and the difference between straight wire and error case struct is almost gone.

Felix

Set the vi to subroutine (assuming you are using a sub-vi vi in a for loop) and you'll probably halve those timesthumbup1.gif

Link to comment

Set the vi to subroutine (assuming you are using a sub-vi vi in a for loop) and you'll probably halve those timesthumbup1.gif

As expected changes the numbers (more than factor 2 in most cases). But again, in a recent LV version I don't see much difference between using an error case or running the wire through.

So the performance improvement by not doing dogmatic error checking in each and any VI will only be measurable in the most critical sections.

I'm going to do some more benchmarks when a subVI's error terminals are not wired at all.

EDIT: Result: Bad impact on performance. Always wire the error wire to the SubVIs!

Felix

Edited by Black Pearl
Link to comment

Well, the error check is a single operation, so I wouldn't expect much difference in all but the simplest VIs. For me it's more of a consideration of what the proper logic to apply when presented with an error, and about allowing a fast route out of code to some block that will handle the error (oh how I wish there was a way to implement exceptions in data flow- not that unwinding the stack is without speed problems in procedural languages).

What does surprise me is the need to wire up the cluster. Why should LabVIEW care if it is using the default buffer for the front panel or operating in place on a buffer owned by its caller? I wouldn't expect that to affect speed at all.

Link to comment

What does surprise me is the need to wire up the cluster. Why should LabVIEW care if it is using the default buffer for the front panel or operating in place on a buffer owned by its caller? I wouldn't expect that to affect speed at all.

With my limited understanding of the compiler, here the exlanaition.

When you wire both terminals (inside the subVI), the data is unchanged. If I don't wire the terminals in the SubVI, it actually changes whenever an error comes in.

If I don't wire the SubVI in my benchmark VI, a new buffer must be allocated each iteration.

Anyhow, I'm also more interested in the functionality aspects on error handling in this discussion.

Felix

Link to comment

I'm going to do some more benchmarks when a subVI's error terminals are not wired at all.

EDIT: Result: Bad impact on performance. Always wire the error wire to the SubVIs!

Something else to consider--by stringing the error wire through all sub vis you're probably imposing artifical data flow constraints on your software, limiting the compiler's ability to create parallel clumps. As far as I know, NI tweaks and improves the compiler with every major release. Adopting general coding practices that optimize compiled code for one release might turn around and bite you in the backside with the next release. Wouldn't a better overall strategy be to wire your code for clarity and logical soundness first, then go back and apply optimizations if they're needed?

...and about allowing a fast route out of code to some block that will handle the error

I'm curious about this... do you try to optimize the error execution path or do you mean you just skip unnecessary delays? I can understand skipping lengthy processes when they aren't needed for the current error condition, but I don't see why it would matter if it takes 100 ms or 75 ms to execute the error path. Doesn't the very nature of there being an error mean the system or component is in an unexpected state and all bets are off?

(oh how I wish there was a way to implement exceptions in data flow- not that unwinding the stack is without speed problems in procedural languages).

It's not difficult to implement similar behavior in Labview. It's not practical on the vi level by any stretch of the imagination... but it's not difficult. All you have to do is create an ExceptionHandler object that contains an instance of itself and has a single HandleException method. As you progress down your call stack each vi wraps it's own ExceptionHandler object (if it has one) around the one it was given and passes it to the sub vi. See the attached example. (I first proposed the idea here.)

Some differences:

-LV doesn't inherently support exceptions so you have to check for errors, raise your own exceptions, and invoke the exception handling code yourself. The code shows one example of how to do it.

-In other languages often execution continues at the procedure that handled the exception. In this example the exception bubbles up until it is handled and execution continues with the sub vi that generated it, not with the sub vi that handled it. I don't know if "resume where handled" behavior is possible with LV without brute force.

I've not used this in a real app and it's clearly too heavy to implement for each vi, but it could be useful at an architectual level when errors occur at lower layer that you want to manage from a higher layer.

ExceptionHandling.zip

Link to comment

I'm curious about this... do you try to optimize the error execution path or do you mean you just skip unnecessary delays? I can understand skipping lengthy processes when they aren't needed for the current error condition, but I don't see why it would matter if it takes 100 ms or 75 ms to execute the error path. Doesn't the very nature of there being an error mean the system or component is in an unexpected state and all bets are off?

I'm mostly concerned with execution time from the triggering of the error state back to whatever layer in the call stack handles the error. I have analysis code which can run in the minute to hour timescale. If an error occurs after 30 seconds, I don't want to wait a half hour to reach my handler. So a lot of the code gets wrapped up in error cases even if it is primitive operations which are inherently incapable of generating an error conditions.

I'll take a look at that exception code once I land in front of a PC. Sounds interesting.

Link to comment

Something else to consider--by stringing the error wire through all sub vis you're probably imposing artifical data flow constraints on your software, limiting the compiler's ability to create parallel clumps. As far as I know, NI tweaks and improves the compiler with every major release. Adopting general coding practices that optimize compiled code for one release might turn around and bite you in the backside with the next release. Wouldn't a better overall strategy be to wire your code for clarity and logical soundness first, then go back and apply optimizations if they're needed?

This is a good point but as this topic focuses around the use of Classes wouldn't the dataflow and hence parallelism be predominately defined by the Class in/out terminals of the method VI (making the Error in/out terminals irrelevant)?

Link to comment

I have an important question. I will be doing my CLD exam soon. Do the evaluators check that subvis are wrapped in an error case structure? I know that vi analyzer does. Maybe I should explain in the comments that this is (mostly) pointless and refer the evaluators to this thread :book:

Can anyone from NI that conducts exam reviews comment?

Link to comment

...Maybe I should explain in the comments that this is (mostly) pointless and refer the evaluators to this thread

I think this thread is still lacking evidence to say that wrapping VIs in an error cluster is 'pointless'. I think at the end of the day it comes down to a matter of style.

In this case you are sitting NI's exam with their marking system. Style is rated pretty high and it's pretty well known they use VI Analyzer to assist marking ;)

Link to comment

This is a good point but as this topic focuses around the use of Classes wouldn't the dataflow and hence parallelism be predominately defined by the Class in/out terminals of the method VI (making the Error in/out terminals irrelevant)?

Depends on what you're doing in the sub vi. If all you're doing is calling methods sequentially on a single object, then yeah, the error wire isn't going to affect parallelism. Personally I find I don't do that very often. I don't know if that's good, or bad, or neither... just that I don't run into many situations where I end up doing that. Often in my code there are far more opportunities for parallelism by not making execution dependent on the error wire. If you look at the diagram posted above, LookupAduInfo must be called prior to LookupKeyCode only because of the error wire. In this particular case any potential savings are insignificant. If each sub vi on the diagram had multiple layers of additional sub vis to drill through and I allowed the data to dictate execution flow instead of the error wire, who knows?

I think this thread is still lacking evidence to say that wrapping VIs in an error cluster is 'pointless'.

Just to clarify, I don't mean to imply that I think wrapping an entire sub VI block diagram in an error cluster is pointless. In fact, there specifically *is* a point in doing it--to ensure the sub vi code doesn't execute if there's an error. Turns out that's not a goal that contributes to my code quality or clarity when applied universally to my projects. *shrug*

Link to comment

Often in my code there are far more opportunities for parallelism by not making execution dependent on the error wire.

I don't see how thus point is a benefit of not wrapping: Functions\methods can be run in parallel and then the errors concatenated at the end. Just because they are wrapped does not mean they have to be serialized etc...

Link to comment

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Unfortunately, your content contains terms that we do not allow. Please edit your content to remove the highlighted words below.
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...

Important Information

By using this site, you agree to our Terms of Use.