I've been receiving ERA exceptions on Storage.setValue calls that are irreproducible in the simulator, any ideas on preventing/fixing them?

Report:

Error Name: Unhandled Exception
Occurrences: 4
First Occurrence: 2021-03-08
Last Occurrence: 2021-03-09
Devices:
vívoactive® 3: 7.70
App Versions: 1.10.9
Languages: eng
Backtrace:
FastingWidgetApp.initialize:58

Code:

52 function initialize() {
53     AppBase.initialize();
54 
55     fasting_duration_hours = Storage.getValue("fasting_duration_hours");
56     if (fasting_duration_hours == null) {
57         fasting_duration_hours = 13;
58         Storage.setValue("fasting_duration_hours", fasting_duration_hours); // ERA error report: "Unhandled Exception"
59     }

Been receiving reports prior to this one in the Fenix 5 family of devices and Vivoactive 3. No report of this happening in any other devices.
Any idea how I can prevent this exception from being thrown? I can use a Try - Catch, but then what? I still need to store the data for the widget to be functional.

My questions are:
- Is there a way to check for Storage limitations that are device specific? As I said in the Simulator I cannot reproduce this.
- Is there a way to simulate a device with an almost-full storage?


  • I just have more and more reports for this exception. Any ideas?

    I would expect that Storage running out of space would throw an "Toybox.Lang.StorageFullException", not a generic one. So I'm not 100% sure that is even the case.

    Meanwhile I got more error reports:

    Error Name: Unhandled Exception
    Occurrences: 14
    First Occurrence: 2021-03-08
    Last Occurrence: 2021-03-10
    Devices:
     DescentTm Mk2 / DescentTm Mk2i: 4.10
    vívoactive® 3: 7.70
    App Versions: 1.10.9
    Languages: dut, eng
    Backtrace:
    FastingWidgetApp.initialize:58

  • Unfortunately, I think you don't get to see the exact exception that was thrown. (If you created your own custom exception with custom text, you wouldn't be able to see the custom text either, for privacy purposes -- I assume.)

    Any idea how I can prevent this exception from being thrown? I can use a Try - Catch, but then what? I still need to store the data for the widget to be functional.

    I'm not sure what else you can do here except to notify the user that storage is full. Are you writing any other data (like history) which accumulates over time? If that's the case, then you could delete older data. (But I'm guessing it's not).

    - Is there a way to simulate a device with an almost-full storage?

    I guess you could edit the device's simulator.json (*), after making a backup:

    "appStorageCapacity": 262144,

    (*)

    Windows: %APPDATA%\Garmin\ConnectIQ\Devices\${DEVICENAME}

    Mac/Linux (unconfirmed): ~/Garmin/ConnectIQ/Devices/${DEVICENAME}

  • What you *could* do is have a try/catch block which tests the exception type using instanceof, and throw your own exceptions (on different lines) for each of the types you are expecting.

    At least that would help you narrow down the problem.

    e.g.

    function initialize() {
        AppBase.initialize();
    
        fasting_duration_hours = Storage.getValue("fasting_duration_hours");
        if (fasting_duration_hours == null) {
            fasting_duration_hours = 13;
            try {
                Storage.setValue("fasting_duration_hours", fasting_duration_hours); // ERA error report: "Unhandled Exception"
            } catch(ex instanceof Toybox.Lang.StorageFullException) {
                throw new Toybox.Lang.Exception();
            } catch(ex instanceof Toybox.Lang.UnexpectedTypeException) {
                throw new Toybox.Lang.Exception();
            } catch(ex) {
                throw new Toybox.Lang.Exception();
            }
        }

  • Thank you for the suggestions, I was truly blocked!

    I have a Venu and a Fenix 6 pro for testing, but I rely on the simulator for all other devices. These have some of the highest memory allowances for widgets (as far as I can tell), so I rely on the simulator for the lower memory devices like the Enduro, Fenix base versions and the Vivoactive 3.

    I will try the "appStorageCapacity" adjustment, and the custom exceptions. These sound promising.

    I didn't find the pattern on the devices, but I suspect they have a low memory allowance that the simulator cannot/doesn't, well... simulate.
    I added a bit fancier glance view which could've increased the overall code footprint, but I never ran into out of memory issues while testing.

    Now, I received several emails from people who have used the widget for a long time, but now after the update it started crashing on menu actions. This doesn't seem related to the ERA exceptions, but that is all ERA reports.


  • I will try this, if it confirms the storage issue at least I'll have something to work with.

    Thanks for the hint! Thumbsup

  • Hi FlowState,

    Finally I pushed an update with this try-catch logic Yesterday. Today I had a look in ERA and unfortunately I still get the unhandled exception thrown in the generic exception line (14 in your example).
    I still haven't tried editing the simulator to check if I get the same generic exception when there is no memory space, this is my next step.
  • Hi again FlowState,

    Finally I couldn't find "appStorageCapacity" in the simulator.json there is a generic memory allowance for each app type in the device, but that's all I could find.

    My widget uses a fixed set of storage data which can only grow to a maximum fixed size. For this I enforce a configured maximum size of data entries in a dictionary, which I start replacing in FIFO fashion, whenever a new one is to be recorded after the size limit is reached (I use an index array with the data keys to manage this).

    This hasn't change. I did used to have 0 (int) as the default value for the fasting_duration_hours  which would mean no interval set. I changed this to 13 recently.

    I did test locally to check that the data types were the same before and after storing/reading from storage (for the values 0 and 13. So I find it hard to believe that this could be the cause, but due to general flakyness of CiQ, I can't also put it aside completely. This value is only set on the application's first execution where the value is null (as you can read in the code snippet).

  • Finally I couldn't find "appStorageCapacity" in the simulator.json there is a generic memory allowance for each app type in the device, but that's all I could find.

    I see app memory limits in compiler.json. appStorageCapacity is in simulator.json, but apparently only for certain devices like MARQ, Fenix 6*, FR245M, etc. Maybe it's an override (and there's some hidden default somewhere?)

    Maybe you could try temporarily adding the appStorageCapacity key to simulator.json for whatever device you're testing in the simulator? Or try testing with one of the devices that already has it (after changing the value).

  • Thanks for the hint, I'll give it a go and let you know if it worked! Thumbsup

  • I think I found the problem.

    In older CiQ versions pre 3.2.0 writing to Storage is not possible when running in background context. Since my widget has a background context, I moved the "first-run" storage init logic to the getinitialView() method in order to work-around this limitation.

    I had to connect dots from loose posts and the some vague documentation because I couldn't find anything in the docs that explicitly states what can and can't be done in the background context.

    Here its mentioned: https://developer.garmin.com/connect-iq/core-topics/persisting-data/

    Connect IQ 3.2 introduces the ability to access the Application.Storage module from background processes. The background process can modify storage using Application.Storage.setValue(), Application.Storage.deleteValue() and Application.Storage.clearValues().

    But in https://developer.garmin.com/connect-iq/core-topics/backgrounding/
    there is no reference to its limitations.

    Same in the API documentation for Background and Storage, no reference to limitations based on scope.

    Oh well, at least the problem was found. Hopefully the "Background" context will get better documentation in the future, preferably bundled in the SDK's docs since Garmin web hosted documentation seems to leave broken links all the time.