Bug 35687 - ImageIO: Repeatedly saving images causes increased memory pressure
Summary: ImageIO: Repeatedly saving images causes increased memory pressure
Status: RESOLVED ANSWERED
Alias: None
Product: iOS
Classification: Xamarin
Component: Xamarin.iOS.dll ()
Version: XI 9.1 (iOS 9.1)
Hardware: PC Mac OS
: Normal normal
Target Milestone: Untriaged
Assignee: Manuel de la Peña [MSFT]
URL:
Depends on:
Blocks:
 
Reported: 2015-11-09 09:30 UTC by Adam Hartley [MSFT]
Modified: 2015-11-20 05:07 UTC (History)
4 users (show)

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


Attachments
Modified cs code that fixes the memory leaks. (2.25 KB, text/plain)
2015-11-20 05:07 UTC, Manuel de la Peña [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] 2015-11-09 09:30:33 UTC
## Sample

https://drive.google.com/file/d/0B3WrxROLAJ68cWI2RG9jMW05SG8/view?usp=sharing

Includes Xamarin and Swift versions + Instrument traces 

## Steps to reproduce

1. Run app on a device
2. Tap "Pick Photo"
3. Choose a photo

Repeat

## Expected result

As per the Swift version, memory usage should not continue to grow

## Actual result

Memory leak and eventual termination by the system

## Notes

=== Xamarin Studio ===

Version 5.9.7 (build 22)
Installation UUID: 50da5323-689a-4d63-ad69-cbe7400a5606
Runtime:
Mono 4.0.4 ((detached/d481017)
GTK+ 2.24.23 (Raleigh theme)

Package version: 400040004

=== Apple Developer Tools ===

Xcode 7.1 (9079)
Build 7B91b

=== Xamarin.iOS ===

Version: 9.0.1.29 (Business Edition)
Hash: 1d27ac2
Branch: master
Build date: 2015-09-25 18:08:44-0400

=== Xamarin.Android ===

Version: 5.1.8.0 (Business Edition)
Android SDK: /Users/markw/Library/Developer/Xamarin/android-sdk-macosx
Supported Android versions:
2.3 (API level 10)
4.0.3 (API level 15)
4.4 (API level 19)
5.0 (API level 21)
5.1 (API level 22)
Java SDK: /usr
java version "1.7.0_79"
Java(TM) SE Runtime Environment (build 1.7.0_79-b15)
Java HotSpot(TM) 64-Bit Server VM (build 24.79-b02, mixed mode)

=== Xamarin Android Player ===

Not Installed

=== Xamarin.Mac ===

Not Installed

=== Build Information ===

Release ID: 509070022
Git revision: 6bd1f169df44ca96addf8a035316c535a4fa46fa
Build date: 2015-09-30 12:30:15-04
Xamarin addins: 1c3e5c0859bdfec0ecd481a57ad6c03bc22f5536

=== Operating System ===

Mac OS X 10.11.1
Darwin Marks-MacBook-Air.local 15.0.0 Darwin Kernel Version 15.0.0
Sat Sep 19 15:53:46 PDT 2015
root:xnu-3247.10.11~1/RELEASE_X86_64 x86_64
Comment 1 Manuel de la Peña [MSFT] 2015-11-20 05:05:19 UTC
Hello,

The bug is in the user code rather than on the platform. The code has the following memory leaks:

mediaInfo leak
===========

The user has exposed the following method as the delegate for the image selection:

[Export("imagePickerController:didFinishPickingMediaWithInfo:")]
public void FinishedPickingMedia(UIImagePickerController picker, NSDictionary mediaInfo)

One of the several memory leaks is happening because the NSDictioary is not Disposed once the method is done. In order to ensure that the disposal does happen once we leave the scope the [Transient] attribute must be used. The transient attribute can be used on properties AND parameters in methods, we are interested in the second used case. This means that the method should be declared as follows:

[Export("imagePickerController:didFinishPickingMediaWithInfo:")]
public void FinishedPickingMedia(UIImagePickerController picker, [Transient] NSDictionary mediaInfo)

Do notice the addition of the attribute BEFORE the mediaInfo parameter.

CGImage leak
==========

The CGImage returned by the UIImage must be disposed once the user has done with it. That is, the property returns a new CGImage and we need to clean after our code has done with it. The original code is:

	var image = mediaInfo [UIImagePickerController.OriginalImage] as UIImage;
	var op = new NSBlockOperation ();
	op.AddExecutionBlock(() => {
		var tempDir = Path.GetTempPath ();
		var uuid = new NSUuid();
		var file = uuid.Description;
		var url = NSUrl.FromFilename(Path.Combine(tempDir, file));
		var dest = CGImageDestination.Create(url, UTType.JPEG, 1);
		dest.AddImage(image.CGImage);
		dest.Close();
		dest.Dispose();
        	image.Dispose();
	});
	queue.AddOperation (op);

A minimum change to the code that will fix the issue is as follows:

	var image = mediaInfo [UIImagePickerController.OriginalImage] as UIImage;
	var op = new NSBlockOperation ();
	op.AddExecutionBlock(() => {
		var tempDir = Path.GetTempPath ();
		var uuid = new NSUuid();
		var file = uuid.Description;
		var url = NSUrl.FromFilename(Path.Combine(tempDir, file));
		var dest = CGImageDestination.Create(url, UTType.JPEG, 1);
		var cimage = image.CGImage;
		dest.AddImage(cimage);
		dest.Close();
		dest.Dispose();
		image.Dispose();
		url.Dispose();
		cimage.Dispose();
	});
	queue.AddOperation (op);

Do notice that a new variable was created that holds the CGImage and that is disposed at the end of the block AND a new dispose call was added for the NSUrl which was also leaking. A much nicer approach is to use "using" blocks to ensure that the objects are correctly disposed in the following way:

	var image = mediaInfo [UIImagePickerController.OriginalImage] as UIImage;
	var op = new NSBlockOperation ();
	op.AddExecutionBlock (() => {
		var tempDir = Path.GetTempPath ();
		using (var uuid = new NSUuid ()) {
			var file = uuid.Description;
			using (var url = NSUrl.FromFilename(Path.Combine(tempDir, file)))
			using (var dest = CGImageDestination.Create(url, UTType.JPEG, 1))
			using (var cimage = image.CGImage) {
				dest.AddImage(cimage);
				dest.Close ();
				image.Dispose (); // important cause the block retains the image
			}
 		}
	});
	queue.AddOperation (op);

With this changes the application does not leak images after every selection and has a memory footprint very similar to the native swift code.
Comment 2 Manuel de la Peña [MSFT] 2015-11-20 05:07:59 UTC
Created attachment 13911 [details]
Modified cs code that fixes the memory leaks.

This file contains the recommeded changes to the original code to fix the several memory leaks found in the users code.