c# - Pérdida de memoria al utilizar DirectorySearcher.FindAll()
.net memory-leaks (5)
¿Has probado de using
y Dispose()
? Información desde here
Actualizar
Intente llamar a de.Close();
antes del final del uso.
Realmente no tengo un servicio de dominio activo para probar esto, lo siento.
Tengo un proceso de larga ejecución que necesita hacer muchas consultas en Active Directory con bastante frecuencia. Para este propósito, he estado usando el espacio de nombres System.DirectoryServices, usando las clases DirectorySearcher y DirectoryEntry. He notado una pérdida de memoria en la aplicación.
Se puede reproducir con este código:
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())
{
}
}
}
}
La documentación de estas clases indica que perderán memoria si no se llama a Dispose (). Lo he intentado sin disponer también, solo pierde más memoria en ese caso. He probado esto con las versiones de framework 2.0 y 4.0 ¿Alguien se ha encontrado con esto antes? ¿Hay alguna solución?
Actualización: Intenté ejecutar el código en otro dominio de aplicación, y tampoco pareció ayudar.
Debido a las restricciones de implementación, la clase SearchResultCollection no puede liberar todos sus recursos no administrados cuando se recolecta la basura. Para evitar una pérdida de memoria, debe llamar al método Dispose cuando el objeto SearchResultCollection ya no sea necesario.
http://msdn.microsoft.com/en-us/library/system.directoryservices.directorysearcher.findall.aspx
EDITAR:
He podido reprochar la fuga aparente utilizando perfmon y agregando un contador para Bytes privados en el nombre del proceso de la aplicación de prueba (Experiments.vshost para mí)
el contador de Bytes privados crecerá de manera constante mientras la aplicación está en bucle, comienza alrededor de 40,000,000, y luego crece alrededor de un millón de bytes cada pocos segundos. La buena noticia es que el contador vuelve a la normalidad (35,237,888) cuando finaliza la aplicación, por lo que finalmente se está produciendo algún tipo de limpieza.
He adjuntado una captura de pantalla de cómo se ve perfmon cuando está goteando
Actualizar:
He intentado algunas soluciones alternativas, como deshabilitar el almacenamiento en caché en el objeto DirectoryServer, y no ayudó.
El comando FindOne () no pierde memoria, pero no estoy seguro de lo que tendría que hacer para que esa opción le funcione, probablemente edite el filtro constantemente, en mi controlador de AD, solo hay un dominio, así que findall y findone dan el mismo resultado.
También intenté poner en cola a 10.000 trabajadores de agrupación de subprocesos para crear el mismo DirectorySearcher.FindAll (). Terminó mucho más rápido, sin embargo, aún perdía memoria y, en realidad, los bytes privados aumentaban hasta 80 MB, en lugar de solo 48 MB para la fuga "normal".
Entonces, para este problema, si puede hacer que FindOne () funcione para usted, tiene una solución alternativa. ¡Buena suerte!
Encontré una manera rápida y sucia alrededor de esto.
Tuve un problema similar en mi programa con el crecimiento de la memoria, pero al cambiar .GetDirectoryEntry (). Propiedades ("cn"). Valor para
.GetDirectoryEntry (). Propiedades ("cn"). Value.ToString con una if, antes para asegurarse de que .value no era nulo
Pude decirle a GC.Collect que se deshaga del valor temporal en mi foreach. Parece que el .valor en realidad mantenía vivo el objeto en lugar de permitir que se recolectara.
La envoltura administrada realmente no filtra nada. Si no llama Dispose
recursos no utilizados aún se reclamarán durante la recolección de basura.
Sin embargo, el código administrado es una envoltura encima de la API ADSI basada en COM y cuando creas una entrada de DirectoryEntry
el código subyacente llamará a la función ADsOpenObject
. El objeto COM devuelto se libera cuando se elimina DirectoryEntry
o durante la finalización.
- Esta pérdida de memoria ocurre en todas las versiones de Windows XP, Windows Server 2003, Windows Vista, Windows Server 2008, Windows 7 y Windows Server 2008 R2.
- Esta pérdida de memoria se produce solo cuando utiliza el proveedor de WinNT junto con las credenciales. El proveedor de LDAP no pierde memoria de esta manera.
Sin embargo, la fuga es de solo 8 bytes y, por lo que puedo ver, está utilizando el proveedor LDAP y no el proveedor WinNT.
Calling DirectorySearcher.FindAll
realizará una búsqueda que requiere una limpieza considerable. Esta limpieza se realiza en DirectorySearcher.Dispose
. En su código, esta limpieza se realiza en cada iteración del bucle y no durante la recolección de basura.
A menos que realmente haya una pérdida de memoria no documentada en la API ADSI de LDAP, la única explicación que se me ocurre es la fragmentación del montón no administrado. La API ADSI se implementa mediante un servidor COM en proceso y cada búsqueda probablemente asignará algo de memoria en el montón no administrado de su proceso. Si esta memoria se fragmenta, el montón puede tener que crecer cuando se asigna espacio para nuevas búsquedas.
Si mi hipótesis es cierta, una opción sería ejecutar las búsquedas en un dominio de aplicación separado que luego se puede reclamar para descargar ADSI y reciclar la memoria. Sin embargo, aunque la fragmentación de la memoria puede aumentar la demanda de memoria no administrada, esperaría que hubiera un límite superior a la cantidad de memoria no administrada que se requiere. A menos que por supuesto tenga una fuga.
Además, puede intentar jugar con la propiedad DirectorySearcher.CacheResults
. ¿Al configurarlo en false
elimina la fuga?
Por extraño que sea, parece que la pérdida de memoria solo ocurre si no haces nada con los resultados de la búsqueda. La modificación del código en la pregunta de la siguiente manera no pierde ninguna memoria:
using (var src = mySearcher.FindAll())
{
var enumerator = src.GetEnumerator();
enumerator.MoveNext();
}
Esto parece ser causado por el campo interno searchObject que tiene una inicialización perezosa, mirando SearchResultCollection con Reflector:
internal UnsafeNativeMethods.IDirectorySearch SearchObject
{
get
{
if (this.searchObject == null)
{
this.searchObject = (UnsafeNativeMethods.IDirectorySearch) this.rootEntry.AdsObject;
}
return this.searchObject;
}
}
La disposición no cerrará el identificador no administrado a menos que se inicialice 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;
}
..
}
}
Al llamar a MoveNext en el ResultsEnumerator, se llama a SearchObject en la colección, lo que garantiza que también se elimine correctamente.
public bool MoveNext()
{
..
int firstRow = this.results.SearchObject.GetFirstRow(this.results.Handle);
..
}
La fuga en mi aplicación se debió a que otro búfer no administrado no se lanzó correctamente y la prueba que hice fue engañosa. El problema está resuelto ahora.