Bug 25511 - Usability: Disposing an object that might have been surfaced many times, invalidates all instances.
Summary: Usability: Disposing an object that might have been surfaced many times, inva...
Status: RESOLVED FIXED
Alias: None
Product: iOS
Classification: Xamarin
Component: XI runtime ()
Version: XI 8.4.0
Hardware: Macintosh Mac OS
: Normal normal
Target Milestone: 8.10
Assignee: Sebastien Pouliot
URL:
Depends on:
Blocks:
 
Reported: 2014-12-18 15:55 UTC by renan jegouzo
Modified: 2015-07-01 05:18 UTC (History)
6 users (show)

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


Attachments
test project files (5.75 KB, application/zip)
2014-12-19 00:53 UTC, renan jegouzo
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 FIXED

Description renan jegouzo 2014-12-18 15:55:23 UTC
when I'm instantiate UIFont in multiple threads at the same time, the handle on the native object seems to be lost in the managed UIFont.
Comment 1 Sebastien Pouliot 2014-12-18 18:36:28 UTC
AFAIK UIFont _should_ be thread safe and Apple documented it as such.

Still bugs (ours or Apple) are always possible. Can you tell us which specific UIFont API you are using ?

Also was this on an iOS device ? Simulator ? both ?

Which version of iOS ?

and, finally, if you're not using XI 8.4 (as stated) above please include the version of the software you're using. Thanks!

* The easiest way to get exact version information is to use the "Xamarin Studio" menu, "About Xamarin Studio" item, "Show Details" button and copy/paste the version informations (you can use the "Copy Information" button).
Comment 2 renan jegouzo 2014-12-19 00:33:07 UTC
I have this bugs on all my devices and all simulators.

this code:
			var r=new Random();
			for(int i=0; i<10; i++) {
				new System.Threading.Thread((System.Threading.ThreadStart)delegate {
					while(true) {
						Thread.Sleep(r.Next()&63);
						using(var f=UIFont.FromName("Helvetica", 20.0f)) {
							Thread.Sleep(100);
							Console.WriteLine(f.Ascender);
						}
					}
				}).Start();
			}


simulator output:
14-12-19 06:28:00.789 uifont_test[1622:1470441] NaN
2014-12-19 06:28:00.794 uifont_test[1622:1470438] NaN
2014-12-19 06:28:00.809 uifont_test[1622:1470444] NaN
2014-12-19 06:28:00.816 uifont_test[1622:1470435] NaN
2014-12-19 06:28:00.826 uifont_test[1622:1470443] NaN
2014-12-19 06:28:00.880 uifont_test[1622:1470440] 18.400390625
2014-12-19 06:28:00.890 uifont_test[1622:1470442] NaN
2014-12-19 06:28:00.895 uifont_test[1622:1470439] NaN
2014-12-19 06:28:00.901 uifont_test[1622:1470437] NaN
2014-12-19 06:28:00.922 uifont_test[1622:1470444] NaN
2014-12-19 06:28:00.935 uifont_test[1622:1470438] NaN


device output:
2014-12-19 06:28:47.465 uifont_test[2224:821416] 0
2014-12-19 06:28:47.478 uifont_test[2224:821417] 0
2014-12-19 06:28:47.482 uifont_test[2224:821409] 0
2014-12-19 06:28:47.498 uifont_test[2224:821413] 0
2014-12-19 06:28:47.504 uifont_test[2224:821415] 0
2014-12-19 06:28:47.568 uifont_test[2224:821412] 18,400390625
2014-12-19 06:28:47.575 uifont_test[2224:821411] 0
2014-12-19 06:28:47.583 uifont_test[2224:821418] 0
2014-12-19 06:28:47.585 uifont_test[2224:821410] 0
2014-12-19 06:28:47.587 uifont_test[2224:821414] 0

===========================================================

