Так уж получилось, что я использую версию 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