Bug 38991 - NSImage.LockFocus causing a memory leak
Summary: NSImage.LockFocus causing a memory leak
Status: RESOLVED ANSWERED
Alias: None
Product: Xamarin.Mac
Classification: Desktop
Component: Runtime ()
Version: Master
Hardware: PC Mac OS
: --- normal
Target Milestone: ---
Assignee: Chris Hamons
URL:
Depends on:
Blocks:
 
Reported: 2016-02-22 15:32 UTC by Adam Hartley [MSFT]
Modified: 2016-02-23 22:39 UTC (History)
3 users (show)

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


Attachments
Sample (375.30 KB, application/zip)
2016-02-22 15:32 UTC, Adam Hartley [MSFT]
Details


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:
Status:
RESOLVED ANSWERED

Description Adam Hartley [MSFT] 2016-02-22 15:32:36 UTC
Created attachment 15125 [details]
Sample

## Steps to reproduce

1. Download and run attached sample
2. Enter a number, such as 500, and click the button
3. Observe memory usage

## Expected result

Memory should not leak

## Actual result

Memory leaks using up several GB depending on how many times it is run

## Notes

Appears to be leaking at copiedImage.LockFocus (line 108 of MainWindowController)

Disposing copiedImage (line 92) does free the memory, but only after the loop is complete (so it can still rack up a large allocation).

=== Xamarin Studio ===

Version 5.10.2 (build 56)
Installation UUID: 11a26d86-0d21-407a-8da9-8c197ecc0ad1
Runtime:
	Mono 4.2.2 (explicit/996df3c)
	GTK+ 2.24.23 (Raleigh theme)

	Package version: 402020030

=== Xamarin.Profiler ===

Version: 0.31.0
Location: /Applications/Xamarin Profiler.app/Contents/MacOS/Xamarin Profiler

=== Apple Developer Tools ===

Xcode 7.2.1 (9548.1)
Build 7C1002

=== Xamarin.iOS ===

Version: 9.4.1.25 (Business Edition)
Hash: 962a050
Branch: master
Build date: 2016-01-29 16:59:11-0500

=== Xamarin.Android ===

Version: 6.0.1.10 (Business Edition)
Android SDK: /Users/Adam/Library/Developer/Xamarin/android-sdk-macosx
	Supported Android versions:
		4.0.3 (API level 15)
		4.4   (API level 19)
		5.0   (API level 21)
		5.1   (API level 22)
		6.0   (API level 23)

SDK Tools Version: 24.4.1
SDK Platform Tools Version: 23.1
SDK Build Tools Version: 23.0.1

Java SDK: /usr
java version "1.8.0_73"
Java(TM) SE Runtime Environment (build 1.8.0_73-b02)
Java HotSpot(TM) 64-Bit Server VM (build 25.73-b02, mixed mode)

=== Xamarin Android Player ===

Not Installed

=== Xamarin.Mac ===

Version: 2.4.1.6 (Business Edition)

=== Build Information ===

Release ID: 510020056
Git revision: bb74ff467c62ded42b7b7ac7fdd2edc60f8647b0
Build date: 2016-01-26 16:24:41-05
Xamarin addins: 8b797d7ba24d5abab226c2cf9fda77f666263f1b
Build lane: monodevelop-lion-cycle6-c6sr1

=== Operating System ===

Mac OS X 10.11.3
Darwin Adams-Retina-MacBook-Pro.local 15.3.0 Darwin Kernel Version 15.3.0
    Thu Dec 10 18:40:58 PST 2015
    root:xnu-3248.30.4~1/RELEASE_X86_64 x86_64
Comment 1 Chris Hamons 2016-02-23 22:39:53 UTC
So this turns out to actually not be a leak. If you replace your code with:

for(var i=0; i<reloadCount; ++i)
{
	using (var pool = new NSAutoreleasePool ())
	{
		var kittenImage = NSImage.ImageNamed ("kitten.jpg");

		//Create new image because if the resource image is loaded once by ImageNamed, 
		//then it will reuse the same image instead of loading it to memory again
		var copiedImage = CreateScaledImage(kittenImage);

		_leakyView.Image.Dispose ();
		_leakyView.Image = copiedImage;
		_leakyView.NeedsDisplay = true;
	}
}

then your memory usage will stay pinned low, even if you type in 999 and multiple it by some large number.

In general, you don't need to care as much about managed memory usage, as the GC will clean things up as long as you don't create hard references to "dead" parts of your graph. However, NSImage contains a large native half, the image data, that every NSImage.ImageNamed will make a copy of.

If you dispose of the previous image right before overwriting, you are letting us know we can clean up that image "early". That later point would be when you start to have sufficient memory pressure to cause a GC.Collect to happen.

However, with only that fix, your memory usage will ballon while in your tight loop and only go back to "sane" levels when you return from your handler. The "problem" is that there is a native auto release pool that only returns memory when cleaned up. This is fine when you aren't loading a 39KB image 999 times in a tight loop on the UI thread without catching your breath.

However, if you really want the resources cleaned up now, and not at some point in the future when it is easy, you can create your own autorelease pool to wrap the operation in question.