2 // System.Net.WebConnectionGroup
5 // Gonzalo Paniagua Javier (gonzalo@ximian.com)
6 // Martin Baulig (martin.baulig@xamarin.com)
8 // (C) 2003 Ximian, Inc (http://www.ximian.com)
9 // Copyright 2011-2014 Xamarin, Inc (http://www.xamarin.com)
13 // Permission is hereby granted, free of charge, to any person obtaining
14 // a copy of this software and associated documentation files (the
15 // "Software"), to deal in the Software without restriction, including
16 // without limitation the rights to use, copy, modify, merge, publish,
17 // distribute, sublicense, and/or sell copies of the Software, and to
18 // permit persons to whom the Software is furnished to do so, subject to
19 // the following conditions:
21 // The above copyright notice and this permission notice shall be
22 // included in all copies or substantial portions of the Software.
24 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
25 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
26 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
27 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
28 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
29 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
30 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
34 using System.Threading;
35 using System.Collections;
36 using System.Collections.Generic;
37 using System.Net.Configuration;
38 using System.Net.Sockets;
39 using System.Diagnostics;
43 class WebConnectionGroup
47 LinkedList<ConnectionState> connections;
51 public WebConnectionGroup (ServicePoint sPoint, string name)
55 connections = new LinkedList<ConnectionState> ();
59 public event EventHandler ConnectionClosed;
61 void OnConnectionClosed ()
63 if (ConnectionClosed != null)
64 ConnectionClosed (this, null);
69 List<WebConnection> connectionsToClose = null;
71 //TODO: what do we do with the queue? Empty it out and abort the requests?
72 //TODO: abort requests or wait for them to finish
75 var iter = connections.First;
76 while (iter != null) {
77 var cnc = iter.Value.Connection;
81 // Closing connections inside the lock leads to a deadlock.
82 if (connectionsToClose == null)
83 connectionsToClose = new List<WebConnection>();
85 connectionsToClose.Add (cnc);
86 connections.Remove (node);
90 if (connectionsToClose != null) {
91 foreach (var cnc in connectionsToClose) {
93 OnConnectionClosed ();
98 public WebConnection GetConnection (HttpWebRequest request, out bool created)
101 return CreateOrReuseConnection (request, out created);
105 static void PrepareSharingNtlm (WebConnection cnc, HttpWebRequest request)
107 if (!cnc.NtlmAuthenticated)
110 bool needs_reset = false;
111 NetworkCredential cnc_cred = cnc.NtlmCredential;
113 bool isProxy = (request.Proxy != null && !request.Proxy.IsBypassed (request.RequestUri));
114 ICredentials req_icreds = (!isProxy) ? request.Credentials : request.Proxy.Credentials;
115 NetworkCredential req_cred = (req_icreds != null) ? req_icreds.GetCredential (request.RequestUri, "NTLM") : null;
117 if (cnc_cred == null || req_cred == null ||
118 cnc_cred.Domain != req_cred.Domain || cnc_cred.UserName != req_cred.UserName ||
119 cnc_cred.Password != req_cred.Password) {
124 bool req_sharing = request.UnsafeAuthenticatedConnectionSharing;
125 bool cnc_sharing = cnc.UnsafeAuthenticatedConnectionSharing;
126 needs_reset = (req_sharing == false || req_sharing != cnc_sharing);
129 cnc.Close (false); // closes the authenticated connection
134 ConnectionState FindIdleConnection ()
136 foreach (var cnc in connections) {
140 connections.Remove (cnc);
141 connections.AddFirst (cnc);
148 WebConnection CreateOrReuseConnection (HttpWebRequest request, out bool created)
150 var cnc = FindIdleConnection ();
153 PrepareSharingNtlm (cnc.Connection, request);
154 return cnc.Connection;
157 if (sPoint.ConnectionLimit > connections.Count || connections.Count == 0) {
159 cnc = new ConnectionState (this);
160 connections.AddFirst (cnc);
161 return cnc.Connection;
165 cnc = connections.Last.Value;
166 connections.Remove (cnc);
167 connections.AddFirst (cnc);
168 return cnc.Connection;
175 internal Queue Queue {
176 get { return queue; }
179 internal bool TryRecycle (TimeSpan maxIdleTime, ref DateTime idleSince)
181 var now = DateTime.UtcNow;
185 List<WebConnection> connectionsToClose = null;
189 idleSince = DateTime.MinValue;
194 var iter = connections.First;
195 while (iter != null) {
196 var cnc = iter.Value;
204 if (count <= sPoint.ConnectionLimit && now - cnc.IdleSince < maxIdleTime) {
205 if (cnc.IdleSince > idleSince)
206 idleSince = cnc.IdleSince;
211 * Do not call WebConnection.Close() while holding the ServicePoint lock
212 * as this could deadlock when attempting to take the WebConnection lock.
216 if (connectionsToClose == null)
217 connectionsToClose = new List<WebConnection> ();
218 connectionsToClose.Add (cnc.Connection);
219 connections.Remove (node);
222 recycled = connections.Count == 0;
225 // Did we find anything that can be closed?
226 if (connectionsToClose == null)
229 // Ok, let's get rid of these!
230 foreach (var cnc in connectionsToClose)
233 // Re-take the lock, then remove them from the connection list.
237 class ConnectionState : IWebConnectionState {
238 public WebConnection Connection {
243 public WebConnectionGroup Group {
248 public ServicePoint ServicePoint {
249 get { return Group.sPoint; }
259 public DateTime IdleSince {
260 get { return idleSince; }
263 public bool TrySetBusy ()
265 lock (ServicePoint) {
269 idleSince = DateTime.UtcNow + TimeSpan.FromDays (3650);
274 public void SetIdle ()
276 lock (ServicePoint) {
278 idleSince = DateTime.UtcNow;
282 public ConnectionState (WebConnectionGroup group)
285 idleSince = DateTime.UtcNow;
286 Connection = new WebConnection (this, group.sPoint);