Why does IHttpAsyncHandler leak memory under load?
- by Anton
I have noticed that the .NET IHttpAsyncHandler (and the IHttpHandler, to a lesser degree) leak memory when subjected to concurrent web requests.
In my tests, the development web server (Cassini) jumps from 6MB memory to over 100MB, and once the test is finished, none of it is reclaimed.
The problem can be reproduced easily. Create a new solution (LeakyHandler) with two projects:
An ASP.NET web application (LeakyHandler.WebApp)
A Console application (LeakyHandler.ConsoleApp)
In LeakyHandler.WebApp:
Create a class called TestHandler that implements IHttpAsyncHandler.
In the request processing, do a brief Sleep and end the response.
Add the HTTP handler to Web.config as test.ashx.
In LeakyHandler.ConsoleApp:
Generate a large number of HttpWebRequests to test.ashx and execute them asynchronously.
As the number of HttpWebRequests (sampleSize) is increased, the memory leak is made more and more apparent.
LeakyHandler.WebApp TestHandler.cs
namespace LeakyHandler.WebApp
{
public class TestHandler : IHttpAsyncHandler
{
#region IHttpAsyncHandler Members
private ProcessRequestDelegate Delegate { get; set; }
public delegate void ProcessRequestDelegate(HttpContext context);
public IAsyncResult BeginProcessRequest(HttpContext context, AsyncCallback cb, object extraData)
{
Delegate = ProcessRequest;
return Delegate.BeginInvoke(context, cb, extraData);
}
public void EndProcessRequest(IAsyncResult result)
{
Delegate.EndInvoke(result);
}
#endregion
#region IHttpHandler Members
public bool IsReusable
{
get { return true; }
}
public void ProcessRequest(HttpContext context)
{
Thread.Sleep(10);
context.Response.End();
}
#endregion
}
}
LeakyHandler.WebApp Web.config
<?xml version="1.0"?>
<configuration>
<system.web>
<compilation debug="false" />
<httpHandlers>
<add verb="POST" path="test.ashx" type="LeakyHandler.WebApp.TestHandler" />
</httpHandlers>
</system.web>
</configuration>
LeakyHandler.ConsoleApp Program.cs
namespace LeakyHandler.ConsoleApp
{
class Program
{
private static int sampleSize = 10000;
private static int startedCount = 0;
private static int completedCount = 0;
static void Main(string[] args)
{
Console.WriteLine("Press any key to start.");
Console.ReadKey();
string url = "http://localhost:3000/test.ashx";
for (int i = 0; i < sampleSize; i++)
{
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
request.Method = "POST";
request.BeginGetResponse(GetResponseCallback, request);
Console.WriteLine("S: " + Interlocked.Increment(ref startedCount));
}
Console.ReadKey();
}
static void GetResponseCallback(IAsyncResult result)
{
HttpWebRequest request = (HttpWebRequest)result.AsyncState;
HttpWebResponse response = (HttpWebResponse)request.EndGetResponse(result);
try
{
using (Stream stream = response.GetResponseStream())
{
using (StreamReader streamReader = new StreamReader(stream))
{
streamReader.ReadToEnd();
System.Console.WriteLine("C: " + Interlocked.Increment(ref completedCount));
}
}
response.Close();
}
catch (Exception ex)
{
System.Console.WriteLine("Error processing response: " + ex.Message);
}
}
}
}