How to kill a child process after a given timeout in Bash?

前端 未结 8 1727
青春惊慌失措
青春惊慌失措 2020-11-22 15:24

I have a bash script that launches a child process that crashes (actually, hangs) from time to time and with no apparent reason (closed source, so there isn\'t much I can do

相关标签:
8条回答
  • 2020-11-22 15:34

    Assuming you have (or can easily make) a pid file for tracking the child's pid, you could then create a script that checks the modtime of the pid file and kills/respawns the process as needed. Then just put the script in crontab to run at approximately the period you need.

    Let me know if you need more details. If that doesn't sound like it'd suit your needs, what about upstart?

    0 讨论(0)
  • 2020-11-22 15:39

    (As seen in: BASH FAQ entry #68: "How do I run a command, and have it abort (timeout) after N seconds?")

    If you don't mind downloading something, use timeout (sudo apt-get install timeout) and use it like: (most Systems have it already installed otherwise use sudo apt-get install coreutils)

    timeout 10 ping www.goooooogle.com
    

    If you don't want to download something, do what timeout does internally:

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

    In case that you want to do a timeout for longer bash code, use the second option as such:

    ( cmdpid=$BASHPID; 
        (sleep 10; kill $cmdpid) \
       & while ! ping -w 1 www.goooooogle.com 
         do 
             echo crap; 
         done )
    
    0 讨论(0)
  • 2020-11-22 15:43
    # Spawn a child process:
    (dosmth) & pid=$!
    # in the background, sleep for 10 secs then kill that process
    (sleep 10 && kill -9 $pid) &
    

    or to get the exit codes as well:

    # 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=$?
    
    0 讨论(0)
  • 2020-11-22 15:43
    sleep 999&
    t=$!
    sleep 10
    kill $t
    
    0 讨论(0)
  • 2020-11-22 15:46

    One way is to run the program in a subshell, and communicate with the subshell through a named pipe with the read command. This way you can check the exit status of the process being run and communicate this back through the pipe.

    Here's an example of timing out the yes command after 3 seconds. It gets the PID of the process using pgrep (possibly only works on Linux). There is also some problem with using a pipe in that a process opening a pipe for read will hang until it is also opened for write, and vice versa. So to prevent the read command hanging, I've "wedged" open the pipe for read with a background subshell. (Another way to prevent a freeze to open the pipe read-write, i.e. read -t 5 <>finished.pipe - however, that also may not work except with 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
    
    0 讨论(0)
  • 2020-11-22 15:47

    Here's an attempt which tries to avoid killing a process after it has already exited, which reduces the chance of killing another process with the same process ID (although it's probably impossible to avoid this kind of error completely).

    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
      )
    }
    

    Use like run_with_timeout 3 sleep 10000, which runs sleep 10000 but ends it after 3 seconds.

    This is like other answers which use a background timeout process to kill the child process after a delay. I think this is almost the same as Dan's extended answer (https://stackoverflow.com/a/5161274/1351983), except the timeout shell will not be killed if it has already ended.

    After this program has ended, there will still be a few lingering "sleep" processes running, but they should be harmless.

    This may be a better solution than my other answer because it does not use the non-portable shell feature read -t and does not use pgrep.

    0 讨论(0)
提交回复
热议问题