Merge pull request #901 from Blewzman/FixAggregateExceptionGetBaseException
[mono.git] / mcs / class / System / System.Net / WebConnectionGroup.cs
index 8f0c5aa19499aadb47b382771922ee0012e76940..0348aeebe4213ab89381860ab52ef1a0a83230b1 100644 (file)
@@ -3,8 +3,10 @@
 //
 // Authors:
 //     Gonzalo Paniagua Javier (gonzalo@ximian.com)
+//      Martin Baulig (martin.baulig@xamarin.com)
 //
 // (C) 2003 Ximian, Inc (http://www.ximian.com)
+// Copyright 2011-2014 Xamarin, Inc (http://www.xamarin.com)
 //
 
 //
 //
 
 using System;
+using System.Threading;
 using System.Collections;
+using System.Collections.Generic;
 using System.Net.Configuration;
 using System.Net.Sockets;
+using System.Diagnostics;
 
 namespace System.Net
 {
@@ -39,67 +44,48 @@ namespace System.Net
        {
                ServicePoint sPoint;
                string name;
-               ArrayList connections;
-               Random rnd;
+               LinkedList<ConnectionState> connections;
                Queue queue;
+               bool closing;
 
                public WebConnectionGroup (ServicePoint sPoint, string name)
                {
                        this.sPoint = sPoint;
                        this.name = name;
-                       connections = new ArrayList (1);
+                       connections = new LinkedList<ConnectionState> ();
                        queue = new Queue ();
                }
 
+               public event EventHandler ConnectionClosed;
+
+               void OnConnectionClosed ()
+               {
+                       if (ConnectionClosed != null)
+                               ConnectionClosed (this, null);
+               }
+
                public void Close ()
                {
                        //TODO: what do we do with the queue? Empty it out and abort the requests?
                        //TODO: abort requests or wait for them to finish
-                       lock (connections) {
-                               WeakReference cncRef = null;
-
-                               int end = connections.Count;
-                               // ArrayList removed = null;
-                               for (int i = 0; i < end; i++) {
-                                       cncRef = (WeakReference) connections [i];
-                                       WebConnection cnc = cncRef.Target as WebConnection;
-                                       if (cnc != null) {
-                                               cnc.Close (false);
-                                       }
+                       lock (sPoint) {
+                               closing = true;
+                               foreach (var cnc in connections) {
+                                       if (cnc.Connection == null)
+                                               continue;
+                                       cnc.Connection.Close (false);
+                                       cnc.Connection = null;
+                                       OnConnectionClosed ();
                                }
                                connections.Clear ();
                        }
                }
 
-               public WebConnection GetConnection (HttpWebRequest request)
+               public WebConnection GetConnection (HttpWebRequest request, out bool created)
                {
-                       WebConnection cnc = null;
-                       lock (connections) {
-                               WeakReference cncRef = null;
-
-                               // Remove disposed connections
-                               int end = connections.Count;
-                               ArrayList removed = null;
-                               for (int i = 0; i < end; i++) {
-                                       cncRef = (WeakReference) connections [i];
-                                       cnc = cncRef.Target as WebConnection;
-                                       if (cnc == null) {
-                                               if (removed == null)
-                                                       removed = new ArrayList (1);
-
-                                               removed.Add (i);
-                                       }
-                               }
-
-                               if (removed != null) {
-                                       for (int i = removed.Count - 1; i >= 0; i--)
-                                               connections.RemoveAt ((int) removed [i]);
-                               }
-
-                               cnc = CreateOrReuseConnection (request);
+                       lock (sPoint) {
+                               return CreateOrReuseConnection (request, out created);
                        }
-
-                       return cnc;
                }
 
                static void PrepareSharingNtlm (WebConnection cnc, HttpWebRequest request)
@@ -109,66 +95,63 @@ namespace System.Net
 
                        bool needs_reset = false;
                        NetworkCredential cnc_cred = cnc.NtlmCredential;
-                       NetworkCredential req_cred = request.Credentials.GetCredential (request.RequestUri, "NTLM");
-                       if (cnc_cred.Domain != req_cred.Domain || cnc_cred.UserName != req_cred.UserName ||
+
+                       bool isProxy = (request.Proxy != null && !request.Proxy.IsBypassed (request.RequestUri));
+                       ICredentials req_icreds = (!isProxy) ? request.Credentials : request.Proxy.Credentials;
+                       NetworkCredential req_cred = (req_icreds != null) ? req_icreds.GetCredential (request.RequestUri, "NTLM") : null;
+
+                       if (cnc_cred == null || req_cred == null ||
+                               cnc_cred.Domain != req_cred.Domain || cnc_cred.UserName != req_cred.UserName ||
                                cnc_cred.Password != req_cred.Password) {
                                needs_reset = true;
                        }
-#if NET_1_1
+
                        if (!needs_reset) {
                                bool req_sharing = request.UnsafeAuthenticatedConnectionSharing;
                                bool cnc_sharing = cnc.UnsafeAuthenticatedConnectionSharing;
                                needs_reset = (req_sharing == false || req_sharing != cnc_sharing);
                        }
-#endif
                        if (needs_reset) {
                                cnc.Close (false); // closes the authenticated connection
                                cnc.ResetNtlm ();
                        }
                }
 
-               WebConnection CreateOrReuseConnection (HttpWebRequest request)
+               ConnectionState FindIdleConnection ()
                {
-                       // lock is up there.
-                       WebConnection cnc;
-                       WeakReference cncRef;
-
-                       int count = connections.Count;
-                       for (int i = 0; i < count; i++) {
-                               WeakReference wr = connections [i] as WeakReference;
-                               cnc = wr.Target as WebConnection;
-                               if (cnc == null) {
-                                       connections.RemoveAt (i);
-                                       count--;
-                                       i--;
+                       foreach (var cnc in connections) {
+                               if (cnc.Busy  || cnc.Connection == null)
                                        continue;
-                               }
 
-                               if (cnc.Busy)
-                                       continue;
-
-                               PrepareSharingNtlm (cnc, request);
+                               connections.Remove (cnc);
+                               connections.AddFirst (cnc);
                                return cnc;
                        }
 
-                       if (sPoint.ConnectionLimit > count) {
-                               cnc = new WebConnection (this, sPoint);
-                               connections.Add (new WeakReference (cnc));
-                               return cnc;
-                       }
+                       return null;
+               }
 
-                       if (rnd == null)
-                               rnd = new Random ();
+               WebConnection CreateOrReuseConnection (HttpWebRequest request, out bool created)
+               {
+                       var cnc = FindIdleConnection ();
+                       if (cnc != null) {
+                               created = false;
+                               PrepareSharingNtlm (cnc.Connection, request);
+                               return cnc.Connection;
+                       }
 
-                       int idx = (count > 1) ? rnd.Next (0, count - 1) : 0;
-                       cncRef = (WeakReference) connections [idx];
-                       cnc = cncRef.Target as WebConnection;
-                       if (cnc == null) {
-                               cnc = new WebConnection (this, sPoint);
-                               connections.RemoveAt (idx);
-                               connections.Add (new WeakReference (cnc));
+                       if (sPoint.ConnectionLimit > connections.Count) {
+                               created = true;
+                               cnc = new ConnectionState (this);
+                               connections.AddFirst (cnc);
+                               return cnc.Connection;
                        }
-                       return cnc;
+
+                       created = false;
+                       cnc = connections.Last.Value;
+                       connections.Remove (cnc);
+                       connections.AddFirst (cnc);
+                       return cnc.Connection;
                }
 
                public string Name {
@@ -178,6 +161,117 @@ namespace System.Net
                internal Queue Queue {
                        get { return queue; }
                }
+
+               internal bool TryRecycle (TimeSpan maxIdleTime, ref DateTime idleSince)
+               {
+                       var now = DateTime.UtcNow;
+
+               again:
+                       bool recycled;
+                       List<WebConnection> connectionsToClose = null;
+
+                       lock (sPoint) {
+                               if (closing) {
+                                       idleSince = DateTime.MinValue;
+                                       return true;
+                               }
+
+                               int count = 0;
+                               for (var node = connections.First; node != null; node = node.Next) {
+                                       var cnc = node.Value;
+
+                                       if (cnc.Connection == null) {
+                                               connections.Remove (node);
+                                               OnConnectionClosed ();
+                                               continue;
+                                       }
+
+                                       ++count;
+                                       if (cnc.Busy)
+                                               continue;
+
+                                       if (count <= sPoint.ConnectionLimit && now - cnc.IdleSince < maxIdleTime) {
+                                               if (cnc.IdleSince > idleSince)
+                                                       idleSince = cnc.IdleSince;
+                                               continue;
+                                       }
+
+                                       /*
+                                        * Do not call WebConnection.Close() while holding the ServicePoint lock
+                                        * as this could deadlock when attempting to take the WebConnection lock.
+                                        * 
+                                        */
+
+                                       if (connectionsToClose == null)
+                                               connectionsToClose = new List<WebConnection> ();
+                                       connectionsToClose.Add (cnc.Connection);
+                                       cnc.Connection = null;
+                               }
+
+                               recycled = connections.Count == 0;
+                       }
+
+                       // Did we find anything that can be closed?
+                       if (connectionsToClose == null)
+                               return recycled;
+
+                       // Ok, let's get rid of these!
+                       foreach (var cnc in connectionsToClose)
+                               cnc.Close (false);
+
+                       // Re-take the lock, then remove them from the connection list.
+                       goto again;
+               }
+
+               class ConnectionState : IWebConnectionState {
+                       public WebConnection Connection;
+
+                       public WebConnectionGroup Group {
+                               get;
+                               private set;
+                       }
+
+                       public ServicePoint ServicePoint {
+                               get { return Group.sPoint; }
+                       }
+
+                       bool busy;
+                       DateTime idleSince;
+
+                       public bool Busy {
+                               get { return busy; }
+                       }
+
+                       public DateTime IdleSince {
+                               get { return idleSince; }
+                       }
+
+                       public bool TrySetBusy ()
+                       {
+                               lock (ServicePoint) {
+                                       if (busy)
+                                               return false;
+                                       busy = true;
+                                       idleSince = DateTime.UtcNow + TimeSpan.FromDays (3650);
+                                       return true;
+                               }
+                       }
+
+                       public void SetIdle ()
+                       {
+                               lock (ServicePoint) {
+                                       busy = false;
+                                       idleSince = DateTime.UtcNow;
+                               }
+                       }
+
+                       public ConnectionState (WebConnectionGroup group)
+                       {
+                               Group = group;
+                               idleSince = DateTime.UtcNow;
+                               Connection = new WebConnection (this, group.sPoint);
+                       }
+               }
                
        }
 }