El bug de aptitude que olvida los indicadores Auto

Cuando aptitude o apt instalan un paquete, guardan cierta información conocida como indicador Auto sobre si el paquete fue instalado a petición del usuario (Manual) o porque era necesario para resolver una dependencia (Automático). La herramienta de gestión de paquetes puede decidir posteriormente si puede desinstalar paquetes que ya no son necesarios basándose en este indicador, y de esta forma mantener las dependencias del sistema al mínimo imprescindible, con las ventajas que ello conlleva1.

Por desgracia, la implementación de esta funcionalidad tanto en aptitude como en apt ha sido históricamente defectuosa, y de vez en cuando algunas operaciones provocan que el indicador Auto de algunos paquetes se "olvide". Podéis encontrar múltiples informes de error en el Bug Tracking System de Debian referidos a esta situación, como los #490547 y #638049 de aptitude, y hay más (a los que habría que sumar los reportados contra apt).

Cuando escribí Radiografía de paquetes instalados en Debian, di por hecho que estos bugs habían sido arreglados definitivamente. Fundamentalmente porque, en los meses que transcurrieron entre que apliqué las técnicas que se describen ahí para encontrar y recuperar los indicadores Auto y la publicación del artículo, no había sufrido ningún otro caso de indicador perdido2. No ha sido hasta recientemente que, haciendo una comprobación rutinaria de bibliotecas no instaladas automáticamente, me he encontrado que nuevamente tenía paquetes sin el indicador Auto que deberían haberlo tenido. Desde entonces estoy realizando chequeos periódicos, tanto de paquetes de bibliotecas como del resto de paquetes, lo que me ha aportado valiosa información acerca de la naturaleza del bug, como explicaré más adelante.

El caso es que el bug difícilmente podía haber sido arreglado: resulta que aptitude ha estado desde 2011 sin un mantenedor activo la mayor parte del tiempo. Si queréis saber toda la —lamentable— historia, leed la resolución del Comité Técnico en #750135 Determine maintainer of aptitude package. La situación finalmente se ha desbloqueado el pasado mes de Agosto, y ahora el repositorio de aptitude vuelve a tener vida. Pero eso no significa necesariamente que alguien vaya a trabajar activamente en arreglar esos bugs, en todo caso que si alguien consigue algo, su trabajo puede ser ahora incorporado a upstream en vez de languidecer en el BTS.

Uno de los puntos fundamentales a la hora de encontrar la causa de un bug o error es ser capaz de reproducirlo. Si se consigue, se tiene la mitad del camino recorrido para resolverlo3. En este caso era importante saber si el bug se producía "de forma aleatoria" o estaba ligado a ciertas situaciones. Lo primero hubiera significado algún tipo de condición de carrera, y esos errores son difíciles de encontrar y solucionar. Pero si es un error que se puede reproducir una y otra vez, los programadores tienen una buena oportunidad de pillarlo "in fraganti". En mi caso el darme cuenta del bug casi de inmediato, cuando todavía me acordaba con detalle qué había pasado anteriormente, y el hecho de poder reproducirlo fácilmente me ha permitido deducir que estaba ante un bug del segundo tipo, por lo que tenía una buena oportunidad de cazarlo. Era cuestión de analizar detenidamente la situación (los paquetes y sus dependencias antes y después de la actualización) y ver si ello me permitía descubrir la combinación de factores que desencadenaban el bug.

Voy a explicar la situación exacta para que sigáis conmigo mis razonamientos: la anterior actualización había introducido un nuevo paquete llamado pelican4. Este paquete no era más que una actualización del paquete python-pelican, pero con el nombre cambiado5. Además le acompañaba una nueva versión del paquete python-pelican pero sin contenido (lo que se llama un paquete transicional) con una dependía de pelican. Hasta aquí todo normal, es lo que se hace cuando se quiere cambiar el nombre a un paquete: introducir uno con el nombre nuevo y descontinuar el paquete con el nombre antiguo. Como parte de este cambio, el paquete nuevo "hereda" las dependencias del antiguo, mientras que el antiguo se queda sin dependencias (al estar vacío), excepto una al paquete nuevo (para forzar al gestor de paquetes a instalar el nuevo paquete si se tenía instalado el antiguo).

En ese momento no le di más importancia al cambio. Fue mucho más tarde, cuando me puse a revisar la lista de paquetes instalados manualmente6 y a compararla con la última versión que tenía de la misma, cuando me encontré con todos estos paquetes "nuevos" sin el indicador de Auto:

