Стендап Сьогодні 📢 Канал в Telegram @stendap_sogodni
🤖🚫 AI-free content. This post is 100% written by a human, as is everything on my blog. Enjoy!Пости з тегом #Рівночасність
08.11.2024
Пошук перегонів даних в Go
🏁 Перегони даних — це коли два рівночасні процеси наввипередки оновлюють змінну, без відома про існування один одного (Мені не дуже зрозуміло, чому це називається перегонами.) В результаті отримуємо напівзіпсоване значення, в якому не врахована частина обчислень. Причому, звісно, рівночасність — непередбачуване явище, тому псування трапляється не кожного разу, а може взагалі чекати на високе навантаження в продакшні.
Одним словом. Шукав, чому випадково хибиться тест. Звузив причину до ділянки з використанням go-redis#ClusterClient.ForEachMaster - вона збирала значення з усіх серверів Redis в один масив. Спочатку думав, що тесту бракує очікування (ще одна класична причина випадкових хиб) та намагався виправити. Тут є легка перевірка, насправді: додай свідомо перебільшене очікування — може, 10 секунд — якщо це не виправляє тест, то проблема в іншому.
А потім помітив в документації по ForEachMaster, що вона-то рівночасна!. Звісно, через рівночасність зміст вихідного масиву був неповним, до того ж для кожного хибного випадку різний. Щоб виправити, я додав збір результатів через канал, але можна було б й мьютекс поставити. Такі API з прихованою рівночасністю мене обурюють, бо з ними хоч знаєш, а не зробиш правильно.
Тут я згадав, що в Go є детектор перегонів та раптом дізнався, що він не увімкнений за замовчуванням. Увімкнув — та дійсно, проблемний тест відразу “засвітився”. Хотів вже увімкнути для всіх тестів та локальних запусків, але виявив пару проблем з цим. По-перше, тести з детектором тривають в три рази більше. По-друге, як не дивно, але для компіляції з детектором потрібно увімкнути CGO, а це йде зі своїм пакетом ускладнень, як-от неможливість кроскомпілювати з macOS в Linux. Довелося відмовитись.
Нарешті, детектор знайшов ще одну гідну обурення ситуацію. Такого я ще не бачив. Офіційних серіалізатор Protobuf у JSON - protojson
- додає у випадкові місця JSON пробіли. Та цього навіть не можна вимкнути!. Пояснюють вони це тим, що “споживачі мусять не очікувати чіткого формату”, оскільки команда Protobuf з ним ще не визначилася (sic!) Тому поки відстежуємо обговорення, якому вже 4 роки, а в тесті довелося декодувати JSON та порівнювати не рядком, а за змістом.
22.12.2024
Дев-адвент 22: дрібні, але важливі виправлення
Хоч мій трекер збудований на швидкому введенні тегів в несподіваний момент — мушу зізнатися, що вже довгий час (може, з пів року чи більше) сповіщення не відпрацьовували. Тобто, вони зʼявлялися, але перехід в застосунок відбувався без відкриття форми додавання проби. Ба більше, довгий час відкриття застосунку зі сповіщення взагалі викидувало через критичну помилку. 😳
З помилкою я вже цього місяця розібрався. Це була одна з тих бісячих ситуацій у Swift, коли застосунок отримує EXC_BREAKPOINT
в асемблерному коді (тобто системному, без вихідного тексту) - та піди зʼясуй, в чому справа. Виявилося, що версія метода делегату didReceive з async
не працює з моделлю рівночасності. (Класична проблема з офіційними API, на жаль.) Тому, контрінтуїтивно, повернувся до старої версії — з колбеком — та це мене врятувало. (На жаль, щоб це дізнатися, довелося, як в пазлі, пробувати всі комбінації по черзі.)
Але форма все ще не відкривалася. Причому код, що її відкриває зі сповіщення, був той самий, що й зі списку — тобто проблема була десь посередині. Думав, що в бізнес-логіці — ну, може, я погано шукаю пробу, яку треба відкрити. Може, не встигаю створити (бо створюються тільки проби за минуле, тому по відкриттю на сповіщення про зараз проба створюється “just-in-time”.)
Та виявилося, ні. Це була ще одне непорозуміння життєвого циклу, цього разу — всього застосунку. Бо застосунок у SwiftUI - це нащадок структури App. А в ній, як і в компонентах, в конструкторі стан @State
ще не має привʼязки. Та якщо в конструкторі створити делегат сповіщень та передати йому той стан, що є — то він не зможе редагувати справжній стан.
Розвʼязалося все просто (коли вже знаєш, як воно працює!) Переніс створення делегату до обробника onAppear - тобто з конструктора в body
. І все! Форма відкривається вчасно.