Home | History | Annotate | Download | only in 3.4.2
      1 #!/bin/bash
      2 #
      3 # Script for running java with a timeout.
      4 #
      5 # The timeout in seconds must be the first argument.  The rest of the arguments
      6 # are passed to the java binary itself.
      7 #
      8 # For example:
      9 #     java-timeout 120 -cp classes.jar org.junit.runner.JUnitCore
     10 # runs:
     11 #     java -cp classes.jar org.junit.runner.JUnitCore
     12 # with a timeout of 2 minutes.
     13 
     14 set -euo pipefail
     15 
     16 # Prints a message and terminates the process.
     17 function fatal() {
     18   echo "FATAL: $*"
     19   exit 113
     20 }
     21 
     22 # Function that is invoked if java is terminated due to timeout.
     23 # It take the process ID of the java command as an argument if it has already
     24 # been started, or the empty string if not. It should very rarely receive the
     25 # empty string as the pid, but it is possible.
     26 function on_timeout() {
     27   echo 'FATAL: command timed out'
     28 
     29   local pid="${1-}"
     30   shift || fatal '[on_timeout] missing argument: pid'
     31   test $# = 0 || fatal '[on_timeout] too many arguments'
     32 
     33   if [ "$pid" != '' ]; then
     34     # It is possible that the process already terminated, but there is not much
     35     # we can do about that.
     36     kill -TERM -- "-$pid"  # Kill the entire process group.
     37   fi
     38 }
     39 
     40 # Executes java with the given argument, waiting for a termination signal from
     41 # runalarm which this script is running under. The arguments are passed to the
     42 # java binary itself.
     43 function execute() {
     44   # Trap SIGTERM, which is what we will receive if runalarm interrupts us.
     45   local pid  # Set below after we run the process.
     46   trap 'on_timeout $pid' SIGTERM
     47   # Starts java within a new process group and saves it process ID before
     48   # blocking waiting for it to complete. 'setsid' starts the process within a
     49   # new process group, which means that it will not be killed when this shell
     50   # command is killed. This is needed so that the signal handler in the trap
     51   # command above to be invoked before the java command is terminated (and will
     52   # in fact have to terminate it itself).
     53   setsid java "$@" & pid="$!"; wait "$pid"
     54 }
     55 
     56 # Runs java with a timeout. The first argument is either the timeout in seconds
     57 # or the string 'execute', which is used internally to execute the command under
     58 # runalarm.
     59 function main() {
     60   local timeout_secs="${1-}"
     61   shift || fatal '[main]: missing argument: timeout_secs'
     62   # The reset of the arguments are meant for the java binary itself.
     63 
     64   if [[ $timeout_secs = '0' ]]; then
     65     # Run without any timeout.
     66     java "$@"
     67   elif [[ $timeout_secs = 'execute' ]]; then
     68     # This means we actually have to execute the command.
     69     execute "$@"
     70   elif (( timeout_secs < 30 )); then
     71     # We want to have a timeout of at least 30 seconds, so that we are
     72     # guaranteed to be able to start the java command in the subshell. This also
     73     # catches non-numeric arguments.
     74     fatal 'Must specify a timeout of at least 30 seconds.'
     75   else
     76     # Wrap the command with runalarm if available. If runalarm is not
     77     # installed try timeout which is available on Mac if GNU coreutils
     78     # is installed.
     79     if type runalarm > /dev/null 2>&1 ; then
     80       runalarm -t "$timeout_secs" "$0" 'execute' "$@"
     81     elif type gtimeout > /dev/null 2>&1 ; then
     82       gtimeout "${timeout_secs}s" "$0" 'execute' "$@"
     83     else
     84       # No way to set a timeout available, just execute directly.
     85       echo "Warning: unable to enforce timeout." 1>&2
     86       java "$@"
     87     fi
     88   fi
     89 }
     90 
     91 
     92 main "$@"
     93