exitTo() Out Of Memory Error

My yacht racing app is too large to run in 124kB, so, I have split it into two components: STARTING , which is operational in the period up to the starting gun, then RACING takes over for the actual race.

I use exitTo() to link the two components.

In testing, the RACING app crashes with out Of Memory Error when it is invoked from STARTING, even though there is plenty of memory for the individual components run independently. RACING runs independently with Peak Memory of 102 Kb.

However the crash is intermittent: ~90% on the device and ~10% in the simulator (videolink here ). I have of course been at pains to ensure that the the test environments are identical.

I rebooted the Mac and ran in the same manner - with no crash. (VIDEO HERE ) showing the second app running correctly with a PEAK memory of 105Kb.
I can't show the console output as it's the exited to app that's crashing, but when I run it on a physical device I get:
(VA-HR)
ERROR: Out Of Memory Error
DETAILS: Failed invoking <symbol>
STORE_ID: 00000000000000000000000000000000
CALLSTACK:
@PC = 0x30000dad
@PC = 0x30000e64
/Users/.../Documents/watchApp/raceQs_Racing/source/main.mc (onStart:27)

(F5)
---
Error: Out Of Memory Error
Details: 'Failed invoking <symbol>'
Time: 2019-01-10T02:31:55Z
Part-Number: 006-B2697-00
Firmware-Version: '12.00'
Language-Code: eng
ConnectIQ-Version: 3.0.7
Filename: QS_RACE
Appname: QsPlus
Stack:
- pc: 0x30000f9f


It seems to me that there may be a latency issue: the first app has not completely cleared memory before the second app is loaded.

I can't think of any way to delay the loading of RACING since I lose control on the execution of exitTo(). Any ideas?
  • Former Member
    Former Member over 6 years ago
    So, my underlying question is why does it crash almost every time on the F5/VAHR devices but very, very rarely on the sim?


    As much as we try to make the simulator behave exactly like the devices, it is ultimately an impossible task. Even things that essentially function the same will have slight differences in timing, or data behavior. This might mean that your application is responding to data that is presenting differently on the device, or the exact memory usage of your application is a little bit different on each platform.

    It's always possible there is a bug in the system, but if reducing your memory usage makes an Out of Memory Error go away, it is relatively safe to assume the error is accurate and the application is running out of memory.

    If you have a reproducible test case in the simulator, the Connect IQ team may be interested in running it to confirm the system is actually failing a memory allocation in an appropriate scenario.

  • The smallest string in CIQ still takes up 10 bytes or something,

    I have started to examine the Memory Usage Stats, and indeed find that e null string takes 10 bytes, and a 1-character string takes 11. I Wonder what the 10-byte overheard is?

  • I don't want to give away too many details, but a few of those bytes are the string length and a null terminator. The rest is overhead for the VM.

    That said, this little bit of test code generates csv data that shows an empty String actually takes up 24 bytes of application memory, and allocations appear to grows in blocks of 8 bytes. I have not looked at why the discrepancy between the memory viewer and this output exists, but it may be something I need to investigate further when I have some free time... or Brian can chime in.

    using Toybox.Math;
    using Toybox.Lang;
    using Toybox.System;

    (:test) module test {

    function get_used_memory() {
    var systemStats = System.getSystemStats();
    if (systemStats != null) {
    return systemStats.usedMemory;
    }

    return 0;
    }

    const RANDOM_CHARACTERS =
    "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";

    function get_random_bytes(length) {
    var s = "";

    for (var i = 0; i < length; ++i) {
    var j = Math.rand() % RANDOM_CHARACTERS.length();
    s += RANDOM_CHARACTERS.substring(j, j+1);
    }

    return s;
    }


    function generate_data_point(logger, count, length) {
    var array = new [count];

    var before = get_used_memory();

    for (var i = 0; i < count; ++i) {
    array= get_random_bytes(length);
    }

    var after = get_used_memory();
    var delta = (after - before);
    var average = 1.0 * delta / count;

    System.println(Lang.format("$1$, $2$, $3$", [ count.format("%3u"), length.format("%2u"), average ] ));
    }

    (:test) function string_memory_usage(logger) {
    System.println("strings, bytes per string, average size");
    for (var i = 0; i < 32; ++i) {
    generate_data_point(logger, 100, i);
    }

    return true;
    }

    }
    [/code]

    Generates this...

    strings, bytes per string, average size
    100, 0, 24.000000
    100, 1, 24.000000
    100, 2, 24.000000
    100, 3, 24.000000
    100, 4, 24.000000
    100, 5, 24.000000
    100, 6, 24.000000
    100, 7, 24.000000
    100, 8, 32.000000
    100, 9, 32.000000
    100, 10, 32.000000
    100, 11, 32.000000
    100, 12, 32.000000
    100, 13, 32.000000
    100, 14, 32.000000
    100, 15, 32.000000
    100, 16, 40.000000
    100, 17, 40.000000
    100, 18, 40.000000
    100, 19, 40.000000
    100, 20, 40.000000
    100, 21, 40.000000
    100, 22, 40.000000
    100, 23, 40.000000
    100, 24, 48.000000
    100, 25, 48.000000
    100, 26, 48.000000
    100, 27, 48.000000
    100, 28, 48.000000
    100, 29, 48.000000
    100, 30, 48.000000
    100, 31, 48.000000

  • Former Member
    Former Member over 6 years ago
    The difference here is between the size of the memory that was allocated (e.g. 10 bytes) and the space it takes up in the heap after overhead (e.g. 24 bytes).

    It isn't really something that occurred to me before, but the size in the heap isn't something the memory viewer shows currently. We might want to consider showing the size with overhead in the viewer.

    The heap block size is currently 8 bytes, which is why you see the size changing in multiples of 8. All the sizes you see in the viewer currently are getting rounded up to the nearest multiple of 8, and then another 8 is added to track the block in the heap. Keep in mind that these details are not guaranteed to stay the same on future versions of the heap though.
  • I am considering developing two apps, one to "load" my data and another to "run" with it, and it sounds like a good enough idea to send the data from the loader app to the runner app in an "exitTo" function (intent)... but not sure how I want to package/format that data, all I really know is that from this discussion it sounds better to package it as a single array of basic values or as a string, and worse to use a dictionary especially if that dictionary is complex  RaceQs, if you can say, what was the solution you ended up using?  

  • I abandoned that approach. The transition between apps required user input to approve the loading of the second app, so it was not possible to make it "seamless". I have worked very hard on reducing the use of big data types (float, double, arrays dictionaries etc.) using integer arithmetic where possible, (I store lat/lon as decimal degrees * 0x7fffffff/180, sometimes called "semicircles") , have replaced most text strings with resource strings and have limited the app functionality to fit into the tight space.