Как убить дочерний процесс после заданного таймаута в Bash?



У меня есть скрипт bash, который запускает дочерний процесс, который время от времени падает (на самом деле, зависает) и без видимой причины (закрытый источник, поэтому я не могу с этим ничего поделать). В результате я хотел бы иметь возможность запустить этот процесс в течение заданного периода времени и убить его, если он не вернется успешно через заданное время.



есть простой и надежная способ достичь этого с помощью bash?



С. П.: скажите меня, если этот вопрос лучше подходит для serverfault или суперпользователя.

629   8  

8 ответов:

(как видно в: bash FAQ запись #68: "как я могу запустить команду и прервать ее (тайм-аут) через N секунд?")

если вы не против скачать что-то, используйте timeout (sudo apt-get install timeout) и использовать его как: (большинство систем уже установлен, в противном случае использовать sudo apt-get install coreutils)

timeout 10 ping www.goooooogle.com

если вы не хотите что-то скачивать, сделайте то, что timeout делает внутренне:

( cmdpid=$BASHPID; (sleep 10; kill $cmdpid) & exec ping www.goooooogle.com )

в случае, если вы хотите сделать тайм-аут дольше Баш код, используйте второй вариант как таковой:

( cmdpid=$BASHPID; 
    (sleep 10; kill $cmdpid) \
   & while ! ping -w 1 www.goooooogle.com 
     do 
         echo crap; 
     done )
# 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=$?
sleep 999&
t=$!
sleep 10
kill $t

У меня также был этот вопрос и нашел еще две вещи очень полезными:

  1. переменная секунд в bash.
  2. команда "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).)

предполагая, что у вас есть (или вы можете легко сделать) файл pid для отслеживания PID ребенка, вы можете создать скрипт, который проверяет modtime файла pid и убивает/возрождает процесс по мере необходимости. Затем просто поместите скрипт в crontab для запуска примерно в тот период, который вам нужен.

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

один из способов-запустить программу в подрешетке и связаться с подрешеткой через именованный канал с через 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

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

run_with_timeout ()
{
  t=
  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 секунды.

Это похоже на другие ответы, которые используют фоновый процесс тайм-аута, чтобы убить дочерний процесс после задержки. Я думаю, что это почти то же, что и расширенный ответ Дэна (https://stackoverflow.com/a/5161274/1351983), за исключением того, что оболочка таймаута не будет убита, если она уже закончилась.

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

Это может быть лучшим решением, чем мой другой ответ, потому что он не использует непортативную функцию оболочки read -t и не использовать pgrep.

вот третий ответ, который я представил здесь. Это один обрабатывает прерывания сигнала и очищает фоновые процессы, когда SIGINT получено. Он использует $BASHPID и exec трюк, используемый в лучшие ответы чтобы получить PID процесса (в данном случае $$ на sh ссылка). Он использует FIFO для связи с подрешеткой, которая отвечает за убийство и очистку. (Это как труба в моем второй ответ, но наличие именованного канала означает что обработчик сигнала может писать в него.)

run_with_timeout ()
{
  t= ; shift

  trap cleanup 2

  F=$$.fifo ; rm -f $F ; mkfifo $F

  # first, run main process in background
  "$@" & 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

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

Comments

    Ничего не найдено.