Баг в libunwind, который прожил в продакшене 18 лет, у OpenAI выливался в больше чем десяток падений в день. Парадокс в том, что ошибка срабатывала в окне шириной буквально в одну процессорную инструкцию, то есть примерно за 100 пикосекунд. Для русскоязычной IT-аудитории тут важен не только сам дефект, но и вывод: даже почти мифическая гонка может стать регулярной аварией, если у вас достаточно необычная нагрузка.
Об этой истории сообщает Habr / Новости со ссылкой на рассказ OpenAI. Речь идет о GNU libunwind, одной из самых распространенных библиотек для раскрутки стека. Именно такие компоненты обычно считаются скучной частью инфраструктуры: они давно живут в экосистеме, стоят глубоко под приложением и редко попадают в поле зрения, пока не начинают ронять сервисы в моменты, когда, казалось бы, ломаться уже нечему. Здесь произошло именно это.
Проблему нашли в инфраструктуре Rockset, которую OpenAI купила в 2024 году и использует в ChatGPT для работы с данными и поиска по перепискам. Инженеры столкнулись с «невозможными» крашами в C++: обычная функция завершалась, но возвращалась не туда. Иногда управление улетало по нулевому адресу, иногда указатель стека оказывался смещен на 8 байт. Такой набор симптомов выглядит не как банальная ошибка в бизнес-логике, а как поломка на уровне рантайма, компилятора, железа или очень неудачного стечения обстоятельств. Хуже всего, что под каждую рабочую гипотезу быстро находилось опровержение.
Перелом наступил не после гениального озарения, а после смены метода. Сначала команда разбирала единичные core dump-файлы почти как патологоанатом разбирает один сложный случай: внимательно, глубоко и без результата. Затем подход сменили на статистический. Вместо одного красивого падения инженеры собрали и разметили все дампы памяти Rockset за год, причем скрипт для этой работы написал сам ChatGPT. Когда данные очистили и свели в одну выборку, выяснилось неприятное, но полезное: под вывеской «один загадочный баг» скрывались сразу две независимые проблемы. Первая оказалась аппаратной: на одном из хостов Azure процессор просто считал неправильно. Вторая и была тем самым дефектом в libunwind.
Сам баг в libunwind, по данным OpenAI, появился еще в 2007–2008 годах, когда в библиотеке впервые добавили поддержку раскрутки C++-исключений для x86_64. Механика сбоя звучит как инженерская шутка, которую никто не хотел бы видеть в проде. Когда C++ обрабатывает исключение, рантайм раскручивает стек и восстанавливает регистры. В этот момент библиотека меняет указатель стека, и на одну инструкцию структура с адресом возврата оказывается за пределами зоны, которую ядро обещает не трогать. Если ровно в этот микроскопический промежуток приходит системный сигнал, ядро перезаписывает память, и адрес возврата превращается в NULL. На бумаге это выглядит слишком редким, чтобы проявиться вообще. На масштабе OpenAI оказалось, что нет, вполне проявляется.
Почему ошибка всплыла только сейчас, хотя прожила почти два десятилетия? Потому что редкий баг любит не время, а сочетание факторов. В нагрузке Rockset, как описывает OpenAI, сошлись сразу три условия: система очень часто бросала исключения, очень часто получала сигналы, а одно из недавних изменений увеличило объем стека, который занимал обработчик сигнала. По отдельности такие детали могут годами не давать эффекта. Вместе они переводят дефект из категории «практически невозможно» в режим регулярных падений. Это неприятное напоминание всем, кто сопровождает высоконагруженный C++-код: поведение инфраструктуры определяется не только качеством алгоритма, но и комбинацией редких событий в рантайме, ядре и железе.
Практический вывод для разработчиков здесь даже полезнее, чем сама история про одну инструкцию. Когда сервис падает «необъяснимо», команда почти автоматически ищет одну первопричину и пытается сложить все симптомы в единый сюжет. OpenAI показала обратное: иногда связного сюжета нет, потому что вы смешали два разных класса сбоев. Пока аппаратная ошибка и гонка в библиотеке выглядели как один баг, расследование буксовало. Как только дампы начали разбирать массово, а не поштучно, картина стала проще. Это хороший аргумент в пользу нормальной телеметрии, хранения crash dump-ов и инструментов, которые позволяют не героически «читать один стек глазами», а искать закономерности по всей популяции инцидентов.
Для бизнеса и платформенных команд здесь тоже есть сухой, но важный урок. Чем глубже компания уходит в собственную инфраструктуру, тем выше шанс, что ее будут ломать не модные уязвимости и не релизы фреймворков, а старые системные компоненты, которые все считали надежными по умолчанию. OpenAI решила проблему прагматично: перешла с GNU libunwind на механизм раскрутки стека из libgcc, а в саму библиотеку отправила воспроизводимый пример и исправление. Это не история про «ИИ нашел баг, который люди не могли найти». Скорее это история о том, что большие системы наконец создают такую нагрузку, при которой даже 100 пикосекунд перестают быть абстракцией. И если этот кейс что-то меняет в отрасли, то не веру в магию отладки, а требования к качеству данных, на которых эту отладку вообще можно вести.