Сказка об интеграции

written on Tuesday, October 30, 2012

Когда машины были большие, а юникс молодой, программы исполнялись линейно. Запустился, сделал чего надо и вышел в сад. В нашей же современной постпостюниксовой реальности все немного веселее.

В функцию main() врывается какая-то мутная хреновина, забирает себе управление, а мой код запускает, когда сама захочет по всяким событиям и таймерам. Таймеры у нее внутре, события притекают сами, вобщем торжество черной магии и культа мифического пользователя.

В гуевых программах евентлуп обычно дается в нагрузку к графическому фрейморку. В хитрых библиотеках, работающих с асинхронным io - в нагрузку к асинхронному io. В мире мейнстримового линукса все как-то договорились и юзают glib, но шаг вправо, шаг влево и начинается ой.

Опять libre

У библиотеки libre, так же как у библиотеки sofia-sip, есть свой евентлуп. Sofia-sip еще как-то интегрировалась с glib, но он мне тоже нафиг не нужен. Насетапил кода, дернул функцию re_main() и все, она заблочилась на веки вечные, а мне надо, чтобы родной евентлуп графического тулкита тоже жил.

Один тред, su_step

Два евентлупа в одном треде - это ой. В случае с sofia-sip я делал простой хак: ставил хендлер на начало каждой итерации родного евентлупа, а внутри хендлера дергал su_step - функцию, которая продергивала одну итерацию сипового евентлупа. Вобщемто это работало и для прототипа было достаточно.

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

Как временно решение для стадии прототипа, я просто гонял в родном евентлупе паразитный таймер каждые 100ms.

Два треда, message-passing

Следующий вариант был веселее. Графика спокойно работала в родном треде, а для куска, работающего с протоколом и сетью, отстреливался отдельный тред, который сразу блочился в вызов re_main() и гонял сиповый евентлуп по-настоящему.

Когда в графическом интерфейсе тыкалась какая-то кнопка, код сетевой части выполнялся в контексте графичкского треда. Это фигово, потомучто блокировки. Когда в сетевой части прилетал какой-то ивент, типа входящего звонка, она пыталась дергать в своем потоке состояние графических виджетов и это тоже было плохо, потомучто не работало.

Ясное дело, пришлось скатываться к мессадж-пассингу. Разделение на графическую и сиповую часть было очень четким, вызовов, персекающих этот borderline было буквально три штуки и они все возвращали void.

Прямые вызовы очень просто и прозрачно завернулись в асинхронную отправку сообщения, сообщение запихивалось в mailbox нужного потока, выполнение шло дальше. Поток с той стороны смотрел в mailbox, доставал из сообщения инфу о том, коого нужно пнуть и фактическое выполнение происходило уже в правильном потоке.

Глубоководные степные космонавты ООП, писавшие реализацию этого механизма, почему-то обзывали это системой "акторов". Ну да ладно, богиня Лейн все равно запихнет каждому из них по тридцать-три обсервера в каждое ухо и никто не уйдет не отдиспатченым.

Проблема с этой реализацией мессадж пассинга была в том, что кто-то еще должен был ходить и смотреть в мейлбокс. Все конечно были очень умные, почти как я, потомучто ходилка-смотрелка мейлбокса была засунута в начало каждой итерации евентлупа, а евентлуп мог спать. Как мега-вариант, для каждого мейлбокса запускался свой тред, в котором евентлупа не быо вообще, а был бесконечный цикл доставания сообщений из мейлбокса.

В общем пришлось делать тот же самый воркарранд - в обоих процессах гонялся паразитный таймер, который ходил смотреть, нет ли чего повыполнять в мейлбоксе.

Два потока, медскилзы на пайпах

Сегодня я увидел еще более магическую штуку - очередь выполнения, сделанная на пайпе. Дешево-сердито и threadsafe без блокировок. релизация есть в самой libre.

Работает достаточно просто - системный pipe() возвращает два файловых дескриптора, на один из которых натравливается принимающего соощения евентлупа, а во второй пишутся бинарные структурки, содержащие magic и указатель на чего дернуть.

Во второй дескриптор можно писать сообщения из любого потока - в принимающем потоке сразу срабатывает хендлер. Писать указатели на функцию через пайпы я не стал, но идея использования fd-листенера, как метода выдать пинок нужному евентлупу, мне понравилась и я ее заюзал вместо дурных таймеров.

Вобщем мыши плакали, кололись, но продолжали не юзать ERLAИГ.

This entry was tagged code, threads and voip