Too Many Objects Error

My app works perfectly on the watch in my "Sim" mode (for development/testing) when replaying an old yacht race, but crashes with "Too Many Objects Error" when running with live data from the GPS.
It looks to me like Position.enableLocationEvents(Position.LOCATION_CONTINUOUS, method(:GPSData)); is generating objects, but that doesn't make any sense really.
In my "Sim" mode, I'm calling my app's main processing function with data that I'm retrieving from my web site.
In live mode, I'm calling it with data from the Position.enableLocationEvents callback.
Other than that the two modes are identical.
In "Sim" mode. the app runs for an entire race of over 2 hours.
In Live mode, it crashes after around two minutes.
ERROR: Too Many Objects Error
DETAILS: Failed invoking <symbol>
STORE_ID: 00000000000000000000000000000000
CALLSTACK:
144)*Failed while writing stack trace*

I'm monitoring memory each callback cycle, and memory usage remains static up to the crash.
Memory Total:125272 Used:101208 Free:24056


Sure, I'm pushing the watch, running with around 24Kb free, but with no way to monitor object usage, I'm in a big hole.
The issue has started since I added the latest feature which has increased memory usage by around 30Kb.
Any thoughts (before I slit my throat)?
  • What are you doing in you GPSData() function?

    I've run 10-14 hours with CIQ apps getting continuous data with no problem (I stopped the tests as battery usage was what I wanted to test), so it's something other that just getting the data.

    Are you doing the test in the sim or on a real device? (playing back a .fit in the sim to feed GPS data, even it's it not for the activity you're doing the app for)
  • If the problem started after adding your most recent feature, I'd suggest looking there to see if there is something you can do to avoid using objects unnecessarily. Using arrays or dictionaries of string, double, long, and user-defined classes are potential problems. Holding on to class instances from the ConnectIQ API will also be problematic (e.g., keep an array of the last 10 objects passed to your onPosition() callback might be a bad idea).

    Of course, there could be a leak in the ConnectIQ framework itself. It isn't incredibly likely as lots of applications use enablePositionEvents() and I don't recall ever hearing about this type of error before.

    If you need help figuring it out, I'm happy to look over your code to see if there is anything that stands out.

    Travis
  • Travis, Jim, thanks once again for your input.
    Let me reply point by point.
    Jim
    What are you doing in you GPSData() function?

    Just serializing the positionInfo object:
    function GPSData(positionInfo){
    timeSecs = positionInfo.when.value();//in whole seconds
    lat = positionInfo.position.toDegrees()[0]; //lat, lon in degrees,
    lon = positionInfo.position.toDegrees()[1];
    SOG = positionInfo.speed * mps2Knots; //SOG in knots
    COG = Math.toDegrees(positionInfo.heading);
    heading = positionInfo.heading; // TEMP for old timer display
    speed = positionInfo.speed; // TEMP for old timer display
    Ui.requestUpdate(); // display timer i
    dispData();
    Ui.requestUpdate(); // display new data fields
    }

    my Sim code populates the timeSecs, lat, lon,...speed global variables in a timer callback and then calls dispData() which is where all the work is done. The app executes the GPS code or SIM code depending on a Settings variable
    if (Application.getApp().getProperty("liveRunning")){
    Position.enableLocationEvents(Position.LOCATION_CONTINUOUS, method(:GPSData));
    }
    else{
    var simulator = new simulatorClass();
    simulator.start();
    }

    Are you doing the test in the sim or on a real device?

    On a real device - the VA-HR.
    It only crashes when I use GPS data. It doesn't crash when I run with my Simulated data ( being generated by a timer working through recorded live data which I'm loading from my web site. )
    It doesn't crash in the watch simulator using either simulated FIT data or my simulated data fro my web site.
    Travis:
    If the problem started after adding your most recent feature, I'd suggest looking there to see if there is something you can do to avoid using objects unnecessarily. Using arrays or dictionaries of string, double, long, and user-defined classes are potential problems. Holding on to class instances from the ConnectIQ API will also be problematic (e.g., keep an array of the last 10 objects passed to your onPosition() callback might be a bad idea).

    Yes, when I excise my recently added code, it doesn't crash, and that code certainly does introduce some additional objects. BUT that code is being executed just as vigorously when I'm running in my simulated data mode as with the GPS mode, <b>but only crashes with GPS data</b>.
    I spent the first month of this project understanding about the object limitations in MC so have been as frugal as I can with objects. The frustrating thing is that there's no way to monitor my object usage, so I never know how close I am to the limit!
    Of course, there could be a leak in the ConnectIQ framework itself. It isn't incredibly likely as lots of applications use enablePositionEvents() and I don't recall ever hearing about this type of error before.

    Indeed, my app has been running fine up until the I ported the latest piece of the app, so there was nothing to alert me to the issue.
    The evidence above would strongly suggest a memory leak from ConnectIQ framework, but I don't know of any way to investigate that. Any suggestions? I would be happy to build a simple app to test, but I don't know how to track the usage of objects.

    If you need help figuring it out, I'm happy to look over your code to see if there is anything that stands out.

    I really do appreciate the offer, but the module in question is 1500 lines of code which I have migrated from the javascript and C of my pebble app. I fear the work involved in reviewing the code would be massive. However I would be happy to send it to you privately if you can provide an email address, maybe through a private message.
  • UPDATE.
    I think my problem is all to do with Garbage Collection.
    Remember, the code runs fine on the watch in my Sim mode, so it's running all the same code as when running live except it's getting position data from my web site, fed by a timer, not the GPS.
    The issue seems to be the length of the function dispData(); that's being called once the position.info object has been unpacked. It's currently around 800 lines.
    It seems to me that the Position.enableLocationEvents is closing but the GC isn't getting a chance to clean up before my code is being executed.
    When I split code out of dispData into separate functions, then it runs longer between crashes.
    Also, if I add printLn's in the code it seems to give GC a chance to tidy up and also improves the reliability.
    But it's still crashing.
    I need a strategy to cause GC to tidy up all Garmin's garbage before my code is invoked.
    I have tried putting in a 500 msec timer delay in before calling dsapData as follows:
    function GPSData(positionInfo){
    timeSecs = positionInfo.when.value();//in whole seconds
    lat = positionInfo.position.toDegrees()[0]; //lat, lon in degrees,
    lon = positionInfo.position.toDegrees()[1];
    SOG = positionInfo.speed * mps2Knots; //SOG in knots
    COG = Math.toDegrees(positionInfo.heading);
    heading = positionInfo.heading; // TEMP for old timer display
    speed = positionInfo.speed; // TEMP for old timer display
    Ui.requestUpdate();
    GCTimer.start(method(:dispData), 500, false);
    }

    Seems to improve things, but still not 100% but I think I'm on the right track.
  • function GPSData(positionInfo){
    timeSecs = positionInfo.when.value();//in whole seconds
    lat = positionInfo.position.toDegrees()[0]; //lat, lon in degrees,
    lon = positionInfo.position.toDegrees()[1];
    SOG = positionInfo.speed * mps2Knots; //SOG in knots
    COG = Math.toDegrees(positionInfo.heading);
    heading = positionInfo.heading; // TEMP for old timer display
    speed = positionInfo.speed; // TEMP for old timer display
    Ui.requestUpdate();
    GCTimer.start(method(:dispData), 500, false);
    }


    I'm wondering about the last two lines. Not sure why they might cause the problem, but not sure why they are needed and it might be something in dispData().
    This code gets executed every second on a real device (in the sim, when playing back a .fit recorded with "smart recording", it could slow down to every 4-6 seconds). The way I typically do things, is I start a 1 second timer when the app start, that among some other "housekeeping" calls Ui.requestUpdate(), and that's about the only time I call it - onUpdate() then gets called every second (I'll also call it after onSettingsChanged() or in the delegate after a user action, but that's about it.)

    And then each second (on a real device), you are starting a one shot timer for 1/2 a second for running dispData(). I'm not quite sure why a timer is used for that, to be honest, as by the name I'm guessing "display data" and maybe another Ui.requestUpdate() (more than one?). Those lines are what I'd be looking at first. onUpdate() might be getting called much more often than you need.

    Everything else looks normal in the function, in fact in my code I do a little bit more based on quality/accuracy, and I ignore the lat/lon/etc, if the quality is less than (and at times less than or equal to) QUALITY_POOR - something else that happens in the real world and not the sim when just playing back a .fit. In some things, I also don't even allow recording to be started until I get a Quality>QUALITY_POOR so I know I have a good GPS fix. In the sim, you can change the quality, but it's not just for a reading based on the .fit, but for everything until you change it. Maybe you're getting data that's causing issues elsewhere in your code?
  • Former Member
    Former Member
    Monkey C does not have a Garbage Collector. The language uses reference counting, and objects are destroyed as soon as their reference count is 0. You do have to be careful of circular references because these will be leaked and are not cleaned up until the application exits.
  • objects are destroyed as soon as their reference count is 0.

    I am struggling with concepts such as "reference counting". Is it possible that there's a latency in the destruction of the objects?

    I'm improving the up-time of my app by artificially inserting delays in my code with
    • adding a 500 ms timer delay between receiving the data and processing or
    • inserting System.println debug lines in my long data processing function
    • breaking up a single long function into multiple short ones

    Which seems to support that theory.


    You do have to be careful of circular references because these will be leaked and are not cleaned up until the application exits.

    Could you provide an example of a circular reference in this context please?
  • Hey, Jim, I'm not sure you've picked up on my "Sim" mode - it's not using the Fit replay system at all. It's reading a track file that I have pre-recorded (at a 1Hz frequency) and feeding the location data to my dispData function with a timer. To compress my testing time, I can modify the replay speed, and usually run it at 2x normal speed. Even at 4x speed, the app runs fine on the watch in the that mode, so I don't think your observations would account for the difficulties I'm having running the app on the watch with the position data coming from the GPS instead of my Sim code.
    Also your comments regarding data quality fortunately don't apply to my app since it's designed for sailing on open water where there is always a good view of the sky.
  • Also your comments regarding data quality fortunately don't apply to my app since it's designed for sailing on open water where there is always a good view of the sky.


    But while you are testing on the real watch with live GPS data, you're on land, correct? Also, You'll get various inaccuracies as the GPS is first getting the satellites.
  • But while you are testing on the real watch with live GPS data, you're on land, correct? Also, You'll get various inaccuracies as the GPS is first getting the satellites.

    Yes, I'm testing on land and finding it takes longer to acquire a fix, but over the past three days I have been testing on water and getting the problem.