Skip to content

Latest commit

 

History

44 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

prunner — Process Runner

prunner запускает набор вспомогательных команд и, в обычном режиме, контролирует их до обнаружения завершения или исчезновения указанного главного процесса. После этого на POSIX-системах prunner останавливает управляемую группу каждой запущенной команды, включая оставшихся в ней потомков. На других платформах останавливается только непосредственный запущенный процесс.

Зависимости

  • Python 3;
  • модуль psutil.

Запуск

prunner -p PID [-d DIR] [-f FILE] [-r '[PARAMS] COMMAND']... \
    [-a '[PARAMS] COMMAND']... [OPTIONS]
prunner --disable-monitor [-d DIR] [-f FILE] \
    [-r '[PARAMS] COMMAND']... [OPTIONS]

Основные параметры:

-p, --monitor-pid PID          PID главного процесса
-d, --run-from-dir DIR         команды из каталога
-f, --run-from-file FILE       команды из файла
-r, --run COMMAND              команда из командной строки; можно повторять
-a, --run-after COMMAND        команда после завершения или исчезновения
                               главного процесса; можно повторять
-c, --check-period SEC         период проверки, по умолчанию 5 секунд
-t, --terminate-timeout SEC    ожидание после SIGTERM, по умолчанию 5 секунд
-v, --verbose                  служебные сообщения prunner
-V, --version                  версия
--disable-monitor              режим запуска без supervisor

Без --disable-monitor параметр --monitor-pid обязателен. Указанный процесс должен существовать в момент запуска prunner. prunner сохраняет identity этого процесса один раз и не принимает позднее переиспользование того же PID за продолжение его жизни. PID самого prunner нельзя использовать в качестве --monitor-pid.

--check-period должен быть положительным конечным числом и не может превышать threading.TIMEOUT_MAX текущей версии Python. --terminate-timeout должен быть неотрицательным конечным числом.

Источники команд

Источники --run-from-file, --run-from-dir и --run можно использовать одновременно. Все найденные команды образуют один список независимых процессов. Порядок источников фиксирован и не зависит от порядка параметров командной строки: сначала команды из --run-from-file в порядке строк файла, затем исполняемые файлы из --run-from-dir в лексикографическом порядке, затем команды -r/--run в порядке их указания.

Файл должен быть текстовым файлом в UTF-8 и содержать по одной команде на строку:

[restart] prog1 arg1 arg2
# комментарий
[restart=5,restart_pause=2] prog2 arg1
[verbose,shell=0] prog3 --flag value
prog4 arg1

Пустые строки и строки, в которых первый непробельный символ — #, игнорируются. Начальные и конечные пробелы команды удаляются. Ошибка чтения явно указанного файла, включая ошибку декодирования UTF-8, считается ошибкой конфигурации.

Каталог задаёт по одной команде на каждый находящийся непосредственно в нём обычный исполняемый файл. Файлы запускаются напрямую, без shell и аргументов, в лексикографическом порядке. Символические ссылки среди элементов каталога игнорируются, даже если ведут на исполняемый файл. Сам явно указанный путь FILE или DIR разрешается обычными средствами ОС, поэтому такой путь может быть символической ссылкой. Отсутствующий или недоступный явно указанный каталог считается ошибкой конфигурации.

Команды из --run-from-file, --run и --run-after по умолчанию выполняются через shell. prunner не изолирует их и не проверяет содержимое на безопасность: файлы, каталоги и строки команд должны поступать только из доверенного источника. Для --run-from-dir это требование действует в течение всего lifecycle, включая рестарты: менее доверенные пользователи не должны иметь возможности изменять содержимое executable-файлов, создавать, удалять или переименовывать элементы каталога либо изменять сам каталог и доступные им на запись родительские компоненты пути. Проверки типа файла, symbolic link и executable bit используются только для отбора команд и не являются границей безопасности.

-r/--run можно указывать многократно; команды сохраняют порядок аргументов командной строки. Одинаковые команды не объединяются и контролируются независимо.

Пустой файл или каталог не считается настроенной командой. Допустимость итоговой конфигурации определяется после чтения всех источников:

Режим Обычные команды Только --run-after Нет команд
обычный supervisor не менее одной; hooks необязательны разрешено ошибка
--disable-monitor не менее одной; hooks игнорируются ошибка ошибка

В режиме --disable-monitor параметры --run-after не выполняются и не могут заменить обычную команду.

