Y así, obviamente, hubo algunas cosas innecesariamente tontas que aprendimos cuando arreglamos esto. Una es, como, está bien, entonces hay una clase que tiene una documentación que dice que cierra todos los recursos abiertos mantenidos por esta instancia de REPL, y luego no cierra o no cerraba previamente uno de los recursos principales que mantenía. Lo cual es como, sí, duh, eso va a filtrar tu memoria. Pero luego, sí, también es realmente difícil de detectar, y eso es un poco como las cosas de las que estaba hablando, ¿verdad? Obviamente hay esta línea extra, que también es parte del problema aquí, pero, como, reemplazar eval con esta función eval sin contexto, y eval sin contexto es realmente solo eval puesto en un archivo separado para que no pueda capturar ninguna variable. Eso es todo. Ese es el núcleo del error que arreglamos aquí, porque de lo contrario esta llamada eval que tuvimos que hacer no podría, como, obviamente eval es malvado, y lo sabemos, y no deberíamos estar haciendo esto, pero en nuestro caso esto es justo lo que tenemos que hacer. Esto estaba realmente capturando suficiente del ámbito exterior que estaba manteniendo objetos en memoria por mucho más tiempo del que deberían haber estado.
Y luego, como en cualquier buena historia, hay un error en el núcleo de Node.js que terminamos arreglando. Así que, como, aprendimos que el manejo de oyentes en el núcleo de Node.js REPL en realidad tenía un problema muy similar, donde estaba llamando al proceso de oyente de prepaint, básicamente añadiendo un oyente de eventos, y estaba pasando un callback. Y ese callback no dependía de ninguna variable en el ámbito, pero aún así era suficiente para capturar todo el ámbito exterior, por lo que mantenía viva la instancia de REPL que lo creó, aunque eso era completamente innecesario. Y la solución es simplemente, como, llevar esto al nivel superior para que no, no sea el cierre que captura la instancia real de REPL que lo añade más. Es una cosa bastante fácil de pasar por alto. Pero afortunadamente se nos permite hacer contribuciones de código abierto al núcleo de Node.js, y podemos simplemente arreglar esto.
Así que, de todos modos, y llegando al cierre de esto, podrías preguntarte, ¿cómo pruebas realmente esto? Obviamente arreglamos todos estos errores y luego nuestro CI estaba en verde, pero, como, ¿cómo te aseguras de que no retrocedes en esto, verdad? Eso es complicado. Porque, como, obviamente aún podrías, ya sabes, notar si las cosas van realmente mal, e introduces fugas de memoria similares a las que vimos anteriormente, pero si algo va un poco mal, no lo notarás de inmediato. Y parte del núcleo del problema es que la recolección de basura en JavaScript es inobservable, ¿verdad? O al menos esto solía ser cierto durante mucho, mucho tiempo. Ya no lo es. Así que algunas de las cosas más nuevas, esto no es tan nuevo, esto es, como, de hace un par de años en este punto, pero, como, ahora hay algo llamado registro de finalización en JavaScript. Y usarlo es increíblemente difícil. Y yo... O no usarlo, usarlo correctamente es increíblemente difícil. Usarlo es bastante fácil, en realidad. Pero sí, así que esto es complicado de hacer bien, pero puedes observar la finalización de objetos a través de esta API. Como, puedes obtener un callback que se llama cuando un objeto es eliminado del heap de JavaScript. Así que perfecto. Hay algunas APIs específicas de Node.js que puedes usar. Hay objetos de consulta Vieta, lo cual es bastante genial. Te animaría a probarlo, una de tus aplicaciones en algún momento, donde puedes pasar un constructor a esta función. Y te dará todos los objetos en memoria que son creados a partir de este constructor. Y así, esto es, como, una manera fácil de saber si, ya sabes, hay algo que todavía está en memoria que no debería estar.
Comments