//
// Authors:
// Gonzalo Paniagua Javier (gonzalo@ximian.com)
+// Martin Baulig (martin.baulig@xamarin.com)
//
// (C) 2003 Ximian, Inc (http://www.ximian.com)
+// Copyright 2011-2014 Xamarin, Inc (http://www.xamarin.com)
//
//
//
using System;
+using System.Threading;
using System.Collections;
+using System.Collections.Generic;
using System.Net.Configuration;
using System.Net.Sockets;
+using System.Diagnostics;
namespace System.Net
{
{
ServicePoint sPoint;
string name;
- ArrayList connections;
- Random rnd;
+ LinkedList<ConnectionState> connections;
Queue queue;
+ bool closing;
public WebConnectionGroup (ServicePoint sPoint, string name)
{
this.sPoint = sPoint;
this.name = name;
- connections = new ArrayList (1);
+ connections = new LinkedList<ConnectionState> ();
queue = new Queue ();
}
- public WebConnection GetConnection ()
+ public event EventHandler ConnectionClosed;
+
+ void OnConnectionClosed ()
{
- WebConnection cnc = null;
- lock (connections) {
- WeakReference cncRef = null;
-
- // Remove disposed connections
- int end = connections.Count;
- ArrayList removed = null;
- for (int i = 0; i < end; i++) {
- cncRef = (WeakReference) connections [i];
- cnc = cncRef.Target as WebConnection;
- if (cnc == null) {
- if (removed == null)
- removed = new ArrayList (1);
-
- removed.Add (i);
- }
- }
+ if (ConnectionClosed != null)
+ ConnectionClosed (this, null);
+ }
- if (removed != null) {
- for (int i = removed.Count - 1; i >= 0; i--)
- connections.RemoveAt ((int) removed [i]);
+ public void Close ()
+ {
+ //TODO: what do we do with the queue? Empty it out and abort the requests?
+ //TODO: abort requests or wait for them to finish
+ lock (sPoint) {
+ closing = true;
+ foreach (var cnc in connections) {
+ if (cnc.Connection == null)
+ continue;
+ cnc.Connection.Close (false);
+ cnc.Connection = null;
+ OnConnectionClosed ();
}
-
- cnc = CreateOrReuseConnection ();
+ connections.Clear ();
}
+ }
- return cnc;
+ public WebConnection GetConnection (HttpWebRequest request, out bool created)
+ {
+ lock (sPoint) {
+ return CreateOrReuseConnection (request, out created);
+ }
}
- WebConnection CreateOrReuseConnection ()
+ static void PrepareSharingNtlm (WebConnection cnc, HttpWebRequest request)
{
- // lock is up there.
- WebConnection cnc;
- WeakReference cncRef;
-
- int count = connections.Count;
- for (int i = 0; i < count; i++) {
- WeakReference wr = connections [i] as WeakReference;
- cnc = wr.Target as WebConnection;
- if (cnc == null) {
- connections.RemoveAt (i);
- count--;
- i--;
- continue;
- }
+ if (!cnc.NtlmAuthenticated)
+ return;
- if (cnc.Busy)
- continue;
+ bool needs_reset = false;
+ NetworkCredential cnc_cred = cnc.NtlmCredential;
- return cnc;
+ bool isProxy = (request.Proxy != null && !request.Proxy.IsBypassed (request.RequestUri));
+ ICredentials req_icreds = (!isProxy) ? request.Credentials : request.Proxy.Credentials;
+ NetworkCredential req_cred = (req_icreds != null) ? req_icreds.GetCredential (request.RequestUri, "NTLM") : null;
+
+ if (cnc_cred == null || req_cred == null ||
+ cnc_cred.Domain != req_cred.Domain || cnc_cred.UserName != req_cred.UserName ||
+ cnc_cred.Password != req_cred.Password) {
+ needs_reset = true;
}
- if (sPoint.ConnectionLimit > count) {
- cnc = new WebConnection (this, sPoint);
- connections.Add (new WeakReference (cnc));
+ if (!needs_reset) {
+ bool req_sharing = request.UnsafeAuthenticatedConnectionSharing;
+ bool cnc_sharing = cnc.UnsafeAuthenticatedConnectionSharing;
+ needs_reset = (req_sharing == false || req_sharing != cnc_sharing);
+ }
+ if (needs_reset) {
+ cnc.Close (false); // closes the authenticated connection
+ cnc.ResetNtlm ();
+ }
+ }
+
+ ConnectionState FindIdleConnection ()
+ {
+ foreach (var cnc in connections) {
+ if (cnc.Busy || cnc.Connection == null)
+ continue;
+
+ connections.Remove (cnc);
+ connections.AddFirst (cnc);
return cnc;
}
- if (rnd == null)
- rnd = new Random ();
+ return null;
+ }
+
+ WebConnection CreateOrReuseConnection (HttpWebRequest request, out bool created)
+ {
+ var cnc = FindIdleConnection ();
+ if (cnc != null) {
+ created = false;
+ PrepareSharingNtlm (cnc.Connection, request);
+ return cnc.Connection;
+ }
- int idx = (count > 1) ? rnd.Next (0, count - 1) : 0;
- cncRef = (WeakReference) connections [idx];
- cnc = cncRef.Target as WebConnection;
- if (cnc == null) {
- cnc = new WebConnection (this, sPoint);
- connections.RemoveAt (idx);
- connections.Add (new WeakReference (cnc));
+ if (sPoint.ConnectionLimit > connections.Count) {
+ created = true;
+ cnc = new ConnectionState (this);
+ connections.AddFirst (cnc);
+ return cnc.Connection;
}
- return cnc;
+
+ created = false;
+ cnc = connections.Last.Value;
+ connections.Remove (cnc);
+ connections.AddFirst (cnc);
+ return cnc.Connection;
}
public string Name {
internal Queue Queue {
get { return queue; }
}
+
+ internal bool TryRecycle (TimeSpan maxIdleTime, ref DateTime idleSince)
+ {
+ var now = DateTime.UtcNow;
+
+ again:
+ bool recycled;
+ List<WebConnection> connectionsToClose = null;
+
+ lock (sPoint) {
+ if (closing) {
+ idleSince = DateTime.MinValue;
+ return true;
+ }
+
+ int count = 0;
+ for (var node = connections.First; node != null; node = node.Next) {
+ var cnc = node.Value;
+
+ if (cnc.Connection == null) {
+ connections.Remove (node);
+ OnConnectionClosed ();
+ continue;
+ }
+
+ ++count;
+ if (cnc.Busy)
+ continue;
+
+ if (count <= sPoint.ConnectionLimit && now - cnc.IdleSince < maxIdleTime) {
+ if (cnc.IdleSince > idleSince)
+ idleSince = cnc.IdleSince;
+ continue;
+ }
+
+ /*
+ * Do not call WebConnection.Close() while holding the ServicePoint lock
+ * as this could deadlock when attempting to take the WebConnection lock.
+ *
+ */
+
+ if (connectionsToClose == null)
+ connectionsToClose = new List<WebConnection> ();
+ connectionsToClose.Add (cnc.Connection);
+ cnc.Connection = null;
+ }
+
+ recycled = connections.Count == 0;
+ }
+
+ // Did we find anything that can be closed?
+ if (connectionsToClose == null)
+ return recycled;
+
+ // Ok, let's get rid of these!
+ foreach (var cnc in connectionsToClose)
+ cnc.Close (false);
+
+ // Re-take the lock, then remove them from the connection list.
+ goto again;
+ }
+
+ class ConnectionState : IWebConnectionState {
+ public WebConnection Connection;
+
+ public WebConnectionGroup Group {
+ get;
+ private set;
+ }
+
+ public ServicePoint ServicePoint {
+ get { return Group.sPoint; }
+ }
+
+ bool busy;
+ DateTime idleSince;
+
+ public bool Busy {
+ get { return busy; }
+ }
+
+ public DateTime IdleSince {
+ get { return idleSince; }
+ }
+
+ public bool TrySetBusy ()
+ {
+ lock (ServicePoint) {
+ if (busy)
+ return false;
+ busy = true;
+ idleSince = DateTime.UtcNow + TimeSpan.FromDays (3650);
+ return true;
+ }
+ }
+
+ public void SetIdle ()
+ {
+ lock (ServicePoint) {
+ busy = false;
+ idleSince = DateTime.UtcNow;
+ }
+ }
+
+ public ConnectionState (WebConnectionGroup group)
+ {
+ Group = group;
+ idleSince = DateTime.UtcNow;
+ Connection = new WebConnection (this, group.sPoint);
+ }
+ }
}
}