Il y a quelques jours, j'ai écrit à propos de Rewind VM, une machine virtuelle déterministe où chaque exécution d'une construction Nix est une fonction pure de ses entrées, y compris l'ordonnancement des threads ! Je l'ai utilisée pour trouver, reproduire et résoudre de nombreuses conditions de course dans les constructions Nix, mais cela me rend un peu fou. Quelle est cette superpuissance, et pourquoi personne d'autre ne l'utilise-t-elle ?
L'outil a rapidement gagné un panneau de code source, des cadres de pile, des signets, un onglet "Compare", un bon support GDB, des voies de threads qui montrent qui a tenu la CPU à chaque étape, et "Check from here", qui aide à trouver l'étape exacte où une condition de course se produit. Je suis entré dans chaque nouvelle fonction pensant que ce serait un grand soulèvement de code, mais je continuais à rencontrer la même chose : la partie difficile de chaque fonction était déjà faite, et Nix l'avait faite.
Par exemple, un débogueur a besoin des entrées exactes du programme, de ses symboles de débogage, de ses sources, des sources de chaque bibliothèque sous celui-ci, et d'un moyen pour quelqu'un d'autre d'obtenir tout cela sur leur machine. C'est exactement ce qu fait une dérivation, et Nix rend cela facile.
Deux guichetiers, un compte
Un exemple classique simple d'une condition de course est deux threads déposant sur un compte bancaire. Chaque dépôt lit le solde, écrit une ligne dans le grand livre, puis stocke le solde plus le dépôt. Si un guichetier s'exécute entre la lecture et le stockage de l'autre, il écrit un solde obsolète sur les dépôts de l'autre.
static void deposit(int teller, long amount){long seen = balance;char line[64];int n = snprintf(line, sizeof line,"teller %d: %ld + %ld\n",teller, seen, amount);if (write(1, line, n) != n)return;balance = seen + amount;}
Un guichetier qui s'exécute entre la lecture et le stockage de l'autre écrit un solde obsolète sur les dépôts de l'autre.
11
Sur mon ordinateur portable à 16 cœurs, la banque a perdu de l'argent dans 396 sur 1 000 exécutions. Épinglé à un seul cœur avec taskset, elle n'a perdu de l'argent dans aucun des 1 000 : un seul cœur à peine commute les threads au milieu d'un dépôt, c'est pourquoi Rewind perturbe l'ordonnancement.
La VM de Rewind a un seul CPU, donc son premier passage est trop bon. rewind check relance la construction sous des ordonnancements perturbés, chaque fois demandant au noyau invité de réordonner à des étapes différentes, et réduit la première échec à une seule étape :
$ rewind check --where github:fzakaria/rewindvm#bank...step 3237 décide : une réordonnance là rend l'exécution échouer
passing: run 12feb5f832205a72, schedule 0
failing: run c2f1cfac0c288f3e, schedule 1 over steps 3237..3238
Les deux exécutions sont la même machine, sortie pour sortie, jusqu'à l'étape 3237, où seulement celle qui échoue reçoit une réordonnance.
Chose gratuite une : les entrées
L'argument de check est une dérivation et peut être une référence flake. La beauté de Nix est qu'il connaît les entrées d'une dérivation, donc c'est tout ce dont nous avons besoin pour assurer que l'exécution est reproductible. Les entrées de la dérivation sont la source, le compilateur, les bibliothèques, le noyau, et la configuration de la VM. Rewind nix réalise les entrées de la dérivation, emballe la fermeture dans une image erofs en lecture seule, et démarre la VM dessus. L'identifiant de l'exécution est un hash de ses entrées, de la même manière qu'un chemin de stock, et rewind show affiche la commande qui le recrée :
$ rewind show 12feb5f8
rewind nix /nix/store/cr8rl40rjb9cmmcd9sxn9mpdrhvp53jj-bank-0.1.0.drv --epoch --clock branches --name bank-0.1.0 # 12feb5f832205a72
Lorsque la construction réussit, l'invité signale le hash NAR de chaque sortie, et rewind nix le compare avec la copie de l'hôte et avec chaque cache binaire de substitution de Nix, en ne fetchant que le .narinfo :
$ rewind nix github:fzakaria/rewindvm#bank.../nix/store/27a4q7w90jzp4wg5kaaajvb64zll6yqg-bank-0.1.0 dbf0df3de84b7a84 matches your store
Cela nous aide à valider que la construction dans la VM est la même que sur mon ordinateur portable, et que l'exécution de la VM est reproductible par Nix.
Source: Hacker News · Résumé par HeadlinesBriefing