Bug 4188 - Memory leak using CALayer's RenderInContext(CGContext) on a background thread
Summary: Memory leak using CALayer's RenderInContext(CGContext) on a background thread
Alias: None
Product: iOS
Classification: Xamarin
Component: General ()
Version: 5.2
Hardware: Macintosh Mac OS
: --- normal
Target Milestone: Untriaged
Assignee: Bugzilla
Depends on:
Reported: 2012-04-02 12:01 UTC by Louis Boux
Modified: 2013-12-05 18:34 UTC (History)
4 users (show)

Is this bug a regression?: ---
Last known good build:

test project related to this case (2.69 MB, application/zip)
2012-04-02 12:01 UTC, Louis Boux

Notice (2018-05-24): bugzilla.xamarin.com is now in read-only mode.

Please join us on Visual Studio Developer Community and in the Xamarin and Mono organizations on GitHub to continue tracking issues. Bugzilla will remain available for reference in read-only mode. We will continue to work on open Bugzilla bugs, copy them to the new locations as needed for follow-up, and add the new items under Related Links.

Our sincere thanks to everyone who has contributed on this bug tracker over the years. Thanks also for your understanding as we make these adjustments and improvements for the future.

Please create a new report on Developer Community or GitHub with your current version information, steps to reproduce, and relevant error messages or log files if you are hitting an issue that looks similar to this resolved bug and you do not yet see a matching new report.

Related Links:

Description Louis Boux 2012-04-02 12:01:50 UTC
Created attachment 1610 [details]
test project related to this case

Version Info:
Mono 2.10.9
MonoTouch 5.2.10

This is a long story, and please do tell me if something isn't clear.

I'm currently working on an iPad app where I'm using a bunch of thumbnails in a scroll view that represents other sections of that app, and since the content of these other sections always changes, I constantly need to reload everything, at a reasonable pace of course. To be able to show so much content in that single scroll view, I have no choice but to use images representing the views that are in it, or the scrolling would be massively laggy. The loading of these views also need to be Async, again to prevent lagging.

So basically I'm doing all that by creating a background thread where i will create the desired views and call RenderInContext() on their layers to extract the thumbnails. However, doing all that seems to cause a memory leak, and not a small one. I have created a test project that represents this issue quite well, it should be in the attached files.

What does the test project do : 
There are two buttons, pressing any of them will create a bunch of views (300 in this case) in a container view and renders that container to an image (one is async and the other is one does it on the main thread). 60% of the randomly created views are simple UIViews and 40% are UILabels, all of random size and color. The rendered view's size, as well as the rendered image's size, is currently 500x500. The labels' text length is 30 to 180 character long with only capital letters. (These values accurately represent what is rendered in my real app)

Here's a list of what I have tested using that test project so far :
- No matter what the size of the rendered thumbnail is, the leak is still the same (results may vary for sizes under 100x100 and over 2000x2000)
- Executing the same thing on the main thread does not seem to leak 
- Removing only the line that calls RenderInContext() completely fixes the leak
- If 100% of the rendered views are simple UIViews, the memory doesn't rise as much and stops rising after a few clicks.
- The length of the text in the labels directly affects how heavily RenderInContext() leaks. Shorter text will leak less, and longer text will leak more. Not only the length of the string changes the results, the characters too. I've done the same tests with random characters from the dark depth of the unexplored regions of UTF32 and the leaks were colossal (about 2x bigger).
- Even if everything is the same color, the leak will be the same.
- Tested on the device (iPad 2) and it does crash after spamming some memory warnings.

Why I think this is a memory leak :
I have tested this project with the profiler by taking a memory snapshot between every tap on the "Worker" button and the changes between each snapshot were negligible.

Additional experiments

I was trying to mess around with SGen's configurations by using the related environment variables (to see if I could find something that would alter this project's results) only to find out that I had to update monodevelop to a beta version ( and MonoTouch to an alpha version (5.3).

related link : http://stackoverflow.com/questions/9909767/using-environment-variables-in-monodevelop-with-monotouch (thanks Rolf)

Updating to these versions does enable the use of environment variable and lets me mess around with SGen, but I soon found out that any Async call to UIKit in MonoTouch 5.3 completely freezes that thread forever.

So I just gave up on that front since 5.3 is an Alpha version, but I still thought that it would be pertinent to mention it.
Comment 1 Sebastien Pouliot 2012-04-02 17:06:42 UTC
I'll look at your sample code. Threading and UI components can be tricky as most of them cannot be used outside the main thread - it required synchronization (and it's easy to miss some).

