Telegram Group Search
Слишком часто вспоминаю этот мем в последнее время, поэтому пусть будет здесь :)
ClickHouse и баги в Poco

В мире C++ есть такая известная библиотека, как Poco. По сути, это универсальный фреймворк для Web-приложений на C++, который содержит кучу всего: JSON, парсинг конфигов, HTTP клиент и сервер, работу с БД и много чего еще. Библиотека довольно старая, написана в объектно-ориентированном стиле. Ее код довольно простой и содержит всякой сложной шаблонной магии, в которой можно закопаться [передаю привет Boost.Asio :)] И HTTP-сервер в ней написан просто, без корутин и прочей асинхронности: когда серверу приходит соединение, то с начала его жизни и до конца оно обрабатывается потоком из тредпула, причем при ожидании данных от клиента поток блокируется.

Удивительно, но это одна из немногих широко известных и при этом простых реализаций HTTP-сервера на C++ :) А еще эту библиотеку использует ClickHouse (правда, у них есть свой форк Poco, но об этом попозже)

Я недавно выяснил, что под большой нагрузкой Poco начинает сильно тормозить через некоторое время. Оказалось, что там есть баг: из-за гонки счетчик числа активных потоков внутри библиотеки сбивается, из-за чего она начинает думать, что запущено много потоков, а на самом деле активен только один. А поскольку у сервера есть лимит на число потоков, то этот один поток в итоге вынужден обрабатывать все запросы, хотя в настройках указано, что потоков должно быть больше.

Решил посмотреть, как с этим багом живет ClickHouse. А оказалось, что они пропатчили этот баг в своем форке и никому об этом не сказали :) Поэтому я решил довести этот фикс до upstream'а.

Обидно, что ClickHouse держит патчи в своем форке и не пытается их законтрибутить :( Но буквально недавно разработчики ClickHouse пошли еще дальше и влили свой форк Poco в основной репозиторий ClickHouse, поформатировав при этом код 🐳 При этом в качестве одной из причин назвали «upstream development does not look very active these days», хотя, судя по репозиторию Poco, недавние PRы все еще вливаются. Таким образом, дальнейший перенос патчей будет еще более затруднительным :(
Pro tip: не используйте Google Картинки для чтения манов по командам и функциям Linux
Гепардово гнездо
Животные делятся на: а) принадлежащих Императору, б) набальзамированных, в) прирученных, г) молочных поросят, д) сирен, е) сказочных, ж) бродячих собак, з) включённых в эту классификацию, и) бегающих как сумасшедшие, к) бесчисленных, л) нарисованных тончайшей…
Классификация языков программирования по Борхесу

According to the Celestial Emporium of Benevolent Knowledge, programming languages can be classified into the following categories:

1) Those that generate code through interpretive dance
2) Those that can only be programmed using an abacus and Morse code
3) Those that are written entirely in emojis
4) Those that communicate only through telepathy and require the programmer to have a specific set of psychic abilities
5) Those that can only be executed during specific planetary alignments
6) Those that are programmed by feeding data to a swarm of bees
7) Those that require the programmer to have a PhD in theology to understand the syntax
8) Those that can only be executed on a computer that is submerged in water
9) Those that are programmed by casting spells and incantations
10) Those that use only punctuation marks and symbols, such as "%%%", ">>>", or "&&&"
11) Those whose syntax is based on the mating habits of insects
12) Those that require the programmer to wear a top hat while coding
13) Those that are written entirely in reverse order
14) Those that are only understood by computers with a certain level of emotional intelligence
15) Those that are designed specifically for time travelers
16) Those that use a language that only the gods can understand
17) Those that use curly braces, but only for aesthetic purposes
18) Those that only allow for single-letter variable names
19) Those that are based on the principle of chaos theory
20) Those that require the programmer to chant a mantra before compiling
21) Those that can only be run on a computer that has been blessed by a priest or shaman
22) Those that are made entirely out of cheese
23) Those that are written in a language that does not exist yet
24) Those that are powered by the thoughts of a telepathic octopus
25) Those that are created by a computer program that has achieved consciousness
26) Those that are written in binary code by extraterrestrial beings
27) Those that are created by ancient civilizations that have been lost to time
28) Those that are invented by mad scientists in their secret labs
29) Those that are created by wizards to cast spells
30) Those that use curly braces {} and those that don't
31) Those that can only be understood by dogs
32) Those whose syntax resembles a recipe for banana bread

