//
using System;
+using System.Collections.Generic;
+using System.Diagnostics;
using System.Collections;
using System.Net.Sockets;
using System.Security.Cryptography.X509Certificates;
X509Certificate clientCertificate;
IPHostEntry host;
bool usesProxy;
- Hashtable groups;
+ WebConnectionGroup firstGroup;
+ Dictionary<string,WebConnectionGroup> groups;
bool sendContinue = true;
bool useConnect;
- object locker = new object ();
object hostE = new object ();
-#if NET_1_1
bool useNagle;
-#endif
-#if NET_2_0
BindIPEndPoint endPointCallback = null;
-#endif
-
+ bool tcp_keepalive;
+ int tcp_keepalive_time;
+ int tcp_keepalive_interval;
+ Timer idleTimer;
+
// Constructors
internal ServicePoint (Uri uri, int connectionLimit, int maxIdleTime)
{
this.uri = uri;
this.connectionLimit = connectionLimit;
- this.maxIdleTime = maxIdleTime;
+ this.maxIdleTime = maxIdleTime;
this.currentConnections = 0;
- this.idleSince = DateTime.Now;
+ this.idleSince = DateTime.UtcNow;
}
// Properties
get { return uri; }
}
-#if NET_2_0
static Exception GetMustImplement ()
{
return new NotImplementedException ();
get { return endPointCallback; }
set { endPointCallback = value; }
}
-#endif
public X509Certificate Certificate {
get { return certificate; }
get { return clientCertificate; }
}
-#if NET_2_0
[MonoTODO]
public int ConnectionLeaseTimeout
{
throw GetMustImplement ();
}
}
-#endif
public int ConnectionLimit {
get { return connectionLimit; }
public DateTime IdleSince {
get {
- return idleSince;
- }
- internal set {
- lock (locker)
- idleSince = value;
+ return idleSince.ToLocalTime ();
}
}
-
+
public int MaxIdleTime {
get { return maxIdleTime; }
set {
if (value < Timeout.Infinite || value > Int32.MaxValue)
throw new ArgumentOutOfRangeException ();
- this.maxIdleTime = value;
+
+ lock (this) {
+ maxIdleTime = value;
+ if (idleTimer != null)
+ idleTimer.Change (maxIdleTime, maxIdleTime);
+ }
}
}
get { return protocolVersion; }
}
-#if NET_2_0
[MonoTODO]
public int ReceiveBufferSize
{
throw GetMustImplement ();
}
}
-#endif
public bool SupportsPipelining {
get { return HttpVersion.Version11.Equals (protocolVersion); }
}
-#if NET_1_1
+
public bool Expect100Continue {
get { return SendContinue; }
set { SendContinue = value; }
}
- [MonoTODO ("Use me")]
public bool UseNagleAlgorithm {
get { return useNagle; }
set { useNagle = value; }
}
-#endif
internal bool SendContinue {
get { return sendContinue &&
set { sendContinue = value; }
}
// Methods
-
-#if !NET_2_0
- public override int GetHashCode()
+
+ public void SetTcpKeepAlive (bool enabled, int keepAliveTime, int keepAliveInterval)
{
- return base.GetHashCode ();
+ if (enabled) {
+ if (keepAliveTime <= 0)
+ throw new ArgumentOutOfRangeException ("keepAliveTime", "Must be greater than 0");
+ if (keepAliveInterval <= 0)
+ throw new ArgumentOutOfRangeException ("keepAliveInterval", "Must be greater than 0");
+ }
+
+ tcp_keepalive = enabled;
+ tcp_keepalive_time = keepAliveTime;
+ tcp_keepalive_interval = keepAliveInterval;
}
-#endif
-
+
+ internal void KeepAliveSetup (Socket socket)
+ {
+ if (!tcp_keepalive)
+ return;
+
+ byte [] bytes = new byte [12];
+ PutBytes (bytes, (uint) (tcp_keepalive ? 1 : 0), 0);
+ PutBytes (bytes, (uint) tcp_keepalive_time, 4);
+ PutBytes (bytes, (uint) tcp_keepalive_interval, 8);
+ socket.IOControl (IOControlCode.KeepAliveValues, bytes, null);
+ }
+
+ static void PutBytes (byte [] bytes, uint v, int offset)
+ {
+ if (BitConverter.IsLittleEndian) {
+ bytes [offset] = (byte) (v & 0x000000ff);
+ bytes [offset + 1] = (byte) ((v & 0x0000ff00) >> 8);
+ bytes [offset + 2] = (byte) ((v & 0x00ff0000) >> 16);
+ bytes [offset + 3] = (byte) ((v & 0xff000000) >> 24);
+ } else {
+ bytes [offset + 3] = (byte) (v & 0x000000ff);
+ bytes [offset + 2] = (byte) ((v & 0x0000ff00) >> 8);
+ bytes [offset + 1] = (byte) ((v & 0x00ff0000) >> 16);
+ bytes [offset] = (byte) ((v & 0xff000000) >> 24);
+ }
+ }
+
// Internal Methods
internal bool UsesProxy {
set { useConnect = value; }
}
- internal bool AvailableForRecycling {
- get {
- return CurrentConnections == 0
- && maxIdleTime != Timeout.Infinite
- && DateTime.Now >= IdleSince.AddMilliseconds (maxIdleTime);
+ WebConnectionGroup GetConnectionGroup (string name)
+ {
+ if (name == null)
+ name = "";
+
+ /*
+ * Optimization:
+ *
+ * In the vast majority of cases, we only have one single WebConnectionGroup per ServicePoint, so we
+ * don't need to allocate a dictionary.
+ *
+ */
+
+ WebConnectionGroup group;
+ if (firstGroup != null && name == firstGroup.Name)
+ return firstGroup;
+ if (groups != null && groups.TryGetValue (name, out group))
+ return group;
+
+ group = new WebConnectionGroup (this, name);
+ group.ConnectionClosed += (s, e) => currentConnections--;
+
+ if (firstGroup == null)
+ firstGroup = group;
+ else {
+ if (groups == null)
+ groups = new Dictionary<string, WebConnectionGroup> ();
+ groups.Add (name, group);
}
+
+ return group;
}
- internal Hashtable Groups {
- get {
- if (groups == null)
- groups = new Hashtable ();
+ void RemoveConnectionGroup (WebConnectionGroup group)
+ {
+ if (groups == null || groups.Count == 0) {
+ // No more connection groups left.
+ if (group != firstGroup)
+ throw new InvalidOperationException ();
+ else
+ firstGroup = null;
+ return;
+ }
+
+ if (group == firstGroup) {
+ // Steal one entry from the dictionary.
+ var en = groups.GetEnumerator ();
+ en.MoveNext ();
+ firstGroup = en.Current.Value;
+ groups.Remove (en.Current.Key);
+ } else {
+ groups.Remove (group.Name);
+ }
+ }
+
+ internal bool CheckAvailableForRecycling (out DateTime outIdleSince)
+ {
+ outIdleSince = DateTime.MinValue;
+
+ TimeSpan idleTimeSpan;
+ WebConnectionGroup singleGroup, singleRemove = null;
+ List<WebConnectionGroup> groupList = null, removeList = null;
+ lock (this) {
+ if (firstGroup == null) {
+ idleSince = DateTime.MinValue;
+ return true;
+ }
- return groups;
+ idleTimeSpan = TimeSpan.FromMilliseconds (maxIdleTime);
+
+ /*
+ * WebConnectionGroup.TryRecycle() must run outside the lock, so we need to
+ * copy the group dictionary if it exists.
+ *
+ * In most cases, we only have a single connection group, so we can simply store
+ * that in a local variable instead of copying a collection.
+ *
+ */
+
+ singleGroup = firstGroup;
+ if (groups != null)
+ groupList = new List<WebConnectionGroup> (groups.Values);
+ }
+
+ if (singleGroup.TryRecycle (idleTimeSpan, ref outIdleSince))
+ singleRemove = singleGroup;
+
+ if (groupList != null) {
+ foreach (var group in groupList) {
+ if (!group.TryRecycle (idleTimeSpan, ref outIdleSince))
+ continue;
+ if (removeList == null)
+ removeList = new List<WebConnectionGroup> ();
+ removeList.Add (group);
+ }
}
+
+ lock (this) {
+ idleSince = outIdleSince;
+
+ if (singleRemove != null)
+ RemoveConnectionGroup (singleRemove);
+
+ if (removeList != null) {
+ foreach (var group in removeList)
+ RemoveConnectionGroup (group);
+ }
+
+ if (groups != null && groups.Count == 0)
+ groups = null;
+
+ if (firstGroup == null) {
+ idleTimer.Dispose ();
+ idleTimer = null;
+ return true;
+ }
+
+ return false;
+ }
+ }
+
+ void IdleTimerCallback (object obj)
+ {
+ DateTime dummy;
+ CheckAvailableForRecycling (out dummy);
}
internal IPHostEntry HostEntry
{
protocolVersion = version;
}
-#if !TARGET_JVM && !NET_2_1
- WebConnectionGroup GetConnectionGroup (string name)
- {
- if (name == null)
- name = "";
-
- WebConnectionGroup group = Groups [name] as WebConnectionGroup;
- if (group != null)
- return group;
-
- group = new WebConnectionGroup (this, name);
- Groups [name] = group;
- return group;
- }
+#if !TARGET_JVM
internal EventHandler SendRequest (HttpWebRequest request, string groupName)
{
WebConnection cnc;
- lock (locker) {
+ lock (this) {
+ bool created;
WebConnectionGroup cncGroup = GetConnectionGroup (groupName);
- cnc = cncGroup.GetConnection (request);
+ cnc = cncGroup.GetConnection (request, out created);
+ if (created) {
+ ++currentConnections;
+ if (idleTimer == null)
+ idleTimer = new Timer (IdleTimerCallback, null, maxIdleTime, maxIdleTime);
+ }
}
return cnc.SendRequest (request);
}
#endif
-#if NET_2_0
- [MonoNotSupported ("")]
public bool CloseConnectionGroup (string connectionGroupName)
{
- throw new NotImplementedException ();
- }
-#endif
-
- internal void IncrementConnection ()
- {
- lock (locker) {
- currentConnections++;
- idleSince = DateTime.Now.AddMilliseconds (1000000);
+ lock (this) {
+ WebConnectionGroup cncGroup = GetConnectionGroup (connectionGroupName);
+ if (cncGroup != null) {
+ cncGroup.Close ();
+ return true;
+ }
}
- }
- internal void DecrementConnection ()
- {
- lock (locker) {
- currentConnections--;
- if (currentConnections == 0)
- idleSince = DateTime.Now;
- }
+ return false;
}
internal void SetCertificates (X509Certificate client, X509Certificate server)
clientCertificate = client;
}
-#if NET_2_0
internal bool CallEndPointDelegate (Socket sock, IPEndPoint remote)
{
if (endPointCallback == null)
return true;
}
}
-#endif
}
}