linux Bash: espera con tiempo de espera




bash tutorial (4)

Aquí hay una versión simplificada de la respuesta de Aaron Digulla, que usa el truco kill -0 que Aaron Digulla deja en un comentario:

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

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

En mi caso, quería estar set -e -x safe y devolver el código de estado, así que utilicé:

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

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

exit $status

Un estado de salida de 143 indica SIGTERM, casi seguro desde nuestro tiempo de espera.

En un script Bash, me gustaría hacer algo como:

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

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

Es decir, lanza dos aplicaciones en segundo plano y dales 60 segundos para completar su trabajo. Entonces, si no terminan dentro de ese intervalo, mátalos.

Lamentablemente, lo anterior no funciona, ya que el timeout es un ejecutable, mientras que wait es un comando de shell. Traté de cambiarlo a:

timeout 60 bash -c wait $pidApp1 $pidApp2

Pero esto todavía no funciona, ya wait solo se puede wait en un PID lanzado dentro del mismo shell.

¿Algunas ideas?


Answer #1

Escriba los PID en los archivos e inicie las aplicaciones de esta manera:

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

wait $pid
kill $killerPid

Eso crearía otro proceso que duerme por el tiempo de espera y mata el proceso si no se ha completado hasta el momento.

Si el proceso se completa más rápido, el archivo PID se elimina y el proceso asesino finaliza.

killChildrenOf es una secuencia de comandos que recupera todos los procesos y elimina todos los elementos killChildrenOf de un determinado PID. Consulte las respuestas de esta pregunta para ver las diferentes formas de implementar esta funcionalidad: la mejor forma de eliminar todos los procesos secundarios

Si quiere salir de BASH, puede escribir los PID y los tiempos de espera en un directorio y mirar ese directorio. Cada minuto más o menos, lea las entradas y verifique los procesos que aún existen y si han expirado.

EDITAR Si desea saber si el proceso ha fallado con éxito, puede usar kill -0 $pid

EDIT2 O puede intentar grupos de procesos. kevinarpe dijo: Para obtener PGID para un PID (146322):

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

En mi caso: 145974. Entonces PGID se puede utilizar con una opción especial de kill para terminar todos los procesos en un grupo: kill -- -145974


Answer #2

Tanto su ejemplo como la respuesta aceptada son demasiado complicados, ¿por qué no solo utiliza el timeout ya que ese es exactamente su caso de uso? El comando timeout incluso tiene una opción incorporada ( -k ) para enviar SIGKILL después de enviar la señal inicial para terminar el comando ( SIGTERM por defecto) si el comando todavía se está ejecutando después de enviar la señal inicial (ver man timeout ).

Si el script no requiere necesariamente wait y reanudar el flujo de control después de esperar, es simplemente una cuestión de

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

Si lo hace, sin embargo, es igual de fácil guardando los PID de timeout lugar:

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

P.ej

$ 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

Answer #3

Escribí una función bash que esperará hasta que finalicen los PID o hasta que se agote el tiempo de espera, que vuelva a ser distinto de cero si se excede el tiempo de espera e imprime todos los PID que no terminen.

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
}

Para usar esto, solo wait_timeout $timeout $PID1 $PID2 ...





shell