Test de conditions de course multithreads

54

Lire les commentaires à cette réponse , en particulier:

Ce n'est pas parce que vous ne pouvez pas écrire un test qu'il n'est pas cassé. Comportement indéfini qui fonctionne habituellement comme prévu (C et C ++ en sont remplis), conditions de compétition , réorganisation possible en raison d'un modèle de mémoire faible ... - CodesInChaos Il y a 7 heures

@CodesInChaos s'il ne peut pas être reproduit, le code écrit pour 'réparer' ne peut pas être testé non plus. Et mettre du code non testé en live est un crime pire à mon avis - RhysW Il y a 5 heures

... je me demande s’il existe de bonnes méthodes générales pour déclencher de façon constante des problèmes de production causés rarement par des conditions de concurrence en situation de test.

Dan Neely
la source
1
étape par étape (assemblage) instruction par instruction aux deux extrémités
freaket à cliquet
1
L'analyse statique peut souvent montrer un potentiel d'UB, sans préciser si cela compte comme test si
jk.
Désolé de demander, mais que signifie 'UB'?
Doug
2
Belle question, je serais intéressant de voir les solutions potentielles à cela.
RhysW
1
@Doug Comportement non défini, qui peut inclure, sans toutefois s'y limiter, les conditions de
concurrence

Réponses:

85

Après avoir été dans cette folle entreprise depuis environ 1978, après avoir passé presque tout ce temps dans l'informatique temps réel embarquée, travaillant dans plusieurs systèmes multitâches, multithread, multi-systèmes, parfois avec plusieurs processeurs physiques, après avoir chassé plus que ma part de race conditions, mon avis réfléchi est que la réponse à votre question est assez simple.

Non.

Il n'y a pas de bonne manière générale de déclencher une situation de concurrence critique lors des tests.

Votre seul espoir est de les concevoir complètement en dehors de votre système.

Quand et si vous trouvez que quelqu'un d'autre en a inséré un, vous devez le placer dans une fourmilière, puis le remodeler pour l'éliminer. Une fois que vous avez conçu son faux pas (prononcé f *** up) de votre système, vous pouvez aller le libérer des fourmis. (Si les fourmis l'ont déjà consommé, ne laissant que des os, mettez en place une pancarte indiquant "C'est ce qui arrive aux personnes qui soumettent les conditions de course au projet XYZ!" Et laissent-y.)

John R. Strohm
la source
22
Je suis complètement d'accord. En d'autres termes, cela ressemble beaucoup à la blague - Patient: "Docteur, ça me fait mal quand je fais ça ..." Docteur: "Alors arrêtez de le faire!"
Mark Rushakoff
Bonne réponse. Si quelque chose cause un problème non testable, essayez de le contourner pour commencer, évitez le problème tout à fait!
RhysW
Ma seule question est: quelle taille d'une fourmilière devrais-je utiliser? (+1 BTW).
Peter K.
15
+1 pour la prononciation correcte de faux pas . (Et le reste de la réponse.)
Blrfl
1
@PeterK., Il s’agit de l’un des rares cas de développement logiciel, avec les moniteurs, la RAM et les lecteurs de disque, où les plus gros IS sont meilleurs.
John R. Strohm
16

Si vous êtes dans la chaîne d'outils ms. Mme recherche a créé un outil qui forcera les nouveaux interlevings pour chaque course et peut recréée exécute son appelé échec aux échecs .

voici une vidéo le montrant en cours d'utilisation.

rediffusion
la source
5
Cela semble impressionnant; Je vais devoir trouver le temps de l'essayer à un moment donné.
Dan Neely
16

Le meilleur outil que je connaisse pour ce type de problèmes est une extension de Valgrind appelée Helgrind .

En gros, Valgrind simule un processeur virtuel et exécute votre fichier binaire (non modifié), afin de pouvoir vérifier chaque accès à la mémoire. En utilisant ce cadre, Helgrind surveille les appels système pour déduire lorsqu'un accès à une variable partagée n'est pas correctement protégé par un mécanisme d'exclusion mutuelle. De cette façon, il peut détecter une situation de concurrence théorique même si cela ne s'est pas réellement produit.

Intel vend un outil très similaire appelé Intel Inspector .

Ces outils donnent d'excellents résultats, mais votre programme sera considérablement plus lent pendant l'analyse.

Julien
la source
1
Valgrind est-il encore un outil uniquement * nix?
Dan Neely
1
Oui, Linux, MacOSX, Android et quelques BSD: valgrind.org/info/platforms.html
Julien
1
ThreadSanitizer est un outil similaire. Cela fonctionne différemment de Helgrind, ce qui lui donne l'avantage d'être beaucoup plus rapide, mais nécessite une intégration dans la chaîne d'outils.
Sebastian Redl
7