Классификация была честно сгенерена ChatGPT с таким запросом:

Generate a classification of programming languages, as it was taken from Borges' Celestial Emporium of Benevolent Knowledge. The classification must be absurd and chaotic, just like the Borges' version

Генерация повторялось несколько раз, и лучшие варианты вы видите в списке выше :)
https://github.com/rhboot/shim

> shim is a trivial EFI application that, when run, attempts to open and execute another application.

Тем временем trivial application:
$ cloc .
--------------------------------------------------------------------------------
Language files blank comment code
--------------------------------------------------------------------------------
C 471 16913 36193 110168
C/C++ Header 163 6244 14252 38215
make 8 166 8 1048
Markdown 4 175 0 770
Bourne Shell 3 11 2 570
Bourne Again Shell 1 40 47 469
YAML 2 7 2 343
Text 2 22 0 150
diff 3 9 87 55
Assembly 2 5 27 44
CSV 1 0 0 2
--------------------------------------------------------------------------------
SUM: 660 23592 50618 151834
--------------------------------------------------------------------------------

Конечно же, если внимательно посмотреть на репозиторий, то там можно увидеть завендоренные библиотеки (например, посмотрите на папку Cryptlib/). Тем не менее, даже если учитывать .c-файлы только в корне репозитория (исключая все, что начинается на test) — то выйдет около 11000 строк кода (исключая пустые строки и комменты)
Объяснение формулы Байеса

Если вам нравятся длинные статьи, то можете почитать объяснение Юдковского на LessWrong или по этой ссылке (на английском). Ниже будет мое компактное изложение основных идей.

Рассмотрим следующую задачу:

> Пусть существует заболевание с частотой распространения среди населения 0,001 и метод диагностического обследования, который с вероятностью 0,9 выявляет больного, но при этом имеет вероятность 0,01 ложноположительного результата — ошибочного выявления заболевания у здорового человека. Найти вероятность того, что человек здоров, если он был признан больным при обследовании.

(Если вы никогда ранее не сталкивались с похожими задачами, то попробуйте сначала прикинуть ответ, а потом уже дочитать вниз и проверить свои догадки.)

Понятно, что можно решать ее, используя формулу Байеса напрямую (посчитать P(A), P(B), P(B|A) и из этого вывести ответ на задачу P(A|B)), но есть и более короткий путь.

Поскольку вероятность быть больным до теста равна 0.001, то получается, что шансы болезни изначально 1:999 (или отношение больных и здоровых равно 1/999). Далее нам требуется поделить вероятность true positive на вероятность false positive — получится 0.9/0.01 = 90. Наконец, умножаем 1/999 на 90 и получаем 90/999, или в переводе обратно в вероятность быть здоровым, 999/(90+999) ≈ 0.9174.

Строгое математическое доказательство остается в качестве упражнения читателю :) Но интуитивно это решение можно понимать с помощью водопада (картинка взята отсюда) На картинке синяя вода — это вероятность здорового, а красная — вероятность больного. Фиолетовая область — это то, что мы наблюдаем после теста: либо больного с положительным тестом, либо здорового с ложноположительным тестом. При этом доля обнаруженных случаев (true positive rate, TPR) — 90%, а ложноположительных срабатываний (false positive rate, FPR) — 30%. Тогда изначально отношение синей и красной воды было 80/20 = 4, а в фиолетовой области оно стало (80*30)/(20*90) = 4/3, т.е. разделилось на 90/30 = 3 — отношение TPR и FPR.

(продолжение ниже)
(начало в предыдущем посте)

Теперь разберем вот этот xkcd. Машина дает true positive с вероятностью 1 и false positive с вероятностью 1/36. Даже если пессимистично предположить, что Солнце взорвется с вероятностью 10^-9 (т.е. шансы равны примерно 1:10^9), то получается, что после теста шансы взрыва Солнца оказываются 36:10^9. Это пренебрежимо мало, и скорее всего результат был false positive.

Отсюда, кстати, следует интересный вывод: экстраординарные заявления (т.е. те, у которых изначальная вероятность ничтожно мала) требуют экстраординарных доказательств (т.е. таких, у которых высокая вероятность true positive и крайне низкая вероятность false positive)

