A tale of threads

Today's lesson is about thread interaction between Asp.Net and the HttpWebRequest object of the System.Net namespace.

The CLR provides a threadpool. This facility provides threads for normal work ( worker threads ), I/O work ( I/O threads) and Timer Threads. Normally, the limit of threads for managed processes is set as follows:

WorkerThreads: 25 per CPU

I/O Threads: 1000

Asp.Net uses worker threads to dispatch incoming requests to page handlers (ASPX/ASMX etc). If you write an ASPX page, your request will run on a worker thread.

It turns out that System.Net also uses the threadpool. Worker Threads are used to kick of connect's to servers, and I/O threads are used to service I/O completions for Sockets.

Normally, people create middle tier applications by creating a WebRequest inside of their Page_Load(), and then doing a GetResponse(). Normally, a synchronous function (like GetResponse()) would complete on the same thread on which it was invoked. However, GetResponse() is actually implemented internally as follows:

void GetResponse() {

IAsyncResult ar = BeginGetResponse(null,null);

ar.WaitOne();

return EndGetResponse(ar);

}

It so happens that while you are waiting for the operation to complete, another thread is being spawned iternally to service the request, by kicking off the connect to the server, parsing the response etc.

If you have a page which is under high load, then you can run into potential deadlock issues. The user's code is running on a worker thread. When the user does a GetResponse(), another threadpool worker thread is used internally to kick off the operation. Imagine that there are 25 threadpool worker threads which are executing asp.net code (eg: page handlers etc). One of these threads is executing a page which is trying to do the webrequest.GetResponse().

System.Net will do a ThreadPool.QueueUserWorkItem() to complete the GetResponse(). However, since the CLR threadpool is exhausted ( uniprocessor limit is 25), CLR will not create a new thread to service the request. If no thread becomes available, then the GetResponse() will never complete. At the same time, the worker thread that asp.net is running the user code on, will not complete either. Now multiply this situation across all threads, and you have a classic deadlock.

This problem is mitigated somewhat in the V1 & V1.1 versions of the framework. There, System.Net will query the threadpool for available threads. If it determines that there are no free threads available to service the request, it will throw an exception, so that the users code can handle it.

Note that this problem can also happen in standalone applications.

So, the next time you get an exception from GetResponse() or GetRequestStream(), saying that “There are no available free threads to service this operation“ it is by design :-)