- GitHub 7-9 : générateur de site de documentation, ASCII Art, Fair Analytics
- Quelques conseils pour améliorer ses graphiques
Petit rapport d’une expérience professionnelle rageante.
Le titre est un peu mensonger, quoique. L’un des projets auxquels je participe dans ma vie professionnelle consiste en la création de super-calculateurs destinés au traitement massif de données pour divers clients. Bien évidemment, vu l’ampleur de la tâche, il y a beaucoup d’équipes différentes sur des parties également très différentes. En l’occurrence, je ne touche pas du tout à la conception de la partie hardware pure.
Il se trouve que je travaille sur un logiciel installé sur l’ensemble des noeuds composants les différents racks de la bestiole finale et que ce logiciel, en Python, effectue beaucoup d’actions et de calculs en parallèle. Comme beaucoup de logiciels sur le cluster, celui-ci requiert une parfaite synchronisation des noeuds, et ce comme condition critique (des noeuds non-synchronisés et toute la machine s’arrête pour des raisons de cohérence de données par exemple).
Pour garantir cela, NTP est installé sur chaque noeud. Cependant, j’ai développé une petite surcouche pour surveiller manuellement NTP et l’heure afin de détecter des changements soudains et abruptes pour lesquels NTP serait trop lent pour réagir. C’est le cas par exemple si NTP était stable pendant un long moment et recontactera le serveur qu’une fois toute les X secondes où X peut valoir des milliers de secondes ! Imaginez si je force l’heure sur un noeud (date -s
), change celui-ci de plusieurs heures et doit attendre plusieurs minutes pour voir une resynchronisation ! Inacceptable et la machine crashera avant quelque part.
Bref, mon petit module Python semble fonctionner individuellement, mais une fois intégrée à la machine, lorsque je change l’heure dans le futur de quelques heures, bien que celle-ci est ramenée correctement dans le passé, la machine semble figée. Plus rien ne marche. De surcroit aucun log n’est disponible à aucun niveau que ce soit, et aucun problème ne semble réellement survenir à part un mystérieux arrêt total du fonctionnement.
Erreur de conception ? Dans l’OS ? Dans la stack software que l’on rajoute ? Panique à bord. Et bien il se trouve que le problème provient de… Python. En effet, la stack logicielle qui gère la machine est, comme on peut s’en douter, fortement asynchrone, distribuée et parallèle (ce qui était déjà en soit la galère pour tracer le problème) et donc beaucoup de procédures sont appelées avec un timeout et gérées par un système maison qui repose sur des queues en Python.
Manque de bol, la méthode get
avec timeout et appel bloquant bloque sur la ligne suivante car dans le module threading, la classe Condition
n’attend pas durant $\delta$ la durée du timeout mais calcul le temps restant à partir du… temps système. Ainsi, lorsque je change manuellement le temps de $t$ à $t+T$ via date -s
par exemple, il y a un laps de temps durant lequel le logiciel appelle get
avec un timeout $\delta$. Le processus va bloquer, mais pendant ce temps, j’ai détecté le changement de temps et ramené la machine à $t+w$. Comme Python se base sur le temps système, le temps à atteindre devient $t+T-t + \delta= T + \delta$. Autrement dit, le timeout considéré n’est plus le même: tout est bloqué jusqu’à $T+T+\delta$ (et évidemment comme j’ai changé par 3 heures dans le futur, je n’ai pas eu la patience d’attendre et de me rendre compte que tout allait bien après cette durée :D).
Vous pouvez faire le test chez vous en ouvrant deux terminaux. Dans le premier, démarrez Python en CLI:
1 2 3 4 | import Queue #queue pour Python 3 qe = Queue.Queue() qe.get(block=True, timeout=15) |
Et pendant les 15 prochaines secondes, dans le second terminal date -s <nouvelle_date_dans_le_passé>
(il faudra les droits pour cela). En temps normal, vous devriez avoir une exception Empty
indiquant que la queue est vide, ce qui n’est plus le cas après le changement de date.
Alors qu’ai-je fait pour résoudre le problème ? Patché Queue (même si en fait c’est threading
qui pose problème) en créant une classe perso qui hérite de Queue et surcharge get
avec un algorithme qui prend en compte le temps écoulé plutôt que le temps restant (avec quelques subtilités).
Et vous ? Quelles ont été les chasses aux bugs et les choses les plus étranges que vous ayez-vu (en Python ou autre) ?