HTTPS SSH

Что это?

Это небольшой пример, демонстрирующий использование CSP-подобных каналов и механизма таймеров SObjectizer. В данном примере совсем нет агентов: только голые нити и каналы. Зато есть демонстрация интересного трюка, основанного на возможности отменить отложенное сообщение.

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

Как раз для слежения за временем работы ping-ов и используется трюк с отменяемыми отложенными сообщениями.

Как это работает?

Сразу после запуска дочернего процесса рабочий поток создает у себя экземпляр класса process_deadline. В своем конструкторе этот экземпляр отсылает в отдельный канал отложенное сообщение send_sigint. А в деструкторе -- отменяет доставку этого сообщения.

Получается следующий принцип: если дочерний процесс завершается быстро, то экземпляр process_deadline разрушается и отменяет доставку отложенного сообщения send_sigint. Если же дочерний процесс работает слишком долго, то отложенное сообщение send_sigint успевает дойти до получателя, который пошлет сигнал SIGINT дочернему процессу (идентификатор которого содержится в send_sigint).

Можно обратить внимание на то, что в process_deadline определен только конструктор. Деструктор определять не нужно, он будет сгенерирован автоматически. Отмена же отложенного сообщения произойдет из-за разрушения объекта типа timer_id_t, который хранится внутри process_deadline.

Еще несколько моментов

Завершение дочернего процесса в два шага

Поток, который отсылает сигналы SIGINT дочерним процессам, не ограничивается посылкой только одного сигнала. Сначала он отсылает SIGINT, после чего самому себе посылает отложенное на две секунды сообщение send_sigkill. Получив send_sigkill поток проверяет статус процесса. Если процесс продолжает работать, то ему отсылается сигнал SIGKILL.

Примечание. Посылка SIGKILL выполняется без проверки того, принадлежит ли PID тому же самому процессу. Этой проверки нет для того, чтобы не увеличивать объем кода примера. Пожалуйста, не копируйте бездумно код из этого примера в свой продакшен-код. Для продакшена нужно использовать более сложную схему, например, запрещать рабочему потоку вызывать wait() если судьбой дочернего процесса занялся поток-deadliner.

Объекты joiner и closer в функции main

Можно обратить внимание на наличие объектов joiner-ов и closer-ов в функции main. Например:

thread logger_thread;
auto logger_joiner = auto_join( logger_thread );
auto log_ch_closer = auto_close_mchains(
        mchain_props::close_mode_t::drop_content, log_ch );
logger_thread = thread( logger, log_ch );

Они нужны для обеспечения exception safety. Объект joiner в своем деструкторе вызывает join для указанного ему объекта типа thread. Объект closer закрывает заданный mchain. Вместе же наличие joiner-ов и closer-ов позволяет не думать о ручной очистке ресурсов и ручной остановке рабочих потоков, если где-то ниже по коду выскакивает исключение.

Однако, очень важен порядок объявления joiner-ов и closer-ов. Объект closer должен быть объявлен после joiner-а. Тогда его деструктор будет вызван раньше. Получится, что сперва будет закрыт mchain, на котором рабочий поток висит в receive. Это приведет к выходу из receive и завершению рабочего потока. А потом уже будет вызван join для этого потока.

Если же joiner и closer поменять местами, то возникнет классический дедлок: главная нить будет висеть на join-е для рабочего потока, а рабочий поток будет находиться внутри receive, ожидая, когда же главная нить закроет mchain.

Как взять и попробовать?

Данный заточен под работу исключительно под Linux (и используемой в примере библиотеки procxx есть проблемы под FreeBSD).

Для компиляции примера потребуется Ruby, RubyGems и Rake. Обычно все эти инструменты идут в одном пакете. Но может потребоваться устанавливать их по отдельности. Например:

sudo apt-get install ruby
sudo apt-get install rake

После установки Ruby (+RubyGems+Rake) нужно установить Mxx_ru:

gem install Mxx_ru

Или, если gem требует прав администратора:

sudo gem install Mxx_ru

После этого можно забрать исходный код примера с BitBucket-а и компилировать:

# Забираем исходники Mercurial-ом
hg clone https://bitbucket.org/sobjectizerteam/child_deadline_example
cd child_deadline_example
# Забираем все необходимые зависимости.
mxxruexternals
# Компилируем.
cd dev
ruby build.rb

Либо же, без Mercurial-а:

# Забираем и распаковываем исходники
wget https://bitbucket.org/sobjectizerteam/child_deadline_example/get/v.1.0.1.tar.bz2
tar xaf v.1.0.0.tar.bz2
cd sobjectizerteam-child_deadline_example-0dae1dd47c02
# Забираем все необходимые зависимости.
mxxruexternals
# Компилируем.
cd dev
ruby build.rb

В результате компиляции в target/release должны оказаться libso-5.5.16.1.so и child_deadline_example. Запускать нужно child_deadline_example.