The Labs \ Source Viewer \ SSCLI \ System.Net \ ConnectionGroup

  1. //------------------------------------------------------------------------------
  2. // <copyright file="_ConnectionGroup.cs" company="Microsoft">
  3. //
  4. // Copyright (c) 2006 Microsoft Corporation. All rights reserved.
  5. //
  6. // The use and distribution terms for this software are contained in the file
  7. // named license.txt, which can be found in the root of this distribution.
  8. // By using this software in any fashion, you are agreeing to be bound by the
  9. // terms of this license.
  10. //
  11. // You must not remove this notice, or any other, from this software.
  12. //
  13. // </copyright>
  14. //------------------------------------------------------------------------------
  15. namespace System.Net
  16. {
  17.     using System.Collections;
  18.     using System.Diagnostics;
  19.     using System.Threading;
  20.    
  21.     //
  22.     // ConnectionGroup groups a list of connections within the ServerPoint context,
  23.     // this used to keep context for things such as proxies or seperate clients.
  24.     //
  25.     internal class ConnectionGroup
  26.     {
  27.        
  28.         //
  29.         // Members
  30.         //
  31.         private const int DefaultConnectionListSize = 3;
  32.        
  33.         private ServicePoint m_ServicePoint;
  34.         private string m_Name;
  35.         private int m_ConnectionLimit;
  36.         private ArrayList m_ConnectionList;
  37.         private object m_Event;
  38.         private Queue m_AuthenticationRequestQueue;
  39.         internal bool m_AuthenticationGroup;
  40.         private HttpAbortDelegate m_AbortDelegate;
  41.         private bool m_NtlmNegGroup;
  42.         private int m_IISVersion = -1;
  43.        
  44.        
  45.         //
  46.         // Constructors
  47.         //
  48.         internal ConnectionGroup(ServicePoint servicePoint, string connName)
  49.         {
  50.             m_ServicePoint = servicePoint;
  51.             m_ConnectionLimit = servicePoint.ConnectionLimit;
  52.             m_ConnectionList = new ArrayList(DefaultConnectionListSize);
  53.             //it may grow beyond
  54.             m_Name = MakeQueryStr(connName);
  55.             m_AbortDelegate = new HttpAbortDelegate(Abort);
  56.             GlobalLog.Print("ConnectionGroup::.ctor m_ConnectionLimit:" + m_ConnectionLimit.ToString());
  57.         }
  58.        
  59.         //
  60.         // Accessors
  61.         //
  62.         internal ServicePoint ServicePoint {
  63.             get { return m_ServicePoint; }
  64.         }
  65.        
  66.         internal int CurrentConnections {
  67.             get { return m_ConnectionList.Count; }
  68.         }
  69.        
  70.         internal int ConnectionLimit {
  71.             get { return m_ConnectionLimit; }
  72.             set {
  73.                 m_ConnectionLimit = value;
  74.                 PruneExcesiveConnections();
  75.                 GlobalLog.Print("ConnectionGroup::ConnectionLimit.set m_ConnectionLimit:" + m_ConnectionLimit.ToString());
  76.             }
  77.         }
  78.        
  79.         private ManualResetEvent AsyncWaitHandle {
  80.             get {
  81.                 if (m_Event == null) {
  82.                     //
  83.                     // lazy allocation of the event:
  84.                     // if this property is never accessed this object is never created
  85.                     //
  86.                     Interlocked.CompareExchange(ref m_Event, new ManualResetEvent(false), null);
  87.                 }
  88.                
  89.                 ManualResetEvent castedEvent = (ManualResetEvent)m_Event;
  90.                
  91.                 return castedEvent;
  92.             }
  93.         }
  94.        
  95.         private Queue AuthenticationRequestQueue {
  96.             get {
  97.                 if (m_AuthenticationRequestQueue == null) {
  98.                     lock (m_ConnectionList) {
  99.                         if (m_AuthenticationRequestQueue == null) {
  100.                             m_AuthenticationRequestQueue = new Queue();
  101.                         }
  102.                     }
  103.                 }
  104.                 return m_AuthenticationRequestQueue;
  105.             }
  106.             set { m_AuthenticationRequestQueue = value; }
  107.         }
  108.        
  109.         //
  110.         // Methods
  111.         //
  112.        
  113.         static internal string MakeQueryStr(string connName)
  114.         {
  115.             return ((connName == null) ? "" : connName);
  116.         }
  117.        
  118.        
  119.         /// <devdoc>
  120.         /// <para>
  121.         /// These methods are made available to the underlying Connection
  122.         /// object so that we don't leak them because we're keeping a local
  123.         /// reference in our m_ConnectionList.
  124.         /// Called by the Connection's constructor
  125.         /// </para>
  126.         /// </devdoc>
  127.         internal void Associate(Connection connection)
  128.         {
  129.             lock (m_ConnectionList) {
  130.                 m_ConnectionList.Add(connection);
  131.             }
  132.             GlobalLog.Print("ConnectionGroup::Associate() Connection:" + connection.GetHashCode());
  133.         }
  134.        
  135.        
  136.        
  137.         /// <devdoc>
  138.         /// <para>
  139.         /// Used by the Connection's explicit finalizer (note this is
  140.         /// not a destructor, since that's never calld unless we
  141.         /// remove reference to the object from our internal list)
  142.         /// </para>
  143.         /// </devdoc>
  144.         internal void Disassociate(Connection connection)
  145.         {
  146.             lock (m_ConnectionList) {
  147.                 m_ConnectionList.Remove(connection);
  148.             }
  149.         }
  150.        
  151.         /// <devdoc>
  152.         /// <para>
  153.         /// Called when a connection is idle and ready to process new requests
  154.         /// </para>
  155.         /// </devdoc>
  156.         internal void ConnectionGoneIdle()
  157.         {
  158.             if (m_AuthenticationGroup) {
  159.                 lock (m_ConnectionList) {
  160.                     GlobalLog.Print("ConnectionGroup::ConnectionGoneIdle() setting the event");
  161.                     AsyncWaitHandle.Set();
  162.                 }
  163.             }
  164.         }
  165.        
  166.         /// <devdoc>
  167.         /// <para>
  168.         /// Causes an abort of any aborted requests waiting in the ConnectionGroup
  169.         /// </para>
  170.         /// </devdoc>
  171.         private bool Abort(HttpWebRequest request, WebException webException)
  172.         {
  173.             lock (m_ConnectionList) {
  174.                 AsyncWaitHandle.Set();
  175.             }
  176.             return true;
  177.         }
  178.        
  179.         /// <devdoc>
  180.         /// <para>
  181.         /// Removes aborted requests from our queue.
  182.         /// </para>
  183.         /// </devdoc>
  184.         private void PruneAbortedRequests()
  185.         {
  186.             lock (m_ConnectionList) {
  187.                 Queue updatedQueue = new Queue();
  188.                 foreach (HttpWebRequest request in AuthenticationRequestQueue) {
  189.                     if (!request.Aborted) {
  190.                         updatedQueue.Enqueue(request);
  191.                     }
  192.                 }
  193.                 AuthenticationRequestQueue = updatedQueue;
  194.             }
  195.         }
  196.        
  197.         /// <devdoc>
  198.         /// <para>
  199.         /// Removes extra connections that are found when reducing the connection limit
  200.         /// </para>
  201.         /// </devdoc>
  202.         private void PruneExcesiveConnections()
  203.         {
  204.             lock (m_ConnectionList) {
  205.                 int connectionLimit = ConnectionLimit;
  206.                 if (CurrentConnections > connectionLimit) {
  207.                     int numberToPrune = CurrentConnections - connectionLimit;
  208.                     for (int i = 0; i < numberToPrune; i++) {
  209.                         ((Connection)m_ConnectionList[i]).CloseOnIdle();
  210.                     }
  211.                     m_ConnectionList.RemoveRange(0, numberToPrune);
  212.                 }
  213.             }
  214.         }
  215.        
  216.         /// <devdoc>
  217.         /// <para>
  218.         /// Forces all connections on the ConnectionGroup to not be KeepAlive.
  219.         /// </para>
  220.         /// </devdoc>
  221.         internal void DisableKeepAliveOnConnections()
  222.         {
  223.             // The timer thread is allowed to call this. (It doesn't call user code and doesn't block.)
  224.             GlobalLog.ThreadContract(ThreadKinds.Unknown, ThreadKinds.SafeSources | ThreadKinds.Timer, "ConnectionGroup#" + ValidationHelper.HashString(this) + "::DisableKeepAliveOnConnections");
  225.            
  226.             lock (m_ConnectionList) {
  227.                 GlobalLog.Print("ConnectionGroup#" + ValidationHelper.HashString(this) + "::DisableKeepAliveOnConnections() Name = " + m_Name + ", Count:" + m_ConnectionList.Count);
  228.                 foreach (Connection currentConnection in m_ConnectionList) {
  229.                     //
  230.                     // For each Connection set KeepAlive to false
  231.                     //
  232.                     GlobalLog.Print("ConnectionGroup#" + ValidationHelper.HashString(this) + "::DisableKeepAliveOnConnections() setting KeepAlive to false Connection#" + ValidationHelper.HashString(currentConnection));
  233.                     currentConnection.CloseOnIdle();
  234.                 }
  235.                 m_ConnectionList.Clear();
  236.             }
  237.         }
  238.        
  239.        
  240.        
  241.         private Connection FindMatchingConnection(HttpWebRequest request, string connName, out Connection leastbusyConnection)
  242.         {
  243.             int minBusyCount = Int32.MaxValue;
  244.             bool freeConnectionsAvail = false;
  245.            
  246.             leastbusyConnection = null;
  247.            
  248.             lock (m_ConnectionList) {
  249.                
  250.                 //
  251.                 // go through the list of open connections to this service point and pick
  252.                 // the first empty one or, if none is empty, pick the least busy one.
  253.                 //
  254.                 minBusyCount = Int32.MaxValue;
  255.                 foreach (Connection currentConnection in m_ConnectionList) {
  256.                     GlobalLog.Print("ConnectionGroup::FindMatchingConnection currentConnection.BusyCount:" + currentConnection.BusyCount.ToString());
  257.                    
  258.                     if (currentConnection.LockedRequest == request) {
  259.                         leastbusyConnection = currentConnection;
  260.                         return currentConnection;
  261.                     }
  262.                    
  263.                     GlobalLog.Print("ConnectionGroup::FindMatchingConnection: lockedRequest# " + ((currentConnection.LockedRequest == null) ? "null" : currentConnection.LockedRequest.GetHashCode().ToString()));
  264.                     if (currentConnection.BusyCount < minBusyCount && currentConnection.LockedRequest == null) {
  265.                         leastbusyConnection = currentConnection;
  266.                         minBusyCount = currentConnection.BusyCount;
  267.                         if (minBusyCount == 0) {
  268.                             freeConnectionsAvail = true;
  269.                         }
  270.                     }
  271.                 }
  272.                
  273.                 //
  274.                 // If there is NOT a Connection free, then we allocate a new Connection
  275.                 //
  276.                 if (!freeConnectionsAvail && CurrentConnections < ConnectionLimit) {
  277.                     //
  278.                     // If we can create a new connection, then do it,
  279.                     // this may have complications in pipeling because
  280.                     // we may wish to optimize this case by actually
  281.                     // using existing connections, rather than creating new ones
  282.                     //
  283.                     // Note: this implicately results in a this.Associate being called.
  284.                     //
  285.                    
  286.                     GlobalLog.Print("ConnectionGroup::FindMatchingConnection [returning new Connection] CurrentConnections:" + CurrentConnections.ToString() + " ConnectionLimit:" + ConnectionLimit.ToString());
  287.                     leastbusyConnection = new Connection(this);
  288.                 }
  289.             }
  290.            
  291.             return null;
  292.             // only if we have a locked Connection that matches can return non-null
  293.         }
  294.        
  295.         private Connection FindConnectionAuthenticationGroup(HttpWebRequest request, string connName)
  296.         {
  297.             Connection leastBusyConnection = null;
  298.            
  299.             GlobalLog.Print("ConnectionGroup::FindConnectionAuthenticationGroup [" + connName + "] for request#" + request.GetHashCode() + ", m_ConnectionList.Count:" + m_ConnectionList.Count.ToString());
  300.            
  301.             //
  302.             // First try and find a free Connection (i.e. one not busy with Authentication handshake)
  303.             // or try to find a Request that has already locked a specific Connection,
  304.             // if a matching Connection is found, then we're done
  305.             //
  306.             lock (m_ConnectionList) {
  307.                 Connection matchingConnection;
  308.                 matchingConnection = FindMatchingConnection(request, connName, out leastBusyConnection);
  309.                 if (matchingConnection != null) {
  310.                     return matchingConnection;
  311.                 }
  312.                 if (AuthenticationRequestQueue.Count == 0) {
  313.                     if (leastBusyConnection != null) {
  314.                         if (request.LockConnection) {
  315.                             m_NtlmNegGroup = true;
  316.                             m_IISVersion = leastBusyConnection.IISVersion;
  317.                         }
  318.                         if (request.LockConnection || (m_NtlmNegGroup && !request.Pipelined && request.UnsafeOrProxyAuthenticatedConnectionSharing && m_IISVersion >= 6)) {
  319.                             GlobalLog.Print("Assigning New Locked Request#" + request.GetHashCode().ToString());
  320.                             leastBusyConnection.LockedRequest = request;
  321.                         }
  322.                         return leastBusyConnection;
  323.                     }
  324.                 }
  325.                 else if (leastBusyConnection != null) {
  326.                     AsyncWaitHandle.Set();
  327.                 }
  328.                 AuthenticationRequestQueue.Enqueue(request);
  329.             }
  330.            
  331.             //
  332.             // If all the Connections are busy, then we queue ourselves and need to wait. As soon as
  333.             // one of the Connections are free, we grab the lock, and see if we find ourselves
  334.             // at the head of the queue. If not, we loop backaround.
  335.             // Care is taken to examine the request when we wakeup, in case the request is aborted.
  336.             //
  337.             while (true) {
  338.                 GlobalLog.Print("waiting");
  339.                 request.AbortDelegate = m_AbortDelegate;
  340.                
  341.                 if (!request.Aborted)
  342.                     AsyncWaitHandle.WaitOne();
  343.                
  344.                 GlobalLog.Print("wait up");
  345.                 lock (m_ConnectionList) {
  346.                     if (request.Aborted) {
  347.                         PruneAbortedRequests();
  348.                         // Note that request is not on any connection and it will not be submitted
  349.                         return null;
  350.                     }
  351.                    
  352.                     FindMatchingConnection(request, connName, out leastBusyConnection);
  353.                     if (AuthenticationRequestQueue.Peek() == request) {
  354.                         GlobalLog.Print("dequeue");
  355.                         AuthenticationRequestQueue.Dequeue();
  356.                         if (leastBusyConnection != null) {
  357.                             if (request.LockConnection) {
  358.                                 m_NtlmNegGroup = true;
  359.                                 m_IISVersion = leastBusyConnection.IISVersion;
  360.                             }
  361.                             if (request.LockConnection || (m_NtlmNegGroup && !request.Pipelined && request.UnsafeOrProxyAuthenticatedConnectionSharing && m_IISVersion >= 6)) {
  362.                                 leastBusyConnection.LockedRequest = request;
  363.                             }
  364.                            
  365.                             return leastBusyConnection;
  366.                         }
  367.                         AuthenticationRequestQueue.Enqueue(request);
  368.                     }
  369.                     if (leastBusyConnection == null) {
  370.                         AsyncWaitHandle.Reset();
  371.                     }
  372.                 }
  373.             }
  374.         }
  375.        
  376.         /// <devdoc>
  377.         /// <para>
  378.         /// Used by the ServicePoint to find a free or new Connection
  379.         /// for use in making Requests. Under NTLM and Negotiate requests,
  380.         /// this function depricates itself and switches the object over to
  381.         /// using a new code path (see FindConnectionAuthenticationGroup).
  382.         /// </para>
  383.         /// </devdoc>
  384.         internal Connection FindConnection(HttpWebRequest request, string connName)
  385.         {
  386.             Connection leastbusyConnection = null;
  387.             Connection newConnection = null;
  388.             bool freeConnectionsAvail = false;
  389.            
  390.             if (m_AuthenticationGroup || request.LockConnection) {
  391.                 m_AuthenticationGroup = true;
  392.                 return FindConnectionAuthenticationGroup(request, connName);
  393.             }
  394.            
  395.             GlobalLog.Print("ConnectionGroup::FindConnection [" + connName + "] m_ConnectionList.Count:" + m_ConnectionList.Count.ToString());
  396.            
  397.             lock (m_ConnectionList) {
  398.                
  399.                 //
  400.                 // go through the list of open connections to this service point and pick
  401.                 // the first empty one or, if none is empty, pick the least busy one.
  402.                 //
  403.                 int minBusyCount = Int32.MaxValue;
  404.                 foreach (Connection currentConnection in m_ConnectionList) {
  405.                     GlobalLog.Print("ConnectionGroup::FindConnection currentConnection.BusyCount:" + currentConnection.BusyCount.ToString());
  406.                     if (currentConnection.BusyCount < minBusyCount) {
  407.                         leastbusyConnection = currentConnection;
  408.                         minBusyCount = currentConnection.BusyCount;
  409.                         if (minBusyCount == 0) {
  410.                             freeConnectionsAvail = true;
  411.                             break;
  412.                         }
  413.                     }
  414.                 }
  415.                
  416.                
  417.                 //
  418.                 // If there is NOT a Connection free, then we allocate a new Connection
  419.                 //
  420.                 if (!freeConnectionsAvail && CurrentConnections < ConnectionLimit) {
  421.                     //
  422.                     // If we can create a new connection, then do it,
  423.                     // this may have complications in pipeling because
  424.                     // we may wish to optimize this case by actually
  425.                     // using existing connections, rather than creating new ones
  426.                     //
  427.                     // Note: this implicately results in a this.Associate being called.
  428.                     //
  429.                    
  430.                     GlobalLog.Print("ConnectionGroup::FindConnection [returning new Connection] freeConnectionsAvail:" + freeConnectionsAvail.ToString() + " CurrentConnections:" + CurrentConnections.ToString() + " ConnectionLimit:" + ConnectionLimit.ToString());
  431.                     newConnection = new Connection(this);
  432.                 }
  433.                 else {
  434.                     //
  435.                     // All connections are busy, use the least busy one
  436.                     //
  437.                    
  438.                     GlobalLog.Print("ConnectionGroup::FindConnection [returning leastbusyConnection] freeConnectionsAvail:" + freeConnectionsAvail.ToString() + " CurrentConnections:" + CurrentConnections.ToString() + " ConnectionLimit:" + ConnectionLimit.ToString());
  439.                     GlobalLog.Assert(leastbusyConnection != null, "Connect.leastbusyConnection != null|All connections have BusyCount equal to Int32.MaxValue.");
  440.                    
  441.                     newConnection = leastbusyConnection;
  442.                 }
  443.             }
  444.            
  445.             return newConnection;
  446.         }
  447.        
  448.        
  449.         [System.Diagnostics.Conditional("DEBUG")]
  450.         internal void Debug(int requestHash)
  451.         {
  452.             foreach (Connection connection in m_ConnectionList) {
  453.                 connection.Debug(requestHash);
  454.             }
  455.         }
  456.     }
  457. }

Developer Fusion