Merge pull request #5714 from alexischr/update_bockbuild
[mono.git] / mcs / class / System / System.Net / WebConnectionGroup.cs
1 //
2 // System.Net.WebConnectionGroup
3 //
4 // Authors:
5 //      Gonzalo Paniagua Javier (gonzalo@ximian.com)
6 //      Martin Baulig (martin.baulig@xamarin.com)
7 //
8 // (C) 2003 Ximian, Inc (http://www.ximian.com)
9 // Copyright 2011-2014 Xamarin, Inc (http://www.xamarin.com)
10 //
11
12 //
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:
20 // 
21 // The above copyright notice and this permission notice shall be
22 // included in all copies or substantial portions of the Software.
23 // 
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.
31 //
32
33 using System;
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;
40
41 namespace System.Net
42 {
43         class WebConnectionGroup
44         {
45                 ServicePoint sPoint;
46                 string name;
47                 LinkedList<ConnectionState> connections;
48                 Queue queue;
49                 bool closing;
50
51                 public WebConnectionGroup (ServicePoint sPoint, string name)
52                 {
53                         this.sPoint = sPoint;
54                         this.name = name;
55                         connections = new LinkedList<ConnectionState> ();
56                         queue = new Queue ();
57                 }
58
59                 public event EventHandler ConnectionClosed;
60
61                 void OnConnectionClosed ()
62                 {
63                         if (ConnectionClosed != null)
64                                 ConnectionClosed (this, null);
65                 }
66
67                 public void Close ()
68                 {
69                         List<WebConnection> connectionsToClose = null;
70
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
73                         lock (sPoint) {
74                                 closing = true;
75                                 var iter = connections.First;
76                                 while (iter != null) {
77                                         var cnc = iter.Value.Connection;
78                                         var node = iter;
79                                         iter = iter.Next;
80
81                                         // Closing connections inside the lock leads to a deadlock.
82                                         if (connectionsToClose == null)
83                                                 connectionsToClose = new List<WebConnection>();
84
85                                         connectionsToClose.Add (cnc);
86                                         connections.Remove (node);
87                                 }
88                         }
89
90                         if (connectionsToClose != null) {
91                                 foreach (var cnc in connectionsToClose) {
92                                         cnc.Close (false);
93                                         OnConnectionClosed ();
94                                 }
95                         }
96                 }
97
98                 public WebConnection GetConnection (HttpWebRequest request, out bool created)
99                 {
100                         lock (sPoint) {
101                                 return CreateOrReuseConnection (request, out created);
102                         }
103                 }
104
105                 static void PrepareSharingNtlm (WebConnection cnc, HttpWebRequest request)
106                 {
107                         if (!cnc.NtlmAuthenticated)
108                                 return;
109
110                         bool needs_reset = false;
111                         NetworkCredential cnc_cred = cnc.NtlmCredential;
112
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;
116
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) {
120                                 needs_reset = true;
121                         }
122
123                         if (!needs_reset) {
124                                 bool req_sharing = request.UnsafeAuthenticatedConnectionSharing;
125                                 bool cnc_sharing = cnc.UnsafeAuthenticatedConnectionSharing;
126                                 needs_reset = (req_sharing == false || req_sharing != cnc_sharing);
127                         }
128                         if (needs_reset) {
129                                 cnc.Close (false); // closes the authenticated connection
130                                 cnc.ResetNtlm ();
131                         }
132                 }
133
134                 ConnectionState FindIdleConnection ()
135                 {
136                         foreach (var cnc in connections) {
137                                 if (cnc.Busy)
138                                         continue;
139
140                                 connections.Remove (cnc);
141                                 connections.AddFirst (cnc);
142                                 return cnc;
143                         }
144
145                         return null;
146                 }
147
148                 WebConnection CreateOrReuseConnection (HttpWebRequest request, out bool created)
149                 {
150                         var cnc = FindIdleConnection ();
151                         if (cnc != null) {
152                                 created = false;
153                                 PrepareSharingNtlm (cnc.Connection, request);
154                                 return cnc.Connection;
155                         }
156
157                         if (sPoint.ConnectionLimit > connections.Count || connections.Count == 0) {
158                                 created = true;
159                                 cnc = new ConnectionState (this);
160                                 connections.AddFirst (cnc);
161                                 return cnc.Connection;
162                         }
163
164                         created = false;
165                         cnc = connections.Last.Value;
166                         connections.Remove (cnc);
167                         connections.AddFirst (cnc);
168                         return cnc.Connection;
169                 }
170
171                 public string Name {
172                         get { return name; }
173                 }
174
175                 internal Queue Queue {
176                         get { return queue; }
177                 }
178
179                 internal bool TryRecycle (TimeSpan maxIdleTime, ref DateTime idleSince)
180                 {
181                         var now = DateTime.UtcNow;
182
183                 again:
184                         bool recycled;
185                         List<WebConnection> connectionsToClose = null;
186
187                         lock (sPoint) {
188                                 if (closing) {
189                                         idleSince = DateTime.MinValue;
190                                         return true;
191                                 }
192
193                                 int count = 0;
194                                 var iter = connections.First;
195                                 while (iter != null) {
196                                         var cnc = iter.Value;
197                                         var node = iter;
198                                         iter = iter.Next;
199
200                                         ++count;
201                                         if (cnc.Busy)
202                                                 continue;
203
204                                         if (count <= sPoint.ConnectionLimit && now - cnc.IdleSince < maxIdleTime) {
205                                                 if (cnc.IdleSince > idleSince)
206                                                         idleSince = cnc.IdleSince;
207                                                 continue;
208                                         }
209
210                                         /*
211                                          * Do not call WebConnection.Close() while holding the ServicePoint lock
212                                          * as this could deadlock when attempting to take the WebConnection lock.
213                                          * 
214                                          */
215
216                                         if (connectionsToClose == null)
217                                                 connectionsToClose = new List<WebConnection> ();
218                                         connectionsToClose.Add (cnc.Connection);
219                                         connections.Remove (node);
220                                 }
221
222                                 recycled = connections.Count == 0;
223                         }
224
225                         // Did we find anything that can be closed?
226                         if (connectionsToClose == null)
227                                 return recycled;
228
229                         // Ok, let's get rid of these!
230                         foreach (var cnc in connectionsToClose)
231                                 cnc.Close (false);
232
233                         // Re-take the lock, then remove them from the connection list.
234                         goto again;
235                 }
236
237                 class ConnectionState : IWebConnectionState {
238                         public WebConnection Connection {
239                                 get;
240                                 private set;
241                         }
242
243                         public WebConnectionGroup Group {
244                                 get;
245                                 private set;
246                         }
247
248                         public ServicePoint ServicePoint {
249                                 get { return Group.sPoint; }
250                         }
251
252                         bool busy;
253                         DateTime idleSince;
254
255                         public bool Busy {
256                                 get { return busy; }
257                         }
258
259                         public DateTime IdleSince {
260                                 get { return idleSince; }
261                         }
262
263                         public bool TrySetBusy ()
264                         {
265                                 lock (ServicePoint) {
266                                         if (busy)
267                                                 return false;
268                                         busy = true;
269                                         idleSince = DateTime.UtcNow + TimeSpan.FromDays (3650);
270                                         return true;
271                                 }
272                         }
273
274                         public void SetIdle ()
275                         {
276                                 lock (ServicePoint) {
277                                         busy = false;
278                                         idleSince = DateTime.UtcNow;
279                                 }
280                         }
281
282                         public ConnectionState (WebConnectionGroup group)
283                         {
284                                 Group = group;
285                                 idleSince = DateTime.UtcNow;
286                                 Connection = new WebConnection (this, group.sPoint);
287                         }
288                 }
289                 
290         }
291 }
292