Memory not consistently being released

Monkey C reference counting to release memory appears to fail to on the device, but works on the SDK simulator.

I am investigating why my app is crashing "Out of Memory" and am observing the device is not releasing memory as expected, or as on the SDK sim.

As I am unable to post images on the forum, I have written up a description with supporting charts here.

https://docs.google.com/document/d/1K8kKF5g_N1aRKGjb0iwLB76WdcJkWV5SXZcm3jJxcX0/edit?usp=sharing

I can reliably crash my app on the device but not in the SDK simulator.

  • Expected behaviour:The sim should perform the same as the device.

    Or the device should behave like the simulator...

    In looking at your code, it *seems* that you want a selection in secondMenu to take you back to the mainView. It wasn't clear (to me at least) from our discussion that you were wanting to call Ui.popView() from the menu handler that you were also going to call Ui.pushView() in. In this case, since a new view has been pushed, the system will *not* automatically pop a view. So, to be clear, doing the Ui.popView() call in firstMenu is a reasonable thing to do, provided that you only pop the view if you also push a view.

    That said, the call to Ui.switchToView() from within the menu seems wrong. In secondMenuDelegate, when you call Ui.popView(), you are removing secondMenu from the view stack leaving firstMenu and mainView. You then call Ui.switchToView() to get yourself 'back to the initial view'. But when you do this, the view stack now has mainView on the view stack twice.

    I believe that there are two ways to achieve what you're trying to do. The first:
    • Call Ui.popView() from firstMenuDelegate before pushing secondMenu. This leaves the view stack as mainView, secondMenu after firstMenuDelegate.onMenuItem() returns.
    • Do not do any view management from secondMenuDelegate, letting the system pop secondMenu for you. This leaves the view stack with only mainView after secondMenuDelegate.onMenuItem() returns.
    The other option is to
    • Skip the call to Ui.popView() in firstMenuDelegate, and do the Ui.pushView() of secondMenu.
    • Call Ui.popView() from secondMenuDelegate.onMenuItem(). Your call will pop secondMenu from the view stack, and the system will pop firstMenu for you, leaving the view stack with just mainView.
    The decision about which technique to use depends on whether or not you want any input on secondMenu to take you back to firstMenu. If you do want that, you *probably* want to leave firstMenu on the stack and then remove it when the appropriate menu item has been selected and you know you want to get back to the mainView. A normal UI concern would be what should happen if the user presses back while looking at secondMenu... should they be taken back to the mainView, or should they see firstMenu?

    Given the view management you're doing (without the previously contentious call to Ui.popView()), the view stack is clearly going to grow every time you select the menu item in secondMenu. This definitely shows up in the memory view window if you look at the view stack shown there.

    As for why the allocations don't show up in the simulator, I believe the reason is fairly simple. The view stack in the simulator is *not* allocated from your ConnectIQ application memory, it is allocated from the the simulator's heap. I'm not 100% certain that this is different from how the device behaves (it is possible that the view stack for the app is allocated from device memory not app memory, but I don't know yet).

    If the only leak that is happening here is a single view stack entry, I'd expect the leak to be relatively small (maybe 16 bytes) every time you get back to the mainView. Given the graphs that you're showing, I'm betting that making a call to Ui.switchToView() with a menu on top of the stack causes the menu and the delegate to get leaked.

    Thank you for providing the code sample. I'm sure it took a while to make, but without it we would not have found any of the problems mentioned.
  • So, to be clear, doing the Ui.popView() call in firstMenu is a reasonable thing to do, provided that you only pop the view if you also push a view.


    Yes, and that's the solution I have adopted in order for the view stack not to grow.
    However I do have issues with your following comments.

    That said, the call to Ui.switchToView() from within the menu seems wrong. In secondMenuDelegate, when you call Ui.popView(), you are removing secondMenu from the view stack leaving firstMenu and mainView. You then call Ui.switchToView() to get yourself 'back to the initial view'. But when you do this, the view stack now has mainView on the view stack twice.


    I need the switchToView in the secondMenu because in the app has multiple options, some of which switch to a different view.
    The the demo app, the two copies of mainView are only there because firstMenuDelegate hasn't popped it.

    I believe that there are two ways to achieve what you're trying to do. The first:

    Call Ui.popView() from firstMenuDelegate before pushing secondMenu. This leaves the view stack as mainView, secondMenu after firstMenuDelegate.onMenuItem() returns.

    Do not do any view management from secondMenuDelegate, letting the system pop secondMenu for you. This leaves the view stack with only mainView after secondMenuDelegate.onMenuItem() returns.


    No, this doesn't work, the system does not pop the second menu.

    With popView() in firstMenuDelegate,
    and no popView() in the secondMenuDelegate and
    switchToView in the secondMenuDelegate

    The views still pile up on the view stack - try it yourself, in the sim memory viewer, you can see the views increase and on the device you can see the memory increase.

    The other option is to
    Skip the call to Ui.popView() in firstMenuDelegate, and do the Ui.pushView() of secondMenu.
    Call Ui.popView() from secondMenuDelegate.onMenuItem(). Your call will pop secondMenu from the view stack, and the system will pop firstMenu for you, leaving the view stack with just mainView.

    Again, no.
    That's how the demo app is currently coded and the views stack up again
    As for why the allocations don't show up in the simulator, I believe the reason is fairly simple. The view stack in the simulator is *not* allocated from your ConnectIQ application memory, it is allocated from the the simulator's heap. I'm not 100% certain that this is different from how the device behaves (it is possible that the view stack for the app is allocated from device memory not app memory, but I don't know yet).

    I find your references to "ConnectIQ application memory" and the "simulator's heap" quite obscure, especially as you have declined the opportunity to provide an overview of the CIQ engine.

    If the only leak that is happening here is a single view stack entry, I'd expect the leak to be relatively small (maybe 16 bytes) every time you get back to the mainView. Given the graphs that you're showing, I'm betting that making a call to Ui.switchToView() with a menu on top of the stack causes the menu and the delegate to get leaked.

    But I'm seeing a memory creep of 1280 bytes, and that's a heap more than 16!
    Thank you for providing the code sample. I'm sure it took a while to make, but without it we would not have found any of the problems mentioned.


    Yup and it's a good platform to demonstrate alternative options.

  • I find your references to "ConnectIQ application memory" and the "simulator's heap" quite obscure, especially as you have declined the opportunity to provide an overview of the CIQ engine.


    I read this as:
    - ConnectIQ application memory: The memory specifically set aside in the simulator for the currently running simulated app. Analogous to the memory set aside in the real device for the currently running app (e.g. 128 KB KB for a device app on VAHR).

    - Simulator's heap: The available pool of memory for the simulator itself, as provided by the operating system (e.g. Mac OS or Windows). Analogous to entire pool of memory available to the real device.

    He's just saying that the memory for the view stack (the references to views, not the views themselves) is in global sim memory, and not the specific memory pool allocated for the running CIQ app. That would explain why CIQ app memory usage doesn't increase in the simulator when you push an additional reference to an existing view.

    The only problem with this is I ran your test case a few dozen times and saw no increase in the memory usage for the simulator itself on Windows. However, this may be because the leak is relatively small (in the simulator at least), as Travis said, so the OS didn't need to allocate more memory in the limited test that I did. Maybe if I automated the test and ran it a few hundred or thousand times, there would be a noticeable increase.
  • I read this as:
    - ConnectIQ application memory: The memory specifically set aside in the simulator for the currently running simulated app. Analogous to the memory set aside in the real device for the currently running app (e.g. 128 KB KB for a device app on VAHR).

    - Simulator's heap: The available pool of memory for the simulator itself, as provided by the operating system (e.g. Mac OS or Windows). Analogous to entire pool of memory available to the real device.

    He's just saying that the memory for the view stack (the references to views, not the views themselves) is in global sim memory, and not the specific memory pool allocated for the running CIQ app. That would explain why CIQ app memory usage doesn't increase in the simulator when you push an additional reference to an existing view.

    But why is that of any relevance to us developers??

    The only problem with this is I ran your test case a few dozen times and saw no increase in the memory usage for the simulator itself on Windows. However, this may be because the leak is relatively small (in the simulator at least), as Travis said, so the OS didn't need to allocate more memory in the limited test that I did. Maybe if I automated the test and ran it a few hundred or thousand times, there would be a noticeable increase.

    The whole point is that the memory doesn't increase in the sim. Run it on a device and you'll see the memory increase.

  • It might not be that relevant, except to possibly explain the source of the discrepancy. OTOH, if there were really some cases where a CIQ app could consume unlimited amounts of shared device memory, that would be significant because it could mean that one misbehaving app could crash other apps or the whole device, which is not supposed to be possible afaik.

    Yep, I get that memory usage increases on the device. Like I said, unless there was some clever reference counting on the view stack, I don’t see how it couldn’t increase, in either the sim or real device, one way or another (app-specific or shared memory).
  • No, this doesn't work, the system does not pop the second menu.

    With popView() in firstMenuDelegate,
    and no popView() in the secondMenuDelegate and
    switchToView in the secondMenuDelegate

    The views still pile up on the view stack

    Of course they do, you are calling switchToView and I specifically said Do not do any view management from secondMenuDelegate, letting the system pop secondMenu for you. I've verified this works in the simulator.

    Again, no.
    That's how the demo app is currently coded and the views stack up again

    Again, you are reading right past my words. If you eliminate the call to switchToView() (as I suggested), and call popView from just one of the delegates, you will always end up at the base view. I've verified.. multiple times.

    I'm working on the premise that what you are doing in your test app is what you are doing in your actual app... If you are switching to the main view from a menu. This seems wrong because that view is already on the view stack. It is really wrong because you have multiple copies of the same view on the view stack, so when you start pressing back to exit the app, you need to do so once for every time you've selected the option on the second menu.

    If your actual application needs to switch the base view from within a menu (at any view stack depth), you need to be sure that the appropriate number of views have been popped from the stack before you call switchToView(). Your test code pushes 2 menus, pops 1, and then switches.. adding one view on the stack every time.

    I find your references to "ConnectIQ application memory" and the "simulator's heap" quite obscure

    FlowState described it just fine. The device (and the simulator) have a much larger pool of memory available to them. This is the device (or simulator) memory space. Your application (the ConnectIQ app you are writing and testing) is given a slice of the memory available to the device.. that slice of the system memory is all of the memory available to your application. The rest of the memory is kept for the system to do whatever it needs to do.

    ..especially as you have declined the opportunity to provide an overview of the CIQ engine.

    I am allowed to help on the forums. I volunteer my time here. I'm *not* allowed to describe every detail about how our framework is implemented. As I've said before, if you have specific questions, I will do what I can to answer them.

    But I'm seeing a memory creep of 1280 bytes, and that's a heap more than 16!

    In your real app, are you calling switchToView with a newly allocated view and delegate? Or are you just pushing an existing view onto the stack? If you are pushing a new view/delegate, then that is why the memory keeps growing. After going through the menu twice, you should be able to press back twice and still end up at the base view of the view stack with all of your memory available again.
  • Of course they do, you are calling switchToView and I specifically said Do not do any view management from secondMenuDelegate, letting the system pop secondMenu for you. I've verified this works in the simulator.

    Apologies, yes, I misinterpreted your direction "Do not do any view management" to refer only to popView.
    However, these approaches would not be applicable in the app because I use some of the options in the menu structure to provide navigation to different views.

    After going through the menu twice, you should be able to press back twice and still end up at the base view of the view stack with all of your memory available again.

    Yes, pressing back does indeed clear the view stack as you described, but I'm looking for a solution that takes the user from a selection in the second menu directly to the next logical application view. Having to get him/her to press back as part of that process flow would be counterintuitive.

    Your application (the ConnectIQ app you are writing and testing) is given a slice of the memory available to the device.

    So the memory available to the our apps is not, as others have suggested, prescribed by the hardware, but an allocation by the Garmin "System" code?



  • Apologies, yes, I misinterpreted your direction "Do not do any view management" to refer only to popView.
    However, these approaches would not be applicable in the app because I use some of the options in the menu structure to provide navigation to different views.


    Yes, pressing back does indeed clear the view stack as you described, but I'm looking for a solution that takes the user from a selection in the second menu directly to the next logical application view. Having to get him/her to press back as part of that process flow would be counterintuitive.


    I don’t think the suggestion is to literally ask users to press Back so that the view is popped. I think the suggestion is that when the selection is made in the second menu, the app code does something like this:
    - Sets a “global” variable that stores the selection
    - Pops all the way back to the first view
    - In the first view’s onShow, the code looks at the selection variable and automatically switches to the correct view

    As Travis pointed out, the current solution just piles up an indefinite number of references to the first view on the view stack. Even if this somehow took no extra memory and could go on forever, it’s def wrong because now your user has to press Back more than twice to completely exit the app, if they choose to do it that way. If the user made 100 selections in the second menu, now they have to press Back 100 times to exit, which is not user friendly. Imagine you changed a dozen settings in the Garmin watch settings menu, so now you have to press Back a dozen times to get out. Nobody would want that.

    The app is not doing the right thing. It has exposed a difference in the sim and device, but it’s still not doing the right thing.

    And if you really want it to work that way (constantly pushing additional views), you would have to expect it to use more memory with each new reference to a view.

    So the memory available to the our apps is not, as others have suggested, prescribed by the hardware, but an allocation by the Garmin "System" code?




    I’m pretty sure it’s both:
    - devices have a limited amount of physical memory, which applies to every device in the world. It just so happens Garmin’s limit is real small and it noticeably affects apps
    - the device firmware allocates a fixed amount of memory for each running app, which is in line with the physical constraints

    If an elevator had a certain weight / occupancy limit which was determined by the laws of physics and basic engineering principles and there was a guy in the elevator who prevented you from getting in if the limit was about to be exceeded, it would not be fair to say this is an “artificial” limit. It might look that way to someone who didn’t consider why that limit exists tho.

    A Garmin employee already said that devices don’t have extra memory lying around. You can choose to believe him or not, but either way I’m sure we’re not getting extra memory for our apps.

    Also, Garmin does not advertise the CIQ memory limit to end users as a marketing bullet point. So I really see no incentive for them to have a completely artificial limit, or to lie or mislead on this point. They are not going to get customers to buy a new device because it has twice the memory for CIQ data fields. Customers have no idea what that is or what it means.
  • As Travis pointed out, the current solution just piles up an indefinite number of references to the first view on the view stack.

    Yes, but remember, I built the demo primarily to demonstrate the differences between the way the sim and the device display memory which got me into the pickle with field crashes in the first place.
    In my app, I now include the popView() in the first menu (commented "CONTENTIOUS") to manage the view stack.
    It seems to me to be a simpler approach than handling the selection in a global.


    They are not going to get customers to buy a new device because it has twice the memory for CIQ data fields. Customers have no idea what that is or what it means.


    I don't know how much memory is available on the device, but I'm pretty sure it's in the Mb's not Kb's and if a F/W mod could reallocate just another couple of hundred Kb's for the apps, I, for one could build a much better app. Then they might get customers to buy more watches.
    (Apple doesn't inform the dev's about the space available on the AW, either, but it's in the many Mb's range.)
  • Yes, but remember, I built the demo primarily to demonstrate the differences between the way the sim and the device display memory which got me into the pickle with field crashes in the first place.
    In my app, I now include the popView() in the first menu (commented "CONTENTIOUS") to manage the view stack.
    It seems to me to be a simpler approach than handling the selection in a global.


    Yep, that sounds a lot better to me, too.

    The point is that piling up an indefinite number of references to views on the view stack, no matter how or why it happens, doesn't seem right to me. Your demo may be doing it in a different way, but your real app was obviously doing it too, otherwise this thread would not exist.

    Just because it takes up no additional memory in the simulator doesn't mean that it would've provided a good user experience, even if it were possible to do it in the real device.

    You're not just consuming memory, you're adding views -- views which the user has to skip past when they press Back.

    If you were expecting switchToView() to clear out the entire view stack, well I guess it just doesn't work that way.

    I don't know how much memory is available on the device, but I'm pretty sure it's in the Mb's not Kb's and if a F/W mod could reallocate just another couple of hundred Kb's for the apps, I, for one could build a much better app. Then they might get customers to buy more watches.
    (Apple doesn't inform the dev's about the space available on the AW, either, but it's in the many Mb's range.)


    Yes, like I alluded to, there is a big difference between devices like a phone or laptop with so much memory that apps can basically use as much as they want (within reason) and devices like a Garmin which have much less memory, either to reduce cost and/or to improve battery life. There is a reason runners still buy Garmins and not Apple Watches, despite Apple's superior marketing, brand appeal and much nicer screen. It's all about the battery life. You really can't directly compare AW and Garmin watches, because if you were to go by specs and attractiveness alone, Garmin would've lost already. Garmin only has the (smaller) running/fitness market because of battery life, physical buttons (for some) and a screen that works well in sunlight. The truth is, most non-runners don't even see the point of a Garmin. I sure didn't, before I started running. I thought wearing a GPS watch was insane.

    It's like comparing an e-reader to an iPad. Anybody can clearly see that the iPad has a better screen (for most purposes) and can do a lot more than an e-reader. So why do people buy e-readers? Because e-readers are purpose-built for a given task (like reading text in sunlight / with low eye strain), which usually results in a compromise. It's the same reason bikes still exist even though many people drive a car to work every day. But you wouldn't make comparisons like "Why does does an e-reader's screen only display shades of grey while an iPad has million of colours?" or "Why is my bike so slow compared to my car?"

    I think you'll find that on any device, if your app consumes a certain amount of memory, the OS will eventually terminate it. Either that or the whole system will crash. It just so happens that Garmin determines that limit beforehand, since the limit is so small and devs need to know what it is. And it so happens that on most devices, you will never reach that limit (especially on devices which have virtual memory backed by hard drive / SSD / flash).

    Anyway, you're not the first CIQ developer that wished for more memory to be available for their device app or data field.

    If you need 2X or 10X the memory you have now (e.g. for nicer graphics/UX), it's probably not going to happen. If you only need 10% more (for example), as discussed before, there's sometimes a few ways to cut the fat which can be explored, whether it's by refactoring code or by reducing memory usage in other ways.

    And I think my point still stands. Garmin insists that there is no additional memory available for CIQ apps, and I really don't see how you could convince to them to change that, even if it were possible to do so. You could always develop exclusively for 645 Music, Fenix 5 X and Fenix 5 Plus.

    Also, if some customers really wanted a watch with more memory for better apps (like you suggested), then Garmin could just turn around and say "Okay, buy this more expensive watch, like Fenix 5 Plus."