Exposer un bogue multi-threading nécessite de forcer différents threads d'exécution à exécuter leurs étapes dans un ordre entrelacé particulier. En général, c'est difficile à faire sans déboguer manuellement ou manipuler le code pour obtenir une sorte de "handle" permettant de contrôler cet entrelacement. Cependant, le fait de modifier un code qui se comporte de manière imprévisible aura souvent une influence sur cette imprévisibilité. Il est donc difficile à automatiser.

Jaroslav Tulach a décrit une astuce intéressante dans Practical API Design : si vous avez des instructions de journalisation dans le code en question, manipulez le consommateur de ces instructions de journalisation (par exemple, un pseudo-terminal injecté) de manière à ce qu'il accepte les messages de journalisation individuels dans un journal donné. ordre basé sur leur contenu. Cela vous permet de contrôler l'entrelacement d'étapes dans différents threads sans avoir à ajouter quoi que ce soit au code de production qui n'existe pas déjà.

Kilian Foth
la source
2
J'ai fait la même chose avant d'utiliser le référentiel injecté pour mettre en sommeil les threads qui l'appellent dans des ordres spécifiques pour forcer l'entrelacement que je veux. Ayant écrit du code qui le fait, je suis enclin à +1 la réponse de @ John ci-dessus. Sérieusement, cette matière est si pénible à utiliser correctement et ne donne toujours que les meilleures garanties de conjecture car il pourrait y avoir des entrelacs légèrement différents avec des résultats différents; la meilleure approche consiste simplement à éliminer toutes les conditions de concurrence possibles par une analyse statique et / ou un regroupement soigneux du code pour tout état partagé
Jimmy Hoffa
6

Il n’ya aucun moyen d’être absolument sûr que différents types de comportement indéfini (dans des conditions de concurrence particulières) n’existent pas.

Cependant, il existe un certain nombre d’outils qui illustrent bon nombre de ces situations. Vous pourrez peut-être prouver qu'un problème existe actuellement avec de tels outils, même si vous ne pouvez pas prouver que votre correctif est valide.

Quelques outils intéressants à cet effet:

Valgrind est un vérificateur de mémoire. Il trouve les fuites de mémoire, les lectures de mémoire non initialisée, les utilisations de pointeurs en suspens et les accès hors limites.

Helgrind est un vérificateur de sécurité des threads. Il trouve des conditions de course.

Les deux fonctionnent par instrumentation dynamique, c'est-à-dire qu'ils prennent votre programme tel quel et l'exécutent dans un environnement virtualisé. Cela les rend peu intrusifs, mais lents.

UBSan est un vérificateur de comportement non défini. Il trouve divers cas de comportement indéfini en C et C ++, tels que des débordements d'entiers, des décalages hors de la plage, etc.

MSan est un vérificateur de mémoire. Il a des objectifs similaires à ceux de Valgrind.

TSan est un vérificateur de sécurité des threads. Ses objectifs sont similaires à ceux de Helgrind.

Ces trois éléments sont intégrés au compilateur Clang et génèrent du code au moment de la compilation. Cela signifie que vous devez les intégrer dans votre processus de construction (en particulier, vous devez compiler avec Clang), ce qui les rend beaucoup plus difficiles à configurer au départ que * grind, mais d'un autre côté, leur temps d'exécution est beaucoup moins important.

Tous les outils que j'ai énumérés fonctionnent sous Linux et certains sous MacOS. Je ne pense pas que tout fonctionne sur Windows de manière fiable pour le moment.

Sebastian Redl
la source
1

Il semble que la plupart des réponses ici confondent cette question avec "comment puis-je détecter automatiquement les conditions de course?" quand la question est vraiment "comment reproduire les conditions de concurrence dans les tests quand je les trouve?"

Pour ce faire, introduisez une synchronisation dans votre code, qui sert uniquement à des tests. Par exemple, si une condition de concurrence critique se produit lorsque l'événement X se produit entre l'événement A et l'événement B, écrivez du code qui teste votre application et attend que l'événement X se produise à la suite de l'événement A. Vous aurez probablement besoin d'un moyen pour que vos tests discutent avec votre application ("hé, je teste cette chose, attendez donc cet événement à cet endroit").

J'utilise node.js et mongo, où certaines actions impliquent la création de données cohérentes dans plusieurs collections. Dans ces cas, mes tests unitaires appellent l’application pour lui indiquer «configurer une attente pour l’événement X» et, une fois que l’application l’a configurée, le test de l’événement X sera exécuté et les tests indiqueront ensuite: l’application ("j’ai terminé avec l’attente de l’événement X") afin que le reste des tests s’effectue normalement.

La réponse ici explique ce genre de chose en détail dans le contexte de python: https://stackoverflow.com/questions/19602535/how-can-i-reproduce-the-race-conditions-in-this-python-code- de manière fiable

BT
la source