вторник, 15 июля 2014 г.

[prog.c++] Еще про тонкости ACE (не забывать про #include в .cpp-файлах с main-ом)

Сегодня из-за собственной забывчивости убил несколько часов на разбирательство с крахом совершенно безобидного unit-теста. Сначала удалось выяснить, что падение происходит при попытке обращения к ACE-овскому Timer_Queue_Adapter-у. Потом оказалось, что к такому поведению привело изъятие #include <ace/Basic_Types.h> из одного из заголовочных файлов.

Ларчик открывался просто. В каждом cpp-файле, где определяется main(), необходимо делать #include <ace/OS.h> (или OS_main.h, или ACE.h -- главное, чтобы хотя бы один из ACE-овских файлов был заргужен). Это необходимо потому, что ACE делает свое определение функции main, в котором скрывается инициализация и деинициализация внутренностей ACE. А символ main после этих определений оказывается всего лишь #define-ом, при использовании которого пользователь определяет не реальный main, а всего лишь вспомогательную для ACE функцию ace_main_i().

Вот в моем unit-тесте не было #include <ace/OS.h>, но ранее все работало из-за того, что в других заголовочных файлах подгружались ACE-овские заголовки и main() для ACE определялась правильно. Когда же я проделал рефакторинг, выяснилось, что больше никакие ACE-овские заголовки в unit-тесте не загружаются, main оказывается настоящей main, внутренности ACE не инициализируются должным образом, отсюда и крах приложения.

В общем, актуальность отказа от ACE в SObjectizer в очередной раз появилась на повестке дня. Правда, сделать это будет не так уж и просто. На вскидку, без тщательного изучения кодовой базы, можно говорить про следующие вещи, в которых ACE нужно будет заменить на что-то еще:
  • логирование сообщений о фатальных ошибках в ядре so_5. Это не критичная функциональность. Можно либо тупо перейти на работу только с std::cerr, либо же определить интерфейс, реализацию которого можно будет назначить через so_environment_params_t (по умолчанию будет задействована реализация на основе std::cerr);
  • реализация timer_thread. Очень важная штука, без таймеров нет SObjectizer-а. Сейчас в so_5 интерфейс timer_thread реализуется через ACE_Thread_Timer_Queue_Adapter. Можно сделать свою реализацию на основе только стандартной библиотеки C++11 (посредством std::map для хранения заявок и std::condition_variable::wait_until для ожидания наступления очередной заявки или поступления запроса на создание новой заявки/удаления существующей). Реализация не кажется слишком уж сложной, но пока она не будет в должной мере протестирована в реальных проектах коэффициент моего спокойного сна будет совсем никакой ;)
  • в подпроекте so_log нужно будет много чего переписать, т.к. там сейчас определены собственные реализации Backend-ов для ACE_Logging. Вроде бы ничего сложного;
  • самым кардинальным образом нужно будет переделать so_5_transport, в котором вся работа с сокетами построена на ACE_Socket-ах и ACE_Reactor-ах. Очень нехилый кусок работы. Тут важно выбрать адекватную замену и понять, какие же выигрыши будут от такой замены. В качестве альтернатив ACE я бы здесь рассматривал libevent/libev, libuv и, может быть, Boost.Asio. На крайний случай можно и POCO. Если кто-то знает другие хорошие библиотеки для организации сетевого взаимодействия (с нормальной поддержкой Windows и Linux), прошу поделиться знаниями;
  • работа с DLL в so_sysconf. В принципе, это довольно простая для создания кросс-платформенности абстракция, так что можно будет сделать свою обертку над LoadLibrary и dlopen;
  • запуск приложения в режиме сервиса Windows или демона Unix, так же используется в so_sysconf. Такие вещи я сам вручную не делал, использовал функциональность ACE. На что заменять и насколько сложно сделать свою реализацию вручную не имею представления;
  • во многих примерах/тестах/приложениях используется функциональность ACE_Get_Opt для работы с аргументами командной строки. И хотя кода для работы с ACE_Get_Opt приходится писать много, результат того стоит, т.к. обеспечивается чуть ли не полная поддержка POSIX-овского стандарта на формат аргументов командной строки. На что менять не знаю. На ум сразу приходит Boost.ProgramOptions, но стоит ли только ради этого закладываться на зависимость от Boost-а -- не понятно.
В общем, работы нужно будет проделать прилично. Оправдано ли это -- не знаю, далеко не уверен. Если бы к нам были обращения о том, что SObjectizer -- штука хорошая, но использованию препятствует ACE, можно было бы проделать полный переход с ACE на что-то другое. Но таких обращений не было, поэтому приоритет у этой задачи самый маленький.

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

Комментариев нет: