Rewrite and fix the web stack's connection reuse and idle logic.
[mono.git] / mcs / class / System / System.Net / ServicePointManager.cs
index 60ff9c5364ab5c416db68c2b29a936624ba710f3..6e23af5ce04be141ac6a10d72171062e106a284c 100644 (file)
 
 #if SECURITY_DEP
 
+#if MONOTOUCH
+using Mono.Security.Protocol.Tls;
+using MSX = Mono.Security.X509;
+using Mono.Security.X509.Extensions;
+#else
 extern alias MonoSecurity;
-
-using System.Text.RegularExpressions;
 using MonoSecurity::Mono.Security.X509.Extensions;
 using MonoSecurity::Mono.Security.Protocol.Tls;
 using MSX = MonoSecurity::Mono.Security.X509;
 #endif
 
+using System.Text.RegularExpressions;
+#endif
+
 using System;
+using System.Threading;
 using System.Collections;
+using System.Collections.Generic;
 using System.Collections.Specialized;
 using System.Configuration;
 using System.Net.Configuration;
@@ -48,6 +56,7 @@ using System.Security.Cryptography.X509Certificates;
 
 using System.Globalization;
 using System.Net.Security;
+using System.Diagnostics;
 
 //
 // notes:
@@ -123,7 +132,7 @@ namespace System.Net
                
                private static ICertificatePolicy policy = new DefaultCertificatePolicy ();
                private static int defaultConnectionLimit = DefaultPersistentConnectionLimit;
-               private static int maxServicePointIdleTime = 900000; // 15 minutes
+               private static int maxServicePointIdleTime = 100000; // 100 seconds
                private static int maxServicePoints = 0;
                private static bool _checkCRL = false;
                private static SecurityProtocolType _securityProtocol = SecurityProtocolType.Ssl3 | SecurityProtocolType.Tls;
@@ -320,7 +329,8 @@ namespace System.Net
                        if (address == null)
                                throw new ArgumentNullException ("address");
 
-                       RecycleServicePoints ();
+                       if ((servicePoints.Count % 4) == 0)
+                               RecycleServicePoints ();
 
                        var origAddress = new Uri (address.Scheme + "://" + address.Authority);
                        
@@ -340,8 +350,8 @@ namespace System.Net
                        address = new Uri (address.Scheme + "://" + address.Authority);
                        
                        ServicePoint sp = null;
+                       SPKey key = new SPKey (origAddress, usesProxy ? address : null, useConnect);
                        lock (servicePoints) {
-                               SPKey key = new SPKey (origAddress, usesProxy ? address : null, useConnect);
                                sp = servicePoints [key] as ServicePoint;
                                if (sp != null)
                                        return sp;
@@ -370,16 +380,23 @@ namespace System.Net
                
                // Internal Methods
 
-               internal static void RecycleServicePoints ()
+               static void RecycleServicePoints ()
                {
-                       ArrayList toRemove = new ArrayList ();
                        lock (servicePoints) {
+                               var toRemove = new ArrayList ();
+                               var idleList = new SortedDictionary<DateTime, ServicePoint> ();
                                IDictionaryEnumerator e = servicePoints.GetEnumerator ();
                                while (e.MoveNext ()) {
                                        ServicePoint sp = (ServicePoint) e.Value;
-                                       if (sp.AvailableForRecycling) {
+                                       DateTime idleSince;
+                                       if (sp.CheckAvailableForRecycling (out idleSince)) {
                                                toRemove.Add (e.Key);
+                                               continue;
                                        }
+
+                                       while (idleList.ContainsKey (idleSince))
+                                               idleSince = idleSince.AddMilliseconds (1);
+                                       idleList.Add (idleSince, sp);
                                }
                                
                                for (int i = 0; i < toRemove.Count; i++) 
@@ -389,25 +406,18 @@ namespace System.Net
                                        return;
 
                                // get rid of the ones with the longest idle time
-                               SortedList list = new SortedList (servicePoints.Count);
-                               e = servicePoints.GetEnumerator ();
-                               while (e.MoveNext ()) {
-                                       ServicePoint sp = (ServicePoint) e.Value;
-                                       if (sp.CurrentConnections == 0) {
-                                               while (list.ContainsKey (sp.IdleSince))
-                                                       sp.IdleSince = sp.IdleSince.AddMilliseconds (1);
-                                               list.Add (sp.IdleSince, sp.Address);
-                                       }
+                               foreach (var sp in idleList.Values) {
+                                       if (servicePoints.Count <= maxServicePoints)
+                                               break;
+                                       servicePoints.Remove (sp);
                                }
-                               
-                               for (int i = 0; i < list.Count && servicePoints.Count > maxServicePoints; i++)
-                                       servicePoints.Remove (list.GetByIndex (i));
                        }
                }
 #if SECURITY_DEP
                internal class ChainValidationHelper {
                        object sender;
                        string host;
+                       RemoteCertificateValidationCallback cb;
 
 #if !MONOTOUCH
                        static bool is_macosx = System.IO.File.Exists (OSX509Certificates.SecurityLibrary);
@@ -426,19 +436,19 @@ namespace System.Net
                        }
 #endif
 
-                       public ChainValidationHelper (object sender)
+                       public ChainValidationHelper (object sender, string hostName)
                        {
                                this.sender = sender;
+                               host = hostName;
                        }
 
-                       public string Host {
+                       public RemoteCertificateValidationCallback ServerCertificateValidationCallback {
                                get {
-                                       if (host == null && sender is HttpWebRequest)
-                                               host = ((HttpWebRequest) sender).Address.Host;
-                                       return host;
+                                       if (cb == null)
+                                               cb = ServicePointManager.ServerCertificateValidationCallback;
+                                       return cb;
                                }
-
-                               set { host = value; }
+                               set { cb = value; }
                        }
 
                        // Used when the obsolete ICertificatePolicy is set to DefaultCertificatePolicy
@@ -451,7 +461,6 @@ namespace System.Net
                                        return null;
 
                                ICertificatePolicy policy = ServicePointManager.CertificatePolicy;
-                               RemoteCertificateValidationCallback cb = ServicePointManager.ServerCertificateValidationCallback;
 
                                X509Certificate2 leaf = new X509Certificate2 (certs [0].RawData);
                                int status11 = 0; // Error code passed to the obsolete ICertificatePolicy callback
@@ -464,7 +473,7 @@ namespace System.Net
                                // the certificates that the server provided (which generally does not include the root) so, only  
                                // if there's a user callback, we'll create the X509Chain but won't build it
                                // ref: https://bugzilla.xamarin.com/show_bug.cgi?id=7245
-                               if (cb != null) {
+                               if (ServerCertificateValidationCallback != null) {
 #endif
                                chain = new X509Chain ();
                                chain.ChainPolicy = new X509ChainPolicy ();
@@ -494,7 +503,7 @@ namespace System.Net
                                                status11 = -2146762490; //CERT_E_PURPOSE 0x800B0106
                                        }
 
-                                       if (!CheckServerIdentity (certs [0], Host)) {
+                                       if (!CheckServerIdentity (certs [0], host)) {
                                                errors |= SslPolicyErrors.RemoteCertificateNameMismatch;
                                                status11 = -2146762481; // CERT_E_CN_NO_MATCH 0x800B010F
                                        }
@@ -504,7 +513,7 @@ namespace System.Net
                                        // Ideally we should return the SecTrustResult
                                        OSX509Certificates.SecTrustResult trustResult = OSX509Certificates.SecTrustResult.Deny;
                                        try {
-                                               trustResult = OSX509Certificates.TrustEvaluateSsl (certs, Host);
+                                               trustResult = OSX509Certificates.TrustEvaluateSsl (certs, host);
                                                // We could use the other values of trustResult to pass this extra information
                                                // to the .NET 2 callback for values like SecTrustResult.Confirm
                                                result = (trustResult == OSX509Certificates.SecTrustResult.Proceed ||
@@ -550,8 +559,8 @@ namespace System.Net
                                        user_denied = !result && !(policy is DefaultCertificatePolicy);
                                }
                                // If there's a 2.0 callback, it takes precedence
-                               if (cb != null) {
-                                       result = cb (sender, leaf, chain, errors);
+                               if (ServerCertificateValidationCallback != null) {
+                                       result = ServerCertificateValidationCallback (sender, leaf, chain, errors);
                                        user_denied = !result;
                                }
                                return new ValidationResult (result, user_denied, status11);