Hola a todos!
Hoy vamos a hablar sobre un memory leak encontrado en Silverlight 4 y reportado en el foro de silverilght.net por el usuario tsheflin. Como sabrás Silverlight está basado en tecnología .net lo que significa que puedes escribir tus aplicaciones en C# o en Visual Basic y tienes todas las cosas buenas de .net como gestión automática de memoria, recolección de elementos no utilizados y seguridad de tipos
Pero algunas veces algo sale más y aun teniendo la gestión automática de memoria de .net nos podemos encontrar con problemas, es por eso que hoy vamos a ver cómo podemos identificar estos problemas con WinDBG y sos para Silverlight.
Voy a usar como ejemplo el codigo subido por el usuario del foro de Silverlight.net. Puedes descargarlo desde aquí.
Lo primero de todo necesitamos ejecutar nuestra aplicación en modo release. Esto es importante porque hay pequeñas diferencias entre el modo debug y el release. Una vez que tenemos la aplicación ejecutándose es tiempo de WinDGB.
Si tu navegador es Windows Internet Explorer 8 y estás ejecutándolo sobre Windows 7 o Vista, sabrás que IE ejecuta cada tab en un proceso separado y por eso primero tienes que identificar cual es el proceso de IE que está ejecutando el código, en mi caso es el proceso 7958.
Con WinDBG abierto (puedes descargarlo de aquí) tienes que ir a File -> Attach to process (F6), y selecciona la id de tu proceso (en mi maquina es 7958), inmediatamente aparecerá en la ventana de salida toda la información básica de depuración. Teniendo en cuenta que nuestra aplicación es una aplicación administrada y WinDBG está diseñado para depurar aplicaciones nativas necesitamos una extensión del depurador para trabajar con aplicaciones administradas. Lo que necesitamos es SOS (Son of Strike) que está localizado en C:Program Files (x86)Microsoft Silverlight4.0.50401.0sos.dll. Para cada version de silverligh hay una versión de SOS, así que depende de la versión de Silveright que estés depurando tendrás que usar una u otra.
Para cargar SOS escribimos
.load C:Program Files (x86)Microsoft Silverlight4.0.50401.0sos.dll
Lo siguiente que necesitamos encontrar es una direccion valida del tipo que estamos buscando SilverlightVisualTreeRemovalFail.SilverlightControl1 y para eso tenemos que usar el comando !dumpheap para volcar todos los tipos filtrados por este tipo.
!dumpheap -type SilverlightVisualTreeRemovalFail.SilverlightControl1 0:005> !dumpheap -type SilverlightVisualTreeRemovalFail.SilverlightControl1 Address MT Size 087aad6c 04a45230 92 087ab460 04a45230 92 087ab6f0 04a45230 92 087ac01c 04a45230 92 087ac710 04a45230 92 087ace04 04a45230 92 087ad4f8 04a45230 92 087adbec 04a45230 92 087ae2e0 04a45230 92 087ae9d4 04a45230 92 087af0c8 04a45230 92 087af7bc 04a45230 92 087afeb0 04a45230 92 087b05a4 04a45230 92 087b0c98 04a45230 92 087b138c 04a45230 92 087b1a80 04a45230 92 087b2174 04a45230 92 087b2868 04a45230 92 087b2f5c 04a45230 92 087b3650 04a45230 92 087b3d44 04a45230 92 087b4438 04a45230 92 087b4b2c 04a45230 92 087b5220 04a45230 92 087b5914 04a45230 92 087b6008 04a45230 92 087b66fc 04a45230 92 087b6df0 04a45230 92 087b74e4 04a45230 92 087b7bd8 04a45230 92 087b82cc 04a45230 92 087b89c0 04a45230 92 087b90b4 04a45230 92 087b97a8 04a45230 92 087b9e9c 04a45230 92 087ba590 04a45230 92 087bac84 04a45230 92 087bb378 04a45230 92 087bba6c 04a45230 92 087bc160 04a45230 92 087bc854 04a45230 92 087bcf48 04a45230 92 087bd63c 04a45230 92 087bdd30 04a45230 92 087be424 04a45230 92 087beb18 04a45230 92 087bf20c 04a45230 92 087bf900 04a45230 92 087bfff4 04a45230 92
En la ventana de salida encontramos direcciones del tipo que hemos solicitado, seleccionamos una 087b1a80 y buscamos que objetos están referenciando este objeto (esto es lo que está causando que el objeto no sea recolectado) escribiendo esto:
!gcroot 087b1a80 0:005> !gcroot 087b1a80 Note: Roots found on stacks may be false positives. Run "!help gcroot" for more info. Scan Thread 5 OSTHread 10e8 Scan Thread 22 OSTHread 1f8 Scan Thread 23 OSTHread 1588 DOMAIN(07134BE0):HANDLE(Pinned):4a912f8:Root: 09784260(System.Object[])-> 08796c78(System.Collections.Generic.Dictionary`2[[System.IntPtr, mscorlib],[System.Object, mscorlib]])-> 09788260(System.Collections.Generic.Dictionary`2+Entry[[System.IntPtr, mscorlib],[System.Object, mscorlib]][])-> 087b1e90(System.Windows.Controls.ControlTemplate)-> 087b1a80(SilverlightVisualTreeRemovalFail.SilverlightControl1)
Encontramos que el objeto (087b1a80) está referenciado por otro objeto y en lo alto de la lista podemos encontrar cual es el objeto que lo está referenciando.
Ahora que hemos identificado que el objeto está referenciado por un GCHandle pineado el recolector siempre encontrará un camino hasta este objeto haciendo que nunca sea recolectado. Como el código que controla esta funcionalidad es de Microsoft no podemos hacer nada al respecto, pero en caso de que sea nuestro lo que podemos hacer es simplemente eliminar las referencias para que el objeto sea recolectado.
Además podemos volcar todos los tipos que hay en el heap y abrirlo con el CLRProfiler usando !traverseheap.
Saludos.
Luis Guerrero.