Xamarin Studio
Version 5.5.4 (build 15)
Installation UUID: 2a46676f-5eb8-4569-9aa7-630cd15bdfc2
Runtime:
	Mono 3.10.0 ((detached/92c4884)
	GTK+ 2.24.23 (Raleigh theme)

	Package version: 310000031


Apple Developer Tools
Xcode 6.1.1 (6611)
Build 6A2008a

Xamarin.iOS
Version: 8.4.0.47 (Business Edition)
Hash: 7244769
Branch: 
Build date: 2014-12-11 14:54:30-0500

Operating System
Mac OS X 10.10.1
Darwin macbook.home 14.0.0 Darwin Kernel Version 14.0.0
    Fri Sep 19 00:26:44 PDT 2014
    root:xnu-2782.1.97~2/RELEASE_X86_64 x86_64
Comment 3 renan jegouzo 2014-12-19 00:53:12 UTC
Created attachment 9137 [details]
test project files
Comment 4 Udham Singh 2014-12-19 04:13:14 UTC
I have checked this issue with sample app attached in comment 3 and getting same output provided in comment 2.

Screencast : http://www.screencast.com/t/s6dol3173oO

Application Out with Device : https://gist.github.com/Udham1/45f2848c40a44734fb32
Application Out with Simulator : https://gist.github.com/Udham1/038d2e55ff33df036aed

Ide Log : https://gist.github.com/Udham1/2d228d91787b3457fa1f


Environment Info :

=== Xamarin Studio ===

Version 5.5.4 (build 15)
Installation UUID: ce927b2a-2c07-44c5-b186-09cfdafba6dc
Runtime:
	Mono 3.12.0 ((detached/a813491)
	GTK+ 2.24.23 (Raleigh theme)

	Package version: 312000068

=== Apple Developer Tools ===

Xcode 6.1 (6602)
Build 6A1052c

=== Xamarin.Mac ===

Version: 1.11.3.0 (Enterprise Edition)

=== Xamarin.iOS ===

Version: 8.4.0.47 (Enterprise Edition)
Hash: 7244769
Branch: 
Build date: 2014-12-11 14:54:30-0500

=== Build Information ===

Release ID: 505040015
Git revision: f93940a35458a18052f1a25e106e62ca970d9c40
Build date: 2014-11-19 15:32:41-05
Xamarin addins: dc23cbd91a3a0e1d326328e1229e86c942a49ec8

=== Operating System ===

Mac OS X 10.9.4
Darwin Xamarin76s-Mac-mini.local 13.3.0 Darwin Kernel Version 13.3.0
    Tue Jun  3 21:27:35 PDT 2014
    root:xnu-2422.110.17~1/RELEASE_X86_64 x86_64
Comment 5 Rolf Bjarne Kvinge [MSFT] 2014-12-19 04:44:46 UTC
The problem here is that UIFont.FromName("Helvetica", 20.0f) returns the same managed instance every time it's called (because iOS returns the same native object).

So what happens is that all the threads will call that method, and get the same managed UIFont instance. Then one thread will be the first to dispose it (which will deconnect the native and managed instance, basically setting the Handle property to IntPtr.Zero), and all the subsequent uses in other threads will see a disposed UIFont.

I'm not entirely sure how we can fix this in a sane way.
Comment 6 Sebastien Pouliot 2014-12-19 09:40:24 UTC
The quick workaround is not to call Dispose (or use "using") on the UIFont, i.e. from

                       using(var f=UIFont.FromName("Helvetica", 20.0f)) {
                            Thread.Sleep(100);
                            Console.WriteLine(f.Ascender);
                        }

to

                       var f=UIFont.FromName("Helvetica", 20.0f);
                       Thread.Sleep(100);
                       Console.WriteLine(f.Ascender);

Like Rolf said since the same (native) instance is always returned so the same managed instance will also be reused. IOW there's no memory penalty (and a small performance gain) not to call dispose in this specific case.

Since you avoid the call to Dispose the Handle will not be set to Zero, to this avoid the race where this same instance could be returned again (with an invalid handle).
Comment 8 renan jegouzo 2014-12-22 08:46:07 UTC
for now, I've overloaded with  a ref counter

static ... refCount=new Dictionnary<UIfont,int> ();
Comment 9 renan jegouzo 2014-12-25 04:54:46 UTC
I'm wondering, the destructor doesn't call dispose by itself ?
So the problem should stay, when the font is released even when avoiding to call Dispose()

In all cases,all that is not clean at all, I'm trying to use NSAttributedString with lots of different fonts, in background rendering... and it's nearly impossible without a clean UIFont object.
Comment 10 Rolf Bjarne Kvinge [MSFT] 2015-01-05 03:47:33 UTC
A better workaround is to cache all the UIFont objects statically in a hash table, and use those objects in all background threads.
Comment 11 renan jegouzo 2015-01-05 04:02:54 UTC
it's what I tried, but not possible when I hit this kind of code:

public override void Apply(NSMutableAttributedString str, int start, int len)
			{
				var range=new NSRange(start,len);
				if(attribute.HasFlag(Attribute.Underline)) 
					str.AddAttribute(UIStringAttributeKey.UnderlineStyle,new NSNumber((int)NSUnderlineStyle.Single),range);
				if(attribute.HasFlag(Attribute.Italic)||attribute.HasFlag(Attribute.Bold)) {
					NSRange r;
					var f=(UIFont)str.GetAttribute(UIStringAttributeKey.Font,start,out r);
					var d=f.FontDescriptor;
					var n=d.SymbolicTraits;
					if(attribute.HasFlag(Attribute.Italic))
						n|=UIFontDescriptorSymbolicTraits.Italic;
					if(attribute.HasFlag(Attribute.Bold))
						n|=UIFontDescriptorSymbolicTraits.Bold;
					var nd=d.CreateWithTraits(n);
					var nf=UIFont.FromDescriptor(nd,f.PointSize);
					if(nf!=null) 
						str.AddAttribute(UIStringAttributeKey.Font,nf,range);
					else  
						Debug.Print("font not found "+n.ToString()+" with "+nd.ToString()+"  trait");
				}

and you must keep in mind that a font, is real huge object that eats lots of memory.
Comment 12 Rolf Bjarne Kvinge [MSFT] 2015-01-05 07:58:00 UTC
I guess your approach from comment 8 would be just as good as any other solution then.
Comment 13 renan jegouzo 2015-01-05 09:03:09 UTC
the (8) and hash table are exactly the same thing. without a clean xamarin code, it's just impossible to really use UIFont that's all. 
what I need it's to implement myself a c# wrapper for UIFont, if you let crap code like this in your version.
Comment 14 renan jegouzo 2015-01-05 09:04:56 UTC
that's means a wrapper for all function that use UIFont too, to never use your unbelievable code.
Comment 15 Rolf Bjarne Kvinge [MSFT] 2015-01-06 05:42:44 UTC
Try using this: https://gist.github.com/rolfbjarne/9637a5a3892c5326314c

This approach is using a dictionary to keep a reference count of the UIFonts in use. Call FontTable.Add to add something to the dictionary, and dispose the returned value when done with the object.
Comment 16 Miguel de Icaza [MSFT] 2015-01-12 12:31:52 UTC
To augment the discussion.

The issue here is that UIFont.FromName is not creating a new object every time it is invoke with the same arguments, it will instead return a cached instance.

Xamarin tracking system: https://trello.com/c/wKZyugio/437-many-managed-peers-on-a-single-native-instance

What we can do is treat UIFont.FromName specially so that it returns a new instance as opposed to sharing the same managed instance and provide the '==' operator for this case.
Comment 17 Sebastien Pouliot 2015-02-27 13:25:42 UTC
Fixed in maccore/master 657793abcb5d62f2e13819ce7d7f0e9a392190c6

QA: unit tests added in the same revision
Comment 18 Mark Woollard 2015-07-01 02:19:33 UTC
From the above thread it would seem that the issue is addressed for UIFont.FromName, however I am seeing the same issue with system fonts (UIFont.SystemFontOfSize etc). Using dictionary to map UIFont.Handle to usage count addresses the issue in our specific case but thought I'd raise here as behaviour should really be consistent regardless of how a font is obtained.
Comment 19 Rolf Bjarne Kvinge [MSFT] 2015-07-01 03:15:50 UTC
@Mark, we fixed this for all the UIFont API, not only FromName.
Comment 20 Mark Woollard 2015-07-01 04:02:44 UTC
Can you confirm which Mono release? We are currently having to stick with 3.12.1 due to 4.0.1.44 breaking our unit tests (crash using MOQ package - which I believe is fixed but not in stable release yet). Thanks
Comment 21 Rolf Bjarne Kvinge [MSFT] 2015-07-01 05:18:43 UTC
@Mark, this is fixed in Xamarin.iOS (not Mono) 8.10 (which is the current Stable version now).