Fix GC issue on CoreCLR where finalized objects are returned by Runtime.GetNSObject (#14070)
Fixes race condition where finalized objects are resurrected by the finalizer and for a brief moment `Runtime.TryGetNSObject`/`Runtime.GetNSObject` would return them even though a new instance is supposed to be used. In multi-threaded scenarios this would result in reused objects (same native handle) to suddenly become invalid when the UI thread processes the delayed finalization. Fixes #13921
This commit is contained in:
Родитель
5eda463eaf
Коммит
0622ae4af2
|
@ -932,6 +932,20 @@ namespace Foundation {
|
|||
if (disposing) {
|
||||
ReleaseManagedRef ();
|
||||
} else {
|
||||
#if NET
|
||||
// By adding an external reference to the object from finalizer we will
|
||||
// resurrect it. Since Runtime class tracks the NSObject instances with
|
||||
// GCHandle(..., WeakTrackResurrection) we need to make sure it's aware
|
||||
// that the object was finalized.
|
||||
//
|
||||
// On CoreCLR the non-tracked objects don't get a callback from the
|
||||
// garbage collector when they enter the finalization queue but the
|
||||
// information is necessary for Runtime.TryGetNSObject to work correctly.
|
||||
// Since we are on the finalizer thread now we can just set the flag
|
||||
// directly here.
|
||||
actual_flags |= Flags.InFinalizerQueue;
|
||||
#endif
|
||||
|
||||
NSObject_Disposer.Add (this);
|
||||
}
|
||||
} else {
|
||||
|
|
|
@ -0,0 +1,64 @@
|
|||
#if __MACOS__
|
||||
using System;
|
||||
using System.Threading;
|
||||
using NUnit.Framework;
|
||||
|
||||
using AppKit;
|
||||
using ObjCRuntime;
|
||||
using Foundation;
|
||||
|
||||
namespace Xamarin.Mac.Tests
|
||||
{
|
||||
[TestFixture]
|
||||
[Preserve (AllMembers = true)]
|
||||
public class NSObjectGCTest
|
||||
{
|
||||
[Test]
|
||||
public void NSObject_GCResurrectTest ()
|
||||
{
|
||||
// Run GC and pump run loop to ensure that NSObject_Disposer has processed everything
|
||||
GC.Collect ();
|
||||
GC.WaitForPendingFinalizers ();
|
||||
PumpLoop ();
|
||||
|
||||
// Create a single object and immediately lose a reference to it, save the handle
|
||||
// to immitate getting the same handle from a native API at later point in time
|
||||
IntPtr nativeHandle = CreateNativeObject ();
|
||||
|
||||
// Ensure that the GC is run and the one new object gets collected
|
||||
GC.Collect ();
|
||||
GC.WaitForPendingFinalizers ();
|
||||
|
||||
// The object should now be resurrected through `NSObject_Disposer.Add(this)`
|
||||
// and so `GCHandle` will return its reference:
|
||||
var o = ObjCRuntime.Runtime.GetNSObject<NSString> (nativeHandle);
|
||||
|
||||
Assert.AreNotEqual (IntPtr.Zero, (IntPtr) o.Handle);
|
||||
|
||||
// Pump the run loop and thus drain the NSObject_Disposer
|
||||
PumpLoop ();
|
||||
|
||||
// The object is invalid now
|
||||
Assert.AreNotEqual (IntPtr.Zero, (IntPtr) o.Handle);
|
||||
}
|
||||
|
||||
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)]
|
||||
private static IntPtr CreateNativeObject ()
|
||||
{
|
||||
var foo = new NSString ("foo");
|
||||
foo.DangerousRetain();
|
||||
return foo.Handle;
|
||||
}
|
||||
|
||||
private static void PumpLoop ()
|
||||
{
|
||||
using (new NSAutoreleasePool ()) {
|
||||
NSEvent? evnt;
|
||||
while ((evnt = NSApplication.SharedApplication.NextEvent ((NSEventMask)NSEventMask.AnyEvent, NSDate.DistantPast, NSRunLoopMode.Default, true)) != null) {
|
||||
NSApplication.SharedApplication.SendEvent (evnt);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif // __MACOS__
|
Загрузка…
Ссылка в новой задаче