blob: 921fc801fe36667bd47419c41718d06c70dfa141 [file] [log] [blame]
James Lemieux2a2559e2018-01-28 02:56:27 -08001#!/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
14set -euo pipefail
15
16# Prints a message and terminates the process.
17function 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.
26function 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.
43function 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).
Colin Cross44058c12020-01-10 15:54:18 -080053 setsid -w java "$@" & pid="$!"; wait "$pid"
James Lemieux2a2559e2018-01-28 02:56:27 -080054}
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.
59function 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
Elliott Hughes060e46f2018-10-26 15:43:42 -070076 # Wrap the command with the standard timeout(1) if available.
77 # "runalarm" is a Google timeout clone, and Mac users who've installed
78 # GNU coreutils have timeout available as "gtimeout".
79 if type timeout > /dev/null 2>&1 ; then
80 timeout "${timeout_secs}" "$0" 'execute' "$@"
81 elif type runalarm > /dev/null 2>&1 ; then
James Lemieux2a2559e2018-01-28 02:56:27 -080082 runalarm -t "$timeout_secs" "$0" 'execute' "$@"
83 elif type gtimeout > /dev/null 2>&1 ; then
84 gtimeout "${timeout_secs}s" "$0" 'execute' "$@"
85 else
86 # No way to set a timeout available, just execute directly.
87 echo "Warning: unable to enforce timeout." 1>&2
88 java "$@"
89 fi
90 fi
91}
92
93
94main "$@"