gpt4 book ai didi

.net - 为什么 IHttpAsyncHandler 在负载下会泄漏内存?

转载 作者:行者123 更新时间:2023-12-04 01:53:10 27 4
gpt4 key购买 nike

我注意到 .NET IHttpAsyncHandler(和 IHttpHandler,在较小程度上)在受到并发 Web 请求时会泄漏内存。

在我的测试中,Visual Studio Web 服务器 (Cassini) 的内存从 6MB 跃升至 100MB 以上,并且一旦测试完成,就没有一个内存被回收。

问题很容易重现。使用两个项目创建一个新的解决方案 (LeakyHandler):

  • 一个 ASP.NET Web 应用程序 (LeakyHandler.WebApp)
  • 控制台应用程序 (LeakyHandler.ConsoleApp)

  • 在 LeakyHandler.WebApp 中:
  • 创建一个实现 IHttpAsyncHandler 的名为 TestHandler 的类。
  • 在请求处理中,做一个简短的 Sleep 并结束响应。
  • 将 HTTP 处理程序作为 test.ashx 添加到 Web.config。

  • 在 LeakyHandler.ConsoleApp 中:
  • 生成大量 HttpWebRequest 到 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

  • 这些对象位于第 2 代堆中并且不会被收集,即使在强制完全垃圾收集之后也是如此。

    重要提示

    即使在强制顺序请求时,即使没有 Thread.Sleep(10) 也存在问题。在 ProcessRequest , 它只是更微妙。该示例通过使问题更加明显而加剧了问题,但基本原理是相同的。

    最佳答案

    我查看了您的代码(并运行它),我不相信您看到的内存增加实际上是内存泄漏。

    您遇到的问题是您的调用代码(控制台应用程序)本质上是在一个紧密的循环中运行。

    但是,您的处理程序必须处理每个请求,并且还被 Thread.Sleep(10) .这样做的实际结果是您的处理程序无法跟上进来的请求,因此它的“工作集”随着更多请求排队等待处理而不断增长。

    我拿了你的代码并在控制台应用程序中添加了一个 AutoResetEvent,做了一个
    .WaitOne()request.BeginGetResponse(GetResponseCallback, request); 之后

    和一个
    .Set()streamReader.ReadToEnd(); 之后

    这具有同步调用的效果,因此在第一个调用回调(并完成)之前不能进行下一个调用。你看到的行为消失了。

    总之,我认为这纯粹是一种失控的情况,实际上根本不是内存泄漏。

    注意:我在 GetResponseCallback 方法中使用以下内容监视内存:

     GC.Collect();
    GC.WaitForPendingFinalizers();
    Console.WriteLine(GC.GetTotalMemory(true));

    [根据安东的评论编辑]
    我并不是说这里根本没有问题。
    如果您的使用场景使得处理程序的这种锤击是一个真实的使用场景,那么显然您遇到了问题。我的观点是,这不是内存泄漏问题,而是容量问题。解决这个问题的方法可能是编写一个运行速度更快的处理程序,或者扩展到多个服务器等。

    泄漏是指资源在完成后被保留,从而增加了工作集的大小。这些资源还没有“完成”,它们在队列中等待服务。一旦它们完成,我相信它们会被正确释放。

    [根据安东的进一步评论进行编辑]
    好的 - 我发现了一些东西!我认为这是在 IIS 下不会发生的 Cassini 问题。您是否在 Cassini(Visual Studio 开发 Web 服务器)下运行您的处理程序?

    当我仅在 Cassini 下运行时,我也看到了这些泄漏的 System.Runtime.Remoting 命名空间实例。如果我将处理程序设置为在 IIS 下运行,我看不到它们。您能否确认您是否属于这种情况?

    这让我想起了我见过的其他一些远程处理/卡西尼问题。 IIRC,有一个类似 IPrincipal 的实例,需要存在于模块的 BeginRequest 中,并且在模块生命周期结束时,需要从 Cassini 中的 MarshalByRefObject 而不是 IIS 派生。出于某种原因,Cassini 似乎在内部进行了一些 IIS 没有的远程处理。

    关于.net - 为什么 IHttpAsyncHandler 在负载下会泄漏内存?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/2823379/

    27 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com