Подтвердить что ты не робот

Bash: подождите с тайм-аутом

В Bash script я хотел бы сделать что-то вроде:

app1 &
pidApp1=$!
app2 &
pidApp2=$1

timeout 60 wait $pidApp1 $pidApp2
kill -9 $pidApp1 $pidApp2

I.e., запустите два приложения в фоновом режиме и дайте им 60 секунд, чтобы завершить свою работу. Затем, если они не закончатся в течение этого интервала, убейте их.

К сожалению, приведенное выше не работает, поскольку timeout является исполняемым, а wait является командой оболочки. Я попытался изменить его на:

timeout 60 bash -c wait $pidApp1 $pidApp2

Но это все еще не работает, поскольку wait может быть вызван только на PID, запущенный в пределах той же оболочки.

Любые идеи?

4b9b3361

Ответ 1

Запишите PID в файлы и запустите приложения следующим образом:

pidFile=...
( app ; rm $pidFile ; ) &
pid=$!
echo $pid > $pidFile
( sleep 60 ; if [[ -e $pidFile ]]; then killChildrenOf $pid ; fi ; ) &
killerPid=$!

wait $pid
kill $killerPid

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

Если процесс завершается быстрее, файл PID удаляется, и процесс убийцы завершается.

killChildrenOf - это script, который извлекает все процессы и убивает всех детей определенного PID. См. Ответы на этот вопрос для разных способов реализации этой функции: Лучший способ убить все дочерние процессы

Если вы хотите выйти за пределы BASH, вы можете написать PID и тайм-ауты в каталог и посмотреть этот каталог. Каждую минуту или около того читайте записи и проверяйте, какие процессы все еще существуют и были ли они выполнены.

РЕДАКТИРОВАТЬ Если вы хотите узнать, успешно ли этот процесс умер, вы можете использовать kill -0 $pid

EDIT2 Или вы можете попробовать группы процессов. kevinarpe сказал: Чтобы получить PGID для PID (146322):

ps -fjww -p 146322 | tail -n 1 | awk '{ print $4 }'

В моем случае: 145974. Затем PGID можно использовать со специальной опцией kill для завершения всех процессов в группе: kill -- -145974

Ответ 2

Как ваш пример, так и принятый ответ чересчур сложны, почему вы используете не только timeout, так как это именно его прецедент? Команда timeout имеет встроенную опцию (-k) для отправки SIGKILL после отправки исходного сигнала для завершения команды (SIGTERM по умолчанию), если команда по-прежнему выполняется после отправки начального сигнала (см. man timeout).

Если script необязательно требует wait и возобновляет поток управления после ожидания, просто вопрос

timeout -k 60s 60s app1 &
timeout -k 60s 60s app2 &
# [...]

Однако, если это так, просто сохраните PID timeout:

pids=()
timeout -k 60s 60s app1 &
pids+=($!)
timeout -k 60s 60s app2 &
pids+=($!)
wait "${pids[@]}"
# [...]

например.

$ cat t.sh
#!/bin/bash

echo "$(date +%H:%M:%S): start"
pids=()
timeout 10 bash -c 'sleep 5; echo "$(date +%H:%M:%S): job 1 terminated successfully"' &
pids+=($!)
timeout 2 bash -c 'sleep 5; echo "$(date +%H:%M:%S): job 2 terminated successfully"' &
pids+=($!)
wait "${pids[@]}"
echo "$(date +%H:%M:%S): done waiting. both jobs terminated on their own or via timeout; resuming script"

.

$ ./t.sh
08:59:42: start
08:59:47: job 1 terminated successfully
08:59:47: done waiting. both jobs terminated on their own or via timeout; resuming script

Ответ 3

Здесь приведена упрощенная версия ответа Аарона Дигуллы, в которой используется трюк kill -0, который Аарон Дигулла оставляет в комментарии:

app &
pidApp=$!
( sleep 60 ; echo 'timeout'; kill $pidApp ) &
killerPid=$!

wait $pidApp
kill -0 $killerPid && kill $killerPid

В моем случае я хотел быть безопасным как set -e -x и вернуть код состояния, поэтому я использовал:

set -e -x
app &
pidApp=$!
( sleep 45 ; echo 'timeout'; kill $pidApp ) &
killerPid=$!

wait $pidApp
status=$?
(kill -0 $killerPid && kill $killerPid) || true

exit $status

Статус выхода из 143 указывает на SIGTERM, почти наверняка из нашего таймаута.

Ответ 4

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

function wait_timeout {
  local limit=${@:1:1}
  local pids=${@:2}
  local count=0
  while true
  do
    local have_to_wait=false
    for pid in ${pids}; do
      if kill -0 ${pid} &>/dev/null; then
        have_to_wait=true
      else
        pids=`echo ${pids} | sed -e "s/${pid}//g"`
      fi
    done
    if ${have_to_wait} && (( $count < $limit )); then
      count=$(( count + 1 ))
      sleep 1
    else
      echo ${pids}
      return 1
    fi
  done   
  return 0
}

Чтобы использовать это, просто wait_timeout $timeout $PID1 $PID2 ...

Ответ 5

Чтобы вставить мой 2с, мы можем построить решение Teixeira для:

try_wait() {
    # Usage: [PID]...
    for ((i = 0; i < $#; i += 1)); do
        kill -0 [email protected] && sleep 0.001 || return 0
    done
    return 1 # timeout or no PIDs
} &>/dev/null

Bash sleep принимает доли секунды, а 0,001 с = 1 мс = 1 кГц = много времени. Однако в UNIX нет лазеек, когда дело доходит до файлов и процессов. try_wait выполняет очень мало.

$ cat &
[1] 16574
$ try_wait %1 && echo 'exited' || echo 'timeout'
timeout
$ kill %1
$ try_wait %1 && echo 'exited' || echo 'timeout'
exited

Мы должны ответить на некоторые сложные вопросы, чтобы идти дальше.

Почему параметр wait не имеет тайм-аута? Возможно, потому что команды timeout, kill -0, wait и wait -n могут точнее сказать машине, чего мы хотим.

Почему wait встроено в Bash, так что timeout wait PID не работает? Может быть, только так Bash может реализовать правильную обработку сигналов.

Рассматривать:

$ timeout 30s cat &
[1] 6680
$ jobs
[1]+    Running   timeout 30s cat &
$ kill -0 %1 && echo 'running'
running
$ # now meditate a bit and then...
$ kill -0 %1 && echo 'running' || echo 'vanished'
bash: kill: (NNN) - No such process
vanished

Будь то в материальном мире или в машинах, поскольку нам требуется некоторая почва для бега, нам также нужна некоторая почва для ожидания.

  • Когда kill не удается, вы вряд ли знаете почему. Если вы не написали процесс или его руководство не назвали обстоятельства, нет способа определить разумное значение тайм-аута.

  • Когда вы написали процесс, вы можете реализовать правильный обработчик TERM или даже ответить "Auf Wiedersehen!" отправь ему по именованной трубе. Тогда у вас есть основания даже для такого заклинания, как try_wait :-)

Ответ 6

app1 &
app2 &
sleep 60 &

wait -n