Geeks Diary

"...momentous encounters in my life..."

Connection management - HTTP

Unlike the server application developers who used Winsock API directly in their code; now most of us rely on different abstractions like WCF to deal with Winsock API (and yes, this is a lot better). Each connection opened between the client and service consumes their resources (each tcp connection requires space for its kernel level structures for example). Therefore it's nice to have a good understanding of when they come and go if you want to build scalable solutions. Consequently I thought I would share some of my experience in connection management with you and this is my first attempt to discuss some subtleties in http connection management in services and clients.

When you use http transport WCF uses System.Net API  in client and http.sys API in the server internally for the underlying communication. i.e. WCF does not directly deal with sockets like it does in tcp transport.

Let's first take a look at what happens in the client side. When you invoke a service method in the proxy class instance, underlying http transport channel uses the API in System.Net.HttpWebRequest class to send the http request to the server. It also creates a System.Net.ServicePoint to manage the connections to the specific server resource. You can think of this ServicePoint instance as a connection pool for http connections. Therefore the actual lifetime of a connection depends on the corresponding ServicePoint settings. For example, the first impression you get on the following code snippet is that the underlying connection is closed and all resources are claimed after the last line.

string uri = "http://localhost:8000/service";
BasicHttpBinding clientBinding = new BasicHttpBinding();
ChannelFactory<IFoo> cf = new ChannelFactory<IFoo>(clientBinding, uri);
IFoo f = cf.CreateChannel();
f.DoSomething();
((IClientChannel)f).Close();
cf.Close();

But if you take a closer look using a tool like tcpview.exe you will notice that the connection will remain open for little bit longer even after closing both channel and channel factory.

HttpConnections

Reason for this is the connection pooling done by the corresponding ServicePoint as I mentioned before. By default ServicePoint sets the MaxIdleTimeout of a connection to 100000 milliseconds and the connections will be closed after 10000 milliseconds without any activity. So in some cases you may want to tweak this value to reach the optimum results. For example, if you have a client that connects to the service once in a blue moon and makes only one or two calls to the service, you might want close the connection and release the resources as soon as you are done. You can do this by setting the desired timeout value to ServicePointManager.MaxServicePointIdleTime property. However it's important to be aware of two things before hand. 1. You have to set this value prior to creating any channels or channel factories in your code. 2. this value will be used as the idle timeout for all ServicePoints created afterwards (even for different endpoints). One way to eliminate these two would be getting your hands on the specific ServicePoint as shown below. Then again, you must ensure that you call FindServicePoint method after calling the service method as runtime creates this ServicePoint during the first call to the service.

string uri = "http://localhost:8000/service";
BasicHttpBinding clientBinding = new BasicHttpBinding();
ChannelFactory<IFoo> cf = new ChannelFactory<IFoo>(clientBinding, uri);                        
IFoo f = cf.CreateChannel();            
f.DoSomething();
ServicePoint sp = ServicePointManager.FindServicePoint(new Uri(uri));
sp.MaxIdleTime = 1000 * 10; // Set idle timeout to 10 seconds
((IClientChannel)f).Close();
cf.Close();

Having this redundant code would be probably painful. So I could compile it to a little WCF extension hooked up as a custom operation behaviour like this.

[AttributeUsage(AttributeTargets.Interface | AttributeTargets.Method)]
public class SetHttpConnectionIdleTimeoutAttribute : Attribute, IOperationBehavior
{
    private int timeout;

    public int Timeout
    {
        get
        {
            return timeout;
        }
        set
        {
            if (value < 0)
            {
                throw new InvalidOperationException("Timeout must be a positive integer.");
            }
            timeout = value;
        }
    }

    public void AddBindingParameters(OperationDescription operationDescription, 
        BindingParameterCollection bindingParameters)
    {
        // nop
    }

    public void ApplyClientBehavior(OperationDescription operationDescription, 
        ClientOperation clientOperation)
    {
        ConnectionManager cm = AddOrFindConnectionManager(clientOperation.Parent);
        cm.EnlistOperation(clientOperation, timeout);
    }

