Why does IHttpAsyncHandler leak memory under load?

Posted by Anton on Stack Overflow See other posts from Stack Overflow or by Anton
Published on 2010-05-12T22:58:40Z Indexed on 2010/05/13 20:24 UTC
Read the original article Hit count: 531

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:

  1. An ASP.NET web application (LeakyHandler.WebApp)
  2. A Console application (LeakyHandler.ConsoleApp)

In LeakyHandler.WebApp:

  1. Create a class called TestHandler that implements IHttpAsyncHandler.
  2. In the request processing, do a brief Sleep and end the response.
  3. Add the HTTP handler to Web.config as test.ashx.

In LeakyHandler.ConsoleApp:

  1. 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);
            }
        }
    }
}

© Stack Overflow or respective owner

Related posts about memory-leaks

Related posts about .NET