Bug 344 - InvalidCastException in NSDate.FromTimeIntervalSinceNow
Summary: InvalidCastException in NSDate.FromTimeIntervalSinceNow
Status: RESOLVED FIXED
Alias: None
Product: MonoMac
Classification: Desktop
Component: Bindings ()
Version: GIT
Hardware: Macintosh Mac OS
: --- normal
Target Milestone: ---
Assignee: Bugzilla
URL:
Depends on:
Blocks:
 
Reported: 2011-08-19 20:12 UTC by Jeremiah Boyle
Modified: 2011-11-02 10:08 UTC (History)
3 users (show)

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


Attachments
demo that reproduces the cast exception (21.68 KB, application/zip)
2011-08-23 00:36 UTC, Jeremiah Boyle
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 Jeremiah Boyle 2011-08-19 20:12:48 UTC
our mac application has suffered from this crash, randomly/occasionally for quite some time. we never reported it because we could not produce a reliable test case. regardless of that fact, i'm reporting it now because i'm banging my head against the wall to no avail.

our code is calling:
  NSDate.FromTimeIntervalSinceNow((double)wait / 1000.0)
where "wait" is some integer greater than zero.

we call this in our ui eventloop (i.e. very often) and only once in a while (like once every few weeks) we get:
  Exception Source:      MonoMac
  Exception Type:        System.InvalidCastException
  Exception Target Site: NSDate.FromTimeIntervalSinceNow
  Exception Message:     Cannot cast from source type to destination type.

the crash happens where the result of Runtime.GetNSObject() is cast to NSDate, as though GetNSObject() is, in some case, returning an object of the wrong type. i can find no documented case where dateWithTimeIntervalSinceNow: returns something other than an NSDate; therefore, the only possibility is that the managed object -> native object caching in Runtime.cs is at fault.

my guess is that the object_map cache in is holding stale values. our occasional crash happens when a newly allocated native NSDate ends up with an address that matches a stale cache item. then the runtime returns the wrong object.

i have a theory but i'm uncertain how to fix. can someone comment / take it from here?
Comment 1 Jeremiah Boyle 2011-08-19 20:50:13 UTC
i'm going to start running with --debug=casts so that next time this occurs we'll see what the source type in the invalid cast is. i'll report back next time i reproduce!
Comment 2 Miguel de Icaza [MSFT] 2011-08-19 21:09:55 UTC
Please provide a test case.

During MonoSpace we theorized that this scenario could happen and would love to see a test case.
Comment 3 Jeremiah Boyle 2011-08-19 22:11:31 UTC
i would love to, miguel, but i'm unable. every test case i've attempted has run for days without crashing.

i've instrumented Runtime.cs to log activity related to the object_map. next time i reproduce i'll report details.
Comment 4 Jeremiah Boyle 2011-08-23 00:35:43 UTC
using --debug=casts and some trace in Runtime.cs i was able to trace the source of the problem to an NSObject subclass in our code. that class is around from back when we used monobjc.

--debug=casts told me that the object that was being cast to an NSDate was actually an instance of our NSObject subclass (let's call it MyObject).

the trace in Runtime.cs showed that when MyObject was deallocated its pointer would be unregistered from the objects_map but then immediately registered again (at the same address). for some reason the managed object was remaining associated with that memory address even though the native object was gone (and that address available for reuse).

i still don't understand why the object was being re-registered, but i found the code that triggers it. MyObject was implementing Dealloc, like so:
        
[Export("dealloc")]
public void Dealloc()
{
    // some code here
    MonoMac.ObjCRuntime.Messaging.void_objc_msgSendSuper(this.SuperHandle, Selector.GetHandle("dealloc"));
}

regardless of what is in the body of Dealloc, just it's presence caused this weird behavior. when i removed it, the weird re-register behavior went away and with it the InvalidCastException.

i replaced the dealloc business with an override of NSObject.Dispose. i believe at the time this code was written monobjc required implementing dealloc to do clean up. i understand that's not the way to do it with monomac, but i am still curious why it causes this odd behavior and whether that behavior is a bug. anyone care to comment?
Comment 5 Jeremiah Boyle 2011-08-23 00:36:45 UTC
Created attachment 166 [details]
demo that reproduces the cast exception
Comment 6 Dan Fry 2011-11-02 05:35:38 UTC
Hi,

I've encountered this bug too, only without using any custom classes. The NSNumber.From*() methods seem to be enough to trigger it. The simplest way to recreate it I've found is to create a console project, add a reference to MonoMac and put the following code in Main():

using (NSAutoreleasePool pool = new NSAutoreleasePool())
{
	NSObject obj = new NSObject();
	NSNumber num = NSNumber.FromInt32 (42);
}

This causes the InvalidCastException every time for me, using MonoDevelop 2.8.1 and the MonoMac build supplied with it on OS X 10.7.2 and Mono 2.10.6.
Comment 7 Dan Fry 2011-11-02 06:53:06 UTC
After some further investigation, I think in my case it's a different bug. The NSNumber class is not added to Class.type_map unless one of its constructors is called. Adding a call to new NSNumber() before the call to NSNumber.FromInt32() prevents the exception.

If the new NSObject() call is omitted, Class.LookUp() enters an infinite loop suggesting that the root cause is the same as that of bug 408.
Comment 8 Miguel de Icaza [MSFT] 2011-11-02 09:13:43 UTC
Dan,

Would you mind opening a separate bug for the NSNumber case, as the original bug reported is a misuse of the MonoMac API?   I am closing this bug due to the original source, while your case is different.

Jeremiah, 

We take over memory management in MonoMac and MonoTouch, which is why hooking up to dealloc is interfering with us.   You *could* go down that route if you wanted to, but it would require you to understand entirely the Runtime.cs and provide all the same code that MonoMac already does for you to avoid stepping on MonoMac's feet.

The right approach as you noted is to override Dispose (bool disposing) which is the right .NET way of doing those things.
Comment 9 Dan Fry 2011-11-02 10:08:27 UTC
I have opened bug 1825 for the problem I was experiencing.