Одна очень удобная штука, которую предоставляет SO-5 разработчику и о которой мы недостаточно часто говорим, -- это таймеры. В виде отложенных и периодических сообщений. Сегодня попробую показать небольшой трюк, который работает благодаря возможности отменить отложенное сообщение.
Есть такая маленькая, но хорошая библиотечка procxx для запуска дочерних процессов в Unix-ах (я ее слегка доработал напильником, но мой pull request пока не приняли). Запуск какой-нибудь внешней программы и чтение ее выхлопа посредством procxx -- это не просто, а очень просто:
procxx::process some_tool( "some_tool", key, value, key2, value2, ... ); some_tool.exec(); std::string line; while( std::getline(some_tool.output(), line) ) handle_line(line); some_tool.wait(); |
Все бы хорошо, но есть одна проблемка: чтение выхлопа идет в блокирующем режиме. И если внешняя программа подвисла, то подвиснет и цикл чтения. Поэтому время работы внешней программы нужно ограничить дедлайном.
Сделать это дедлайн в SO-5 не сложнее, чем запустить внешний процесс с помощью procxx ;)
Самое тривиальное -- это агент, который будет отсылать сигнал SIGKILL слишком долго работающему процессу:
/* * Задача этого агента -- работать на отдельном контексте и насильно прерывать * работу процесса с указанным pid-ом. */ class a_child_deadliner_t final : public so_5::agent_t { public : a_child_deadliner_t( context_t ctx, spdlog::logger & logger ) : so_5::agent_t( ctx ), m_logger( logger ) { so_subscribe_self().event( &a_child_deadliner_t::evt_deadline ); } // Сообщение о том, что дедлайн для процесса наступил и процесс, // если информация о нем еще осталась, должен быть уничтожен. struct msg_deadline : public so_5::message_t { pid_t m_pid; const std::string m_description; msg_deadline( pid_t pid, std::string description ) : m_pid( pid ), m_description( std::move(description) ) {} }; private : spdlog::logger & m_logger; void evt_deadline( const msg_deadline & cmd ) { m_logger.error( "deadline for process [pid={}][description={}]", cmd.m_pid, cmd.m_description ); kill( cmd.m_pid, SIGKILL ); } }; |
А все самое интересное происходит в другом классе, process_deadline_t. Хотя он небольшой по объему, но его логика становится лучше понятна, если сначала показать, как он используется. Используется он так:
procxx::process some_tool( "some_tool", key, value, key2, value2, ... ); some_tool.exec(); process_deadline_t some_tool_deadline( env, deadliner_mbox, some_tool.id(), // PID процесса для остановки. std::chrono::seconds(15), // Сколько времени даем на работу. "some_tool is working too long" // Это будет сохранено в лог. ); std::string line; while( std::getline(some_tool.output(), line) ) handle_line(line); some_tool.wait(); |
Т.е. экземпляр process_deadline_t создается сразу после запуска процесса и живет все время, пока работает дочерний процесс. В своем конструкторе process_deadline_t отсылает агенту a_child_deadliner_t отложенное сообщение msg_deadline. А в деструкторе process_deadline_t происходит отмена этого сообщения. Т.о., если дочерний процесс завершился быстро, то process_deadline_t разрушится еще до того, как отложенное msg_deadline будет доставлено агенту a_child_deadliner_t.. А если нет, то сообщение msg_deadline дойдет до агента a_child_deadliner_t и тот отправит дочернему процессу сигнал SIGTERM.
Однако, вручную деструктор process_deadline_t описывать не нужно. Он будет сгенерирован автоматически, так что нужно всего лишь должным образом описать конструктор. Ну и, для того, чтобы при использовании process_deadline_t было меньше сюрпризов, данный класс является Moveable, но не Copyable.
А трюк заключается в том, что отложенное сообщение отменяется автоматически, когда разрушается последний timer_id для этого сообщения. Единственный timer_id хранится в process_deadline_t. Поэтому когда разрушается process_deadline_t, разрушается и timer_id и происходит отмена отложенного сообщения. Так что все работа -- это вызвать подходящий send в конструкторе process_deadline_t.
class process_deadline_t { process_deadline_t( const process_deadline_t & ) = delete; public : process_deadline_t( so_5::environment_t & env, // Почтовый ящик, на который нужно отослать сообщение об отмене. const so_5::mbox_t & deadliner_mbox, // Процесс, за временем жизни которого идет слежение. pid_t pid, // Сколько времени процессу разрешено работать. std::chrono::seconds deadline, // Пояснение для записи в лог при принудительном завершении // работы процесса std::string description ) : m_timer( so_5::send_periodic< a_child_deadliner_t::msg_deadline >( env, deadliner_mbox, deadline, std::chrono::seconds::zero(), pid, std::move(description) ) ) {} process_deadline_t( process_deadline_t && other ) = default; process_deadline_t & operator=( process_deadline_t && o ) = default; private : so_5::timer_id_t m_timer; }; |
PS. Еще одно маленькое пояснение: используется send_periodic, а не send_delayed, потому, что send_delayed не возвращает timer_id. Из-за этого отосланное через send_delayed отложенное сообщение отменить нельзя. А вот send_periodic возвращает timer_id. И если при вызове send_periodic задать только pause, но оставить нулевой period (как в примере выше), то send_periodic работает так же, как и send_delayed.
Комментариев нет:
Отправить комментарий