Merge pull request #901 from Blewzman/FixAggregateExceptionGetBaseException
[mono.git] / mcs / class / System / System.Net / ServicePoint.cs
index f9bc2a7f148acaecabc39d65884e50fec3b11a12..663816eb9e71e84c03da2dc6f8420b84784d2834 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;
@@ -50,27 +52,27 @@ namespace System.Net
                X509Certificate clientCertificate;
                IPHostEntry host;
                bool usesProxy;
-               Hashtable groups;
+               WebConnectionGroup firstGroup;
+               Dictionary<string,WebConnectionGroup> groups;
                bool sendContinue = true;
                bool useConnect;
-               object locker = new object ();
                object hostE = new object ();
-#if NET_1_1
                bool useNagle;
-#endif
-#if NET_2_0
                BindIPEndPoint endPointCallback = null;
-#endif
-               
+               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.Now;
+                       this.idleSince = DateTime.UtcNow;
                }
                
                // Properties
@@ -79,7 +81,6 @@ namespace System.Net
                        get { return uri; }
                }
 
-#if NET_2_0
                static Exception GetMustImplement ()
                {
                        return new NotImplementedException ();
@@ -90,7 +91,6 @@ namespace System.Net
                        get { return endPointCallback; }
                        set { endPointCallback = value; }
                }
-#endif
                
                public X509Certificate Certificate {
                        get { return certificate; }
@@ -100,7 +100,6 @@ namespace System.Net
                        get { return clientCertificate; }
                }
 
-#if NET_2_0
                [MonoTODO]
                public int ConnectionLeaseTimeout
                {
@@ -111,7 +110,6 @@ namespace System.Net
                                throw GetMustImplement ();
                        }
                }
-#endif
                
                public int ConnectionLimit {
                        get { return connectionLimit; }
@@ -135,20 +133,21 @@ namespace System.Net
 
                public DateTime IdleSince {
                        get {
-                               return idleSince;
-                       }
-                       internal set {
-                               lock (locker)
-                                       idleSince = value;
+                               return idleSince.ToLocalTime ();
                        }
                }
-               
+
                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);
+                               }
                        }
                }
                
@@ -156,7 +155,6 @@ namespace System.Net
                        get { return protocolVersion; }
                }
 
-#if NET_2_0
                [MonoTODO]
                public int ReceiveBufferSize
                {
@@ -167,24 +165,21 @@ namespace System.Net
                                throw GetMustImplement ();
                        }
                }
-#endif
                
                public bool SupportsPipelining {
                        get { return HttpVersion.Version11.Equals (protocolVersion); }
                }
 
-#if NET_1_1
+
                public bool Expect100Continue {
                        get { return SendContinue; }
                        set { SendContinue = value; }
                }
 
-               [MonoTODO ("Use me")]
                public bool UseNagleAlgorithm {
                        get { return useNagle; }
                        set { useNagle = value; }
                }