    public void ApplyDispatchBehavior(OperationDescription operationDescription, 
        DispatchOperation dispatchOperation)
    {
        // nop
    }

    public void Validate(OperationDescription operationDescription)
    {
        // nop
    }

    private ConnectionManager AddOrFindConnectionManager(ClientRuntime clientRuntime)
    {
        ConnectionManager cm = null;
        foreach (IClientMessageInspector mi in clientRuntime.MessageInspectors)
        {
            if (typeof(ConnectionManager) == mi.GetType())
            {
                cm = (ConnectionManager)mi;
                break;
            }
        }

        if (cm == null)
        {
            cm = new ConnectionManager();
            clientRuntime.MessageInspectors.Add(cm);
        }
        return cm;
    }
}

internal class ConnectionManager : IClientMessageInspector
{
    class RequestInfo
    {
        private string action;
        private Uri remoteAddress;

        public RequestInfo(string action, Uri remoteAddress)
        {
            this.action = action;
            this.remoteAddress = remoteAddress;
        }

        public string Action
        {
            get { return action; }
            set { action = value; }
        }

        public Uri RemoteAddress
        {
            get { return remoteAddress; }
            set { remoteAddress = value; }
        }
    }

    Dictionary<string, int> registeredOperations = new Dictionary<string, int>();

    public void AfterReceiveReply(ref Message reply, object correlationState)
    {
        RequestInfo ri = (RequestInfo)correlationState;
        ServicePoint servicePoint = ServicePointManager.FindServicePoint(ri.RemoteAddress);
        if (servicePoint != null)
        {
            Debug.Assert(registeredOperations.ContainsKey(ri.Action));
            int timeout = registeredOperations[ri.Action];
            servicePoint.MaxIdleTime = timeout;
        }
    }

    public object BeforeSendRequest(ref Message request, IClientChannel channel)
    {
        return new RequestInfo(request.Headers.Action, channel.RemoteAddress.Uri);
    }

    public void EnlistOperation(ClientOperation clientOperation, int timeout)
    {
        if (!registeredOperations.ContainsKey(clientOperation.Action))
        {
            registeredOperations.Add(clientOperation.Action, timeout);
        }
    }
}

Now this extension can be used as an attribute in your operation contracts as shown below.

[ServiceContract]
interface IFoo
{
    [OperationContract]
    [SetHttpConnectionIdleTimeout(Timeout=5000)]
    void DoSomething();
}

If you want to close your connection as soon as you are done, make sure that you set your timeout to 0. But once again, keep in mind that reducing the timeout without a careful thought can surely lead into poor performance. Do it only if you really want to do it.

Alright... we talked about how we can manage the connections on the client side. So what about the service side? In some cases you may want to reduce the idle timeout in the server. This is sometimes essential when you are not controlling clients (the problem that drew my attention to this post was exactly that Wink). In the service side, I still could not find a way to get the hands on the underlying connections like we did in the client side. However, you could always configure the HTTP runtime using provided tooling. For example, in Windows Vista you can use following netsh command to reduce the timeout to 20 seconds from its 120 seconds default value.

netsh http add timeout timeouttype=idleconnectiontimeout value=20

Furthermore you could use the following command to view the existing timeouts.

netsh http show timeout

It's a bit unfortunate that changing idle timeout using netsh causes the whole http runtime to change though. I really wish that there will be a feature in the future to control this per registered URL basis.

Posted: Dec 22 2007, 02:35 PM by Buddhike | with 1 comment(s)
Filed under:

Comments

Good Credit said:

Hi! Need advice. There is no one else to ask. My credit is bad (very bad) and I am eager to apply for a card. And I have found two variants for me at one website: secured credit cards and unsecured cards for bad credit. I’d like to make the right choice therefore I am searching for advice or experience. And hope that you answer me as soon as possible.

<a href= carditsimmediatly.cn/discover-platinum-lifetime-apr.html >discover platinum 0% lifetime apr</a>

R56ma87de

# January 4, 2008 9:01 PM