Перейти до вмісту

Блог ·

Два раунди, один номер поля

Дві гілки того самого дня додали варіант до одного й того самого повідомлення protobuf, і обидві обрали те саме число. Дуже мало що в цьому падає гучно.

Автор: Provibr

Дві роботи йшли паралельно на одному репозиторії. Одна додавала змогу спитати гіпервізор, що сталося з фоновим завданням. Друга додавала змогу перевстановити контейнер. Обом був потрібен новий варіант в одному й тому самому повідомленні protobuf, обидві подивилися у файл, обидві побачили, що найбільший використаний номер це тридцять три, і обидві написали тридцять чотири.

Ніхто не був недбалим. Кожна прочитала файл правильно на ту мить, коли вона відгалузилася. Зіткнення існує лише в третьому стані, якого жодна з них ніколи не компілювала: у злитті.

Де виявляється зіткненняЩо стається
Дві гілки окремоНічого. Кожна є правильною проти власної початкової точки
Злиття, у файлі схемиКонфлікт, а далі жорстка помилка компілятора, якщо лишити обидва
Злиття, у закомміченому згенерованому кодіКонфлікт, а далі геть нічого, якщо лишити обидва
Перевірка сумісностіЗелена в обох гілках, бо вона порівнює кожну з її власною базовою лінією
Розгорнуті агент і шлюз, які не згодні між собоюСюди так і не дійшло: команда, яка приходила б порожньою, і повідомлення про це шарами далі від причини

Що насправді несе дріт

Імʼя поля protobuf є зручністю для людей. Тим, що йде через зʼєднання, є номер, зсунутий ліворуч на три біти, з позначкою типу в молодших бітах, тож поле тридцять чотири з навантаженням, перед яким іде довжина, записується як послідовність байтів для двохсот сімдесяти чотирьох. Перейменуйте поле, і на двійковому дроті нічого не зміниться. Змініть його номер, і кожен розгорнутий читач тепер декодує щось інше з тим самим ключем.

Ось чому номер, який одного разу вийшов у релізі, є замороженим. Це не правило стилю. Це єдиний ідентифікатор, спільний для обох кінців.

Що каже інструментарій, а чого не каже

Git помітив. Конфлікт зʼявився у файлі схеми й у закомміченому згенерованому TypeScript, обидва рази як накладені додавання в тому самому місці. Спокусливим розвʼязанням є те, що має очевидно правильний вигляд для двох незалежних функцій: лишити обидва.

У файлі схеми це ловлять. Компіляція дає пласку відмову, яка називає повідомлення й поле, що вже тримає цей номер, і це виміряно, а не припущено: тестове повідомлення з двома варіантами, обидва на тридцять чотири, відхиляють із рядком і колонкою та підказкою наступного вільного номера.

Зі згенерованим TypeScript історія інша, і саме тому це варто записати. Той файл закомічено, бо щоденна робота не має вимагати встановленого інструментарію схеми. Ціною того вибору є те, що він бере участь у злиттях, як будь-який інший файл з кодом, і що це звичайний код. Дубльована гілка в операторі вибору є законним TypeScript. Компіляція невеликого файла з двома однаковими мітками case, обидві з перевіркою тега для поля тридцять чотири, не дає жодної діагностики й завершується чисто. До другої гілки просто ніколи не доходять.

Тож зведений руками згенерований файл дає вам кодувальник, який пише той самий тег для двох різних повідомлень, і декодувальник, який завжди розвʼязує його в те, що стоїть у списку першим. Ніщо не падає під час збірки, ніщо не падає на старті, і ніщо не падає на першому повідомленні. Одна з двох функцій просто неправильна, у спосіб, який виявляється лише на машині на тому кінці зʼєднання.

Чому перевірка сумісності була зеленою

Схему стереже перевірка ламких змін, і вона зробила свою роботу. Вона порівнює запропоновану схему з базовою лінією. Додавання нового варіанта до наявного вибору не є ламкою зміною, і воно нею не було в жодній із гілок, бо базова лінія кожної гілки справді не мала тридцять чотири у використанні.

Це загальна форма проблеми, і її варто сформулювати окремо від protobuf: перевірка, яка порівнює гілку з її власною початковою точкою, не може побачити конфлікт, що існує лише між двома гілками. Той самий клас помилок зʼявляється всюди, де паралельна робота бере значення зі спільного впорядкованого простору. Міграції бази даних тут обирають за позначкою часу в їхньому журналі, і ті позначки є круглими числами, написаними руками, а не прочитаними з реального годинника, тож два раунди, кожен з яких пише одну, обирають власну; міграцію, чия позначка приземляється нижче вже застосованої, пропускають назавжди, а мігратор і далі повідомляє, що схема актуальна. Інший механізм, та сама тиша, та сама причина.

