Merge pull request #901 from Blewzman/FixAggregateExceptionGetBaseException
[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                         //TODO: what do we do with the queue? Empty it out and abort the requests?
70                         //TODO: abort requests or wait for them to finish
71                         lock (sPoint) {
72                                 closing = true;
73                                 foreach (var cnc in connections) {
74                                         if (cnc.Connection == null)
75                                                 continue;
76                                         cnc.Connection.Close (false);
77                                         cnc.Connection = null;
78                                         OnConnectionClosed ();
79                                 }
80                                 connections.Clear ();
81                         }
82                 }
83
84                 public WebConnection GetConnection (HttpWebRequest request, out bool created)
85                 {
86                         lock (sPoint) {
87                                 return CreateOrReuseConnection (request, out created);
88                         }
89                 }
90
91                 static void PrepareSharingNtlm (WebConnection cnc, HttpWebRequest request)
92                 {
93                         if (!cnc.NtlmAuthenticated)
94                                 return;
95
96                         bool needs_reset = false;
97                         NetworkCredential cnc_cred = cnc.NtlmCredential;
98
99                         bool isProxy = (request.Proxy != null && !request.Proxy.IsBypassed (request.RequestUri));
100                         ICredentials req_icreds = (!isProxy) ? request.Credentials : request.Proxy.Credentials;
101                         NetworkCredential req_cred = (req_icreds != null) ? req_icreds.GetCredential (request.RequestUri, "NTLM") : null;
102
103                         if (cnc_cred == null || req_cred == null ||
104                                 cnc_cred.Domain != req_cred.Domain || cnc_cred.UserName != req_cred.UserName ||
105                                 cnc_cred.Password != req_cred.Password) {
106                                 needs_reset = true;
107                         }
108
109                         if (!needs_reset) {
110                                 bool req_sharing = request.UnsafeAuthenticatedConnectionSharing;
111                                 bool cnc_sharing = cnc.UnsafeAuthenticatedConnectionSharing;
112                                 needs_reset = (req_sharing == false || req_sharing != cnc_sharing);
113                         }
114                         if (needs_reset) {
115                                 cnc.Close (false); // closes the authenticated connection
116                                 cnc.ResetNtlm ();
117                         }
118                 }
119
120                 ConnectionState FindIdleConnection ()
121                 {
122                         foreach (var cnc in connections) {
123                                 if (cnc.Busy  || cnc.Connection == null)
124                                         continue;
125
126                                 connections.Remove (cnc);
127                                 connections.AddFirst (cnc);
128                                 return cnc;
129                         }
130
131                         return null;
132                 }
133
134                 WebConnection CreateOrReuseConnection (HttpWebRequest request, out bool created)
135                 {
136                         var cnc = FindIdleConnection ();
137                         if (cnc != null) {
138                                 created = false;
139                                 PrepareSharingNtlm (cnc.Connection, request);
140                                 return cnc.Connection;
141                         }
142
143                         if (sPoint.ConnectionLimit > connections.Count) {
144                                 created = true;
145                                 cnc = new ConnectionState (this);
146                                 connections.AddFirst (cnc);
147                                 return cnc.Connection;
148                         }
149
150                         created = false;
151                         cnc = connections.Last.Value;
152                         connections.Remove (cnc);
153                         connections.AddFirst (cnc);
154                         return cnc.Connection;
155                 }
156
157                 public string Name {
158                         get { return name; }
159                 }
160
161                 internal Queue Queue {
162                         get { return queue; }
163                 }
164
165                 internal bool TryRecycle (TimeSpan maxIdleTime, ref DateTime idleSince)
166                 {
167                         var now = DateTime.UtcNow;
168
169                 again:
170                         bool recycled;
171                         List<WebConnection> connectionsToClose = null;
172
173                         lock (sPoint) {
174                                 if (closing) {
175                                         idleSince = DateTime.MinValue;
176                                         return true;
177                                 }
178
179                                 int count = 0;
180                                 for (var node = connections.First; node != null; node = node.Next) {
181                                         var cnc = node.Value;
182
183                                         if (cnc.Connection == null) {
184                                                 connections.Remove (node);
185                                                 OnConnectionClosed ();
186                                                 continue;
187                                         }
188
189                                         ++count;
190                                         if (cnc.Busy)
191                                                 continue;
192
193                                         if (count <= sPoint.ConnectionLimit && now - cnc.IdleSince < maxIdleTime) {
194                                                 if (cnc.IdleSince > idleSince)
195                                                         idleSince = cnc.IdleSince;
196                                                 continue;
197                                         }
198
199                                         /*
200                                          * Do not call WebConnection.Close() while holding the ServicePoint lock
201                                          * as this could deadlock when attempting to take the WebConnection lock.
202                                          * 
203                                          */
204
205                                         if (connectionsToClose == null)
206                                                 connectionsToClose = new List<WebConnection> ();
207                                         connectionsToClose.Add (cnc.Connection);
208                                         cnc.Connection = null;
209                                 }
210
211                                 recycled = connections.Count == 0;
212                         }
213
214                         // Did we find anything that can be closed?
215                         if (connectionsToClose == null)
216                                 return recycled;
217
218                         // Ok, let's get rid of these!
219                         foreach (var cnc in connectionsToClose)
220                                 cnc.Close (false);
221
222                         // Re-take the lock, then remove them from the connection list.
223                         goto again;
224                 }
225
226                 class ConnectionState : IWebConnectionState {
227                         public WebConnection Connection;
228
229                         public WebConnectionGroup Group {
230                                 get;
231                                 private set;
232                         }
233
234                         public ServicePoint ServicePoint {
235                                 get { return Group.sPoint; }
236                         }
237
238                         bool busy;
239                         DateTime idleSince;
240
241                         public bool Busy {
242                                 get { return busy; }
243                         }
244
245                         public DateTime IdleSince {
246                                 get { return idleSince; }
247                         }
248
249                         public bool TrySetBusy ()
250                         {
251                                 lock (ServicePoint) {
252                                         if (busy)
253                                                 return false;
254                                         busy = true;
255                                         idleSince = DateTime.UtcNow + TimeSpan.FromDays (3650);
256                                         return true;
257                                 }
258                         }
259
260                         public void SetIdle ()
261                         {
262                                 lock (ServicePoint) {
263                                         busy = false;
264                                         idleSince = DateTime.UtcNow;
265                                 }
266                         }
267
268                         public ConnectionState (WebConnectionGroup group)
269                         {
270                                 Group = group;
271                                 idleSince = DateTime.UtcNow;
272                                 Connection = new WebConnection (this, group.sPoint);
273                         }
274                 }
275                 
276         }
277 }
278