В целом, на формулу Байеса можно смотреть как на способ корректировать вероятности после получения дополнительной информации. Если мы получили положительный тест, то требуется скорректировать изначальную вероятность, причем чем больше отношение TPR и FPR, тем более надежный тест и тем больше он скорректирует вероятность.

Вместо одного надежного теста можно также использовать много ненадежных, если они независимы. Так, если запустить машину из xkcd выше десять раз, и все десять раз она заявит, что Солнце взорвалось, то тогда шансы из 1:10^9 превратятся примерно в 3*10^6:1, и можно будет почти наверняка заявлять, что взрыв Солнца произошел.
- Why was the multithreaded programmer's house robbed?
- Because his door was lock-free.
200'000 в ~/.bash_history

Сегодня у меня случился исторический момент — суммарно в ~/.bash_history оказалось 200'000 команд :)

Историю bash'а я веду с мая 2018 года — т.е. на достижение такого числа ушло примерно пять лет. Конечно, по умолчанию bash сохраняет только небольшое число последних команд, но я с самого начала сильно поднял лимит:
HISTSIZE=10000000
HISTFILESIZE=20000000
(можно, кстати, поставить в качестве значений этих переменных -1, тогда история будет просто неограниченной)

За эти пять лет я не один раз переставлял ОС, но историю перенести очень нетрудно: достаточно просто сохранить файлик ~/.bash_history. При этом польза от сохранения длинной истории весомая. Иногда хочется сделать что-то, что ты уже делал раньше, но точные команды уже давно забыты. Тогда можно просто пройтись поиском по истории и найти нужное :)
История одного дебага

Так уж получилось, что я использую версию Telegram Desktop из репозиториев Debian. Обычно оно работает без проблем, но изредка возникают и ошибки, которых нет в upstream. Например, в сентябре 2022 года существовал вот такой страшный баг. Здесь репорт содержит, по сути, две проблемы:
- во-первых, анимированные стикеры и реакции отображались криво, а их части отстутствовали
- во-вторых, при попытке воспроизвести определенный анимированный стикер Telegram начинал кушать ядро CPU и не останавливался до выхода из приложения

У меня проявлялись оба бага. А лучший способ показать, как эти баги стриггерить, конечно же, — создать канал, на котором проблемы заметны :)

Далее я буду рассказывать только про баг с потреблением CPU, потому что первая проблема была оперативно пофикшена мейнтейнером.

Итак, что же можно сделать, если программа начинает есть много CPU? Конечно же, снять perf :) Увы, здесь perf особо не прояснил ситуацию: он лишь показал на функции внутри QBuffer, по которым мало что понятно.

Надо пробовать что-то еще. Раз программа без необходимости на чем-то съедает CPU, значит можно запустить gdb, остановить в случайный момент и посмотреть на стек. Это уже помогло: наверху оказались все те же методы QBuffer, а ниже по стеку — внутренности ffmpeg. После этого я поставил breakpoint и посмотрел, выйдем ли мы когда-то из кода ffmpeg и вернемся ли обратно в код приложения. Этого не произошло — а, значит, при отрисовке что-то зависает.

После чтения кода методом пристального взгляда выяснилось вот что. Чтобы ffmpeg считывал видео для отрисовки, ему надо передать контекст с указателем на функцию read(). При этом read() должен вести себя не совсем так, как принято в UNIX: при достижении конца файла надо возвращать AVERROR_EOF, а не 0. А Telegram возвращал 0, и происходило вот что: ffmpeg пытался читать данные и постоянно получал 0 байт. Он не считал этот ноль концом файла, и ему были нужны новые данные, поэтому он продолжал свои попытки чтения в бесконечном цикле. А коллбэк на read(), установленный из кода Telegram, как раз использовал QBuffer, который я и видел в perf'е. Вот и вся разгадка :)

При этом забавно, что баг не проявлялся в официальной сборке, которая использует более старый ffmpeg, чем упакованный в Debian. Почему так — я детально не разбирался ¯\_(ツ)_/¯

Дальше все стандартно: я создал PR, его поревьюили, влили и притащили патчем в Debian. На этом проблема оказалась решена :)

Что интересно, Telegram Desktop — штука довольно жирная:
- билд телеграма с debug information весит около двух гигабайт o_O
- gdb на нем заметно подтормаживает
- у меня он собирался примерно минут 30-40; при этом для сборки нужно минимум 16 ГБ памяти, иначе линковка падает с OOM
2025/06/26 03:04:32
Back to Top
HTML Embed Code: