Каждый день дает какое-то новое потрясающее открытие.
Например, шикарный дизайн 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