Почему воспоминания о утечке IHTPASYNCHANDLER под нагрузкой?

StackOverflow https://stackoverflow.com/questions/2823379

Вопрос

Я заметил, что .NET IHTPASYNCHANDLER (и IHTTTHANDLER, в меньшую степень) в памяти, когда подвергаются одновременному веб-запросам.

В моем тестах веб-сервер Visual Studio Web Server (CASSINI) прыгает с 6 МБ памяти до более 100 МБ, и после того, как тест закончен, ничто из него не будет восстановлено.

Проблема может быть легко воспроизведена. Создайте новое решение (LeakyHyHandler) с двумя проектами:

  1. Веб-приложение ASP.NET (LeakyHandler.webapp)
  2. Приложение консоли (LeakyHandler.consoleApp))

В LeakyHandler.WebApp:

  1. Создайте класс под названием TestHandler, который реализует IHTPASYNCHANDLER.
  2. В обработке запроса делайте короткий сон и закончите ответ.
  3. Добавьте HTTP-обработчик в web.config как test.ashx.

В LeakyHandler.consolaP:

  1. Создайте большое количество httpwebrequests, чтобы проверить .ashx и выполнить их асинхронно.

По мере увеличения количества httpwebrequests (sampleize) увеличивается, утечка памяти становится все более и более очевидной.

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, чтобы посмотреть в файлы сброса, а несколько подозрительных типов проводятся в памяти и никогда не выпускаются. Каждый раз, когда я пропускаю тест с размером образец 10000, я заканчиваю на 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). Отказ Практический выпускник этого состоит в том, что ваш обработчик не может идти в ногу со своими запросами, поэтому его «рабочий набор» растет и растет как еще больше запросов, ожидающих, чтобы быть обработанным.

Я взял ваш код и добавил авторесурс на приложение консоли, делая

.WaitOne() после request.BeginGetResponse(GetResponseCallback, request);

и а

.Set() после streamReader.ReadToEnd();

Это имеет эффект синхронизации вызовов, поэтому следующий вызов не может быть сделан до тех пор, пока первый вызов не позвонил обратно (и завершено). Поведение, которое вы видите, уходит.

Таким образом, я думаю, что это чисто бегая ситуация, а не на самом деле утечка памяти вообще.

Примечание. Я контролировал память следующим образом, в методе GetResponseCallback:

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

Редактировать в ответ на комментарий от AntonЯ не предлагаю, нет проблем здесь вообще. Если ваш сценарий использования таков, что этот удар обработчика является реальным сценарием использования, то у вас есть проблема. Моя точка зрения заключается в том, что это не проблема утечки мемеры, а проблема потенциала. Путь к решению этого будет, может быть, чтобы написать обработчик, который может работать быстрее или масштабировать на несколько серверов и т. Д.

Утечка заключается в том, когда ресурсы удерживаются после того, как они заканчиваются, увеличивая размер рабочего набора. Эти ресурсы не были «закончены», они в очереди и ожидают обслуживания. Как только они будут завершены, я считаю, что они выпускаются правильно.

Редактировать в ответ на дальнейшие комментарии AntonОК, - я что-то раскрыл! Я думаю, что это проблема кассини, которая не происходит под IIS. Вы используете свой обработчик под Кассини (веб-сервер Visual Studio Development)?

Я тоже вижу эти протекающие интерпретации пространства имен. Я не вижу их, если я настрою обработчик, чтобы бежать под IIS. Можете ли вы подтвердить, если это так для вас?

Это напоминает мне о некоторых других удаленных / кассини, которую я видел. IIRC, имеющий экземпляр чего-то вроде iPrincipal, который должен существовать в начале модуля, а также в конце жизненного цикла модуля, необходимо извлечь из Marshalbyrefobject в Кассини, но не IIS. По какой-то причине похоже, Касини делает некоторую отступление внутренне, что IIS не.

Другие советы

Измерение памяти, которую вы измерите, могут быть выделены, но не используемые CLR. Чтобы проверить попробуйте позвонить:

GC.Collect();
context.Response.Write(GC.GetTotalMemory(true));

в ProcessRequest(). Отказ Сообщить о своем приложении Console App, этот ответ от сервера, чтобы увидеть, сколько памяти действительно используется живыми объектами. Если этот номер остается довольно стабильным, то CLR просто пренебрегает, чтобы сделать GC, потому что он думает, что это достаточно доступной ОЗУ, как оно есть. Если этот номер неуклонно растет, то у вас действительно есть утечка памяти, которую вы могли бы устранить блажок с Windbg и Sos.dll или другими (коммерческими) инструментами.

Редактировать: Хорошо, похоже, у вас есть реальная утечка памяти. Следующим шагом является выяснение того, что держит на этих объектах. Вы можете использовать SOS ! gcroot. Команда для этого. Есть хорошее объяснение для .NET 2.0 здесь, но если вы можете воспроизвести это на .NET 4.0, то у его SOS.DLL имеет гораздо лучшие инструменты, чтобы помочь вам - см. http://blogs.msdn.com/tess/archive/2010/03/01/new-commands-in-sos-for-net-4-0-part-2.aspx.

Нет проблем с вашим кодом, и нет утечки памяти. Попробуйте запустить приложение для консоли пару раз в ряд (или увеличить размер выборки, но остерегайтесь, что в конечном итоге ваши запросы HTTP будут отклонены, если у вас слишком много одновременных запросов).

С вашим кодом я обнаружил, что, когда использование памяти веб-сервера достигло около 130 МБ, было достаточно давления, которое сбрасывает мусор и уменьшил его до 60 МБ.

Может быть, это не поведение, которое вы ожидаете, но время выполнения решил, что более важно быстро реагировать на ваши быстрые входящие запросы, чем проводить время с GC.

Это будет очень вероятно, проблема в вашем коде.

Первое, что я бы проверил, это если вы отсоедините все обработчики событий в вашем коде. Каждый + = должен быть отраженным A - = обработчик событий. Вот как больше всего .NET приложение создает утечку памяти.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top