Баг, що зникає коли на нього дивишся (1/3)Уяви: ловиш баг. Він є. Ставиш breakpoint — зник. Прибираєш — повернувся. Додаєш
console.log — знову зник.
Ні, ти не збожеволів. Ти просто зустрів
Heisenbug.
І це не просто курйоз. Це вікно у дивну правду:
немає об'єктивного стану програми. Є тільки стан відносно того, як ти дивишся.
———
Як народився термінUC Berkeley, 1960-ті. Bruce Lindsay і Jim Gray, два комп'ютерні вчені, б'ються з багом в операційці CAL-TSS (одна з перших time-sharing систем — коли багато користувачів працюють на одному комп'ютері одночасно). Баг є. Потім зникає. Потім повертається. Класика.
Lindsay колись вчив фізику. І в якийсь момент до нього дійшло: та це ж
observer effect! Коли вимірюєш систему — ти її змінюєш.
Так народився "Heisenbug" — каламбур на імені фізика Гайзенберга.
(До речі, технічно це не принцип невизначеності Гайзенберга, а ефект спостерігача — різні речі. Але назва прилипла, і вже пізно щось міняти.)
———
А тепер цифри1985 рік. Jim Gray аналізує логи помилок на кількох десятках систем.
Результат:
131 зі 132 багів — Heisenbugs.
Один. Один нормальний, передбачуваний баг на 132. Решта — примари.
Пізніше дослідники з Illinois вивчили 105 concurrency багів у MySQL, Apache, Mozilla та OpenOffice:
Що знайшли Скільки
Баги тільки між 2 потоками 96%
Баги через ≤4 звернення до пам'яті 92%
"Фікси", що все ще містили баги 60%
96% проблем — між двома потоками. Навіть у системах, де їх сотні. Вікно помилки мікроскопічне. Але цього достатньо.
———
Чому debugger все ламаєBreakpoint — це не пауза. Це втручання.Уяви: твоя програма виконує мільйони операцій на секунду. Ти ставиш breakpoint — і що відбувається?
Програма зупиняється. Debugger каже: "Гей, я тут, чекаю команди". Ти дивишся на змінні. Натискаєш "продовжити". Програма біжить далі.
Здається, ти просто "поставив на паузу". Але насправді ця "пауза" — сотні додаткових операцій процесора. Для багу, який живе в циклі що крутиться мільйони разів на секунду, ця затримка — як вставити годинну перерву між кожним кроком.
Баг залежав від точного timing'у? Ти його щойно вбив своїм breakpoint'ом.
Printf — це вічністьЩо робиш Скільки часу
`printf` в stdout ~4000 нс
`fprintf` в файл ~250 нс
Запис в буфер ~10 нс
4000 наносекунд. А race condition може мати вікно в 50 нс. Твій printf — як стіна між двома бігунами, що мали зіткнутися. Вони більше не зіткнуться.
Debug vs Release — різні програмиСерйозно. Різні:
• Debug: guard bytes навколо пам'яті, змінні в RAM
• Release: агресивні оптимізації, змінні в регістрах
• Floating-point: регістри = 80-bit, пам'ять = 64-bitТи
буквально запускаєш іншу програму, коли перемикаєш режим. І дивуєшся, що баг зник.
———
Колекція абсурдуOpenOffice: не друкує щовівторка (2009)Користувач пише: "OpenOffice не друкує щовівторка. В інші дні — нормально."
Усі думають — тролінг. Але ж ні.
Контекст:
PostScript — це формат файлів для друку. Принтер отримує такий файл і знає, що саме малювати на папері. Більшість офісних програм генерують PostScript, коли ти натискаєш "Друк".
При генерації файлу OpenOffice додавав дату:
%%CreationDate: (Tue Mar 3 19:47:42 2009)
А тепер баг: Linux має утиліту
file, яка визначає тип файлу. Вона дивиться на перші байти і вгадує: "це картинка", "це PDF", "це код".
Для файлів мови Erlang утиліта шукала текст "Tue" на позиції 4. Чому "Tue"? Бо Erlang-файли часто починались з такого патерну.
І от проблема: у вівторок PostScript-файл мав "Tue" рівно на позиції 4. Утиліта думала: "О, це Erlang!" Система друку отримувала "Erlang-файл" замість PostScript. І падала.
Понеділок — "Mon" — працює. Середа — "Wed" — працює. Вівторок — "Tue" — збіг з патерном Erlang — падає.
Webpack: не працює в понеділок (2019)