HeadlinesBriefing HeadlinesBriefing.com

Детерминированный отладчик находит гонки в сборках Nix

Hacker News •
×

Несколько дней назад я писал о 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.

Источник: Hacker News · Сводку подготовил HeadlinesBriefing