Nix wrote half of my debugger
🇬🇧 English
A few days ago I wrote about Rewind VM, a deterministic VM where every run of a Nix build is a pure function of its inputs, thread schedule included! I have been using it to find, reproduce and solve numerous race conditions in Nix builds, but it's been making me feel a little crazy. What is this superpower, and why is nobody else using it? The tool has quickly grown a source panel, stack frames, bookmarks, a "Compare" tab, a lot more gdb support, thread lanes, which show who held the CPU at every step, and "Check from here", which helps find the exact step where a race condition happens. I went into each new feature thinking it would be a big code-lift but I kept running into the same thing: the hard part of each feature was already done, and Nix had done it. A debugger for instance, needs the exact inputs of the program, its debug symbols, its sources, the sources of every library under it, and a way for someone else to get all of that on their machine. That's exactly what a derivation is, and Nix makes that easy.
Two tellers, one account
A classic simple example of a race condition is two threads depositing into one bank account. Each deposit reads the balance, writes a line to the ledger, then stores the balance plus the deposit. If one teller runs between the other's read and store, it writes a stale balance over the other's deposits. 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;}
A teller that runs between the other's read and store writes a stale balance over the other's deposits.
11
On my 16-core laptop, bank lost money in 396 of 1,000 runs. Pinned to a single core with taskset, it lost money in none of 1,000: one core alone rarely switches threads in the middle of a deposit, which is why Rewind perturbs the schedule.
Rewind's VM has one CPU, so its first run passes too. rewind check runs the build again under perturbed schedules, each asking the guest kernel to reschedule at different steps, and narrows the first failure down to one step:$ rewind check --where github:fzakaria/rewindvm#bank...step 3237 decides it: a reschedule there makes the run fail
passing: run 12feb5f832205a72, schedule 0
failing: run c2f1cfac0c288f3e, schedule 1 over steps 3237..3238
The two runs are the same machine, exit for exit, until step 3237, where only the failing one gets a reschedule.
Free thing one: the inputs
The argument to check is a derivation and can be a flake reference. The beauty of Nix is that it knows the inputs of a derivation, so that's all we need to make sure the run is reproducible. The derivation's inputs are the source, the compiler, the libraries, the kernel, and the VM's configuration. Rewind nix realises the derivation's inputs, packs the closure into a read-only erofs image, and boots the VM on it. The run's id is a hash of its inputs, the way a store path is, and rewind show prints the command that makes it again:$ rewind show 12feb5f8
rewind nix /nix/store/cr8rl40rjb9cmmcd9sxn9mpdrhvp53jj-bank-0.1.0.drv --epoch --clock branches --name bank-0.1.0 # 12feb5f832205a72
When the build succeeds, the guest reports each output's NAR hash, and rewind nix checks it against the host's copy and against every binary cache Nix substitutes from, by fetching only the .narinfo:$ rewind nix github:fzakaria/rewindvm#bank.../nix/store/27a4q7w90jzp4wg5kaaajvb64zll6yqg-bank-0.1.0 dbf0df3de84b7a84 matches your store This helps us validate that the build within the VM is the same as the build on my laptop, and that the VM's run is reproducible by Nix.
🇸🇦 العربية
مُحلِّل دوري يجد شروط السباق في بناءات Nix
يومًا ما كتبت عن Rewind VM، آلة افتراضية دورية حيث يكون كل تشغيل لبناء Nix دالة صافية لمدخلاته، بما في ذلك جدول الخيوط! استخدمتها لاكتشاف شروط السباق في بناءات Nix، وتكاثرها، وحلها، لكنها جعلتني أشعر بقلة الاتزان. ما هذه القوة الفائقة، ولماذا لا يستخدمها الآخرون؟
لقد نمت الأداة بسرعة لتصبح لوحة مصادر، إطارات كول، علامات ت Bookmarks، تاب "Compare"، دعمmany لـ GDB، قنوات خيوط تُظهر من امتلك المعالجة في كل خطوة، و"Check from here"، التي تساعد في العثور على الخطوة الدقيقة حيث يحدث شرط سباق. دخلت في كل ميزة جديدة أthought كانت ستتطلب نقلًا ضخًا للnist، لكنني واجهت نفس الشيء مرارًا: الجزء الصعب من كل ميزة كان بالفعل مكتملًا، وNix قام به.
مثلاً، يحتاج المُحلِّل إلى المدخلات الدقيقة لل程序، ورموز التصحيح، ومصادرها، ومصادر كل مكتبة تحتها، وطريقة لكي ي 获取 كل ذلك على جهاز شخص آخر. هذا هو بالتحديد ما تفعله Derivation، وNix يجعله سهلاً.
اثنان من الصناديق، حساب واحد
مثال كلاسيكي بسيط ل-condition سباق هو خيطان يوديعان في حساب بنكي واحد. كل ودعة تقرأ الرصيد، تكتب خطًا في الدفتر، ثم تحفظ الرصيد بالإضافة إلى الودعة. إذا جرى الصندوق بين قراءة الشريك وحفظه، يكتب رصيدًا قديمًا على ودائع الشريك.
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;}
الصناديق الذي يjem بين قراءة الشريك وحفظه يكتب رصيدًا قديمًا على ودائع الشريك.
11
في جهاز اللاب توب الذي يحتوي على 16 أنوية، فقدت Bank المال في 396 من 1,000 تشغيل. تم ربطها بأنوية واحدة مع taskset، لم تفقد المال في أي من 1,000: أنوية واحدة نادرًا ما تغيّر الخيوط في منتصف الودعة،لهذا السبب Rewind يُ disturbance الجدول.
VM Rewind تحتوي على معالجة واحدة، لذاruns الأول يمر بسهولة. rewind check يexecutes Build مرتين تحت جدول م disturbance، كل one يطلب من kernel الضيف重新 تخطيط في خطوات مختلفة، ويقلل الفشل الأول إلى خطوة واحدة:
$ rewind check --where github:fzakaria/rewindvm#bank...step 3237 يحدد: there reschedule يجعل Runs فشل
passing: run 12feb5f832205a72, schedule 0
failing: run c2f1cfac0c288f3e, schedule 1 over steps 3237..3238
الruns الثلاثة نفس الآلة، خرج for خرج، حتى خطوة 3237، حيث يplanes فقط الفشل reschedule.
الشيء/free الأول: المدخلات
arguments لـ check هو Derivation و يمكن أن يكون flake reference. جمال Nix أنه يعلم inputs لـ Derivation، لذا فهذا كل ما نحتاجه لضمان reproducible. inputs Derivation هي Source، Compiler، Libraries، Kernel، و VM configuration. Rewind nix يimplements inputs Derivation، يpack Closure في صورة read-only erofs، و يboot VM عليها. id Run هو hash inputs، كما أن store path، و rewind show يprint command that makes it again:
$ rewind show 12feb5f8
rewind nix /nix/store/cr8rl40rjb9cmmcd9sxn9mpdrhvp53jj-bank-0.1.0.drv --epoch --clock branches --name bank-0.1.0 # 12feb5f832205a72
عندما يًuccess Build، يُبلغ الضيف كل output NAR hash، و rewind nix يcheckها ضد نسخة Host و ضد كل binary cache Nix substitutes من، بـ fetching فقط .narinfo:
$ rewind nix github:fzakaria/rewindvm#bank.../nix/store/27a4q7w90jzp4wg5kaaajvb64zll6yqg-bank-0.1.0 dbf0df3de84b7a84 matches your store
هذا يساعدنا على validate أن Build داخل VM هو نفس Build على اللاب توب، و أن VM Run هو reproducible من Nix.
كيف يساعد Rewind VM في debug شروط السباق في بناءات Nix؟
Rewind VM تسجل جدول الخيوط الدقيق خلال Run بناء Nix. تسمح للمطورين بـ replay Execution تحت جدول م disturbance، تقلل شرط السباق إلى خطوة واحدة، و validate reproducibility باستخدام Nix derivation inputs و NAR hashes.
🇧🇩 বাংলা
নির্ধারিত ডিবাগার নিক্স বিল্ডে রেস কন্ডিশন খুঁজে পায়
কয়েক দিন আগে আমি রিউইন্ড ভিএম সম্পর্কে লিখেছিলাম, একটি নির্ধারিত ভার্চুয়াল মেশিন যেখানে একটি নিক্স বিল্ডের প্রতিটি রান তার ইনপুটের একটি পরিষ্কার ফাংশন, থ্রেড সিডিউলিং অন্তরভুক্ত! আমি এটি নিক্স বিল্ডে রেস কন্ডিশন খুঁজে পাওয়া, পুনরুৎপাদন করা এবং সমাধান করার জন্য ব্যবহার করেছি, কিন্তু এটি আমকে একটু পাগল করে তুলেছে। এই কোন সুপারপাওয়ার, এবং অন্যারা কেন এটি ব্যবহার করছে না?
এই টুলটি দ্রুত একটি সোর্স প্যানেল, স্ট্যাক ফ্রেম, বুকমার্ক, একটি "কমপেয়ার" ট্যাব, অনেক জিডিবি সমর্থন, থ্রেড লেন যা দেখায় যে প্রতিটি স্টেপে কে সিপিইউ ধারণ করেছিল, এবং "চেক ফ্রম হের" যা রেস কন্ডিশন ঘটার ঠিক স্টেপটি খুঁজে পাওয়ে সহায়তা করে। আমি প্রতিটি নতুন ফিচারে প্রবেশ করেছিলাম ভাবতে যে এটি একটি বড় কোড উঠানামা হবে, কিন্তু আমি বারবার একই জিনিসের সাথে দেখা হয়েছিল: প্রতিটি ফিচারের কঠিন অংশই ইতিমধ্যে সম্পন্ন ছিল, এবং নিক্স এটি করেছিল।
উদাহরণ হিসাবে, একটি ডিবাগারের প্রয়োজন হয় প্রোগ্রামের সঠিক ইনপুট, এর ডিবাগ সিম্বলস, এর সোর্সস, তার নিচের প্রতিটি লাইব্রেরির সোর্সস, এবং একটি উপায় যে অন্য কেউ তাদের মেশিনে এই সব কিছু পেতে পারে। এটি ঠিক এমনই যেমন একটি ডেরিভেশন, এবং নিক্স এটিকে সহজ করে।
দুই তালাকার, একটি হিসাব
রেস কন্ডিশনের একটি ক্লাসিক সাধারণ উদাহরण হলো দুই থ্রেড একটি ব্যাংক অ্যাকাউন্টে ডিপোজিট করা। প্রতিটি ডিপোজিট ব্যালেন্স পড়ে, লেজারে একটি লাইন লেখে, তারপর ব্যালেন্স এবং ডিপোজিট স্টোর করে। যদি এক তালাকার অন্যের পড়া এবং স্টোরের মধ্যে চলে যায়, সে অন্যের ডিপোজিটের উপর একটি স্টেল ব্যালেন্স লেখে।
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;}
এক তালাকার যা অন্যের পড়া এবং স্টোরের মধ্যে চলে যায়, অন্যের ডিপোজিটের উপর একটি স্টেল ব্যালেন্স লেখে।
11
আমার 16-কোর ল্যাপটপে, ব্যাংক ১,০০ৰ রানের মধ্যে ৩৯৬টিতে টাকা হারিয়েছে। টাস্কসেট দিয়ে একটি কোরে পিন করা হয়েছিল, ১,০০০টির একটিতেও টাকা হারায়নি: একটি কোর ডিপোজিটের মধ্যে থ্রেড পরিবর্তন করা খুব বিরল, তাই রিউইন্ড সিডিউলকে ব্যাঘিত করে।
রিউইন্ডের ভিএমে একটি সিপিইউ আছে, তাই তার প্রথম রান খুব ভালোভাবে হয়ে যায়। rewind check বিল্ডকে ব্যাঘিত সিডিউলের তहত আবার চালায়, প্রতিটি গেস্ট কার্নেলকে বিভিন্ন স্টেপে রিসিডিউল করার অনুরোধ করে, এবং প্রথম ফেলকে একটি স্টেপে সীমিত করে:
$ rewind check --where github:fzakaria/rewindvm#bank...step 3237 সিদ্ধান্ত: একটি রিসিডিউল রানকে ফেল করে
passing: run 12feb5f832205a72, schedule 0
failing: run c2f1cfac0c288f3e, schedule 1 over steps 3237..3238
দুইটি রান একই মেশিন, ইক্সিট ইক্সিট, স্টেপ 3237 পর্যন্ত, যেখানে কেবল ফেলিং একটি রিসিডিউল পায়।
ফ্রি জিনিস এক: ইনপুট
চেকের তর্ক হলো একটি ডেরিভেশন এবং এটি একটি ফ্লেক রেফারেন্স হতে পারে। নিক্সের সৌন্দর্য হলো এটি ডেরিভেশনের ইনপুট সম্পর্কে জানে, তাই এটিই আমাদের প্রয়োজন যে রান পুনরুৎপাদনীয় হয়। ডেরিভেশনের ইনপুট হলো সোর্স, কম্পাইলার, লাইব্রেরি, কার্নেল, এবং ভিএমের কনফিগারেশন। রিউইন্ড নিক্স ডেরিভেশনের ইনপুট বাস্তবায়ন করে, ক্লোজারকে একটি রিড-অনলি ইরোফস ইমেজে প্যাক করে, এবং ভিএমকে তার উপর বুট করে। রানের আইডি হলো তার ইনপুটের হ্যাশ, যেমন স্টোর পथের মতো, এবং রিউইন্ড শো প্রদর্শন করে কমান্ড যা এটি পুনরায় তৈরি করে:
$ rewind show 12feb5f8
rewind nix /nix/store/cr8rl40rjb9cmmcd9sxn9mpdrhvp53jj-bank-0.1.0.drv --epoch --clock branches --name bank-0.1.0 # 12feb5f832205a72
যখন বিল্ড সফল হয়, গেস্ট প্রতিটি আউটপুটের এনআর হ্যাশ রিপোর্ট করে, এবং রিউইন্ড নিক্স এটি হোস্টের সংস্করণ এবং নিক্স প্রতিস্থাপন করা বিভিন্ন বাইনারি ক্যাশের সাথে তুলনা করে, কেবল .narinfo ফেচ করে:
$ rewind nix github:fzakaria/rewindvm#bank.../nix/store/27a4q7w90jzp4wg5kaaajvb64zll6yqg-bank-0.1.0 dbf0df3de84b7a84 matches your store
এটি আমাদের সহায়তা করে যে ভিএমের মধ্যের বিল্ডটি আমার ল্যাপটপের বিল্ডের সমান, এবং ভিএমের রান নিক্স দ্বারা পুনরুৎপাদনীয়।
রিউইন্ড ভিএম কিভাবে নিক্স বিল্ডে রেস কন্ডিশন ডিবাগ করতে সহায়তা করে?
রিউইন্ড ভিএম নিক্স বিল্ড রানের সময় থ্রেড সিডিউলিং সঠিকভাবে রেকর্ড করে। এটি ব্যাঘিত সিডিউলের তহত পুনরুৎপাদন করার অনুমতি দেয়, রেস কন্ডিশনকে একটি স্টেপে সীমিত করে, এবং নিক্স ডেরিভেশন ইনপুট এবং এনআর হ্যাশ ব্যবহার করে পুনরুৎপাদনীয়তা যাচাই করে।
🇪🇸 Español
Depurador determinista encuentra condiciones de carrera en construcciones Nix
Hace unos días escribí sobre Rewind VM, una máquina virtual determinista donde cada ejecución de una construcción Nix es una función pura de sus entradas, incluida la programación de hilos. He estado usando para encontrar, reproducir y resolver muchas condiciones de carrera en construcciones Nix, pero me está haciendo sentir un poco loco. ¿Qué es esta superpoder? ¿Y por qué nadie más la usa?
La herramienta ha crecido rápidamente con un panel de código fuente, marcos de pila, marcadores, una pestaña "Compare", mucho más soporte para GDB, carriles de hilos que muestran quién tuvo la CPU en cada paso, y "Check from here", que ayuda a encontrar el paso exacto donde ocurre una condición de carrera. Entré en cada nueva función pensando que sería un gran levantamiento de código, pero continuamente encontré lo mismo: la parte difícil de cada función ya estaba hecha, y Nix ya lo había hecho.
Por ejemplo, un depurador necesita las entradas exactas del programa, sus símbolos de depuración, su código fuente, el código fuente de todas las bibliotecas debajo de él, y una manera de que alguien más pueda obtener todo eso en su máquina. Eso es exactamente lo que hace una derivación, y Nix hace que sea fácil.
Dos cajeros, una cuenta
Un ejemplo clásico simple de una condición de carrera es dos hilos depositando en una cuenta bancaria. Cada depósito lee el balance, escribe una línea en el libro mayor, luego almacena el balance más el depósito. Si un cajero se ejecuta entre la lectura y el almacenamiento del otro, escribe un balance obsoleto sobre los depósitos del otro.
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 cajero que se ejecuta entre la lectura y el almacenamiento del otro escribe un balance obsoleto sobre los depósitos del otro.
11
En mi portátil de 16 núcleos, el banco perdió dinero en 396 de 1.000 ejecuciones. Fijado a un solo núcleo con taskset, no perdió dinero en ninguna de 1.000: un solo núcleo rara vez cambia hilos en medio de un depósito, por eso Rewind perturba el horario.
La VM de Rewind tiene una sola CPU, así que su primera ejecución pasa demasiado bien. rewind check vuelve a ejecutar la construcción bajo horarios perturbados, cada uno pidiendo al kernel del guest que reprograma en pasos diferentes, y reduce el primer fallo a un solo paso:
$ rewind check --where github:fzakaria/rewindvm#bank...step 3237 decide: una reprogramación allí hace que la ejecución falle
passing: run 12feb5f832205a72, schedule 0
failing: run c2f1cfac0c288f3e, schedule 1 over steps 3237..3238
Las dos ejecuciones son la misma máquina, salida por salida, hasta el paso 3237, donde solo el que falla recibe una reprogramación.
Cosas gratis uno: las entradas
El argumento de check es una derivación y puede ser una referencia flake. La belleza de Nix es que conoce las entradas de una derivación, por eso es todo lo que necesitamos para asegurar que la ejecución es reproducible. Las entradas de la derivación son la fuente, el compilador, las bibliotecas, el kernel, y la configuración de la VM. Rewind nix realiza las entradas de la derivación, empaqueta el cierre en una imagen erofs de solo lectura, y arranca la VM en ella. El ID de la ejecución es un hash de sus entradas, de la misma manera que una ruta de store, y rewind show imprime el comando que la vuelve a hacer:
$ rewind show 12feb5f8
rewind nix /nix/store/cr8rl40rjb9cmmcd9sxn9mpdrhvp53jj-bank-0.1.0.drv --epoch --clock branches --name bank-0.1.0 # 12feb5f832205a72
Cuando la construcción tiene éxito, el guest informa el hash NAR de cada salida, y rewind nix lo compara con la copia del host y con cada caché binaria de sustitución de Nix, fetchando solo el .narinfo:
$ rewind nix github:fzakaria/rewindvm#bank.../nix/store/27a4q7w90jzp4wg5kaaajvb64zll6yqg-bank-0.1.0 dbf0df3de84b7a84 matches your store
Esto nos ayuda a validate que la construcción dentro de la VM es la misma que en mi portátil, y que la ejecución de la VM es reproducible por Nix.
¿Cómo ayuda Rewind VM a depurar condiciones de carrera en construcciones Nix?
Rewind VM registra la programación de hilos exacta durante una ejecución de construcción Nix. Permite a los desarrolladores reproducir la ejecución bajo horarios perturbados, reduciendo la condición de carrera exacta a un paso, y validando la reproducibilidad usando entradas de derivación Nix y hashes NAR.
🇫🇷 Français
Débogueur déterministe trouve des conditions de course dans les constructions Nix
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.
Comment Rewind VM aide-t-elle à déboguer les conditions de course dans les constructions Nix ?
Rewind VM enregistre l'ordonnancement exact des threads lors d'une exécution de construction Nix. Elle permet aux développeurs de reproduire l'exécution sous des ordonnancements perturbés, réduisant la condition de course à l'étape exacte, et validant la reproductibilité en utilisant les entrées de dérivation Nix et les hashes NAR.
🇮🇳 हिन्दी
निर्धारित डिबगर निक्स बिल्ड में रेस कंडीशन खोजता है
कुछ दिन पहले मैंने Rewind VM के बारे में लिखा था, एक निर्धारित वर्चुअल मशीन जहां निक्स बिल्ड का हर रन अपने इनपुट का एक शुद्ध फंक्शन है, थ्रेड शेड्यूलिंग शामिल! मैंने इसका इस्तेमाल निक्स बिल्ड में रेस कंडीशन को खोजने, पुनरूत्पादित करने और हल करने के लिए किया है, लेकिन यह मुझे थोड़ा पागल बना रहा है। यह कौन सी सुपरपावर है, और दूसरे इसका इस्तेमाल क्यों नहीं कर रहे हैं?
इंस्ट्रूमेंट ने जल्दी ही एक सोर्स पैनल, स्टैक फ्रेम्स, बुकमार्क, एक "कंपेयर" टैब, बहुत सारा जीडीबी सपोर्ट, थ्रेड लेन्स जो दिखाते हैं कि हर चरण में किसने सीपीयू पकड़ा था, और "चेक फ्रॉम हीर" जो रेस कंडीशन होने वाले बिल्ड को खोजने में मदद करता है। मैंने हर नए फीचर में यह सोचकर जाया कि यह एक बड़ा कोड-लिफ्ट होगा, लेकिन मैं हर बार एक ही चीज से ग्रसित हो गया: हर फीचर के कठिन हिस्से पहले से ही किए गए थे, और निक्स ने किए थे।
उदाहरण के लिए, एक डिबगर को कार्यक्रम के सटीक इनपुट, इसके डिबग सिंबल्स, इसके सोर्स, उसके नीचे की हर लाइब्रेरी के सोर्स, और एक तरीके की आवश्यकता होती है जिससे दूसरे अपने मशीन पर इस सब कुछ प्राप्त कर सकें। यह ठीक वैसे ही है जैसे एक डेरिवेशन, और निक्स इसे आसान बनाता है।
दो कैशियर, एक खाता
रेस कंडीशन का एक क्लासिक सरल उदाहरण दो थ्रेड्स द्वारा एक बैंक खाते में जमा करना है। हर जमा बैलेंस पढ़ता है, लेजर में एक पंक्ति लिखता है, फिर बैलेंस और जमा को स्टोर करता है। अगर एक कैशियर दूसरे के पढ़ने और स्टोर करने के बीच चलता है, तो वह दूसरे के जमों पर एक स्टेल बैलेंस लिख देता है।
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;}
एक कैशियर जो दूसरे के पढ़ने और स्टोर करने के बीच चलता है, वह दूसरे के जमों पर एक स्टेल बैलेंस लिख देता है।
11
मेरे 16-कोर लैपटॉप पर, बैंक ने 1,000 रन में से 396 में पैसा खो दिया। taskset के माध्यम से एकल कोर पिन किए गए, इसमें 1,000 में से किसी में भी पैसा नहीं खोया: एकल कोर अक्सर जमा के बीच में थ्रेड्स नहीं बदलता, इसलिए रीविंड शेड्यूल को बाधित करता है।
रीविंड की वीएम में एक सीपीयू है, इसलिए उसका पहला रन बहुत अच्छे से हो जाता है। rewind check बिल्ड को बाधित शेड्यूल के तहत दोबारा चलाता है, हर बार गेस्ट कर्नल से अलग-अलग चरणों में रीशेड्यूलिंग की अनुरोध करता है, और पहले फेल को एक चरण तक कम कर देता है:
$ rewind check --where github:fzakaria/rewindvm#bank...step 3237 तय करता है: वहां रीशेड्यूलिंग रन को फेल कर देती है
passing: run 12feb5f832205a72, schedule 0
failing: run c2f1cfac0c288f3e, schedule 1 over steps 3237..3238
दो रन एक मशीन के हैं, निकाल के लिए समान, चरण 3237 तक, जहां केवल फेल होने वाला रीशेड्यूलिंग प्राप्त करता है।
मुफ्त चीज एक: इनपुट
चेक का तर्क एक डेरिवेशन है और यह एक फ्लेक रिफरेंस हो सकता है। निक्स की सुंदरता यह है कि यह डेरिवेशन के इनपुट को जानता है, इसलिए हमें बस यह सुनिश्चित करने की आवश्यकता है कि रन पुनरूत्पादनीय है। डेरिवेशन के इनपुट हैं: स्रोत, कंपाइलर, लाइब्रेरीज, कर्नल, और वीएम का कॉन्फिगरेशन। रीविंड निक्स डेरिवेशन के इनपुट को पूरा करता है, क्लोजर को एक रीड-ऑनली इरोफ्स इमेज में पैक करता है, और वीएम को उस पर बूट करता है। रन का आईडी इसके इनपुट का हैश है, ठीक वैसे ही जैसे स्टोर पथ, और रीविंड शो कमांड को दोबारा बनाने का कमांड प्रिंट करता है:
$ rewind show 12feb5f8
rewind nix /nix/store/cr8rl40rjb9cmmcd9sxn9mpdrhvp53jj-bank-0.1.0.drv --epoch --clock branches --name bank-0.1.0 # 12feb5f832205a72
जब बिल्ड सफल हो जाता है, तो गेस्ट हर आउटपुट का एनएआर हैश रिपोर्ट करता है, और रीविंड निक्स इसे होस्ट की कॉपी और हर बाइनरी कैश से निक्स के सब्स्टिट्यूट के साथ मिलाता है, केवल .narinfo फेच करता है:
$ rewind nix github:fzakaria/rewindvm#bank.../nix/store/27a4q7w90jzp4wg5kaaajvb64zll6yqg-bank-0.1.0 dbf0df3de84b7a84 matches your store
यह हमें मदद करता है कि वीएम के अंदर की गई बिल्ड मेरे लैपटॉप पर की गई बिल्ड के समान है, और वीएम का रन निक्स द्वारा पुनरूत्पादनीय है।
रीविंड वीएम निक्स बिल्ड में रेस कंडीशन डिबग करने में कैसे मदद करती है?
रीविंड वीएम निक्स बिल्ड के रन के दौरान थ्रेड शेड्यूलिंग को सटीक रिकॉर्ड करती है। यह विकासकों को बाधित शेड्यूल के तहत निषादण पुनरूत्पादित करने की अनुमति देती है, जिससे रेस कंडीशन को एक चरण तक कम किया जा सके, और निक्स डेरिवेशन इनपुट और एनएआर हैश का उपयोग करके पुनरूत्पादनीयता की पुष्टि की जा सके।
🇯🇵 日本語
決定論的デバッガがNixビルドのレースコンディションを発見
数日前、私はRewind VMについて書きました。これは決定論的仮想マシンで、Nixビルドの実行は入力の純粋な関数であり、スケジューリングも含みます!私はこれを使ってNixビルドのレースコンディションを発見し、再現し、解決してきましたが、少し気味の悪さを感じています。このスーパーパワーは何なのでしょうか?なぜ誰も使之ていないのか?
このツールはすばやくソースパネル、スタックフレーム、ブックマーク、"Compare"タブ、GDBのサポート、各ステップでCPUを誰が保持していたかを示
🇧🇷 Português
Depurador determinista encontra condições de corrida em builds Nix
Há alguns dias eu escrevi sobre o Rewind VM, uma máquina virtual determinista onde cada execução de uma build Nix é uma função pura de suas entradas, incluindo o agendamento de threads! Estou usando para encontrar, reproduzir e resolver condições de corrida em builds Nix, mas está me deixando um pouco maluco. Que superpoder é essa, e por que ninguém mais está usando?
A ferramenta rapidamente ganhou um painel de código, quadros de pilha, marcadores, uma aba "Compare", suporte a muito mais GDB, faixas de threads que mostram quem segurou a CPU em cada passo, e "Check from here", que ajuda a encontrar o passo exato onde uma condição de corrida acontece. Eu entrei em cada nova função pensando que seria um grande levantamento de código, mas continuamente encontrando a mesma coisa: a parte difícil de cada função já estava feita, e o Nix já havia feito.
Por exemplo, um depurador precisa das entradas exatas do programa, seus símbolos de depuração, suas fontes, as fontes de cada biblioteca sob ele, e uma maneira de alguém mais obter tudo isso em sua máquina. Isso é exatamente o que uma derivação faz, e o Nix torna isso fácil.
Dois caixas, uma conta
Um exemplo clássico simples de condição de corrida é duas threads depositando em uma conta bancária. Cada depósito lê o saldo, escreve uma linha no razão, então armazena o saldo mais o depósito. Se um caixa corre entre a leitura e o armazenamento do outro, ele escreve um saldo desatualizado sobre os depósitos do outro.
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;}
Um caixa que corre entre a leitura e o armazenamento do outro escreve um saldo desatualizado sobre os depósitos do outro.
11
No meu laptop de 16 cores, o banco perdeu dinheiro em 396 de 1.000 execuções. Preso a uma única core com taskset, não perdeu dinheiro em nenhuma de 1.000: uma core sozinha raramente troca threads no meio de um depósito, é por isso que o Rewind perturba o agendamento.
A VM do Rewind tem uma CPU, então sua primeira execução passa bem. rewind check executa a build novamente sob agendamentos perturbados, cada um pedindo ao kernel do guest para reagendar em passos diferentes, e reduz a primeira falha a um único passo:
$ rewind check --where github:fzakaria/rewindvm#bank...step 3237 decide: um reagendamento ali faz a execução falhar
passing: run 12feb5f832205a72, schedule 0
failing: run c2f1cfac0c288f3e, schedule 1 over steps 3237..3238
As duas execuções são a mesma máquina, saída por saída, até o passo 3237, onde apenas a que falha recebe um reagendamento.
Coisa gratuita um: as entradas
O argumento para check é uma derivação e pode ser uma referência flake. A beleza do Nix é que ele sabe as entradas de uma derivação, então é tudo o que precisamos para garantir que a execução seja reprodutível. As entradas da derivação são a fonte, o compilador, as bibliotecas, o kernel, e a configuração da VM. O rewind nix realiza as entradas da derivação, empacota o fechamento em uma imagem erofs somente leitura, e boota a VM nela. O ID da execução é um hash das suas entradas, da maneira que um caminho de store é, e rewind show imprime o comando que o faz novamente:
$ rewind show 12feb5f8
rewind nix /nix/store/cr8rl40rjb9cmmcd9sxn9mpdrhvp53jj-bank-0.1.0.drv --epoch --clock branches --name bank-0.1.0 # 12feb5f832205a72
Quando a build tem sucesso, o guest relata o hash NAR de cada saída, e rewind nix o compara com a cópia do host e com cada cache de substituição binária do Nix, buscando apenas o .narinfo:
$ rewind nix github:fzakaria/rewindvm#bank.../nix/store/27a4q7w90jzp4wg5kaaajvb64zll6yqg-bank-0.1.0 dbf0df3de84b7a84 matches your store
Isso nos ajuda a validate que a build dentro da VM é a mesma que no meu laptop, e que a execução da VM é reprodutível pelo Nix.
Como o Rewind VM ajuda a depurar condições de corrida em builds Nix?
O Rewind VM registra o agendamento de threads exato durante uma execução de build Nix. Ele permite que os desenvolvedores reproduzam a execução sob agendamentos perturbados, reduzindo a condição de corrida a um passo exato, e validando a reprodutibilidade usando entradas de derivação Nix e hashes NAR.
🇷🇺 Русский
Детерминированный отладчик находит гонки в сборках Nix
Несколько дней назад я писал о Rewind VM — детерминированной виртуальной машине, где каждое выполнение сборки Nix является чистой функцией от её входных данных, включая планирование потоков! Я использовал её для поиска, воспроизведения и решения множества гонок в сборках Nix, но она заставляет меня чувствовать себя немного сумасшедшим. Какова эта суперсила, и почему никто другой не использует её?
Инструмент быстро обзавёлся панелью исходного кода, кадрами стека, закладками, вкладкой "Compare", широкой поддержкой GDB, полосами потоков, показывающими, кто занимал CPU на каждом шаге, и "Check from here", которая помогает найти точный шаг, на котором происходит гонка. Я подходил к каждой новой функции, думая, что это будет большой перенос кода, но каждый раз встречал одно и то же: сложная часть каждой функции уже была сделана, и Nix уже это сделала.
Например, отладчику нужны точные входные программы, её отладочные символы, исходный код, исходный код каждой библиотеки под ней, и способ для кого-то другого получить всё это на своей машине. Это именно то, что делает производная, и Nix делает это легко.
Два кассира, один счёт
Классическим простым примером гонки являются два потока, вносящие депозит в один банковский счёт. Каждый депозит читает баланс, записывает строку в ledger, затем сохраняет баланс плюс депозит. Если один кассир выполняется между чтением и сохранением другого, он перезаписывает устаревший баланс над депозитами другого.
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;}
Кассир, выполняющийся между чтением и сохранением другого, перезаписывает устаревший баланс над депозитами другого.
11
На моём 16-ядерном ноутбуке банк терял деньги в 396 из 1000 запусков. При фиксации на одном ядре с помощью taskset, он не терял денег ни в одном из 1000: одно ядро редко переключает потоки посередине депозита, поэтому Rewind возмущает расписание.
VM Rewind имеет один CPU, так что её первый запуск проходит слишком гладко. rewind check запускает сборку снова под возмущёнными расписаниями, каждый из которых просит гостевой я kernel перепланировать на разных шагах, и сужает первый сбой до одного шага:
$ rewind check --where github:fzakaria/rewindvm#bank...step 3237 решает: перепланирование там заставляет запуск провалиться
passing: run 12feb5f832205a72, schedule 0
failing: run c2f1cfac0c288f3e, schedule 1 over steps 3237..3238
Оба запуска — одна и та же машина, выход за выходом, до шага 3237, где только провальный получает перепланирование.
Бесплатная вещь одна: входные
Аргумент для check — это производная и может быть flake-ссылкой. Красота Nix в том, что она знает входные производной, так что всё, что нам нужно — убедиться, что запуск воспроизводим. Входные производной — это исходный код, компилятор, библиотеки, ядро и конфигурация VM. Rewind nix реализует входные производной, упаковывает замыкание в read-only erofs изображение и загружает VM на нём. ID запуска — это хэш его входных, как и путь к store, и rewind show печатает команду, которая его создаёт:
$ rewind show 12feb5f8
rewind nix /nix/store/cr8rl40rjb9cmmcd9sxn9mpdrhvp53jj-bank-0.1.0.drv --epoch --clock branches --name bank-0.1.0 # 12feb5f832205a72
Когда сборка успешна, гость reports хэш NAR каждого выхода, и rewind nix проверяет его против копии на хосте и против каждого бинарного кэша Nix substitutes, fetching только .narinfo:
$ rewind nix github:fzakaria/rewindvm#bank.../nix/store/27a4q7w90jzp4wg5kaaajvb64zll6yqg-bank-0.1.0 dbf0df3de84b7a84 matches your store
Это помогает нам проверить, что сборка внутри VM такая же, как и на моём ноутбуке, и что запуск VM воспроизводим Nix.
Как Rewind VM помогает отладить гонки в сборках Nix?
Rewind VM записывает точное расписание потоков во время выполнения сборки Nix. Она позволяет разработчикам воспроизводить выполнение под возмущёнными расписаниями, сужая гонку до точного шага, и проверять воспроизводимость, используя входные производной Nix и NAR хэши.
🇨🇳 简体中文
确定性调试器发现 Nix 构建中的竞态条件
几天前,我写了一篇关于 Rewind VM 的文章——这是一种确定性虚拟机,每次运行 Nix 构建都是其输入的纯函数,包括线程调度在内!我一直在使用它来发现、复现并解决 Nix 构建中的许多竞态条件,但它让我感到有点疯狂。这是什么超能力?为什么其他人没有在使用它?
该工具迅速发展出源代码面板、堆栈帧、书签、“Compare”标签页、更多 GDB 支持、显示每一步谁占用了 CPU 的线程通道,以及帮助找到竞态条件发生的确切步骤的“Check from here”功能。我原本以为每个新功能都需要大量代码移植,但每次都遇到同样的情况:每个功能的难点其实早就完成了,而 Nix 已经做到了。
例如,调试器需要程序的精确输入、调试符号、源代码、所有库的源代码,以及让其他人能在自己的机器上获取所有这些信息的方法。这正是 derivation 所提供的,而 Nix 让这一切变得简单。
两个柜员,一个账户
竞态条件的一个经典简单例子是两个线程向同一个银行账户存款。每次存款都会读取余额,向账本写入一行,然后存储余额加上存款金额。如果一个柜员在另一个柜员的读取和存储之间运行,它就会用过时的余额覆盖另一个柜员的存款。
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;}
一个在另一个柜员读取和存储之间运行的柜员,会用过时的余额覆盖另一个柜员的存款。
11
在我的 16 核笔记本电脑上,银行在 1000 次运行中有 396 次丢失了钱。使用 taskset 绑定到单个核心时,1000 次运行中一次都没有丢失钱:单个核心很少在存款过程中切换线程,这就是为什么 Rewind 会扰动调度。
Rewind 的 VM 只有一个 CPU,因此首次运行总是通过。rewind check 会在扰动调度下再次运行构建,每次要求 guest 内核在不同步骤重新调度,并将首次失败缩小到一个步骤:
$ rewind check --where github:fzakaria/rewindvm#bank...step 3237 决定了结果:在此处重新调度会导致运行失败
passing: run 12feb5f832205a72, schedule 0
failing: run c2f1cfac0c288f3e, schedule 1 over steps 3237..3238
两次运行使用相同的机器,逐退出步骤相同,直到第 3237 步,只有失败的那次运行被重新调度。
免费的东西一:输入
check 的参数是一个 derivation,也可以是一个 flake 引用。Nix 的美妙之处在于它知道 derivation 的输入,因此我们只需要确保运行是可复现的。derivation 的输入包括源代码、编译器、库、内核以及 VM 的配置。Rewind nix 会实现 derivation 的输入,将闭包打包成只读的 erofs 镜像,并在其上启动 VM。运行的 ID 是其输入的哈希,就像 store 路径一样,rewind show 会打印出重新生成它的命令:
$ rewind show 12feb5f8
rewind nix /nix/store/cr8rl40rjb9cmmcd9sxn9mpdrhvp53jj-bank-0.1.0.drv --epoch --clock branches --name bank-0.1.0 # 12feb5f832205a72
当构建成功时,guest 会报告每个输出的 NAR 哈希,rewind nix 会将其与主机副本以及 Nix 从每个二进制缓存中获取的 .narinfo 进行比对:
$ rewind nix github:fzakaria/rewindvm#bank.../nix/store/27a4q7w90jzp4wg5kaaajvb64zll6yqg-bank-0.1.0 dbf0df3de84b7a84 matches your store
这有助于我们验证 VM 内的构建与我笔记本电脑上的构建相同,并且 VM 的运行可通过 Nix 复现。
Rewind VM 如何帮助调试 Nix 构建中的竞态条件?
Rewind VM 会记录 Nix 构建运行期间的确切线程调度。它允许开发者在扰动调度下重放执行过程,从而将竞态条件精确缩小到某一步骤,并通过 Nix derivation 输入和 NAR 哈希验证可复现性。