-#endif
 
                internal bool SendContinue {
                        get { return sendContinue &&
@@ -192,14 +187,48 @@ namespace System.Net
                        set { sendContinue = value; }
                }
                // Methods
-               
-#if !NET_2_0
-               public override int GetHashCode() 
+
+               public void SetTcpKeepAlive (bool enabled, int keepAliveTime, int keepAliveInterval)
                {
-                       return base.GetHashCode ();
+                       if (enabled) {
+                               if (keepAliveTime <= 0)
+                                       throw new ArgumentOutOfRangeException ("keepAliveTime", "Must be greater than 0");
+                               if (keepAliveInterval <= 0)
+                                       throw new ArgumentOutOfRangeException ("keepAliveInterval", "Must be greater than 0");
+                       }
+
+                       tcp_keepalive = enabled;
+                       tcp_keepalive_time = keepAliveTime;
+                       tcp_keepalive_interval = keepAliveInterval;
                }
-#endif
-               
+
+               internal void KeepAliveSetup (Socket socket)
+               {
+                       if (!tcp_keepalive)
+                               return;
+
+                       byte [] bytes = new byte [12];
+                       PutBytes (bytes, (uint) (tcp_keepalive ? 1 : 0), 0);
+                       PutBytes (bytes, (uint) tcp_keepalive_time, 4);
+                       PutBytes (bytes, (uint) tcp_keepalive_interval, 8);
+                       socket.IOControl (IOControlCode.KeepAliveValues, bytes, null);
+               }
+
+               static void PutBytes (byte [] bytes, uint v, int offset)
+               {
+                       if (BitConverter.IsLittleEndian) {
+                               bytes [offset] = (byte) (v & 0x000000ff);
+                               bytes [offset + 1] = (byte) ((v & 0x0000ff00) >> 8);
+                               bytes [offset + 2] = (byte) ((v & 0x00ff0000) >> 16);
+                               bytes [offset + 3] = (byte) ((v & 0xff000000) >> 24);
+                       } else {
+                               bytes [offset + 3] = (byte) (v & 0x000000ff);
+                               bytes [offset + 2] = (byte) ((v & 0x0000ff00) >> 8);
+                               bytes [offset + 1] = (byte) ((v & 0x00ff0000) >> 16);
+                               bytes [offset] = (byte) ((v & 0xff000000) >> 24);
+                       }
+               }
+
                // Internal Methods
 
                internal bool UsesProxy {
@@ -212,21 +241,131 @@ namespace System.Net
                        set { useConnect = value; }
                }
 
-               internal bool AvailableForRecycling {
-                       get { 
-                               return CurrentConnections == 0
-                                   && maxIdleTime != Timeout.Infinite
-                                   && DateTime.Now >= 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 (firstGroup != null && name == firstGroup.Name)
+                               return firstGroup;
+                       if (groups != null && groups.TryGetValue (name, out group))
+                               return group;
+
+                       group = new WebConnectionGroup (this, name);
+                       group.ConnectionClosed += (s, e) => currentConnections--;
+
+                       if (firstGroup == null)
+                               firstGroup = group;
+                       else {
+                               if (groups == null)
+                                       groups = new Dictionary<string, WebConnectionGroup> ();
+                               groups.Add (name, group);
                        }
+
+                       return group;
                }
 
-               internal Hashtable Groups {
-                       get {
-                               if (groups == null)
-                                       groups = new Hashtable ();
+               void RemoveConnectionGroup (WebConnectionGroup group)
+               {
+                       if (groups == null || groups.Count == 0) {
+                               // No more connection groups left.
+                               if (group != firstGroup)
+                                       throw new InvalidOperationException ();
+                               else
+                                       firstGroup = null;
+                               return;
+                       }
+
+                       if (group == firstGroup) {
+                               // Steal one entry from the dictionary.
+                               var en = groups.GetEnumerator ();
+                               en.MoveNext ();
+                               firstGroup = en.Current.Value;
+                               groups.Remove (en.Current.Key);
+                       } else {
+                               groups.Remove (group.Name);
+                       }
+               }
+
+               internal bool CheckAvailableForRecycling (out DateTime outIdleSince)
+               {
+                       outIdleSince = DateTime.MinValue;
+
+                       TimeSpan idleTimeSpan;
+                       WebConnectionGroup singleGroup, singleRemove = null;
+                       List<WebConnectionGroup> groupList = null, removeList = null;
+                       lock (this) {
+                               if (firstGroup == null) {
+                                       idleSince = DateTime.MinValue;
+                                       return true;
+                               }
 
-                               return groups;
+                               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.
+                                * 
+                                */
+
+                               singleGroup = firstGroup;
+                               if (groups != null)
+                                       groupList = new List<WebConnectionGroup> (groups.Values);
+                       }
+
+                       if (singleGroup.TryRecycle (idleTimeSpan, ref outIdleSince))
+                               singleRemove = singleGroup;
+
+                       if (groupList != null) {
+                               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 (singleRemove != null)
+                                       RemoveConnectionGroup (singleRemove);
+
+                               if (removeList != null) {
+                                       foreach (var group in removeList)
+                                               RemoveConnectionGroup (group);
+                               }
+
+                               if (groups != null && groups.Count == 0)
+                                       groups = null;
+
+                               if (firstGroup == null) {
+                                       idleTimer.Dispose ();
+                                       idleTimer = null;
+                                       return true;
+                               }
+
+                               return false;
+                       }
+               }
+
+               void IdleTimerCallback (object obj)
+               {
+                       DateTime dummy;
+                       CheckAvailableForRecycling (out dummy);
                }
 
                internal IPHostEntry HostEntry
@@ -271,56 +410,37 @@ namespace System.Net
                {
                        protocolVersion = version;
                }
-#if !TARGET_JVM && !NET_2_1
-               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;
-               }
 
+#if !TARGET_JVM
                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
-#if NET_2_0
-               [MonoNotSupported ("")]
                public bool CloseConnectionGroup (string connectionGroupName)
                {
-                       throw new NotImplementedException ();
-               }
-#endif
-
-               internal void IncrementConnection ()
-               {
-                       lock (locker) {
-                               currentConnections++;
-                               idleSince = DateTime.Now.AddMilliseconds (1000000);
+                       lock (this) {
+                               WebConnectionGroup cncGroup = GetConnectionGroup (connectionGroupName);
+                               if (cncGroup != null) {
+                                       cncGroup.Close ();
+                                       return true;
+                               }
                        }
-               }
 
-               internal void DecrementConnection ()
-               {
-                       lock (locker) {
-                               currentConnections--;
-                               if (currentConnections == 0)
-                                       idleSince = DateTime.Now;
-                       }
+                       return false;
                }
 
                internal void SetCertificates (X509Certificate client, X509Certificate server) 
@@ -329,7 +449,6 @@ namespace System.Net
                        clientCertificate = client;
                }
 
-#if NET_2_0
                internal bool CallEndPointDelegate (Socket sock, IPEndPoint remote)
                {
                        if (endPointCallback == null)
@@ -365,7 +484,6 @@ namespace System.Net
                                return true;
                        }
                }
-#endif
        }
 }