> Why I think this is a memory leak : ...

Did you try MonoDevelop's HeapShot or Apple's Instruments ?

> Additional experiments ...

With 5.3 alpha you're likely hitting the new thread-checks [1] which will throw an exception (maybe something is catching that exception in your case) when an UIKit object instance is used on any thread - except the main one.

[1] http://spouliot.wordpress.com/2012/03/02/linker-vs-bindings-and-ui-thread-checks/
Comment 2 Dynami Le Savard 2012-04-02 17:18:20 UTC
Do the changes in 5.3 mean that calling CALayer.RenderInContext and  UIGraphics.GetImageFromCurrentImageContext() to produce an image of a UIView from a background thread will no longer be a viable option ?
Comment 3 Sebastien Pouliot 2012-04-02 18:11:11 UTC
Yes and no.

Yes. As the previous link [1] explains you'll be able to turn off the thread check helpers - so anything do you today will still be possible tomorrow.

No. The documentation for UIGraphics.GetImageFromCurrentImageContext [2] specifically tells you that:

> You should call this function from the main thread of your application only.

I did not (yet) had the time to review the attached code but my initial feeling, from the description, is that the issue could be related to running UI code on the wrong (not main) threads - which can crash your app and/or cause a lot of weirdness (including leaking memory).

[2] http://developer.apple.com/library/ios/#documentation/uikit/reference/UIKitFunctionReference/Reference/reference.html#//apple_ref/c/func/UIGraphicsGetImageFromCurrentImageContext
Comment 4 Louis Boux 2012-04-03 14:50:10 UTC
The thing is though, is that we had this as working code back in last January. We had a leak in this code, very similar to the one we have now, and came to the same conclusion as you did based on iOS documentation. This was grim news, as it is very improbable to keep our current design without this concept working.

However, it was not producing any leaks as long as the context was being disposed properly, even if it was on a background thread. This was tested on the device successfully and project got a green light. So despite that, we assumed that even though if was not recommended according to iOS documentation, the code didn't produce any leaks as it was.

It's only recently that we re-ran integration tests on the device that we noticed this problem being back, even though no major code changes related to this feature were noticeable.

Just to be on the safe side, we would have reconfirmed that the leak was not present using January's configurations if we could. However, we no longer can obtain MonoTouch from that time period nor do we have the exact version we used at that time.
Comment 5 Sebastien Pouliot 2012-04-03 15:12:00 UTC
If you have a binary of the .app then we might be able to extract the MonoTouch version number (if it was not linked out). You can attach the binary (and mark it private) to the bug report.

Using this information support@xamarin.com will be able to give you the matching MonoTouch installer.
Comment 6 Louis Boux 2012-04-03 15:17:36 UTC
Sadly we don't have that binary anymore
Comment 7 Sebastien Pouliot 2012-04-03 16:12:11 UTC
Your code does a lot of UI work on background threads, including creating UIView and UILabels. 

None of those operations are safe outside the main thread: not on MonoTouch nor on Objective-C. Apple's documentation warns you because the behaviour of such operations is not deterministic (in a single version) or it might change between versions.

Without more data I can't rule out that some MonoTouch internals makes this more likely to occurs now (than before). You can ask support@xamarin.com to give you links to previous MonoTouch versions from the last date you know this worked and work backward... but if this change is related to an iOS version update (e.g. iOS 5.1) then this won't help.

You might also want to try Apple's Instrument and find the API that cause the leak (and execute that part on the main thread), rinse and repeat... You could be _lucky_ and find the "right" mix that (or seems to) works until something else change (like a new version of iOS). 

Honestly I suggest you to find an alternative* that respect Apple guidelines or you risk to find yourself in a painful situation again (and again).

* e.g. drop UIView and UILabel and work with CoreGraphics and/or CoreText (which have less threading restrictions).
Comment 8 PJ 2013-11-19 17:04:42 UTC
This bug has been in the NEEDINFO state with no changes for the last 90 days. Can we put this back into the NEW or CONFIRMED state, or are we still awaiting response?

If there is no change in the status of this bug over the next two weeks, this bug will be marked as NORESPONSE.
Comment 9 PJ 2013-12-05 18:34:51 UTC
This bug has not been changed from the NEEDINFO state since my previous comment, marking as RESOLVED NORESPONSE.

Please feel free to REOPEN this bug at any time if you are still experiencing the issue. Please add the requested information and set the bug back to the NEW (or CONFIRMED) state.