Пример одновременного использования источников:

prunner -p PID -d ./child.d -f ./runlist.txt \
    -r '[restart=2] prog1 arg1 arg2'

Формат команды и параметры

Общий формат:

[param1=value1,param2,param3=value3] command arg...

Блок [...] необязателен. Флаг без значения означает true. Поддерживаются только перечисленные ниже параметры; неизвестный параметр или некорректное значение являются ошибкой конфигурации. Один параметр нельзя указывать в одном блоке повторно.

Если сама команда начинается с [, перед ней нужно поставить пустой блок параметров []. Например, строка [] [literal-command] arg задаёт команду [literal-command] arg, а не блок параметров.

  • restart=-1 — однократный запуск (one-shot). Это значение по умолчанию. После успешного создания процесс может завершиться с любым кодом: это не считается отказом, процесс не перезапускается, а prunner продолжает ждать главный процесс. Ошибка первоначального запуска остаётся ошибкой запуска.
  • restart=0 или просто restart — перезапускать без ограничения числа попыток.
  • restart=N, где N > 0, — разрешить ровно N повторных запусков после первоначального. Следующее завершение считается неисправимым отказом.
  • restart_pause=SEC — пауза перед повторным запуском, по умолчанию равна --check-period. Значение должно быть неотрицательным.
  • verbose — наследовать stdout и stderr от prunner. По умолчанию вывод команды перенаправляется в системное null-устройство (os.devnull).
  • shell=0 — запускать программу напрямую, без shell. Аргументы разбираются как командная строка с shell-подобными кавычками, но подстановки, конвейеры, перенаправления и другие операторы shell не выполняются.
  • shell=1 — выполнить строку через shell. Это значение по умолчанию.

Для логических параметров принимаются true/false и 1/0 без учёта регистра. При restart=-1 ошибка первоначального запуска немедленно завершает prunner с ошибкой. При restart >= 0 неудачная попытка запуска учитывается лимитом restart; следующая попытка выполняется после restart_pause, пока лимит не исчерпан.

Мониторинг и перезапуски

prunner контролирует исходный процесс, соответствующий --monitor-pid, и каждую запущенную команду. Любое обнаруженное завершение или исчезновение главного процесса запускает остановку: его exit status и причина завершения не различаются. Для процессов с restart >= 0 завершение запускает логику перезапуска, пока prunner не обнаружил завершение или исчезновение главного процесса. Процесс с restart=-1 является one-shot: после успешного создания его завершение не влияет на ожидание главного процесса.

Обычная проверка состояния дочерних процессов неблокирующая и не добавляет по 0.5 секунды на каждую команду. Однако перед рестартом prunner синхронно останавливает старую generation процесса. Если в её группе остались потомки, TERM-фаза этой очистки может задержать следующий проход supervisor до --terminate-timeout; после SIGKILL выполняется дополнительное короткое ограниченное ожидание.

Если лимит перезапусков одной команды исчерпан, prunner останавливает все остальные управляемые команды и завершается с ошибкой. Исключение в механизме мониторинга также считается ошибкой, а не отключает мониторинг незаметно.

Архитектура жизненного цикла

Глобальный жизненный цикл prunner внутри реализован как конечный автомат. Логически он разделяет запуск команд, supervisor-мониторинг, подготовку и commit отсоединения в режиме --disable-monitor, очистку управляемых групп, выполнение --run-after и окончательное завершение. После начала остановки новые обычные управляемые команды и их restart-generation не запускаются; глобальная очистка этих групп выполняется не более одного раза. После неё, если обнаружено завершение или исчезновение главного процесса и глобальная очистка успешна, могут последовательно запускаться --run-after hooks. Успешный commit отсоединения передаёт ответственность за запущенные группы самим процессам, поэтому переход к глобальной очистке после него невозможен.

Имена и количество внутренних состояний, а также конкретные точки переходов — детали реализации и не являются публичным API. Публичный контракт составляют параметры командной строки и описанная в этом документе семантика запуска, мониторинга, cleanup, detach, сигналов, hooks и кодов завершения.

Завершение и сигналы

На POSIX-системах каждая управляемая команда запускается в отдельной сессии и группе процессов. При остановке prunner завершает всю сохранившуюся управляемую группу, включая потомков shell:

  1. посылает SIGTERM всем ещё живым управляемым группам;
  2. ждёт не более --terminate-timeout;
  3. посылает SIGKILL оставшимся группам;
  4. выполняет короткое ограниченное ожидание завершения и освобождает завершившиеся непосредственные дочерние процессы.

Этот порядок используется после завершения или исчезновения главного процесса, неисправимого отказа команды, ошибки запуска, SIGINT и SIGTERM. Команда, которая сама создала новую сессию или иным способом покинула управляемую группу, находится за пределами этой гарантии.

Проверка принадлежности группы после завершения её лидера опирается на Linux-семантику идентификаторов групп процессов. На не-POSIX-платформах prunner посылает terminate, а затем kill только непосредственному запущенному процессу; завершение его потомков не гарантируется. Ожидание после kill ограничено, поэтому невозможность подтвердить завершение считается ошибкой cleanup, а не приводит к неограниченному зависанию prunner.

Сигналы обрабатываются в течение всего жизненного цикла, включая первоначальный запуск команд. Таким образом, уже запущенные команды не остаются без очистки, если сигнал пришёл во время старта следующей.

Коды завершения:

Код Причина
0 главный процесс завершился или исчез, а очистка и --run-after успешны; либо команды успешно запущены и отсоединены в режиме --disable-monitor
1 ошибка аргументов, конфигурации, запуска, мониторинга, cleanup или --run-after; исчерпан лимит restart
130 prunner получил SIGINT
143 prunner получил SIGTERM

--run-after

--run-after выполняется только после обнаружения любого завершения или исчезновения процесса --monitor-pid и успешной глобальной очистки управляемых команд. Exit status и сигнал, завершивший главный процесс, не различаются.

Он не выполняется после:

  • SIGINT или SIGTERM;
  • отказа дочерней команды;
  • ошибки запуска или мониторинга;
  • запуска с --disable-monitor.

Пример:

prunner -p PID -d ./child.d \
    --run-after 'send-notification main-process-finished'

Несколько --run-after выполняются последовательно в порядке аргументов. prunner ждёт завершения каждой запущенной команды. Ошибка запуска, cleanup группы hook или ненулевой код меняет итоговый код prunner на 1, а последующие hooks не запускаются.

Каждый hook, до которого дошла очередь, запускается не более одного раза. Параметры restart и restart_pause для него принимаются как часть общего формата, но не применяются; shell и verbose продолжают действовать. Если prunner получает SIGINT или SIGTERM во время hook, текущая группа завершается, а последующие hooks не запускаются.

--disable-monitor

Это режим однократного запуска (launch/detach), а не режим supervisor:

  • --monitor-pid не требуется и, если указан, не отслеживается;
  • prunner запускает все обычные команды и сразу завершается;
  • успешно созданные процессы не перезапускаются и не завершаются при выходе prunner;
  • --run-after не выполняется;
  • при успешном запуске всех команд код завершения равен 0, при ошибке — 1; полученный SIGINT или SIGTERM имеет приоритет и даёт соответственно код 130 или 143.

Если одна из команд не смогла запуститься, prunner завершает уже созданные в рамках этого запуска группы процессов и только затем выходит с кодом 1. На POSIX-системах с pthread_sigmask и sigpending отсоединение происходит атомарно относительно SIGINT и SIGTERM лишь после успешного запуска всего списка. Сигнал, полученный до этой точки commit, отменяет detach и приводит к очистке уже созданных групп. После успешного commit группы намеренно остаются работать независимо от последующего завершения prunner. На платформах без этих POSIX API используется best-effort граница: уже обработанный сигнал по-прежнему отменяет detach, но атомарность решения в точной точке commit не гарантируется.

Параметры restart и restart_pause в этом режиме не действуют. Запущенные команды должны быть готовы продолжить работу после завершения родителя.

Примеры

Запуск файлов из каталога до завершения главного процесса:

prunner -p PID -d ./child.d

Запуск команд из файла:

prunner -p PID -f ./runlist.txt

Передача нескольких команд напрямую:

prunner -p PID \
    -r '[restart] prog1 arg1' \
    -r '[restart=3,verbose] prog2 arg1 arg2'

Тестирование

make check и make test — эквивалентные команды, запускающие полный набор регрессионных тестов проекта.

About

Running and monitoring a process group

Resources

Stars

0 stars

Watchers

2 watching

Forks

Releases

Packages

Contributors

Languages