Запуск процесса и его уничтожение из приложения Ruby on Rails, не выходя из зомби-процесса

Я разрабатываю приложение Ruby on Rails. Частью функциональности является запуск и закрытие соединения с ведомым устройством Modbus через последовательный порт, получение данных и их сохранение в базе данных. Также я сделал скрипт на питоне, который делает именно то, что мне нужно, поэтому я не хочу изобретать велосипед и переписывать его на Ruby.

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

Я запускаю процесс следующим образом, поэтому я могу получить доступ к его pid:

def start
    ...
    @@pids[ object.id ] = IO.popen("python ./python_script_name.py").pid
    ...

@@pids — это хэш, в котором хранятся pid всех запущенных процессов с ключом в качестве object.id (предполагается, что каждый объект может запускать только один процесс)

Когда я хочу закрыть соединение, я убиваю процесс, например:

def stop
    ...
    pid = @@pids[ object.id ]
    system("kill #{pid}")

Это приводит к зомби-процессу (виден после ps aux | grep python):

[python] <defunct>

Я попытался отправить сигнал SIGCHLD в приложение rails (поскольку он является родителем ранее называемого скрипта python), но он не работает.

Я хочу добавить, что мне может понадобиться вызывать/убивать этот процесс довольно часто, поэтому это может привести к огромному количеству процессов-зомби.

Как я могу убить процесс, не оставив зомби-процесс?


person Piotr Pawlik    schedule 24.04.2015    source источник
comment
ваш скрипт на Python плохо реагирует на TERM. попробуй отправить его УБИТЬ?   -  person iced    schedule 24.04.2015
comment
Я пробовал и SIGKILL, и SIGTERM - результат тот же.   -  person Piotr Pawlik    schedule 24.04.2015
comment
предоставьте воспроизводимый пример, поскольку он работает для меня, как и ожидалось, с минимальным скриптом python с sys.stdin.readline() внутри.   -  person iced    schedule 24.04.2015
comment
Скрипт также использует 2 модуля, написанных мной, это слишком много строк кода, чтобы публиковать их здесь. Но, возможно, вы направили меня в правильном направлении - вместо этого я сосредоточусь на сценарии. Отпишусь о результате, когда получу.   -  person Piotr Pawlik    schedule 24.04.2015
comment
Вы проверили, что @@pids возвращает вам правильный pid (почему я всегда выбираю Hash#fetch вместо Hash#[]? Кажется, что с этим объектом может быть достаточно легко запутаться, особенно учитывая перезагрузку разработчика Rails (идентификаторы объектов могут измениться ) Пробовали ли вы создать какой-то другой процесс, который вы контролируете и можете видеть, чтобы увидеть, получает ли он сигнал?(т.е. перехватить его и записать в файл, а затем передать исходному обработчику).   -  person Joshua Cheek    schedule 27.04.2015
comment
@JoshuaCheek Да, я проверил это с помощью byebug gem. Я все равно не мог убить скрипт без него (скрипт убит, проблема в том, что процесс-зомби остался позади). также @iced Что касается перехвата сигнала: я реализовал модуль signal в скрипте Python как для SIGKILL, так и для SIGTERM. Они работают (сигнал ловится нормально), но зомби-процесс остается позади. Я использовал sys.exit(), sys.quit(), даже raise SystemExit - все с тем же результатом. Может быть, есть какой-то другой способ завершить скрипт и сообщить родительскому процессу (в данном случае серверу rails), что он завершен правильно?   -  person Piotr Pawlik    schedule 27.04.2015


Ответы (1)


Проблема была не в скрипте python - сигналы по умолчанию обрабатываются правильно (без изменений после реализации модуля signal).

Проблема в рубиновом скрипте. После уничтожения процесса его родитель (в данном случае сервер rails) должен как-то узнать, что он был убит. Для этого есть функция wait. Из рубиновой документации:

Родительский процесс должен использовать Process.wait для сбора статуса завершения своего дочернего процесса.

Итак, чтобы правильно справиться с уничтожением дочернего процесса, необходимо выполнить следующее:

def stop
    ...
    pid = @@pids[ object.id ]
    Process.kill( 15, pid )
    Process.wait( pid )
    ...
person Piotr Pawlik    schedule 27.04.2015