i   docutils-common          0.12+dfsg-1    - text processing system for reStruc
i   pelican                  3.6.0-4        - blog aware, static website generat
i   python-pelican           3.6.0-1        - blog aware, static website generat
i   python-blinker           1.3.dfsg2-1    - Fast, simple object-to-object and 
i   python-dateutil          2.2-2          - powerful extensions to the standar
i   python-docutils          0.12+dfsg-1    - text processing system for reStruc
i   python-feedgenerator     1.7-2          - Syndication feed generation librar
i   python-jinja2            2.7.3-1        - small but fast and easy to use sta
i   python-markdown          2.6.2-1        - text-to-HTML conversion library/to
i   python-markupsafe        0.23-2         - HTML/XHTML/XML string library for 
i   python-pygments          2.0.1+dfsg-1.1 - syntax highlighting package writte
i   python-roman             2.0.0-1        - module for generating/analyzing Ro
i   python-unidecode         0.04.16-1      - ASCII transliterations of Unicode

Cuando me puse a investigar de dónde habían salido esos paquetes (excepto python-pelican que había instalado yo a mano) es cuando me dí cuenta que todos eran dependencias de pelican —y anteriormente de python-pelican—. ¿Justo hay un cambio de paquete python-pelican a pelican y sus dependencias pierden el indicador Auto? Sospechoso.

Para verificar que efectivamente el bug podía estar relacionado con la "migración de dependencias" entre paquetes, lo primero que hice fue reproducir de nuevo la situación de partida, instalando el anterior python-pelican 3.5.0-1, desinstalando pelican y reseteando los indicadores de estos paquetes nuevamente a Auto. Tras hacer de nuevo el aptitude upgrade los mismos paquetes volvieron a perder el indicador.

Una vez tenía la confirmación fehaciente de que era un bug reproducible y no aleatorio, era el momento de ponerse a investigar más a fondo. Por no alargarme demasiado, resumiré gráficamente la situación con un diagrama de dependencias del paquete pelican (el mismo que utilicé en ese momento):

Diagrama de dependencias de pelican

(Había más dependencias hacia la derecha pero las he descartado porque no aportan nada relevante a la discusión)

Como podéis comprobar vosotros mismos cotejando la lista anterior de paquetes y la gráfica, habían perdido el indicador Auto las dependencias de pelican, así como las dependencias de estas dependencias. Sin embargo, había algunas excepciones: python-pkg-resources, python-six y python-tz, a pesar de ser dependencias directas de pelican, conservaban aún el Auto. La pregunta era por qué, y si había alguna diferencia que lo justificara.

Analizando las dependencias inversas de todos estos paquetes encontré que mientras los paquetes que no tenían otras dependencias inversas que pelican habían perdido su indicador, los que tenían otras dependencias inversas lo conservaban. Este diagrama ampliado aclara lo que quiero decir:

Diagrama de dependencias de pelican ampliado

Este diagrama muestra que otros paquetes aparte de pelican dependen de python-pkg-resources, y lo mismo pasa con python-six, del que también dependen python-cryptography, python-openssl y python-debian además de python-dateutil y python-feedgenerator. El tercer caso, python-tz, es el más problemático, ya que no recibe dependencias de otros paquetes externos, sólo de pelican y python-feedgenerator. Pero curiosamente es el único7 que tiene a la vez una dependencia directa y una indirecta (a través de python-feedgenerator) de pelican.

Por otro lado tenemos paquetes como docutils-common, python-markupsafe y python-roman, que no son dependencias directas de pelican (aunque sí indirectas, a través de python-jinja2 y python-docutils) pero que aún así pierden el indicador. Los tres son dependencias de paquetes —y sólo de estos paquetes— que pierden el indicador Auto, lo que sugiere algún tipo de comportamiento recursivo.

La hipótesis que se estaba formando en mi mente era que al "migrar" las dependencias de un paquete A a otro distinto A' (que en el caso anterior corresponderían a python-pelican y pelican), los paquetes de dichas dependencias —pongamos B, C y D— perdían en el proceso el indicador Auto. En los casos en los que había un tercer paquete Z implicado que también dependía de uno de dichos paquetes —pongamos D— ésta otra dependencia de Z evitaba que D perdiera el Auto. Y además, parecía un comportamiento recursivo: los paquetes que perdían el Auto hacían que a su vez sus dependencias —E, F, G y J— lo perdieran si éstas no tenían una dependencia inversa de otro paquete que lo impidiera (como la de Z en el caso de D).

O sea, si la situación antes de un upgrade era esta (fondo gris para los paquetes con el indicador Auto):

Ejemplo: antes del upgrade

tras el upgrade que "migra" las dependencias de A a A' los paquetes B, C, E, F, G y J habrían perdido el indicador Auto, pero no el paquete D:

Ejemplo: despues del upgrade

El siguiente caso que me permitió poner a prueba esta hipótesis fue cuando entraron en el repositorio de testing/stretch los paquetes de ffmpeg que sustituían a los de libav:

