Ant*_*ton 11 .net memory-leaks ihttpasynchandler ihttphandler
我注意到.NET IHttpAsyncHandler(以及IHttpHandler,在较小程度上)在受到并发Web请求时泄漏内存.
在我的测试中,Visual Studio Web服务器(Cassini)从6MB内存跳到100MB以上,一旦测试完成,它们都没有被回收.
问题可以很容易地重现.使用两个项目创建一个新的解决方案(LeakyHandler):
在LeakyHandler.WebApp中:
在LeakyHandler.ConsoleApp中:
随着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
}
}
Run Code Online (Sandbox Code Playgroud)
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>
Run Code Online (Sandbox Code Playgroud)
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);
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
调试更新
我使用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
这些对象位于第2代堆中,即使在强制完全垃圾回收之后也不会收集这些对象.
重要的提示
即使在强制执行顺序请求时也存在问题,即使没有Thread.Sleep(10)
in ProcessRequest
,它也会更加微妙.这个例子通过使问题变得更加明显而加剧了问题,但基本原理是相同的.
Rob*_*ine 14
我已经看过你的代码(并运行它),我不相信你看到的增加的内存实际上是内存泄漏.
您遇到的问题是您的调用代码(控制台应用程序)基本上是在紧密循环中运行.
但是,你的处理程序必须处理每个请求,并且另外被"nobbled" Thread.Sleep(10)
.这样做的实际结果是你的处理程序无法跟上进来的请求,因此它的"工作集"随着更多请求排队等待处理而增长和增长.
我拿了你的代码,并在控制台应用程序中添加了一个AutoResetEvent
.WaitOne()
后 request.BeginGetResponse(GetResponseCallback, request);
和a
.Set()
后 streamReader.ReadToEnd();
这具有同步呼叫的效果,因此在第一次呼叫回叫(和完成)之前不能进行下一次呼叫.你看到的行为消失了.
总而言之,我认为这纯粹是失控的情况,实际上并不是内存泄漏.
注意:我在GetResponseCallback方法中使用以下内容监视内存:
GC.Collect();
GC.WaitForPendingFinalizers();
Console.WriteLine(GC.GetTotalMemory(true));
Run Code Online (Sandbox Code Playgroud)
[编辑回应安东的评论] 我并不是说这里没有问题.如果你的使用场景使得处理程序的这种锤击是真正的使用场景,那么显然你有一个问题.我的观点是,它不是一个记忆泄漏问题,而是容量问题.解决这个问题的方法可能是编写一个可以更快运行的处理程序,或者扩展到多个服务器等等.
泄漏是在资源完成后保留资源,增加工作集的大小.这些资源尚未"完成",它们处于队列中并等待服务.一旦完成,我相信它们正在被正确发布.
[编辑回应安东的进一步评论] 好的 - 我发现了一些东西!我认为这是一个在IIS下不会发生的Cassini问题.您是否在Cassini(Visual Studio开发Web服务器)下运行处理程序?
当我在Cassini下运行时,我也看到这些泄漏的System.Runtime.Remoting命名空间实例.如果我将处理程序设置为在IIS下运行,我看不到它们.你能否确认一下你的情况?
这让我想起了我见过的其他远程/卡西尼问题.IIRC,有一个类似IPrincipal的实例,需要存在于模块的BeginRequest中,并且在模块生命周期结束时,需要从Cassini中的MarshalByRefObject派生而不是IIS.出于某种原因,似乎卡西尼在内部进行了一些远程操作,而IIS则没有.
归档时间: |
|
查看次数: |
2464 次 |
最近记录: |