Чому тиша є дорогою частиною

Якби обидва номери дожили до релізу, відмова була б віддаленою й загадковою. Protobuf відкидає поле вибору, якого не впізнає, тож агент, зібраний з однієї гілки, отримавши команду з іншої, не отримує помилки. Він отримує команду, у якій нічого немає, і повідомляє щось на кшталт «повідомлення не несло вказівки». Причина лежить шарами далі від того речення, на машині, що належить комусь іншому, і в того, хто це читає, немає підстав запідозрити номер.

Платформа вже відмовляється надсилати вказівки агентам, які не оголосили відповідну можливість, саме тому, що цей режим відмови такий некорисний: агент, який просто застарий, принаймні дає відмову, яку написала платформа, з назвою функції та версією. Той запобіжник покриває розбіжність версій. Він не покриває два номери, що означають дві речі, бо з боку платформи ніщо взагалі не є застарілим.

Правило, яке з цього вийшло

Номер поля, що вийшов у релізі, є замороженим, тож переїжджає той раунд, який ще не вийшов. Номер тридцять чотири вже вийшов у релізі агента як варіант стану завдання, тож на злитті варіант перевстановлення контейнера став тридцять пʼятим, а згенерований код відтворили зі схеми, а не звели руками. Відтворення є важливою половиною: зведений руками згенерований вивід є написаним руками кодувальником, якого ніхто не рецензував як такий.

Практична вказівка для того, хто додаватиме наступний, коротка. Читайте найбільший номер у головній лінії на ту мить, коли ви комітите, а не на ту, коли ви відгалузилися. І кожне злиття, що торкається схеми, перевіряють на це свідомо, людиною, бо жоден інструмент у ланцюжку не дивиться на це з такого боку.

Бронювання, і чому їх варто шанувати

Та сама схема показує, який вигляд має ця дисципліна, коли вона працює. Три номери стоять заброньованими для інтеграцій, які планували й ніколи не збудували, щоб ніхто не міг тихо їх перевикористати. Ще два тримали для двох конкретних панелей керування, і обидва врешті заповнили ті раунди, яким їх обіцяли. Третя панель, яку випадково закінчили раніше, жодного з них не взяла; вона взяла наступний вільний номер далі по списку.

Саме в цьому весь сенс броні. Зʼїсти її тому, що ви випадково були готові першими, є рівно тим способом, у який схема нумерації перестає щось означати, і коментар у файлі каже це буквально.

Заповнення заброньованого номера за призначенням таки потрапляє у звіт перевірки ламких змін, як зняття броні. Той звіт очікуваний, і його правильно прийняти: жодне поле ніколи не несло того номера, тож жоден розгорнутий читач не може мати про нього думки. Це варто знати заздалегідь, бо першого разу воно має тривожний вигляд, і виникає спокуса обрати інший номер, щоб інструмент замовк, а це викидає ту саму бронь, яку ви шанували.

Що ми записали б

  1. Ставтеся до випущеного номера як до незмінного, і ставтеся до «найбільшого номера, який я бачу» як до твердження про вашу гілку, а не про проєкт.
  2. Читайте спільний простір у момент коміту, а не в момент відгалуження. Це єдина мить, коли значення справді застовплюють.
  3. Відтворюйте згенерований код, ніколи не зливайте його. Конфлікт у згенерованому виводі є сигналом запустити генератор, а не запрошенням редагувати.
  4. Знайте, які з ваших перевірок є відносними до базової лінії. Вони не бачать зіткнень між побратимами, і цю прогалину має закривати людина на злитті.
  5. Бронюйте номери, які ви обіцяли або запланували, з коментарем про те, кому вони належать, і приймайте шум, який дає правильне заповнення одного з них.
  6. Шукайте ту саму форму деінде. Будь-яке призначене руками значення у спільному впорядкованому просторі, включно з міграціями, має цю відмову й зазвичай має її тихо.

Заради ясності: нічого зі зіткненням усередині не випустили. Його зловили на злитті, пізніший варіант переїхав, згенерований код перебудували зі схеми, і обидві функції вийшли окремими релізами агента з різницею в кілька годин. Уловимим це зробило те, що хтось прочитав конфлікт, а не розвʼязав його, а це не той процес, який можна автоматизувати, але той, який можна записати.

Із роботи над платформою в Provibr. Версія тієї самої відмови в базі даних є в міграції, яка ніколи не виконалася, а чому підписане оновлення агента відхиляє себе саме, замість довіряти відправникові, є в тому, як підписане оновлення агента себе відхиляє.

Читати далі

Почніть з одного хоста й одного агента

Створіть обліковий запис, встановіть агента однією командою та підключіть свій перший хост Proxmox. Пробний період триває 14 днів і не потребує платіжних даних.