05 September 2012

Маленькая неприятность

Продолжаю плотно общаться с замечательной библиотекой live555.

Каждый день дает какое-то новое потрясающее открытие.

Например, шикарный дизайн message loop'а (они, правда, называют это дело task scheduler'ом). Абсолютный максимум, на что хватило мозгов гражданам -- использовать для спячки select() на хэндлах сокетов с таймаутом, который учитывает, что в системе кто-то мог заказать отложенный вызов функции по таймеру.

Проблем у этого решения есть несколько.

Во-первых, у товарищей есть функция, которая якобы предназначена для того, чтобы заказывать исполнение кода в message loop'е из других тредов. Написана вся эта история ужасно -- народ не используют в реализации вообще никаких примитивов синхронизации для избежания race condition, но проблема даже не в этом... Проблема в том, что если вы закажите таким вот образом исполнить чего-то, этот запрос вообще никак не отразиться на работе select(), т.е. если система ушла в ожидание событий на сокетах с большим таймаутом, то ждать выполнения вашего запроса придется либо до таймаута, либо до какой-то активности на сокете. Упс.
Костыль, который может хоть как-то обойти эту проблему -- ограничить максимальное время таймаута, и тогда реакция на все запросы из сторонних тредов будет не хуже этого интервала времени. Криво, но работает.

Во-вторых, в некоторых местах библиотека пытается работать с файлами в асинхронном режиме. Код изначально вроде как кросс-платформенный, но под Windows работа в этом режиме не поддерживается и по этому поводу в коде есть очень смешной комментарий, где какой-то сопляк рассуждает о том, что мол Windows это вообще игрушечная операционная система, т.к. нельзя использовать select() для хэндлов от файлов (ничего удивительного, т.к. WinSock вообще довольно чужеродная вещь в Win32 API и ввели ее туда лишь для облегчения портирования некоторых программ).
Ирония этой ситуации заключается в том, что на самом деле Windows, в сравнении с Linux, имеет намного более мощные и универсальные функции ожидания событий для разнородных объектов (см. WaitForMultipleObjects), которые, к примеру, позволяли бы более изящно обойти проблему, о которой я писал выше -- выход из ожидания в message loop'е по запросу из внешнего потока... Но авторы библиотеки равно хорошо не знают и не умеют использовать C++, ничего не понимают в дизайне приложения и ООП, не знают Win32 API и не умеют писать хорошие кросс-платформенные приложения.

Кстати, о том, как они пишут на C++.
Я много раз видел библиотеки, которые не использовали std::string, но обязательно писали свой велосипедный аналог. Авторам live555 хватило ума только на свой ужасной кривой хэш-контейнер (писал про него в прошлый раз); до класса строк они не добрались, поэтому весь код по работе с текстом выглядит примерно вот так:
unsigned rtpInfoSize = rtpInfoFmtSize
            + strlen(prevRTPInfo)
            + 1
            + rtspURLSize + strlen(urlSuffix)
            + 5 /*max unsigned short len*/
            + 10 /*max unsigned (32-bit) len*/
            + 2 /*allows for trailing \r\n at final end of string*/;
         rtpInfo = new char[rtpInfoSize];
         sprintf(rtpInfo, rtpInfoFmt,
                 prevRTPInfo,
                 numRTPInfoItems++ == 0 ? "" : ",",
                 rtspURL, urlSuffix,
                 rtpSeqNum,
                 rtpTimestamp
                 );
         delete[] prevRTPInfo;
Скажу больше. Я первый раз в жизни увидел типа C++ программу, в которой даже не сделали обертки для сокетов! Они гуляют по всему году в виде int, и вам только остается молить бога о том, чтобы добрые люди хотя бы внятно называли переменные, типа fServerSocket.

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

Нужный мне функционал я потихоньку перетаскивал в наш проект, который мы собираем в Visual Studio сами понимаете под какой ОС (по факту код будет использоваться в среде Linux/ARM, но на этапе адаптации кода это роли не играет). И вот, во время этого перетаскивания, я получаю ругань компилятора на четыре макроса, что мол де они уже были определены. Не сильно парясь по этому поводу, я обернул эти определения и пошел себе разбирать код дальше. Обертка выглядела вроде как безобидно:
#ifndef EWOULDBLOCK       // already defined 
    #define EWOULDBLOCK WSAEWOULDBLOCK
    #define EINPROGRESS WSAEWOULDBLOCK
    #define EAGAIN WSAEWOULDBLOCK
    #define EINTR WSAEINTR
#endif 
Я перетащил нужный код. Написал вокруг него человеческие врапперы. Полностью оттестировал серверную часть, которая без проблем заработала. Потом пришел черед клиентской части, которая отказывалась работать самым наглым образом -- отваливалась на попытке подключиться по TCP/IP к серверному сокету.
Матюкаясь, стал разбираться в чем дело.
Оказывается, спасибо надо сказать добрым ребятам из boost, части которого у нас попадают в компиляции каждого файла проекта через precompiled headers.
Они зачем-то завели файл cerrno.hpp, который "supply errno values likely to be missing, particularly on Windows", т.е. описывает всякие коды ошибок из POSIX для операционных систем, в которых таких ошибок не существует.
На хрена они это сделали для меня до сих пор остается большой загадкой.
Ну а ребята из live555, не сильно парясь, типа замэпили коды из POSIX, сигнализирующие для неблокирующих сокетов о том, что мол операция все еще в процессе, на аналогичный код из WinSock. Очевидно, что оригинальные коды (они попадали из boost) на Windows я получить никак не мог.

Код в live555 выглядел так:
if (connect(...) != 0)
{
    int const err = envir().getErrno();
    if (err == EINPROGRESS || err == EWOULDBLOCK) 
    {
        // The connection is pending; 
        // we'll need to handle it later.  
        // ... 
        return 0;
    }
    // Failed 
    envir().setResultErrMsg("connect() failed: ");
    return -1;
}
// OK, connected 
// ... 
В итоге, сделал #undef для безобразия из boost, вернул переопределения из live555 и в нашей семье восстановился покой и бесконечное счастие.


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

No comments:

Post a Comment