Se instalarán los siguiente paquetes NUEVOS:
  libavdevice-ffmpeg56{a} libavfilter-ffmpeg5{a} libpgm-5.1-0{a} libpostproc-ffmpeg53{a} libsodium13{a} libswscale-ffmpeg3{a} libzmq3{a} 
Se ELIMINARÁN los siguientes paquetes:
  libavdevice55{u} libavfilter5{u} libpostproc52{u} libswscale3{u} 
Se actualizarán los siguientes paquetes:
  mplayer2 mpv

Aquí tenemos un nuevo caso del patrón "renombramiento de paquetes" (esta vez añadiendo el sufijo --ffmpeg) donde los nuevos paquetes reciben las dependencias que pierden los antiguos. Tras esa actualización, 3 paquetes de bibliotecas dinámicas perdieron el indicador Auto:

i   libopencv-core2.4        2.4.9.1+dfsg-1 - computer vision core library      
i   libopencv-imgproc2.4     2.4.9.1+dfsg-1 - computer vision Image Processing l
i   libtbb2                  4.2~20140122-5 - parallelism library for C++ - runt

Y aquí el diagrama con las dependencias implicadas:

Diagrama de dependencias de ffmpeg

Lo particular de este caso es que es más explícito el comportamiento recursivo de la pérdida del indicador Auto. No sólo afecta a 3 niveles de profundidad en vez de 2 del caso anterior, sino que además es una cadena muy clara y sin interferencias de otros paquetes.

Afinando la puntería

A estas alturas creo que ya os habré convencido sobre la sintomatología del bug8, pero queda por averiguar cuál es su causa (si es que queremos resolverlo). Es obvio que algo no está funcionando correctamente en aptitude, pero aptitude son más de 90.000 líneas de código C++, y una pista con la que poder acotar la búsqueda sería muy de agradecer.

Y es posible que la haya encontrado. Mirando bugs aquí y allá en el BTS he visto el siguiente comentario del propio autor de aptitude Daniel Burrows:

This changed in revision 2085.1.1 of apt. <rant>Fer crying out loud, if I had the time I'd just ditch their handling of the auto flag and do it myself</rant>. What's especially frustrating is that they tried to accommodate aptitude, but they didn't understand what they were doing. Guess it's really my fault for not keeping up on this stuff.

I guess that as a workaround (ew) I can save the auto flag before I call MarkInstall and restore it afterwards.

¿No os da la impresión como a mí de estar contemplando el origen del bug? Me refiero al segundo párrafo. Si lo que hace aptitude actualmente es guardar los indicadores Auto antes de empezar el upgrade y restaurarlos después, al actualizarse el paquete antiguo y quedarse sin las dependencias durante el upgrade, es muy posible que la herramienta pierda debido a ello el rastro de los indicadores Auto de esos paquetes y luego no pueda restaurarlos simplemente porque carece de la información. Cuando hay otros paquetes implicados, la dependencia que permanece "fija" permite que aptitude conserve la información del indicador Auto y pueda restaurarlo al final. Si este proceso es además recursivo, lo cual es una buena suposición9, la hipótesis encajaría perfectamente.

De todas formas, antes de meterme a revisar código creo que lo primero es construir un "test sintético", un ejemplo ficticio de repositorio de paquetes con dependencias (similar al caso A, B, C, ... de arriba) que sirva para probar las veces que haga falta la situación en la que ocurre el bug. A partir de ahí se puede enviar un informe de error a los mantenedores actuales de aptitude, proporcionándoles de paso una forma sencilla de reproducir el bug ellos mismos. Y si tras ello no se les ve interesados, entonces se puede empezar a pensar en meterse a bucear en el código fuente de aptitude.

En cualquier caso seguiré informando.

:wq


  1. el espacio en disco hoy en día se considera "barato", pero más software instalado implica también más bugs, incluyendo más bugs de seguridad. 

  2. probablemente porque ese periodo coincidió en su mayoría con el freeze de Jessie, durante el cual las dependencias entre paquetes cambiaron poco o nada. 

  3. aunque muchas veces la otra mitad del camino puede terminar suponiendo el 80% del trabajo. 

  4. que curiosamente es el paquete del static site generator con el que genero este blog. 

  5. ignoro la razón concreta para querer cambiar el nombre de este paquete, pero Debian no debería permitir renombrar paquetes a la ligera. 

  6. con el comando que detallo en el ya mencionado artículo de Radiografía de paquetes instalados en Debian

  7. siendo estrictos no es el único, python-six también tiene una dependencia directa y una indirecta, pero en este caso eso no invalida el razonamiento si no que lo refuerza. 

  8. al menos de este bug, puede haber otros que se produzcan bajo otras circunstancias similares pero independientes. 

  9. la recursión es bastante común en algoritmos aplicados a árboles y grafos. 

blogroll

social