Merge pull request #2092 from kasthack/system-web-stuff-import
[mono.git] / mcs / class / System / System.Net / ServicePoint.cs
index 2045f00e3601a8874413d1f5e61d80a32fc7e818..ebac3e9cf1d2760763fd5753cd4c47763de54c71 100644 (file)
@@ -31,6 +31,8 @@
 //
 
 using System;
+using System.Collections.Generic;
+using System.Diagnostics;
 using System.Collections;
 using System.Net.Sockets;
 using System.Security.Cryptography.X509Certificates;
@@ -45,29 +47,30 @@ namespace System.Net
                int maxIdleTime;
                int currentConnections;
                DateTime idleSince;
+               DateTime lastDnsResolve;
                Version protocolVersion;
                X509Certificate certificate;
                X509Certificate clientCertificate;
                IPHostEntry host;
                bool usesProxy;
-               Hashtable groups;
+               Dictionary<string,WebConnectionGroup> groups;
                bool sendContinue = true;
                bool useConnect;
-               object locker = new object ();
                object hostE = new object ();
                bool useNagle;
                BindIPEndPoint endPointCallback = null;
                bool tcp_keepalive;
                int tcp_keepalive_time;
                int tcp_keepalive_interval;
-               
+               Timer idleTimer;
+
                // Constructors
 
                internal ServicePoint (Uri uri, int connectionLimit, int maxIdleTime)
                {
                        this.uri = uri;  
                        this.connectionLimit = connectionLimit;
-                       this.maxIdleTime = maxIdleTime;                 
+                       this.maxIdleTime = maxIdleTime; 
                        this.currentConnections = 0;
                        this.idleSince = DateTime.UtcNow;
                }
@@ -132,18 +135,19 @@ namespace System.Net
                        get {
                                return idleSince.ToLocalTime ();
                        }
-                       internal set {
-                               lock (locker)
-                                       idleSince = value.ToUniversalTime ();
-                       }
                }
-               
+
                public int MaxIdleTime {
                        get { return maxIdleTime; }
                        set { 
                                if (value < Timeout.Infinite || value > Int32.MaxValue)
                                        throw new ArgumentOutOfRangeException ();
-                               this.maxIdleTime = value; 
+
+                               lock (this) {
+                                       maxIdleTime = value;
+                                       if (idleTimer != null)
+                                               idleTimer.Change (maxIdleTime, maxIdleTime);
+                               }
                        }
                }
                
@@ -237,20 +241,111 @@ namespace System.Net
                        set { useConnect = value; }
                }
 
