diff --git a/src/SystemConfiguration/NetworkReachability.cs b/src/SystemConfiguration/NetworkReachability.cs index 3fd3a4ad48f5..67d5b84d621f 100644 --- a/src/SystemConfiguration/NetworkReachability.cs +++ b/src/SystemConfiguration/NetworkReachability.cs @@ -523,12 +523,18 @@ public StatusCode SetNotification (Notification? callback) unsafe { rv = SCNetworkReachabilitySetCallback (Handle, &Callback, &ctx) != 0; } + if (!rv) { + gch.Free (); + return StatusCodeError.SCError (); + } } else { if (callback is null) { this.notification = null; unsafe { rv = SCNetworkReachabilitySetCallback (Handle, null, null) != 0; } + if (gch.IsAllocated) + gch.Free (); if (!rv) return StatusCodeError.SCError (); @@ -678,5 +684,12 @@ public bool SetDispatchQueue (DispatchQueue? queue) GC.KeepAlive (queue); return result; } + + protected override void Dispose (bool disposing) + { + if (gch.IsAllocated) + gch.Free (); + base.Dispose (disposing); + } } } diff --git a/tests/cecil-tests/Documentation.KnownFailures.txt b/tests/cecil-tests/Documentation.KnownFailures.txt index 97f6b0bd7cf5..4ea9a35e62de 100644 --- a/tests/cecil-tests/Documentation.KnownFailures.txt +++ b/tests/cecil-tests/Documentation.KnownFailures.txt @@ -16350,6 +16350,7 @@ M:StoreKit.SKStoreProductViewController.LoadProduct(StoreKit.StoreProductParamet M:StoreKit.SKStoreProductViewController.LoadProductAsync(Foundation.NSDictionary,StoreKit.SKAdImpression) M:StoreKit.SKStoreProductViewController.LoadProductAsync(StoreKit.StoreProductParameters,StoreKit.SKAdImpression) M:StoreKit.SKStoreProductViewController.remove_Finished(System.EventHandler) +M:SystemConfiguration.NetworkReachability.Dispose(System.Boolean) M:ThreadNetwork.THClient.CheckPreferredNetworkAsync(Foundation.NSData) M:ThreadNetwork.THClient.DeleteCredentialsForBorderAgentAsync(Foundation.NSData) M:ThreadNetwork.THClient.IsPreferredNetworkAvailableAsync diff --git a/tests/monotouch-test/SystemConfiguration/NetworkReachabilityTest.cs b/tests/monotouch-test/SystemConfiguration/NetworkReachabilityTest.cs index 368f42839548..e4da98fe4040 100644 --- a/tests/monotouch-test/SystemConfiguration/NetworkReachabilityTest.cs +++ b/tests/monotouch-test/SystemConfiguration/NetworkReachabilityTest.cs @@ -12,6 +12,8 @@ #endif using SystemConfiguration; using System.Net; +using System.Threading; +using System.Threading.Tasks; namespace MonoTouchFixtures.SystemConfiguration { @@ -142,5 +144,114 @@ public void Schedule () Assert.IsTrue (defaultRouteReachability.Schedule (CFRunLoop.Main, CFRunLoop.ModeDefault), "Schedule"); Assert.IsTrue (defaultRouteReachability.Unschedule (CFRunLoop.Main, CFRunLoop.ModeDefault), "Unschedule"); } + + [Test] + public void SetNotification () + { + var ip = new IPAddress (0); + using var reachability = new NetworkReachability (ip); + + // Test setting a notification + var statusCode = reachability.SetNotification ((flags) => { + }); + Assert.AreEqual (StatusCode.OK, statusCode, "SetNotification should succeed"); + + // Test clearing the notification (this should free the GCHandle) + statusCode = reachability.SetNotification (null); + Assert.AreEqual (StatusCode.OK, statusCode, "SetNotification(null) should succeed"); + + // Test setting notification again after clearing + statusCode = reachability.SetNotification ((flags) => { + }); + Assert.AreEqual (StatusCode.OK, statusCode, "SetNotification should succeed again"); + + // Test that disposing also works (should free the GCHandle in Dispose) + } + + [Test] + public void SetNotification_GCHandleFreed () + { + // Create weak references to track GC collection + var weakRefs = new WeakReference [10]; + + // Create NetworkReachability instances on a background thread + var thread = new Thread (() => { + for (int i = 0; i < 10; i++) { + var ip = new IPAddress (0); + var reachability = new NetworkReachability (ip); + + // Set a notification to allocate the GCHandle + reachability.SetNotification ((flags) => { + }); + + // Store weak reference to track if object is collected + weakRefs [i] = new WeakReference (reachability); + + // Dispose to ensure GCHandle is freed + reachability.Dispose (); + } + }); + + thread.Start (); + thread.Join (); + + // Force garbage collection + GC.Collect (); + GC.WaitForPendingFinalizers (); + GC.Collect (); + + // Assert that at least one NetworkReachability instance has been collected + var collectedCount = 0; + for (int i = 0; i < weakRefs.Length; i++) { + if (!weakRefs [i].IsAlive) { + collectedCount++; + } + } + + Assert.IsTrue (collectedCount > 0, $"Expected at least one NetworkReachability instance to be collected, but {collectedCount} were collected"); + } + + [Test] + public void SetNotification_GCHandleFreedWithNull () + { + // Create weak references to track GC collection + var weakRefs = new WeakReference [10]; + + // Create NetworkReachability instances on a background thread + var thread = new Thread (() => { + for (int i = 0; i < 10; i++) { + var ip = new IPAddress (0); + var reachability = new NetworkReachability (ip); + + // Set a notification to allocate the GCHandle + reachability.SetNotification ((flags) => { + }); + + // Clear notification to free the GCHandle + reachability.SetNotification (null); + + // Store weak reference to track if object is collected + weakRefs [i] = new WeakReference (reachability); + } + }); + + thread.Start (); + thread.Join (); + + // Force garbage collection + GC.Collect (); + GC.WaitForPendingFinalizers (); + GC.Collect (); + + // Assert that at least one NetworkReachability instance has been collected + var collectedCount = 0; + for (int i = 0; i < weakRefs.Length; i++) { + if (!weakRefs [i].IsAlive) { + collectedCount++; + } + } + + Assert.IsTrue (collectedCount > 0, $"Expected at least one NetworkReachability instance to be collected, but {collectedCount} were collected"); + } } }