Утечка памяти при использовании DirectorySearcher.FindAll () - PullRequest
25 голосов
/ 12 апреля 2011

У меня длительный процесс, которому нужно часто выполнять множество запросов в Active Directory.Для этой цели я использовал пространство имен System.DirectoryServices, используя классы DirectorySearcher и DirectoryEntry.Я заметил утечку памяти в приложении.

Его можно воспроизвести с помощью этого кода:

while (true)
{
    using (var de = new DirectoryEntry("LDAP://hostname", "user", "pass"))
    {
        using (var mySearcher = new DirectorySearcher(de))
        {
            mySearcher.Filter = "(objectClass=domain)";
            using (SearchResultCollection src = mySearcher.FindAll())
            {
            }            
         }
    }
}

В документации по этим классам сказано, что они будут пропускать память, если Dispose () не вызывается.Я пробовал и без утилизации, он просто пропускает больше памяти в этом случае.Я проверил это с обеими версиями платформы 2.0 и 4.0 Кто-нибудь сталкивался с этим раньше?Есть ли какие-нибудь обходные пути?

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

Ответы [ 5 ]

14 голосов
/ 23 мая 2011

Как ни странно, кажется, что утечка памяти происходит, только если вы ничего не делаете с результатами поиска.Изменение кода в вопросе следующим образом не приводит к утечке памяти:

using (var src = mySearcher.FindAll())
{
   var enumerator = src.GetEnumerator();
   enumerator.MoveNext();
}

Кажется, это вызвано тем, что внутреннее поле searchObject имеет отложенную инициализацию, глядя на SearchResultCollection с Reflector:

internal UnsafeNativeMethods.IDirectorySearch SearchObject
{
    get
    {
        if (this.searchObject == null)
        {
            this.searchObject = (UnsafeNativeMethods.IDirectorySearch) this.rootEntry.AdsObject;
        }
        return this.searchObject;
    }
}

Утилизация не закроет неуправляемый дескриптор, пока не будет инициализирован searchObject.

protected virtual void Dispose(bool disposing)
{
    if (!this.disposed)
    {
        if (((this.handle != IntPtr.Zero) && (this.searchObject != null)) && disposing)
        {
            this.searchObject.CloseSearchHandle(this.handle);
            this.handle = IntPtr.Zero;
        }
    ..
   }
}

Вызов MoveNext для ResultsEnumerator вызывает SearchObject для коллекции, таким образом проверяя, что он также удаляется должным образом.*

Утечка в моем приложении произошла из-за того, что какой-то другой неуправляемый буфер не был освобожден должным образом, и мой тест вводил в заблуждение.Проблема решена.

6 голосов
/ 09 мая 2011

Управляемый упаковщик ничего не пропускает.Если вы не вызовете Dispose, неиспользуемые ресурсы будут по-прежнему возвращаться при сборке мусора.

Однако управляемый код является оболочкой поверх API ADSI на основе COM и при создании DirectoryEntryбазовый код вызовет функцию ADsOpenObject .Возвращенный COM-объект освобождается при удалении DirectoryEntry или во время финализации.

Имеется документированная утечка памяти при использовании API ADsOpenObject вместе с набором учетных данных и поставщиком WinNT :

  • Эта утечка памяти происходит во всех версиях Windows XP, Windows Server 2003, Windows Vista, Windows Server 2008, Windows 7 и Windows Server 2008 R2.
  • Эта утечка памяти происходит только при использовании поставщика WinNT вместе с учетными данными.Провайдер LDAP не пропускает память таким образом.

Однако утечка составляет всего 8 байтов, и, насколько я вижу, вы используете провайдера LDAP, а не провайдера WinNT..

Вызов DirectorySearcher.FindAll выполнит поиск, который требует значительной очистки.Эта очистка выполняется в DirectorySearcher.Dispose.В вашем коде эта очистка выполняется в каждой итерации цикла, а не во время сборки мусора.

Если в API LDAP ADSI действительно нет недокументированной утечки памяти, единственное объяснение, которое я могу придумать, - это фрагментациянеуправляемая куча.API ADSI реализован внутрипроцессным COM-сервером, и каждый поиск, вероятно, выделит некоторую память в неуправляемой куче вашего процесса.Если эта память становится фрагментированной, может потребоваться увеличение кучи при выделении пространства для новых поисков.

Если моя гипотеза верна, одним из вариантов будет запуск поисков в отдельном домене приложений, который затем можно будет восстановить для выгрузки.ADSI и переработать память.Однако, хотя фрагментация памяти может увеличить потребность в неуправляемой памяти, я ожидаю, что будет верхний предел того, сколько неуправляемой памяти требуется.Если, конечно, у вас нет утечки.

Кроме того, вы можете попытаться поэкспериментировать со свойством DirectorySearcher.CacheResults .Устраняет ли утечка значение false? 1037 *

3 голосов
/ 09 мая 2011

Из-за ограничений реализации класс SearchResultCollection не может освободить все свои неуправляемые ресурсы при сборке мусора.Чтобы предотвратить утечку памяти, вы должны вызывать метод Dispose, когда объект SearchResultCollection больше не нужен.

http://msdn.microsoft.com/en-us/library/system.directoryservices.directorysearcher.findall.aspx

РЕДАКТИРОВАТЬ:

Я смогВоспроизведите очевидную утечку, используя perfmon, и добавив счетчик для Private Bytes в имя процесса тестового приложения (Experiment.vshost для меня)

Счетчик Private Bytes будет постоянно расти, пока приложение работает, оно запускаетсяоколо 40 000 000, а затем увеличивается примерно на миллион байтов каждые несколько секунд.Хорошей новостью является то, что счетчик возвращается в нормальное состояние (35 237 888), когда вы закрываете приложение, поэтому наконец-то происходит какая-то очистка.

Я приложил снимок экрана того, как выглядит perfmon при утечке.perfmon screenshot of memory leak

Обновление:

Я пробовал несколько обходных путей, например отключение кэширования объекта DirectoryServer, но это не помогло.

Команда FindOne () не приводит к утечке памяти, но я не уверен, что вам нужно сделать, чтобы эта опция работала для вас, возможно, постоянно редактируйте фильтр, на моем контроллере AD естьтолько один домен, так что findall и findone дают тот же результат.

Я также попытался поставить в очередь 10 000 рабочих потоков, чтобы создать тот же DirectorySearcher.FindAll ().Она закончилась намного быстрее, но все равно утекла память, и на самом деле частные байты увеличились до 80 МБ, вместо 48 МБ для «обычной» утечки.

Так что для этой проблемы, если вы можете сделать FindOne ()работа для вас, у вас есть обходной путь.Удачи!

2 голосов
/ 12 апреля 2011

Вы пробовали using и Dispose()? Информация от здесь

Обновление

Попробуйте позвонить de.Close(); до окончания использования.

У меня нет активной службы домена, чтобы проверить это, извините.

0 голосов
/ 17 сентября 2013

Нашел быстрый и грязный способ обойти это.

У меня была похожая проблема в моей программе с увеличением памяти, но при изменении .GetDirectoryEntry (). Properties ("cn"). Значение до

.GetDirectoryEntry (). Properties ("cn"). Value.ToString со знаком if перед тем, чтобы убедиться, что значение .value не равно нулю

Я был в состоянии сказать GC.Collect избавиться от временного значения в моем foreach. Похоже, что .value фактически поддерживал объект, а не позволял его собирать.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...