From aef030024204e416af0d482313359c0aacf4ac52 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Mon, 8 Dec 2025 12:21:49 +0100 Subject: [PATCH 1/4] [Foundation] Ensure NSUrlSessionHandler's session is cleaned up on dispose. Fixes #24376. Fixes https://github.com/dotnet/macios/issues/24376 Doc references: - https://developer.apple.com/documentation/foundation/urlsession/invalidateandcancel() - https://developer.apple.com/documentation/foundation/urlsession/finishtasksandinvalidate() --- .../NSUrlSessionHandlerTest.cs | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 tests/monotouch-test/System.Net.Http/NSUrlSessionHandlerTest.cs diff --git a/tests/monotouch-test/System.Net.Http/NSUrlSessionHandlerTest.cs b/tests/monotouch-test/System.Net.Http/NSUrlSessionHandlerTest.cs new file mode 100644 index 000000000000..8a255351923f --- /dev/null +++ b/tests/monotouch-test/System.Net.Http/NSUrlSessionHandlerTest.cs @@ -0,0 +1,59 @@ +// +// NSUrlSessionHandlerTest.cs +// + +using System; +using System.Net.Http; +using System.Threading.Tasks; +using Foundation; +using NUnit.Framework; +using Xamarin.Utils; + +namespace MonoTests.System.Net.Http { + [TestFixture] + [Preserve (AllMembers = true)] + public class NSUrlSessionHandlerTest { + + // https://github.com/dotnet/macios/issues/24376 + [Test] + public void DisposeAndRecreateBackgroundSessionHandler () + { + bool firstRequestSucceeded = false; + + // First request - should succeed + var done = TestRuntime.TryRunAsync (TimeSpan.FromSeconds (30), async () => { + using (var handler = new NSUrlSessionHandler (NSUrlSessionConfiguration.CreateBackgroundSessionConfiguration ("test-id"))) { + using (var client = new HttpClient (handler)) { + var response = await client.GetByteArrayAsync (NetworkResources.MicrosoftUrl); + Assert.IsNotNull (response, "First request response"); + Assert.IsTrue (response.Length > 0, "First request response length"); + firstRequestSucceeded = true; + } + } + }, out var ex); + + if (!done || !firstRequestSucceeded) { + Assert.Inconclusive ("First request failed or timed out - cannot verify the bug."); + } + + Assert.IsNull (ex, "First request exception"); + + // Second request with new handler using same background session ID - should not timeout + done = TestRuntime.TryRunAsync (TimeSpan.FromSeconds (30), async () => { + using (var handler = new NSUrlSessionHandler (NSUrlSessionConfiguration.CreateBackgroundSessionConfiguration ("test-id"))) { + using (var client = new HttpClient (handler)) { + var response = await client.GetByteArrayAsync (NetworkResources.MicrosoftUrl); + Assert.IsNotNull (response, "Second request response"); + Assert.IsTrue (response.Length > 0, "Second request response length"); + } + } + }, out ex); + + if (!done) { + Assert.Fail ("Second request timedout - this indicates the bug is present."); + } + + Assert.IsNull (ex, "Second request exception"); + } + } +} From 7f7777a4331dc77c0d67b88748f5223066430c31 Mon Sep 17 00:00:00 2001 From: Morten Nielsen Date: Fri, 5 Dec 2025 10:02:52 -0800 Subject: [PATCH 2/4] Ensure UrlSession is cleaned up on dispose --- src/Foundation/NSUrlSessionHandler.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Foundation/NSUrlSessionHandler.cs b/src/Foundation/NSUrlSessionHandler.cs index 589c52efd8cd..fcc432dbbd59 100644 --- a/src/Foundation/NSUrlSessionHandler.cs +++ b/src/Foundation/NSUrlSessionHandler.cs @@ -293,6 +293,7 @@ protected override void Dispose (bool disposing) inflightRequests.Clear (); } + session.InvalidateAndCancel (); base.Dispose (disposing); } @@ -449,6 +450,7 @@ public bool UseCookies { configuration.HttpCookieStorage = null; } session = NSUrlSession.FromConfiguration (configuration, (INSUrlSessionDelegate) new NSUrlSessionHandlerDelegate (this), null); + oldSession.FinishTasksAndInvalidate (); oldSession.Dispose (); } } From a4d2e38c16d15fd278cd05491183b9699c279d3a Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Mon, 8 Dec 2025 12:22:34 +0100 Subject: [PATCH 3/4] I don't think this is needed. --- src/Foundation/NSUrlSessionHandler.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Foundation/NSUrlSessionHandler.cs b/src/Foundation/NSUrlSessionHandler.cs index fcc432dbbd59..c12d535b6ce5 100644 --- a/src/Foundation/NSUrlSessionHandler.cs +++ b/src/Foundation/NSUrlSessionHandler.cs @@ -450,7 +450,6 @@ public bool UseCookies { configuration.HttpCookieStorage = null; } session = NSUrlSession.FromConfiguration (configuration, (INSUrlSessionDelegate) new NSUrlSessionHandlerDelegate (this), null); - oldSession.FinishTasksAndInvalidate (); oldSession.Dispose (); } } From 60a35a08a7f22d9bbde2622920a8dba7ead25126 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Mon, 8 Dec 2025 12:42:03 +0100 Subject: [PATCH 4/4] Improve test. --- .../monotouch-test/System.Net.Http/NSUrlSessionHandlerTest.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/monotouch-test/System.Net.Http/NSUrlSessionHandlerTest.cs b/tests/monotouch-test/System.Net.Http/NSUrlSessionHandlerTest.cs index 8a255351923f..1733f73a7869 100644 --- a/tests/monotouch-test/System.Net.Http/NSUrlSessionHandlerTest.cs +++ b/tests/monotouch-test/System.Net.Http/NSUrlSessionHandlerTest.cs @@ -33,9 +33,11 @@ public void DisposeAndRecreateBackgroundSessionHandler () }, out var ex); if (!done || !firstRequestSucceeded) { + TestRuntime.IgnoreInCI ("Transient network failure - ignore in CI"); Assert.Inconclusive ("First request failed or timed out - cannot verify the bug."); } + TestRuntime.IgnoreInCIIfBadNetwork (ex); Assert.IsNull (ex, "First request exception"); // Second request with new handler using same background session ID - should not timeout @@ -50,9 +52,11 @@ public void DisposeAndRecreateBackgroundSessionHandler () }, out ex); if (!done) { + TestRuntime.IgnoreInCI ("Transient network failure - ignore in CI"); Assert.Fail ("Second request timedout - this indicates the bug is present."); } + TestRuntime.IgnoreInCIIfBadNetwork (ex); Assert.IsNull (ex, "Second request exception"); } }