Как убить дочерний процесс после заданного тайм-аута в Bash?
У меня есть bash script, который запускает дочерний процесс, который время от времени сбой (фактически, зависает) и без видимых причин (закрытый источник, поэтому я не могу с этим поделать). В результате я хотел бы запустить этот процесс за определенное количество времени и убить его, если он не вернется успешно через заданный промежуток времени.
Существует ли простой и надежный способ использования bash?
P.S.: Скажите, если этот вопрос лучше подходит для serverfault или суперпользователя.
Ответы
Ответ 1
(Как видно из:
BASH Запись в FAQ # 68: "Как мне запустить команду и прервать ее (спустя тайм-аут) через N секунд?" )
Если вы не хотите что-то скачивать, используйте timeout
(sudo apt-get install timeout
) и используйте его как:
timeout 10 ping www.goooooogle.com
Если вы не хотите что-то скачивать, выполните таймаут внутри:
( cmdpid=$BASHPID; (sleep 10; kill $cmdpid) & exec ping www.goooooogle.com )
Если вы хотите сделать тайм-аут для более длинного bash кода, используйте второй вариант как таковой:
( cmdpid=$BASHPID;
(sleep 10; kill $cmdpid) \
& while ! ping -w 1 www.goooooogle.com
do
echo crap;
done )
Ответ 2
# Spawn a child process:
(dosmth) & pid=$!
# in the background, sleep for 10 secs then kill that process
(sleep 10 && kill -9 $pid) &
или для получения кодов выхода:
# Spawn a child process:
(dosmth) & pid=$!
# in the background, sleep for 10 secs then kill that process
(sleep 10 && kill -9 $pid) & waiter=$!
# wait on our worker process and return the exitcode
exitcode=$(wait $pid && echo $?)
# kill the waiter subshell, if it still runs
kill -9 $waiter 2>/dev/null
# 0 if we killed the waiter, cause that means the process finished before the waiter
finished_gracefully=$?
Ответ 3
sleep 999&
t=$!
sleep 10
kill $t
Ответ 4
У меня также был этот вопрос и нашел еще две вещи очень полезными:
- Переменная SECONDS в bash.
- Команда "pgrep".
Поэтому я использую что-то подобное в командной строке (OSX 10.9):
ping www.goooooogle.com & PING_PID=$(pgrep 'ping'); SECONDS=0; while pgrep -q 'ping'; do sleep 0.2; if [ $SECONDS = 10 ]; then kill $PING_PID; fi; done
Поскольку это цикл, я включил "sleep 0.2", чтобы поддерживать охлаждение процессора.; -)
(Кстати: ping - неудачный пример, вы просто использовали бы встроенную опцию "-t" (timeout).)
Ответ 5
Предполагая, что вы (или можете легко сделать) файл pid для отслеживания дочернего pid, тогда вы можете создать script, который проверяет время работы pid файла и убивает/респанит процесс по мере необходимости. Затем просто поставьте script в crontab для запуска примерно в тот период, в котором вы нуждаетесь.
Сообщите мне, если вам нужна дополнительная информация. Если это не похоже на то, что вам подходит, а что насчет upstart?
Ответ 6
Один из способов - запустить программу в подоболочке и связаться с подоболочкой через именованный канал с помощью команды read
. Таким образом, вы можете проверить статус выхода процесса, который будет запущен, и передать его обратно через канал.
Здесь приведен пример выключения команды yes
через 3 секунды. Он получает PID процесса, используя pgrep
(возможно, работает только в Linux). Существует также проблема с использованием трубы в том, что процесс открытия трубы для чтения будет зависать, пока он не будет открыт для записи, и наоборот. Поэтому, чтобы предотвратить зависание команды read
, я "вклинил", чтобы открыть канал для чтения с фоновым подоболочкой. (Другой способ предотвратить замораживание, чтобы открыть чтение/запись канала, т.е. read -t 5 <>finished.pipe
- однако это также может не работать, кроме как с Linux.)
rm -f finished.pipe
mkfifo finished.pipe
{ yes >/dev/null; echo finished >finished.pipe ; } &
SUBSHELL=$!
# Get command PID
while : ; do
PID=$( pgrep -P $SUBSHELL yes )
test "$PID" = "" || break
sleep 1
done
# Open pipe for writing
{ exec 4>finished.pipe ; while : ; do sleep 1000; done } &
read -t 3 FINISHED <finished.pipe
if [ "$FINISHED" = finished ] ; then
echo 'Subprocess finished'
else
echo 'Subprocess timed out'
kill $PID
fi
rm finished.pipe
Ответ 7
Вот попытка, которая пытается избежать убийства процесса после того, как он уже вышел, что уменьшает вероятность убийства другого процесса с тем же идентификатором процесса (хотя, вероятно, невозможно полностью избежать такой ошибки).
run_with_timeout ()
{
t=$1
shift
echo "running \"$*\" with timeout $t"
(
# first, run process in background
(exec sh -c "$*") &
pid=$!
echo $pid
# the timeout shell
(sleep $t ; echo timeout) &
waiter=$!
echo $waiter
# finally, allow process to end naturally
wait $pid
echo $?
) \
| (read pid
read waiter
if test $waiter != timeout ; then
read status
else
status=timeout
fi
# if we timed out, kill the process
if test $status = timeout ; then
kill $pid
exit 99
else
# if the program exited normally, kill the waiting shell
kill $waiter
exit $status
fi
)
}
Используйте как run_with_timeout 3 sleep 10000
, который запускает sleep 10000
, но завершает его через 3 секунды.
Это похоже на другие ответы, которые используют процесс тайм-аута в фоновом режиме, чтобы убить дочерний процесс после задержки. Я думаю, что это почти то же самое, что и ответ Дэн (fooobar.com/questions/52302/...), за исключением того, что оболочка тайм-аута не будет убита, если она уже закончилась.
После завершения этой программы все еще будут выполняться несколько затяжных процессов "сна", но они должны быть безвредными.
Это может быть лучшим решением, чем мой другой ответ, потому что он не использует непереносимую оболочку read -t
и не использует pgrep
.
Ответ 8
Вот третий ответ, который я представил здесь. Он обрабатывает прерывания сигнала и очищает фоновые процессы при приеме SIGINT
. Он использует трюк $BASHPID
и exec
, используемый в верхнем ответе, чтобы получить PID процесса (в данном случае $$
в вызове sh
). Он использует FIFO для связи с подоболочкой, которая несет ответственность за убийство и очистку. (Это похоже на трубку в моем втором ответе, но наличие именованного канала означает, что обработчик сигнала также может записывать в него.)
run_with_timeout ()
{
t=$1 ; shift
trap cleanup 2
F=$$.fifo ; rm -f $F ; mkfifo $F
# first, run main process in background
"[email protected]" & pid=$!
# sleeper process to time out
( sh -c "echo \$\$ >$F ; exec sleep $t" ; echo timeout >$F ) &
read sleeper <$F
# control shell. read from fifo.
# final input is "finished". after that
# we clean up. we can get a timeout or a
# signal first.
( exec 0<$F
while : ; do
read input
case $input in
finished)
test $sleeper != 0 && kill $sleeper
rm -f $F
exit 0
;;
timeout)
test $pid != 0 && kill $pid
sleeper=0
;;
signal)
test $pid != 0 && kill $pid
;;
esac
done
) &
# wait for process to end
wait $pid
status=$?
echo finished >$F
return $status
}
cleanup ()
{
echo signal >$$.fifo
}
Я старался избегать условий гонки, насколько я могу. Однако один из источников ошибки, которую я не смог удалить, - это когда процесс заканчивается примерно в то же время, что и таймаут. Например, run_with_timeout 2 sleep 2
или run_with_timeout 0 sleep 0
. Для меня последний дает ошибку:
timeout.sh: line 250: kill: (23248) - No such process
поскольку он пытается убить процесс, который уже вышел сам по себе.