- En el emulador de terminal Ghostty se descubrió una fuga grave que, al ejecutarse durante mucho tiempo, podía consumir decenas de GB de memoria
- La causa del problema fue que, en la lógica no estándar de reutilización de páginas de memoria de la estructura PageList, no se llamaba a
munmap, por lo que la memoria no liberada se iba acumulando
- Como Claude Code CLI genera con frecuencia salidas de gráficos con múltiples code points, aumentó la frecuencia de uso de páginas no estándar y la fuga se hizo visible a gran escala
- La corrección cambió el comportamiento para no reutilizar las páginas no estándar y liberarlas de inmediato, y se usó la función de etiquetas VM de macOS para rastrear y verificar la fuga
- Esta corrección se considera la solución al mayor problema de fuga en Ghostty y está prevista para incluirse en una próxima versión (1.3)
Resumen de la fuga de memoria en Ghostty
- Algunos usuarios reportaron casos en los que Ghostty usaba más de 37 GB de memoria tras permanecer en ejecución por largos periodos
- La fuga existía al menos desde la versión 1.0, y recientemente ciertas apps CLI cumplieron condiciones específicas que dejaron el problema al descubierto
- La corrección ya fue fusionada en GitHub y estará incluida en las builds nightly y en la versión estable 1.3
Estructura de PageList y manejo de memoria
- Ghostty usa una estructura de lista doblemente enlazada llamada PageList para almacenar el contenido de la terminal
- Cada página incluye datos como caracteres, estilos e hipervínculos
- Las páginas se asignan con
mmap y se reutilizan mediante un pool de páginas de tamaño estándar
- Las páginas iguales o menores al tamaño estándar regresan al pool
- Las páginas de tamaño no estándar deben liberarse directamente con
munmap
- La estructura en sí funcionaba bien, pero una falla en la lógica de optimización provocó la fuga
Optimización de scrollback y causa del bug
- Cuando Ghostty supera
scrollback-limit, aplica una optimización para reutilizar la página más antigua
- Esto mejora el rendimiento porque solo ajusta punteros sin asignar una página nueva
- El problema fue que, en ese proceso, solo se cambiaban los metadatos de la página no estándar al tamaño estándar, mientras la memoria real permanecía igual
- Después, al liberarla, se confundía con una página estándar y no se llamaba a
munmap
- Como resultado, las páginas no estándar no se liberaban y se acumulaban, causando una fuga masiva de memoria en ejecuciones prolongadas
Claude Code y la exposición masiva de la fuga
- Claude Code CLI genera con frecuencia salidas de gráficos con múltiples code points, lo que incrementó el uso de páginas no estándar
- Además, como produce mucho contenido de scrollback, la fuga se acumulaba rápidamente
- En el diseño de Ghostty, las páginas no estándar deberían aparecer rara vez, pero por las características de Claude Code la fuga pudo reproducirse en gran volumen
- El desarrollador aclaró que este bug no era un problema de Claude Code, sino una falla en la lógica interna de Ghostty
Qué se corrigió
Rastreo de la fuga con etiquetas VM
- Se usó la función de etiquetas VM del kernel Mach en macOS para asignar una etiqueta específica a las reservas de memoria de PageList
- Esto permite identificar claramente las regiones de memoria de Ghostty durante la depuración
- También ayudó mucho a rastrear la causa de la fuga y validar la corrección
- Con esta función fue posible confirmar visualmente si la memoria relacionada con PageList se liberaba o no
Sistema de prevención de fugas de memoria en Ghostty
- Ghostty detecta y previene fugas de varias maneras
- En builds de depuración y pruebas unitarias usa el allocator de detección de fugas de Zig
- En CI ejecuta todas las pruebas con valgrind
- Usa macOS Instruments para inspeccionar fugas en código Swift
- Los PR relacionados con GTK se validan con pruebas GUI de Valgrind
- Esta fuga solo ocurría bajo condiciones específicas, por lo que no pudo reproducirse con las pruebas existentes
- Se añadió un nuevo caso de prueba para evitar regresiones
Conclusión
- Este problema fue identificado como el caso de fuga de memoria más grande detectado en Ghostty
- Incluso después de la corrección, se seguirá monitoreando mediante reportes de usuarios y pruebas de reproducción
- Los datos de diagnóstico y casos reproducibles aportados por la comunidad fueron decisivos para resolver el problema
- Se enfatiza que contar con un entorno reproducible es clave para solucionar fugas de memoria
1 comentarios
Comentarios de Hacker News
De verdad es una noticia muy bienvenida. Aplausos para todas las personas que participaron en resolver el problema
Ya era un bug que se había mencionado la semana pasada en este hilo
Parece que Claude Code fue el detonante para que este bug quedara expuesto a más usuarios, pero también hubo personas como yo que nunca usaron Claude Code y aun así tuvieron el mismo problema
El criterio para que una página sea clasificada como "no estándar (non-standard)" no es tan blanco o negro como uno pensaría
Además, creo que para quienes usaban configuraciones como
scrollback-limit = 0, la fuga pudo ocurrir con más frecuenciaTambién queda la duda de si, con la forma en que se corrigió, no se estarán eliminando y recreando páginas no estándar innecesariamente, cuando quizá se podrían haber reutilizado páginas antiguas que ya eran no estándar
La forma en que funciona PageList siempre fue la misma, y aun cuando existía el bug, durante el ajuste de capacidad solo se estaba viendo un tamaño incorrecto
No debería haber cambios perceptibles en el rendimiento
También se consideró la alternativa que propones, pero el enfoque actual está suficientemente respaldado por datos de benchmarks
Yo también podría cambiar de opinión, pero esta vez me enfoqué en corregir la fuga en lugar de replantear por completo el modelo
De hecho, era un bug reproducible que provocaba segfault
Ha hecho que la CLI se sienta más nueva que cualquier otra cosa en los últimos 20 años
Fue un gran artículo. Gracias a mitchellh por crear Ghostty
Me cambié el año pasado y no me he arrepentido ni una vez
Eso sí, me sorprendió un poco que la corrección vaya a incluirse en una versión de funciones dentro de varios meses
Pensé que entraría en una versión de corrección de errores
En cuanto empezaron a hablar de páginas pensé: “ah, es memory pooling”, y luego: “seguro es un ring buffer”, y efectivamente era reutilización de scrollback
También imaginé de inmediato dónde estaba el bug: en la parte donde no se liberaba correctamente la memoria de las páginas
El diagrama de alineación de memoria también estuvo genial
Me hizo recordar otra vez que cada intento nuevo trae consigo la posibilidad de fugas
Esta semana me cambié a Ghostty y, mientras desarrollaba una app de UI para terminal, sufrí un crash por OOM
La estructura usaba íconos UTF8 en la barra de pestañas, y al redimensionar la terminal el crash ocurría de inmediato
Era tan fácil de reproducir que ya estaba preparando un reporte de bug, pero se ve muy similar al problema explicado en la publicación del blog
Espero que se resuelva
Le pregunté a @mitchellh qué herramienta usó para hacer la visualización de memoria y, como el sitio web también funcionaba bien en móvil, me dio curiosidad saber qué stack usa
El código para las visualizaciones era de un solo uso, así que validé la precisión más que la calidad
Separé los namespaces por publicación del blog y no los reutilicé
Solo verifiqué que la implementación no hiciera cosas raras (por ejemplo, minar bitcoin, filtrar secretos, etc.)
La clave es transmitir la información, y este tipo de diagramas hace que el contenido sea mucho más fácil de entender
He seguido de cerca el desarrollo de Ghostty
Tiene un ligero aire de sobreingeniería, pero este tipo de postmortems de bugs son material muy valioso para quienes aman la artesanía del software
Me da curiosidad cómo se implementaría algo así en un terminal basado en Rust sin perder rendimiento
Incluso sin conocer bien Ghostty o los emuladores de terminal, fue un artículo fácil de entender
Me impresionó lo accesible que era y lo amable de la explicación
Me recordó la importancia de los reportes de bugs reproducibles
Estoy esperando a que alguien diga: “si hubieran usado Rust, esto no habría pasado”
Rust no garantiza a nivel de lenguaje la "seguridad ante fugas de memoria (leak safety)"
Incluso código Rust seguro puede filtrar memoria — solo que eso no es un problema de seguridad
La API estándar incluso permite fugas de forma explícita, como con Box::leak
Rust simplemente hace que sea más difícil crear fugas no intencionales, pero no las impide por completo