ERA report error in try/catch

Does an ERA error report on a line inside a try/catch block indicate the app crashed, or that the try/catch caught it gracefully?

  • An ERA error report means that the app crashed. You don't get an ERA (or any other kind of indication) when a try/catch block catches an exception. (Unless you decide to put a printf in the catch block or something.)

    Note that in Monkey C, an error is not the same thing as an exception. For example, "array out of bounds" and "unexpected type" are errors, not exceptions that can be caught.

  • The distinction between an exception and an error had escaped me. I had been getting an un-reproducible crash and expected try/catch to provide a graceful work-around until I could track it down.

    But you say that doesn't apply.

    Could you direct me to where I can get guidance on errors/exceptions and try/catch please? Searching for error or try/catch in the Programmer's Guide  wasn't very successful

  • But you say that doesn't apply.

    Right, I'm saying that you can catch exceptions but not errors. The only way to get around an error is to prevent it from happening in the first place with testing and/or guard code. For example, if your app takes an arbitrary number as user input (e.g. from settings) that's used to index an array, you would have to write code to check whether that index is out of bounds or not.

    Could you direct me to where I can get guidance on errors/exceptions and try/catch please? Searching for error or try/catch in the Programmer's Guide  wasn't very successful

    I found the following page by searching google for "connect iq exceptions".

    [https://developer.garmin.com/connect-iq/monkey-c/exceptions-and-errors/]

    Exceptions and Errors

    Monkey C supports structured exception handling for non-fatal errors from which there can be recovery.

    ...

    Errors

    Because Monkey C uses dynamic typing, there are many errors for which the compiler cannot check. If the error is of high enough severity, it will raise an fatal API error and cause your app to terminate at runtime. These errors cannot be caught.

    Array Out Of Bounds
    An attempt is being made to reference an array outside of its allocated bounds
    Circular Dependency
    There is a loop in the dependency graph of a module or object that prevents a module or object from being constructed

    ...

  • Thanks for that link.

    There are plenty of examples of fatal errors which are not handled by try/catch, but I wonder what would constitute a non-fatal error that could be caught with try/catch (apart from ones I explicitly implemented)?

  • In the API doc, you can see what can get thrown that you can try to catch.  For example, with Application.Properties.getValue(mykey), if mykey doesn't exist, you can catch it.  For me, I return a default value in that case and the app can run

    Throws:

  • OK, thanks.

    Not useful for my problem. 

    One of the pitfalls of coming from the world of fully compiled languages.

  • Well, c++ has exceptions but it also has uncatchable errors (e.g. dereferencing a null pointer, running out of memory).

    Java also has uncatchable errors (e.g. out of memory.)

    I'm not aware of any Turing-complete language which has no fatal runtime errors.

    I think the real (perceived) problem here (which I also ran into) is that Monkey C has certain fatal uncatchable errors which you would *expect* to be catchable exceptions, especially if you're expecting the language to be similar to Java, like Array Index out of Bounds. IIRC, the first instance I ran into was String.toNumber() (years ago, and only for CIQ 1 devices), which would produce an Invalid Value error if the string didn't start with a digit. In this case I really would've expected to the method to return null or at least throw an exception. (Since then it's been changed to return null.)

    Perhaps this is controversial, but some people would argue that exceptions which are caused by a bug in the code (as opposed to invalid user input or unexpected behavior from an external service, for example), should *not* be caught. And from this perspective, it makes sense why certain things would be errors and not exceptions.

    This is similar to the school of thought which says that when you write code in C, you should *not* check pointer arguments for NULL values, because if someone passes NULL to to a function, that indicates a bug in the code, and in this case the program should be allowed to crash (so you're aware of the bug and you can fix it), as opposed to masking the bug with NULL check.

    https://softwareengineering.stackexchange.com/questions/403318/why-should-boneheaded-exceptions-not-be-caught-especially-in-server-code

    I am confused because in quite a few places I've already read that the so-called 'boneheaded' exceptions (ones that result from bugs in code) are not supposed to be caught. Instead, they must be allowed to crash the application

  • Perhaps this is controversial, but some people would argue that exceptions which are caused by a bug in the code (as opposed to invalid user input or unexpected behavior from an external service, for example), should *not* be caught. And from this perspective, it makes sense why certain things would be errors and not exceptions.

    And I agree with you.

    In my case the exceptions are towards the "invalid user input or unexpected behaviour from an external service" end of the spectrum. Unfortunately the vanilla "Invalid value" error on a line like:

    DTLL = (DTM * Math.sin( thisDelThetaRads)/Math.sin( Math.toRadians(tackingAngle))).abs()

    takes a bit of work to resolve. It's deep in the navigation algorithm and reliant on GPS data and prior course data.

    I can't replicate the error in the lab or in the field, but am getting numerous ERA crash reports and no feedback from users.

    Is it crashing due to a zero divisor? Is it that the abs() function is failing due to non-numeric operand? What else could I be?

    I could test for zero in the divisor (yes, I have scoured the code for a source) but, its success would rely on me releasing a new version and wailing for the ERA reports. Not a very satisfactory approach.

     

  • I can't replicate the error in the lab or in the field, but am getting numerous ERA crash reports and no feedback from users.

    Is it crashing due to a zero divisor? Is it that the abs() function is failing due to non-numeric operand? What else could I be?

    Maybe you can't replicate the conditions leading up to the error, but you can certainly test your hypotheses about which part of the code is failing.

    Is it crashing due to a zero divisor?

    If you run the following test code, you'll see that it results in an Invalid Value error. So crash due to zero divisor is a possibility.

            var y = 0;
            var x = 1/y;

     Is it that the abs() function is failing due to non-numeric operand?

    Technically abs() doesn't have an operand -- it's a method of Double/Float/Long/Number. Just for the sake of argument, let's see what happens if we try to call it on a string or null value (for example):

    "asdf".abs();

    ^ This results in a Symbol Not Found Error.

    null.abs();

    ^ This results in an Unexpected Type Error.

    What else could I be?

    Maybe the multiplication (DTM * ...)? Let's try the following code:

    var x = null * 5;

    ^ This results in: UnexpectedTypeException: Expected Number/Float/Long/Double, given null/Number

    ---

    How about toRadians()?

    var x = Math.toRadians(null);

    This also results in: Invalid Value with Details: Cannot convert object to Float

    ---

    How about sin()?

    var x = Math.sin(null);

    Another Invalid Value (with Details = "Failed Invoking Symbol).

    ---

    Given all of the above, knowing that ERAs don't show the error details but only the basic error message, and assuming there isn't some catastrophic bug in Monkey C which causes basic arithmetic and trig functions to unexpectedly fail, you can probably narrow the errors down to:

    - divide by zero (which means tackingAngle is 0)

    - null input to toRadians (tackingAngle is null)

    - null input to sin(): (thisDelThetaRads is null)

    Is it possible for tackingAngle or thisDelThetaRads to be null? If not, then it's got to be that tackingAngle is 0.

    Of course you don't want to send out another version of the app that will just crash again, but what would you have done if your try/catch had worked to swallow the error? That would've just hid the error and you'd have a subtly malfunctioning app (as you would likely have to decide on some "default" value for DTLL in the case of a caught exception).

    My point is, at this junction, if you're trying to isolate the bug, it doesn't really matter whether these are errors or exceptions.

    If you want to be sure what the error is (without writing guard code), you could rewrite your code so that each calculation is on a separate line. i.e. Something like this:

            var tmp = Math.sin(thisDelThetaRads);
            var tmp2 = DTM * tmp;
            var tmp3 = Math.toRadians(tackingAngle);
            var tmp4 = Math.sin(tmp3);
            var tmp5 = tmp2 / tmp4;
            DTLL = tmp5.abs();

    Of course if you don't want the app to crash, then you'll have to make an educated guess as I did above, and write guard code (not so different from try/catch, except this code can only circumvent errors you've correctly guessed).

            if (thisDelThetaRads == null) {
            	System.println("thisDelThetaRads == null :(");
            	DTLL = ... // ¯\_(ツ)_/¯
            } else if (tackingAngle == 0) {
            	System.println("tackingAngle == 0 :(");
            	DTLL = ... // ¯\_(ツ)_/¯
            } else if (tackingAngle == null) {
            	System.println("tackingAngle == null :(");
            	DTLL = ... // ¯\_(ツ)_/¯
    		} else {
            	DTLL = (DTM * Math.sin( thisDelThetaRads)/Math.sin( Math.toRadians(tackingAngle))).abs()
        	}

    Now your code may not crash, but you also may not know that anything went wrong, unless you:

    - Ask users to collect logs for you

    - Or if you save something to app properties (with an associated read-only setting), and ask users to check their settings

    Of course your code may still crash if you didn't guess the cause correctly, but if you're worried about that, you can combine the above two approaches: write guard code *and* put each step of the calculation on a separate line.

    Other than that, i don't have any good ideas for ya. Good luck!

  • Mighty thanks for sticking with me. Your analysis of the possible causes of Invalid Value error certainly narrows down the possibilities. 

    I think I will proceed with the guard code approach as the app functionality won't be significantly diminished by exiting the function on detection of the fault. 

    Who knows, I may stumble upon the cause down the track.

    Logging the errors would be useful.  I see some apps automatically generate their own log files, named after the Garmin-created app name (9ABC123.prg).  Any pointers on achieving that ? I thought the user had to create an empty, appropriately named txt file.