95 lines
3.4 KiB
Bash
Executable File
95 lines
3.4 KiB
Bash
Executable File
#!/bin/bash
|
|
#
|
|
# Script for running java with a timeout.
|
|
#
|
|
# The timeout in seconds must be the first argument. The rest of the arguments
|
|
# are passed to the java binary itself.
|
|
#
|
|
# For example:
|
|
# java-timeout 120 -cp classes.jar org.junit.runner.JUnitCore
|
|
# runs:
|
|
# java -cp classes.jar org.junit.runner.JUnitCore
|
|
# with a timeout of 2 minutes.
|
|
|
|
set -euo pipefail
|
|
|
|
# Prints a message and terminates the process.
|
|
function fatal() {
|
|
echo "FATAL: $*"
|
|
exit 113
|
|
}
|
|
|
|
# Function that is invoked if java is terminated due to timeout.
|
|
# It take the process ID of the java command as an argument if it has already
|
|
# been started, or the empty string if not. It should very rarely receive the
|
|
# empty string as the pid, but it is possible.
|
|
function on_timeout() {
|
|
echo 'FATAL: command timed out'
|
|
|
|
local pid="${1-}"
|
|
shift || fatal '[on_timeout] missing argument: pid'
|
|
test $# = 0 || fatal '[on_timeout] too many arguments'
|
|
|
|
if [ "$pid" != '' ]; then
|
|
# It is possible that the process already terminated, but there is not much
|
|
# we can do about that.
|
|
kill -TERM -- "-$pid" # Kill the entire process group.
|
|
fi
|
|
}
|
|
|
|
# Executes java with the given argument, waiting for a termination signal from
|
|
# runalarm which this script is running under. The arguments are passed to the
|
|
# java binary itself.
|
|
function execute() {
|
|
# Trap SIGTERM, which is what we will receive if runalarm interrupts us.
|
|
local pid # Set below after we run the process.
|
|
trap 'on_timeout $pid' SIGTERM
|
|
# Starts java within a new process group and saves it process ID before
|
|
# blocking waiting for it to complete. 'setsid' starts the process within a
|
|
# new process group, which means that it will not be killed when this shell
|
|
# command is killed. This is needed so that the signal handler in the trap
|
|
# command above to be invoked before the java command is terminated (and will
|
|
# in fact have to terminate it itself).
|
|
setsid -w java "$@" & pid="$!"; wait "$pid"
|
|
}
|
|
|
|
# Runs java with a timeout. The first argument is either the timeout in seconds
|
|
# or the string 'execute', which is used internally to execute the command under
|
|
# runalarm.
|
|
function main() {
|
|
local timeout_secs="${1-}"
|
|
shift || fatal '[main]: missing argument: timeout_secs'
|
|
# The reset of the arguments are meant for the java binary itself.
|
|
|
|
if [[ $timeout_secs = '0' ]]; then
|
|
# Run without any timeout.
|
|
java "$@"
|
|
elif [[ $timeout_secs = 'execute' ]]; then
|
|
# This means we actually have to execute the command.
|
|
execute "$@"
|
|
elif (( timeout_secs < 30 )); then
|
|
# We want to have a timeout of at least 30 seconds, so that we are
|
|
# guaranteed to be able to start the java command in the subshell. This also
|
|
# catches non-numeric arguments.
|
|
fatal 'Must specify a timeout of at least 30 seconds.'
|
|
else
|
|
# Wrap the command with the standard timeout(1) if available.
|
|
# "runalarm" is a Google timeout clone, and Mac users who've installed
|
|
# GNU coreutils have timeout available as "gtimeout".
|
|
if type timeout > /dev/null 2>&1 ; then
|
|
timeout "${timeout_secs}" "$0" 'execute' "$@"
|
|
elif type runalarm > /dev/null 2>&1 ; then
|
|
runalarm -t "$timeout_secs" "$0" 'execute' "$@"
|
|
elif type gtimeout > /dev/null 2>&1 ; then
|
|
gtimeout "${timeout_secs}s" "$0" 'execute' "$@"
|
|
else
|
|
# No way to set a timeout available, just execute directly.
|
|
echo "Warning: unable to enforce timeout." 1>&2
|
|
java "$@"
|
|
fi
|
|
fi
|
|
}
|
|
|
|
|
|
main "$@"
|