Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Foundation] Ensure that we do not block when in the background without a background session. #5463

Merged
60 changes: 57 additions & 3 deletions src/Foundation/NSUrlSessionHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@
using nuint = System.UInt32;
#endif

#if !MONOMAC
using UIKit;
#endif

#if SYSTEM_NET_HTTP
namespace System.Net.Http {
#else
Expand Down Expand Up @@ -120,6 +124,10 @@ public partial class NSUrlSessionHandler : HttpMessageHandler
readonly NSUrlSession session;
readonly Dictionary<NSUrlSessionTask, InflightData> inflightRequests;
readonly object inflightRequestsLock = new object ();
#if !MONOMAC && !MONOTOUCH_WATCH
readonly bool isBackgroundSession = false;
NSObject notificationToken; // needed to make sure we do not hang if not using a background session
#endif

static NSUrlSessionConfiguration CreateConfig ()
{
Expand All @@ -142,6 +150,12 @@ public NSUrlSessionHandler (NSUrlSessionConfiguration configuration)
if (configuration == null)
throw new ArgumentNullException (nameof (configuration));

#if !MONOMAC && !MONOTOUCH_WATCH
// if the configuration has an identifier, we are dealing with a background session,
// therefore, we do not have to listen to the notifications.
isBackgroundSession = !string.IsNullOrEmpty (configuration.Identifier);
#endif

AllowAutoRedirect = true;

// we cannot do a bitmask but we can set the minimum based on ServicePointManager.SecurityProtocol minimum
Expand All @@ -159,12 +173,44 @@ public NSUrlSessionHandler (NSUrlSessionConfiguration configuration)
inflightRequests = new Dictionary<NSUrlSessionTask, InflightData> ();
}

#if !MONOMAC && !MONOTOUCH_WATCH

void AddNotification ()
{
if (!isBackgroundSession && notificationToken == null)
notificationToken = NSNotificationCenter.DefaultCenter.AddObserver (UIApplication.WillResignActiveNotification, BackgroundNotificationCb);
}

void RemoveNotification ()
{
if (notificationToken != null) {
NSNotificationCenter.DefaultCenter.RemoveObserver (notificationToken);
mandel-macaque marked this conversation as resolved.
Show resolved Hide resolved
notificationToken = null;
}
}

void BackgroundNotificationCb (NSNotification obj)
{
// we do not need to call the lock, we call cancel on the source, that will trigger all the needed code to
// clean the resources and such
foreach (var r in inflightRequests.Values) {
r.CompletionSource.TrySetCanceled ();
}
}
#endif

public long MaxInputInMemory { get; set; } = long.MaxValue;

void RemoveInflightData (NSUrlSessionTask task, bool cancel = true)
{
lock (inflightRequestsLock)
lock (inflightRequestsLock) {
inflightRequests.Remove (task);
#if !MONOMAC && !MONOTOUCH_WATCH
// do we need to be notified? If we have not inflightData, we do not
if (inflightRequests.Count == 0)
RemoveNotification ();
#endif
}

if (cancel)
task?.Cancel ();
Expand All @@ -174,6 +220,10 @@ void RemoveInflightData (NSUrlSessionTask task, bool cancel = true)

protected override void Dispose (bool disposing)
{
#if !MONOMAC && !MONOTOUCH_WATCH
// remove the notification if present, method checks against null
RemoveNotification ();
#endif
lock (inflightRequestsLock) {
foreach (var pair in inflightRequests) {
pair.Key?.Cancel ();
Expand All @@ -182,7 +232,6 @@ protected override void Dispose (bool disposing)

inflightRequests.Clear ();
}

base.Dispose (disposing);
}

Expand Down Expand Up @@ -254,14 +303,19 @@ protected override async Task<HttpResponseMessage> SendAsync (HttpRequestMessage
tcs.TrySetCanceled ();
});

lock (inflightRequestsLock)
lock (inflightRequestsLock) {
#if !MONOMAC && !MONOTOUCH_WATCH
// Add the notification whenever needed
AddNotification ();
#endif
inflightRequests.Add (dataTask, new InflightData {
RequestUrl = request.RequestUri.AbsoluteUri,
CompletionSource = tcs,
CancellationToken = cancellationToken,
Stream = new NSUrlSessionDataTaskStream (),
Request = request
});
}

if (dataTask.State == NSUrlSessionTaskState.Suspended)
dataTask.Resume ();
Expand Down