Я заметил, что утечка памяти .NET IHttpAsyncHandler (и IHttpHandler, в меньшей степени) при одновременном веб-запросе.
В моих тестах веб-сервер Visual Studio (Cassini) перескакивает с 6 Мб памяти на более чем 100 МБ, и как только тест закончен, ни один из них не восстанавливается.
Проблема может быть легко воспроизведена. Создайте новое решение (LeakyHandler) с двумя проектами:
- Веб-приложение ASP.NET(LeakyHandler.WebApp)
- Консольное приложение (LeakyHandler.ConsoleApp)
В LeakyHandler.WebApp:
- Создайте класс TestHandler, который реализует IHttpAsyncHandler.
- В обработке запроса выполните короткий Сон и завершите ответ.
- Добавить обработчик HTTP в Web.config как test.ashx.
В LeakyHandler.ConsoleApp:
- Создайте большое количество HttpWebRequests для test.ashx и выполните их асинхронно.
По мере увеличения количества HttpWebRequests (sampleSize) утечка памяти становится все более очевидной.
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);
}
}
}
}
Отладка обновления
Я использовал WinDbg для просмотра файлов дампа, и несколько подозрительных типов хранятся в памяти и никогда не выпускаются. Каждый раз, когда я запускаю тест с размером выборки в 10 000, я заканчиваю тем, что еще 10 000 этих объектов хранятся в памяти.
-
System.Runtime.Remoting.ServerIdentity
-
System.Runtime.Remoting.ObjRef
-
Microsoft.VisualStudio.WebHost.Connection
-
System.Runtime.Remoting.Messaging.StackBuilderSink
-
System.Runtime.Remoting.ChannelInfo
-
System.Runtime.Remoting.Messaging.ServerObjectTerminatorSink
Эти объекты лежат в куче Generation 2 и не собираются даже после принудительной полной сборки мусора.
Важное примечание
Проблема существует даже при форсировании последовательных запросов и даже без Thread.Sleep(10)
в ProcessRequest
, она намного более тонкая. Пример усугубляет проблему, делая ее более очевидной, но основные принципы одинаковы.