Issue 169 en cocos reportaba un memory leak usando tilemaps. El reporte inicial era muy bueno (gracias davexunit !), en tanto que incluía un bugdemo simple; lo simplifiqué un poco más eliminando dos clases de la situación.
En esencia, si se definía una escena usando ciertas clases, cuando se hacia un loop creando una nueva instancia de la escena que reemplazaba la anterior como la activa, la memoria crecía sin limite.
El script no retenía referencias directas a las escenas reemplazadas, así que quedaba alguna en cocos o en pyglet, o bien había ciclos uncolectables.
Ahora bien, una de las clases es relativamente compleja, así que no quería empezar a mirar el código todavía; lo que hacia falta era algo que apuntara mas concretamente a las partes responsables.
Buscando un poco encontré objgraph, una pequeña herramienta que informa acerca de los objetos que python tiene en memoria.
Lo primero que hice es para ciertos valores del contador del loop, imprimirlo y llamar a
objgraph.show_growth(limit=15)Esta llamada calcula el numero de instancias de cada clase que vé el interprete, hace un delta con la misma info pero en la llamada anterior a show_growth, imprime las 15 que aumentaron mas.
En la iteración 66 la salida fue
creations: 66 dict 16823 +8720 RectCell 4752 +2880 list 3214 +1320 ImageDataRegion 858 +520 Texture 858 +520 Tile 858 +520 Point3 204 +120 Matrix3 136 +80 instancemethod 189 +40 Camera 68 +40 Batch 66 +40 weakref 1425 +40 RectMapLayer 66 +40
Los objetos mas pequeños tienden a estar incluidos dentro de otros mas grandes, así que tiene sentido mirar las clases mas gordas primero.
La mas gorda aquí es RectMapLayer, y los numeros dicen que a la iteracion 66 tenemos 66 instancias, 40 más que en la llamada anterior a show_growth.
Pero hay una sola instancia de RectMapLayer por escena, así que en alguna parte tenemos referencias que retienen estas instancias.
Basándome en los ejemplos incluidos en los docs de objgraph, hice una pequeña función
Lo que hace es elegir al azar una instancia de la clase indicada, por default 'RectMapLayer', calcula las cadenas de referencias que terminan en esa instancia y muestra el grafo correspondiente.
Hice visible esa función al InterpreterLayer que provee cocos con Y modifiqué el loop para que después de k iteraciones dejara de cambiar de escena, dejando la ultima escena como activa.
Entonces, cuando terminan los cambios de escena levanto la consola python que nos provee cocos con un ctrl + I, le doy 'sw()' y como no le pasé filename levanta el visor-editor de grafos xdot, pero viendo que es chico y se vé bien le pido una fotito png para mostrar acá, con 'sw(fname="leak.png")':
Lo que nos dice el grafo es que:
- al nivel del script tenemos un objeto director, de clase Director
- que tiene un miembro _event_stack
- que contiene una lista de 66 valores,
- uno de los cuales es un dict de un elemento
- que contiene algo llamado on_cocos_resize
- que es un instance method asociado a una instancia
- y esa instancia es la del RectMapLayer que elegimos al azar
Así que el asunto debe estar en esos métodos de RectMapLayer. Es muy poco código, y en seguida se vé cual es el problema: RectMapLayer no está desregistrando el listener.
Aquí solo necesité un par de funciones de objgraph, pero tiene mas, ténganlo presente.
No hay comentarios:
Publicar un comentario