-               internal bool AvailableForRecycling {
-                       get { 
-                               return CurrentConnections == 0
-                                   && maxIdleTime != Timeout.Infinite
-                                   && DateTime.UtcNow >= IdleSince.AddMilliseconds (maxIdleTime);
+               WebConnectionGroup GetConnectionGroup (string name)
+               {
+                       if (name == null)
+                               name = "";
+
+                       /*
+                        * Optimization:
+                        * 
+                        * In the vast majority of cases, we only have one single WebConnectionGroup per ServicePoint, so we
+                        * don't need to allocate a dictionary.
+                        * 
+                        */
+
+                       WebConnectionGroup group;
+                       if (groups != null && groups.TryGetValue (name, out group))
+                               return group;
+
+                       group = new WebConnectionGroup (this, name);
+                       group.ConnectionClosed += (s, e) => currentConnections--;
+
+                       if (groups == null)
+                               groups = new Dictionary<string, WebConnectionGroup> ();
+                       groups.Add (name, group);
+
+                       return group;
+               }
+
+               void RemoveConnectionGroup (WebConnectionGroup group)
+               {
+                       if (groups == null || groups.Count == 0)
+                               throw new InvalidOperationException ();
+
+                       groups.Remove (group.Name);
+               }
+
+               bool CheckAvailableForRecycling (out DateTime outIdleSince)
+               {
+                       outIdleSince = DateTime.MinValue;
+
+                       TimeSpan idleTimeSpan;
+                       List<WebConnectionGroup> groupList = null, removeList = null;
+                       lock (this) {
+                               if (groups == null || groups.Count == 0) {
+                                       idleSince = DateTime.MinValue;
+                                       return true;
+                               }
+
+                               idleTimeSpan = TimeSpan.FromMilliseconds (maxIdleTime);
+
+                               /*
+                                * WebConnectionGroup.TryRecycle() must run outside the lock, so we need to
+                                * copy the group dictionary if it exists.
+                                * 
+                                * In most cases, we only have a single connection group, so we can simply store
+                                * that in a local variable instead of copying a collection.
+                                * 
+                                */
+
+                               groupList = new List<WebConnectionGroup> (groups.Values);
+                       }
+
+                       foreach (var group in groupList) {
+                               if (!group.TryRecycle (idleTimeSpan, ref outIdleSince))
+                                       continue;
+                               if (removeList == null)
+                                       removeList = new List<WebConnectionGroup> ();
+                               removeList.Add (group);
+                       }
+
+                       lock (this) {
+                               idleSince = outIdleSince;
+
+                               if (removeList != null && groups != null) {
+                                       foreach (var group in removeList)
+                                               if (groups.ContainsKey (group.Name))
+                                                       RemoveConnectionGroup (group);
+                               }
+
+                               if (groups != null && groups.Count == 0)
+                                       groups = null;
+
+                               if (groups == null) {
+                                       if (idleTimer != null) {
+                                               idleTimer.Dispose ();
+                                               idleTimer = null;
+                                       }
+                                       return true;
+                               }
+
+                               return false;
                        }
                }
 
-               internal Hashtable Groups {
-                       get {
-                               if (groups == null)
-                                       groups = new Hashtable ();
+               void IdleTimerCallback (object obj)
+               {
+                       DateTime dummy;
+                       CheckAvailableForRecycling (out dummy);
+               }
 
-                               return groups;
+               private bool HasTimedOut
+               {
+                       get {
+                               int timeout = ServicePointManager.DnsRefreshTimeout;
+                               return timeout != Timeout.Infinite &&
+                                       (lastDnsResolve + TimeSpan.FromMilliseconds (timeout)) < DateTime.UtcNow;
                        }
                }
 
@@ -258,33 +353,17 @@ namespace System.Net
                {
                        get {
                                lock (hostE) {
-                                       if (host != null)
-                                               return host;
-
                                        string uriHost = uri.Host;
 
-                                       // There is no need to do DNS resolution on literal IP addresses
-                                       if (uri.HostNameType == UriHostNameType.IPv6 ||
-                                               uri.HostNameType == UriHostNameType.IPv4) {
+                                       if (host == null || HasTimedOut) {
+                                               lastDnsResolve = DateTime.UtcNow;
 
-                                               if (uri.HostNameType == UriHostNameType.IPv6) {
-                                                       // Remove square brackets
-                                                       uriHost = uriHost.Substring(1,uriHost.Length-2);
+                                               try {
+                                                       host = Dns.GetHostEntry (uriHost);
+                                               }
+                                               catch (Exception) {
+                                                       return null;
                                                }
-
-                                               // Creates IPHostEntry
-                                               host = new IPHostEntry();
-                                               host.AddressList = new IPAddress[] { IPAddress.Parse(uriHost) };
-
-                                               return host;
-                                       }
-
-                                       // Try DNS resolution on host names
-                                       try  {
-                                               host = Dns.GetHostByName (uriHost);
-                                       } 
-                                       catch {
-                                               return null;
                                        }
                                }
 
@@ -297,61 +376,41 @@ namespace System.Net
                        protocolVersion = version;
                }
 
-#if !TARGET_JVM
-               WebConnectionGroup GetConnectionGroup (string name)
-               {
-                       if (name == null)
-                               name = "";
-
-                       WebConnectionGroup group = Groups [name] as WebConnectionGroup;
-                       if (group != null)
-                               return group;
-
-                       group = new WebConnectionGroup (this, name);
-                       Groups [name] = group;
-                       return group;
-               }
-
                internal EventHandler SendRequest (HttpWebRequest request, string groupName)
                {
                        WebConnection cnc;
                        
-                       lock (locker) {
+                       lock (this) {
+                               bool created;
                                WebConnectionGroup cncGroup = GetConnectionGroup (groupName);
-                               cnc = cncGroup.GetConnection (request);
+                               cnc = cncGroup.GetConnection (request, out created);
+                               if (created) {
+                                       ++currentConnections;
+                                       if (idleTimer == null)
+                                               idleTimer = new Timer (IdleTimerCallback, null, maxIdleTime, maxIdleTime);
+                               }
                        }
                        
                        return cnc.SendRequest (request);
                }
-#endif
                public bool CloseConnectionGroup (string connectionGroupName)
                {
-                       lock (locker) {
-                               WebConnectionGroup cncGroup = GetConnectionGroup (connectionGroupName);
+                       WebConnectionGroup cncGroup = null;
+
+                       lock (this) {
+                               cncGroup = GetConnectionGroup (connectionGroupName);
                                if (cncGroup != null) {
-                                       cncGroup.Close ();
-                                       return true;
+                                       RemoveConnectionGroup (cncGroup);
                                }
                        }
 
-                       return false;
-               }
-
-               internal void IncrementConnection ()
-               {
-                       lock (locker) {
-                               currentConnections++;
-                               idleSince = DateTime.UtcNow.AddMilliseconds (1000000);
+                       // WebConnectionGroup.Close() must *not* be called inside the lock
+                       if (cncGroup != null) {
+                               cncGroup.Close ();
+                               return true;
                        }
-               }
 
-               internal void DecrementConnection ()
-               {
-                       lock (locker) {
-                               currentConnections--;
-                               if (currentConnections == 0)
-                                       idleSince = DateTime.UtcNow;
-                       }
+                       return false;
                }
 
                internal void SetCertificates (X509Certificate client, X509Certificate server)