Рекурсивно убить процесс R с детьми в Linux - PullRequest
6 голосов
/ 03 февраля 2012

Я ищу общий метод для запуска, а затем уничтожения процесса R, включая, возможно, все форки или другие процессы, которые он вызвал.

Например, пользователь запускает такой скрипт:

library(multicore);
for(i in 1:3) parallel(foo <- "bar");
for(i in 1:3) system("sleep 300", wait=FALSE);
for(i in 1:3) system("sleep 300&");
q("no")

После того, как пользователь завершил сеанс R, дочерние процессы все еще работают:

jeroen@jeroen-ubuntu:~$ ps -ef | grep R
jeroen    4469     1  0 16:38 pts/1    00:00:00 /usr/lib/R/bin/exec/R
jeroen    4470     1  0 16:38 pts/1    00:00:00 /usr/lib/R/bin/exec/R
jeroen    4471     1  0 16:38 pts/1    00:00:00 /usr/lib/R/bin/exec/R
jeroen    4502  4195  0 16:39 pts/1    00:00:00 grep --color=auto R
jeroen@jeroen-ubuntu:~$ ps -ef | grep "sleep"
jeroen    4473     1  0 16:38 pts/1    00:00:00 sleep 300
jeroen    4475     1  0 16:38 pts/1    00:00:00 sleep 300
jeroen    4477     1  0 16:38 pts/1    00:00:00 sleep 300
jeroen    4479     1  0 16:38 pts/1    00:00:00 sleep 300
jeroen    4481     1  0 16:38 pts/1    00:00:00 sleep 300
jeroen    4483     1  0 16:38 pts/1    00:00:00 sleep 300
jeroen    4504  4195  0 16:39 pts/1    00:00:00 grep --color=auto sleep

Что еще хуже, их родительский идентификатор процесса равен 1, что затрудняет их идентификацию. Есть ли способ запустить скрипт R таким образом, чтобы я мог в любое время рекурсивно убить процесс и его дочерние элементы?

Редактировать: так что я не хочу вручную входить, чтобы искать и убивать процессы. Также я не хочу убивать все процессы R, поскольку могут быть и другие, у которых все в порядке. Мне нужен метод для уничтожения определенного процесса и всех его дочерних элементов.

Ответы [ 3 ]

8 голосов
/ 03 февраля 2012

В основном речь идет о многоядерной части.Дети ждут, чтобы вы собрали результаты - см. ?collect.Обычно вы никогда не должны использовать parallel без предоставления средств для очистки, обычно в on.exit. многоядерный очищается в высокоуровневых функциях, таких как mclapply, но если вы используете функции более низкого уровня, вы несете ответственность за выполнение очистки (поскольку multicore не может знать, покинули ли выдети бегают намеренно или нет).

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

 .Last <- function(...) {
     collect(wait=FALSE)
     all <- children()
     if (length(all)) {
         kill(all, SIGTERM)
         collect(all)
     }
 }

Опять же, вышеприведенный не рекомендуемый способ справиться с этим- это скорее последнее средство.Вы действительно должны назначать задания и собирать результаты, такие как

jobs <- lapply(1:3, function(i) parallel({Sys.sleep(i); i}))
collect(jobs)

Что касается дочернего вопроса общего процесса - init наследует потомков только после выхода R, но в .Last вы все равно можете найти их pidsпоскольку в этот момент существует родительский процесс, вы можете выполнить очистку, аналогичную многоядерному случаю.

4 голосов
/ 03 февраля 2012

До того, как пользователь завершит сеанс R, процессы, которые вы хотите завершить, будут иметь идентификатор родительского процесса, равный идентификатору процесса сеанса, который их начал. Возможно, вы могли бы использовать хуки .Last или .Last.sys (см. help(q)), чтобы завершить все процессы с соответствующим PPID в этой точке; их можно подавить с помощью q(runLast=FALSE), так что это не идеально, но я думаю, что это лучший вариант, который у вас есть.

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

Обратите внимание, что если один из child обрабатывает вилки, у внука будет PPID, равный PID child , и он будет сброшен в 1, когда ребенок выходит, что он может сделать до того, как дедушка выйдет. Таким образом, нет надежного способа отловить всех потомков процесса в целом, даже если вы сделаете это до завершения процесса. (Кто-то слышит, что «cgroups» предоставляет способ, но кто-то не знаком с деталями; в любом случае, это необязательная функция, которую предоставляют только некоторые итерации / конфигурации ядра Linux, и недоступная вообще нигде.)

1 голос
/ 03 февраля 2012

Я полагаю, что последняя часть вопроса больше относится к оболочке, нежели к ядру.(Саймон Урбанек ответил на часть multicore лучше, чем кто-либо другой, так как он автор. :))

Если вы используете bash, вы можете найти PID самого последнего запущенного ребенкапроцесс в $!.Вы можете объединить PID, а затем обязательно убить их, когда закроете R.

Если вы хотите быть действительно гонзо, вы можете сохранить родительский PID (т.е. вывод Sys.getpid()) и дочерний PID вфайл и имеет демон очистки, который проверяет, существует ли родительский PID и, если нет, убивает сирот.Я не думаю, что будет так просто получить пакет с именем oRphanKilleR на CRAN.

Вот пример добавления дочернего PID в файл:

system('(sleep 20) & echo $! >> ~/childPIDs.txt', wait = FALSE)

Вы можете изменить это, чтобы создать свою собственную команду оболочки и использовать команду R's tempfile() для создания временного файла (хотя он исчезнет, ​​когда экземпляр R завершится, если вы не приложите особых усилий для сохранения файла с помощью разрешений).

Для некоторых других умных идей, см. этот другой пост на SO .

Вы также можете создать цикл do while в оболочке, который будет проверять,конкретный PID не существует.Пока оно есть, петля спит.Как только цикл завершается (поскольку PID больше не используется), сценарий убивает другой PID.

В принципе, я думаю, что ваше решение будет в сценариях оболочки, а не в R.

...