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 conlleva.
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 perdido. 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 resolverlo. 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 pelican
. Este paquete no era más que una
actualización del paquete python-pelican
, pero con el nombre
cambiado. 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 manualmente
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):

(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:

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 único 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):

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
:

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:

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 bug, 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ón, 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