Уже второе утро Mac зависал при загрузке. Индикатор доходил почти до конца и замирал. Я подумал, что закончилось место на диске. В режиме восстановления выяснилось обратное: свободны были 264 ГБ.
Причина оказалась во временном каталоге. За 16 часов ночные тесты оставили там 30 182 папки и 1 378 905 файлов. Сколько из них нужно, чтобы сломать загрузку macOS, я не знаю. Но с этим каталогом мой Mac не загружался, а после его очистки загрузился.
Место на диске
Где лежали эти файлы
На моём Mac os.tmpdir() указывал не на привычный /tmp, а на пользовательский каталог вида /private/var/folders///T. Документация Node.js описывает os.tmpdir() как путь к системному временному каталогу; на него также может влиять переменная TMPDIR. Поэтому путь, который возвращает функция, лучше проверять на конкретной машине.
В режиме восстановления я разблокировал том с данными, отвёл старые временные каталоги в сторону и после успешной загрузки посчитал содержимое папки T:
Папок
Файлов
Временные файлы моих тестов
25 299
1 189 858
Временные каталоги Vitest
4 603
151 679
Приложения и система
280
37 368
Всего
30 182
1 378 905
Все эти файлы появились между 23:00 воскресенья и 15:00 понедельника. Основную массу дали несколько типов тестовых каталогов: construct-detach-* — около 246 тысяч файлов, construct-attach-* — около 234 тысяч, ghosts-preflight-test-* — ещё около 113 тысяч.
Первая попытка исправления сделала хуже
Сначала я в режиме восстановления переименовал var/folders и tmp в каталоги с суффиксом .old, затем создал пустой tmp. Я хотел убрать накопившиеся файлы с пути загрузки, сохранив старые каталоги для разбора. После перезагрузки Mac попал в цикл и по-прежнему не запускался.
Вот команды той неудачной попытки; пути относятся к моему тому данных в Recovery:
mv /Volumes/Data/private/var/folders /Volumes/Data/private/var/folders.oldmv /Volumes/Data/private/tmp /Volumes/Data/private/tmp.oldmkdir -m 1777 /Volumes/Data/private/tmp
Я упустил родительский каталог /private/var/folders. В моём случае macOS создавала пользовательские папки внутри него, но не восстановила сам каталог после переименования. Пришлось снова зайти в режим восстановления, создать пустой /private/var/folders и вернуть ему владельца root:wheel и права 755. После этого система загрузилась.
mkdir /Volumes/Data/private/var/folderschown root:wheel /Volumes/Data/private/var/folderschmod 755 /Volumes/Data/private/var/folders
Это важная деталь именно этой истории: перенос системного каталога без восстановления его родительской структуры добавил мне вторую неисправность поверх первой.
Откуда взялось почти полтора миллиона файлов
Ночью агенты запускали тесты параллельно в разных git worktree. Каждый отдельный прогон выглядел нормально. Вместе они многократно повторили утечку временных файлов.
Часть тестов создавала каталог через mkdtempSync:
const dir = mkdtempSync(join(tmpdir(), 'construct-detach-'))
Сам вызов создаёт каталог и возвращает его путь. Удаление нужно организовать отдельно. В тестах его не было. Один каталог легко не заметить; тысячи прогонов оставили 25 299 каталогов только от наших тестовых фикстур.
Я уже внёс исправление: каждый запуск получал собственный корневой каталог вида mkr--XXXXXX, а TMPDIR указывал внутрь него. В конце запуска корень удалялся. Изменение попало в main в 00:50 понедельника, но утечка прекратилась только около 04:00.
Причина задержки — старые worktree. Они были созданы до исправления и продолжали выполнять прежний код. Когда я проверил их, два ещё не были обновлены. Правка в main сама по себе не меняет уже запущенные ветки.
Оставался второй
Источник: habr.com