Navigation memory usage increases on subsequent navigations [iOS]
From @NL33 on February 5, 2019 23:7
Edit: Below I describe a bug that I assumed was an issue with the emulator. That might be the case, but alternatively could there be a navigation bug?
In my case, the emulator had been open a long time, and the pages where the issue appeared had experienced a lot of back and forth navigation. Is it possible that the navigation "fills up" (maybe the cache gets full), and this can lead to strange navigation behavior?
In my case, after many many (over a hundred) back and forths on the page, the navigation button just stopped working and the page would automatically navigate back to an earlier page (without any navigation being ordered). Maybe this was because of an over-used navigation?
If not, then it is probably an emulator bug as described below:
Environment
Provide version numbers for the following components (information can be retrieved by running tns info in your project folder or by inspecting the package.json of the project):
- CLI: 5.1.1
- Cross-platform modules: 5.1.1
- Android Runtime: 5.1.0
- iOS Runtime: 5.1.1
Describe the bug I was running the emulator (ios, iPad) for approximately a day and half (ie, starting the emulator and not stopping it for a day and half; the computer was periodically asleep during the time, but the emulator was not shut down.). At that point, it started having faulty router navigation.
Specifically, (i) a navigation button would stop working and (ii) the emulator would navigate away from a certain page immediately upon going back to that page (even though navigation had not been ordered).
Navigation on the app is done with Router, Navigation Extras, and Active Router.
It is very unlikely this is a code error, because I have run the same code multiple times on different emulators (and on my device), without seeing this behavior.
I closed the emulator. On running it again, the behavior has stopped (and navigation is working properly).
So it seems likely to me that the navigation "glitch" was specific to the emulator, and probably a result of the emulator running for so long.
A breakdown in the emulator after running it for so long is not a significant concern. However, it would be helpful to know whether others have seen buggy behavior in an emulator open for a long time, as it would help me confirm whether or not there is some hidden code problem, a bug in router with Nativescript, or an issue with the iOS back button.
To Reproduce
I was running the emulator (using a webpack bundle), focusing on ipad. So I ran:
$ tns run ios --device "iPad Pro (12.9-inch) (2nd generation)" --bundle --env.development --env.uglify --env.aot
The emulator ran for about a day and a half (ie, 36 hrs+) before the behavior began. The computer was asleep periodically during the time, but the emulator was not closed.
Copied from original issue: NativeScript/nativescript-cli#4340
Want to back this issue? Post a bounty on it! We accept bounties via Bountysource.
From @KristianDD on February 8, 2019 12:13
Hi @NL33 , We haven't observed such behavior while developing with the framework on a simulator. The simulator is just a common process running in the operating system and its state is not guaranteed to be properly preserved during sleep/wake cycles.
We recommend that you stop the previous tns run session and start a fresh one.
We are also not aware of any issues with multiple back and forth navigation and also have an automated test that verifies this functionality. Did you experience this issue on a fresh simulator instance within a single tns run session, without wake/awake of the computer? If so there might be a memory leak caused by the code of your application. You can verify that by trying to reproduce the issue with a blank angular project with drawer/tab navigation.
From @NL33 on February 8, 2019 13:57
Thank you, @KristianDD. When I stopped the session and started a new one, the problem went away. My "best" guess is that maybe there is some memory leakage, but just a little, and it takes a long time to build up, so you would only experience it after a long time?
In case that is the issue, I have put clearHistory="true" on the key navigations that were causing issues. I am thinking this is the key way to combat memory leaks. Does that sound right?
From @NL33 on February 8, 2019 22:26
As a bit more info, it does seem that memory use on the app is steadily increasing as the app is used (at least on the emulator). With each navigation, memory use grows. So perhaps that was the issue. I had hoped that putting clearHistory="true" would reset the memory to avoid this problem. But xcode shows memory use increasing with each navigation even with clearHistory.
Is there something else I need to be doing? I have researched possible causes of memory leaks and cannot see what might be happening here, other than if NS 5.1+webpack for iOS has small memory leaks all the time. It would be great if there is something I can run periodically in the app to lower memory usage.
I have tried garbage collection, but that did not seem to make the difference. If the app was at, say, 285 mbs before running GC, it stayed there after, and would slowly increase with further navigation.
Is there anything I can do to reset the memory in the app while it is running?
Moving this issue to the modules repository as it seems related more to a memory leak than to a CLI issue.
@NL33 if possible please provide us with a sample demo app which we could use to investigate the case. It seems that you are using an Angular based app but are you having issues on specific pages or on each navigation? Are you using page transitions and is the issue only happening on iOS or on Android? Posting a simple demo that reproduces the issue will greatly help in understanding the cause.
Thank you, @NickIliev for following up. The app on which this is occurring is fairly involved but I will see if I can create a different app and reproduce it. My app is an Angular app, currently focused on iOS, with the specs noted above (NS 5.1).
I have further detail below. The key question I am facing: is there a way to restart / clear out memory use of an app? So, if an app starts at 50mbs, and then rises through loading some heavier pages to 100mbs, is there a way to get the memory use back down to 50mbs when the user is no longer using those resources and returns to a basic page?
I have tried clearHistory: true between pages. And I have tried activating garbage collection. These do not seem to make a difference (I do not notice a memory drop when they are used). I am not subscribing to any rxjs events (unless that is something that NS does in the background).
Further Detail:
I confess I am not aware of what the expected memory performance would be. The memory issue is roughly the same whether on iphone, ipad, or emulator--so it seems it is not a simulator issue. I am measuring memory use by plugging in my device, running the app through xcode, and tracking memory use on xcode.
Memory use increases as the app is used. In navigating between pages, the back button does seem to help things--so that it may take memory use back down a few mbs. But overall memory use rises. For instance, in my last test on an iphone, my app loaded at about 50mbs. after a few minutes of use it rose to about 80mbs, and then I tested some heavy memory pages, and it went up to 100mbs.
Despite navigating back to pages with very little happening (simple component structure and little data used), memory never fell much below that. Once it hit a level (in this case, 100mbs), it stays around that level, and then rises again for heavier pages.
My hope would be there is a way the app can clear out the memory after it is no longer uses that memory. So if the app loads at 50mbs, there would be a way to clear things out to get it back down to 50mbs when I am back on, say, the home page.
Is that possible?
Or is it to be expected that memory use gradually rises as the app is used, and once it hits a certain level (for example 100mbs) it will not fall back to its original level (like 50mbs)?
One last key note: Based on advice I found to help memory performance, I added enableProd() to main.ts. This may have helped a little. I was told to add this to main.aot.ts as well, but this file is NO LONGER created in my app after upgrading to 5.1.
Further, I see no clear reference in webpack.config.js that aot is present, despite doing the normal command: $ tns run ios --build --env.aot... Maybe this could be related?
@NL33 here you can find the article explaining how to use some memory management techniques. However in the common case, you should not experience any memory issues on navigation (make sure that you are testing on a real device because on some emulators/simulators there will be no memory pressure and the memory won't be released while on a real device the memory will be released in a different manner).
Waiting for your sample app so we could test on our side.
Thanks. I will investigate a sample app later on today (I'll have to create a new app). In the normal case, would you expect that an app's memory use will go to the starting point when it is returned to the starting page?
So, in my case if my app starts at 50mbs, and then through use for several minutes and some heavy pages rises to 100mbs, would you expect it should fall back to about 50 mbs after it has returned to the home page?
@NL33 is the leak happening on a real device or emulator? Is this Android or iOS? In all cases, on a real device, the memory should remain on some stable levels and unused objects should be released and cleared by the GC.
- iOS focused. 2. happening on real devices (and emulator too).
Is there a danger in calling garbage collection? I have it set up to activate whenever I get an ios memory warning, but I am inclined to try calling it whenever I return to the home page as well. I am fine losing the navigation history.
I also have the strange result that in any NS app I create now (using NS 5.1), when I then call $ tns run ios bundle env.uglify env.aot... , trying to get webpack+uglify+aot, I do not see any aot file being created, and no statement in webpack.config.js that there is aot. Should there be a main.aot.ts file in the most recent Nativescript? If so, maybe the fact that it is not there for me is related.
@NickIliev I have created a simple test app at https://github.com/NL33/memory-test-app.
Description is in the readme. Essentially:
I created a webpack bundle ($ tns build ios --bundle --env.uglify --env.aot) and then also ran webpack ($ tns run ios --bundle --env.uglify --env.aot). I then tested the app in xcode, running on a plugged in iphone.
Using the app to test I try to build up memory use by going to the video page, and from there going to the list page, making a bunch of selections, then viewing the selections, and scrolling up and down. This tends to build up the memory use. I then go back to the home page, and repeat.
I did this a few times, and then waited a few minutes, and did a few times more.
The app went from about 45 mbs on load to, after a few minutes of the steps above, 90mbs. Returning to the home page would get the memory footprint down a little each time, but never all the way back. Once I hit 90 mbs it never went below about 75mbs on return to home page, despite the home page clearing out all local storage.
This app is simpler than the app I am working on, so the memory swings are less pronounced. But this app seems similar in that the general trend appears to be an increasing memory footprint as the app is used.
Note that, despite running env.aot in the bundle command, there is no main.aot.ts.
I look forward to hearing your view on whether this performance is normal and if not what can be done.
@NL33 thank you for the detailed information and description of the issue you are facing. I've used your project, and I was able to verify that indeed the memory consumption was increasing. While this is expected, it is also expected for most of the objects to be released on navigation from page A to page B. The good news is that the issue was already addressed by the Core team devs and the patch is on its way via this PR. Once the patch is merged in the release branch, you can tet it with the rc
npm i tns-core-modules@rc --save
I've tested the app you provide with iPhone X and the memory consumption was a pretty stable line that was going back to its original values on navigation.
Thanks, @NickIliev. Sounds good. Is there a timing expectation for the release?
I am just about to put the app on the app store. If (i) the release is coming soon and (ii) will be stable for production apps then I can wait a few more days.
If the release is not expected for a matter of weeks, or it won't be guaranteed stable for production, then maybe its not best for me to wait for it. In that case, anything else I can do to mitigate?
Lastly, do you know what's happening with AOT in the sample app I provided? In that app, like the app I am working on, I did $ tns build ios --bundle --env.aot ... , but I find no evidence of AOT in the app. Its important to me that both uglify and AOT are there.
@NL33 the official release is expected within the end of the week.
About the AOT - with some previous changes (in nativescript-dev-webpack) we are no longer generating the *.aot.ts files (we are handling this logic internally) so that is actually the expected behavior.
@NL33 update: the official release 5.2 is published but the patch is not included. I will update as soon as I have additional information on when this specific patch is going live (through a patch version).
Thanks. Sounds good. I had thought of the main.aot.ts file bc I had read that to help with memory you should put enableProd() in there. I guess putting enableProd() in main.ts will be sufficient. It still would be helpful if there is somewhere in the app that confirms that AOT is in effect (webpack.config.js used to say something I believe that is no longer there).
Hi @NickIliev --any update on the status of that patch?
I just upgraded my app ($tns update)--now using NS 5.2.0 and tns-core-modules 5.2.0. The memory performance was at least as bad as before--testing on iphone with xcode, the build started at 46 mbs, and then within a few mins was up to 100mbs, and would not go down from there. Thanks.
@NL33 - I don't know the time line for this patches or which version it is slated for (5.21 or 5.3). So I can't answer that question.
However, you can easily manually apply the patch to your copy of the TNS-Core-Modules inside your application and then rebuild your app. Just go to the github PR, download the patch, merge it into your app's tns-core-modules and then rebuild your app...
Update: @NL33 - Actually it is even simpler than that, just looked at the PR. Just do:
npm i --save tns-core-modules@rc in your app folder and it will install the latest Release Candidate. The current RC should already have that patch based on the git PR history. Then just rebuild your app.
Awesome. Thanks. I'll give it a shot. I assume its just the tns-core-modules (not the NS version itself) that needs to be updated.
To get the patch to work, is the idea that it clears things out when the "Back" navigation is started (in which case I'll be sure to put in "Back" navigation strategically) or does it clear things out on page load?
After upgrading with $ npm i --save tns-core-modules@rc, I have two issues:
- new security warnings
- memory use issue is not resolved.
On (1) I get this security vulnerability (never there previously):
1 vulnerability required manual review and could not be updated
Details:
Low: Regular Expression Denial of Service
patched in: >=2.3.1
package: braces
Dependency of: @angular/compiler-cli [dev]
Path: @angular/compiler-cli > chokidar > anymatch > micromatch > braces
More info: https://nodesecurity.io/advisories/786
$ npm audit fix does not fix.
Note that I also get "stale file" warnings (warning: stale file '/................-Prefix.pch.gch' is located outside of the allowed root paths). Have not seen these before either.
On (2): I have tested with the patch on device (iPhone SE), and memory performance remains a problem.
Shows that I am using: tns core modules: 5.3.0-rc-2019-02-18-175109-01 . Are we sure the patch is there?
Updating to this rc does not seem to have addressed the issue. According to xcode my app loaded at about 45mbs. I tested going to the video page and playing the video--I got a big jump to 90mbs. After getting to that level, returning to home screen did not fall much. From then on, being on home screen with, as far as I can tell, nothing built up in the app, the home screen was still at 88mbs (even though I destroy the video object on going to the home screen). After a few more minutes I was up to 120mbs, and could not get it down much beyond that.
Again, goal is if I get to the home screen and there are no saved objects, it returns to starting point. I have also tested with activating GC and clearHistory=true on home screen. No difference.
It would be great if there was some kind of "reset" action that could take things back to the start. This patch does not seem to have done it.
is there maybe a viewContainerRef solution I should be trying?
I note that the patch description problem is:"The memory allocated for the modal view, with Frame inside, is not released". I am not familiar with the inner workings of modal view, but I assume this is broad enough to cover my general memory issue.
Guys--I am not aware that my app is "subscribing" to anything that needs unsubscribing--but is it possible that the app might subscribing to things for which I need to unsubscribe, and that could be the issue? Here are 3 possibilities:
- My app uses the fetch method, like:
return fetch(apiAddress,
method: "GET",
headers: new Headers({ 'Authorization': 'Bearer ' + header })
}).then((result) => {
return result.json();
}).then((nextStep) => {
[do stuff]...
})
I had not considered this to be "subscribing" to anything--but is it possible that I need to unsubscribe from this?
-
My app subscribes to activeRoute, like:
this.activeRoute.queryParams.subscribe((params) => {...}
Research indicates that I shouldn't need to manually unsubscribe from activeRoute.
-
Is there rxjs activity built into tns-core-modules, for which I should unsubscribe? When running the app, I encountered an error (unrelated I thought), and the console included
this:complete@file:///app/tns_modules/rxjs/internal/Subscriber.js:79:27
Still hunting for why the memory performance is problematic even after updating to the recent RC.
Bump. Any ideas, guys? Issues are:
- after downloading patch (see reference above), I have a new vulnerability and warnings,
- patch has not meaningfully improved memory,
- is there some subscription that NS users should unsubscribe from? Thanks.
@NickIliev and @NathanaelA This is mission critical. Is there anything that can be done? To keep things easier, let's skip the warnings for now mentioned above--I need a way to improve the memory usage situation.
I downloaded latest patch--I am at: 5.2.1-rc-2019-02-21-122645-01. No difference. I previously used 5.3.0-rc-2019-02-18-175109-01, no difference there either.
In using my app, memory use rises fairly steadily--same problem as always. Certain pages cause a jump in memory, and then returning to the home page (where nothing is happening and items are cleared out) does not take things back to start. So after a few minutes I go from 45 mbs to 70 mbs on the home. A few more minutes I am at 90mbs etc... I need a way to clear things out and return back to the starting memory point (or close to it).
I appreciate follow-up from the NS team. Thank you.
@NL33 I am testing your app with tns-core-modules@rc (5.2.2-rc-2019-02-22-161224-03`) and the memory usage is stable and after navigation it is reverting to almost the initial values (after few minutes navigating from the list and backward and starting from 82mb I am on 89mb - notice that some of the ListView objects are not released after the initial load).
Notice that similar test should be run only on real device (simulators are using the OS resources and are more rarely experiencing memory pressure which means that will release memory no in the same rate as this would happen on a real device).
If you still want to force the GC to be called on a specific period of time, you could use a flag - check this article section for the available flags that can be set in the application package.json (notice that this is not the root package.json but the one inside your application folder).
If none of the above helps, then please let me know if you are testing with the same app or if you are facing another scenario. Also, confirm that you are testing on real device (what model and what version of iOS)
Thank you, @NickIliev. I am testing on real devices (for example, iPhone SE using iOS 11.4; NS 5.2; Angular). Judging memory use through Xcode.
I notice the issue the most on my own app--which is a full blown app. The full blown app has about 24 components (most just display text--no fancy animations going). Each component is lazily loaded. Performance seems excellent other than seeing that memory usage increases steadily.
It uses similar tools as the test app--local storage and video. But the local storage is supposed to be cleared regularly and the video objects destroyed. I do see the issue as well on the test app, but not quite as much bc that app is simpler. I might try to beef up the test app so that it can be a better test.
-
You mention that, on test app, not all components are released. I have a hunch that the issue is related to failure to release components, or maybe failing to unsubscribe to something (I am not actively subscribing to anything--see earlier note--but maybe something is happening there by default). On the test app, just like my larger app, when you return to the home page nothing should be going on. How can I release everything on the home page? So far, calling garbage collection and saying clearHistory=true when going to the home doesn't seem to change things.
-
If it turns out garbage collection is the way to go, is there a problem just calling it every time user goes to the home page? (I am fine losing all navigation and active objects). BTW, I see you have used a GC flag in the application folder in the link you sent, but that seems only for Android.
Thanks!
Regarding I see you have used a GC flag in the application folder - this flag is enabled by design in all NativeScript apps and is not related to the current discussion for iOS (it enables the GS exposure on Android)
Regarding You mention that, on test app, not all components are released. - the object that are not released should be the ones that you still using (refering to). Keep in mind that the GC is not called by the VM at the moment you are no longer using those objects. On real device, the GC will be called on memory pressure (or when triggered manually e.g. as you have done with the throttle flags) but still the actual GC pass can appear after different time on different devices.
Regarding is there a problem just calling it every time user goes to the home page - depends on your business logic - if you are not depending on native objects being preserved - then no problems. In the common case, each time you go back you should be able to free all instanced objects and recreated them when the user re-enters the page so that should be totally fine.
Thanks very much. It is this "not all components are released" piece I am really trying to understand, as I think (hope?) that is the key here. (And my apologies if it seems I keep hammering away at this--I think we are really close, and solving this would address a major concern. I appreciate your patience!)
On the test app, there are basically 3 pieces--1) Home Page, 2) Page to view video, and 3) page to select and view a list.
When I get to the home page (here ), I don't believe I continue to refer to "anything" other than what is displayed on the simple home page itself. I don't require any reference to the video page or list page--they can be entirely created when the user navigates to them. So I want to be sure that when I am on the home page there is nothing in memory still linked to the other pages.
Is there something else I can do to ensure this happens?
On my real, full, app, as mentioned, there are a lot of components and some of them have fairly heavy view structures (stacklayouts within stacklayouts). They are rendered fine individually, but if there is anything from those pages that sticks around when you get back to the home page, that would explain the increasing memory use.
For GC--did you mean to link to an article before? My understanding is that if I want to call GC on every time the home page loads, I could just add the following:
import { GC } from "utils/utils";
....
ngOnInit() {
this.page.on('navigatedTo', (data) => {
GC();
})
But I don't see that make much difference, so maybe I do indeed need a further flag somewhere?
On my main app, I do also get this warning all over the place (the console repeats this about 30 times when the app loads):
CONSOLE WARN file:///app/vendor.js:[1:111....]: Objective-C class name "t" is already in use - using "t28" instead.
I assume that's not related.
@NL33 yep I meant the article approach as there are some additional optimizations behind those flags (e.g. like calling GC multiple times in different VM passes).
I would also like to notice that the garbage collectior might not be called immedialty. In fact on real device, the unused objects might be releaed after memory pressure so you don't ahve ot worry about increasing mmemory unless of course this is leading to OOM. Still, thats highly unlikely and the iOS will usually free the memory once there is enough memory pressure. If you want to understand which objects exactly are the ones that are causing the memry increase then refer to the techniques described here.
Thanks. That is helpful to hear--If I understand what you are saying, it is that even if memory use increases steadily (going from 40mbs to 100mbs after several minutes, and likely up from there...), that iOS has built in safeguards to generally address the concern.
If I could give one more shot at my main question:
When I return to the simple home page, nothing else is happening in the app other than displaying that simple text.
When I am on the home page, is there something I can do to be sure I have cleared out all objects and views so I am returning to the starting memory point?
My hope is that if I start at 40mbs, and go to 100+ after navigating through heavy pages, each time I return to the home page I can go back to close to 40mbs. The only advice I am seeing--like in the article you referenced--is to call GC(). Unless I am doing it wrong (see sample code above), that does not seem to make a difference in clearing everything out. I am hoping there is something else I can do.