Implement timeouts on Windows.
Makes #1664 much less acute.
--
MOS_MIGRATED_REVID=130750731
diff --git a/src/main/java/com/google/devtools/build/lib/shell/Command.java b/src/main/java/com/google/devtools/build/lib/shell/Command.java
index 79b850a..9f35d69 100644
--- a/src/main/java/com/google/devtools/build/lib/shell/Command.java
+++ b/src/main/java/com/google/devtools/build/lib/shell/Command.java
@@ -163,6 +163,16 @@
}
/**
+ * Just like {@link Command(String, Map<String, String>, File, long), but without a timeout.
+ */
+ public Command(
+ String[] commandLineElements,
+ Map<String, String> environmentVariables,
+ File workingDirectory) {
+ this(commandLineElements, environmentVariables, workingDirectory, -1);
+ }
+
+ /**
* Creates a new {@link Command} for the given command line elements. The
* command line is executed without a shell.
*
@@ -180,15 +190,17 @@
*
* @param commandLineElements elements of raw command line to execute
* @param environmentVariables environment variables to replace JVM's
- * environment variables; may be null
+ * environment variables; may be null
* @param workingDirectory working directory for execution; if null, current
- * working directory is used
+ * working directory is used
+ * @param timeoutMillis timeout in milliseconds. Only supported on Windows.
* @throws IllegalArgumentException if commandLine is null or empty
*/
public Command(
String[] commandLineElements,
- final Map<String, String> environmentVariables,
- final File workingDirectory) {
+ Map<String, String> environmentVariables,
+ File workingDirectory,
+ long timeoutMillis) {
if (commandLineElements == null || commandLineElements.length == 0) {
throw new IllegalArgumentException("command line is null or empty");
}
@@ -203,6 +215,7 @@
subprocessBuilder.setArgv(ImmutableList.copyOf(commandLineElements));
subprocessBuilder.setEnv(environmentVariables);
subprocessBuilder.setWorkingDirectory(workingDirectory);
+ subprocessBuilder.setTimeoutMillis(timeoutMillis);
}
/**
@@ -676,7 +689,7 @@
final Consumers.OutErrConsumers outErrConsumers,
final boolean killSubprocessOnInterrupt,
final boolean closeOutputStreams)
- throws CommandException {
+ throws CommandException {
logCommand();
@@ -872,7 +885,7 @@
while (true) {
try {
process.waitFor();
- return new TerminationStatus(process.exitValue());
+ return new TerminationStatus(process.exitValue(), process.timedout());
} catch (InterruptedException ie) {
wasInterrupted = true;
if (killSubprocessOnInterrupt) {
diff --git a/src/main/java/com/google/devtools/build/lib/shell/JavaSubprocessFactory.java b/src/main/java/com/google/devtools/build/lib/shell/JavaSubprocessFactory.java
index 7c85cec..5218490 100644
--- a/src/main/java/com/google/devtools/build/lib/shell/JavaSubprocessFactory.java
+++ b/src/main/java/com/google/devtools/build/lib/shell/JavaSubprocessFactory.java
@@ -60,6 +60,12 @@
}
@Override
+ public boolean timedout() {
+ // Not supported.
+ return false;
+ }
+
+ @Override
public void waitFor() throws InterruptedException {
process.waitFor();
}
@@ -93,6 +99,9 @@
@Override
public Subprocess create(SubprocessBuilder params) throws IOException {
+ if (params.getTimeoutMillis() >= 0) {
+ throw new UnsupportedOperationException("Timeouts are not supported");
+ }
ProcessBuilder builder = new ProcessBuilder();
builder.command(params.getArgv());
if (params.getEnv() != null) {
diff --git a/src/main/java/com/google/devtools/build/lib/shell/Subprocess.java b/src/main/java/com/google/devtools/build/lib/shell/Subprocess.java
index e5710e7..769b6b5 100644
--- a/src/main/java/com/google/devtools/build/lib/shell/Subprocess.java
+++ b/src/main/java/com/google/devtools/build/lib/shell/Subprocess.java
@@ -53,6 +53,11 @@
boolean finished();
/**
+ * Returns if the process timed out.
+ */
+ boolean timedout();
+
+ /**
* Waits for the process to finish.
*/
void waitFor() throws InterruptedException;
diff --git a/src/main/java/com/google/devtools/build/lib/shell/SubprocessBuilder.java b/src/main/java/com/google/devtools/build/lib/shell/SubprocessBuilder.java
index b89cf96..0b70e05 100644
--- a/src/main/java/com/google/devtools/build/lib/shell/SubprocessBuilder.java
+++ b/src/main/java/com/google/devtools/build/lib/shell/SubprocessBuilder.java
@@ -45,6 +45,7 @@
private StreamAction stderrAction;
private File stderrFile;
private File workingDirectory;
+ private long timeoutMillis = -1;
private static Subprocess.Factory factory = JavaSubprocessFactory.INSTANCE;
@@ -115,6 +116,15 @@
return this;
}
+ public SubprocessBuilder setTimeoutMillis(long timeoutMillis) {
+ this.timeoutMillis = timeoutMillis;
+ return this;
+ }
+
+ public long getTimeoutMillis() {
+ return timeoutMillis;
+ }
+
public StreamAction getStderr() {
return stderrAction;
}
diff --git a/src/main/java/com/google/devtools/build/lib/shell/TerminationStatus.java b/src/main/java/com/google/devtools/build/lib/shell/TerminationStatus.java
index c8ee015..2f75b5e 100644
--- a/src/main/java/com/google/devtools/build/lib/shell/TerminationStatus.java
+++ b/src/main/java/com/google/devtools/build/lib/shell/TerminationStatus.java
@@ -29,6 +29,7 @@
public final class TerminationStatus {
private final int waitResult;
+ private final boolean timedout;
/**
* Values taken from the glibc strsignal(3) function.
@@ -79,8 +80,9 @@
*
* @param waitResult the value returned by {@link java.lang.Process#waitFor}.
*/
- public TerminationStatus(int waitResult) {
+ public TerminationStatus(int waitResult, boolean timedout) {
this.waitResult = waitResult;
+ this.timedout = timedout;
}
/**
@@ -110,7 +112,14 @@
* Returns true iff the process exited normally.
*/
public boolean exited() {
- return waitResult < SIGNAL_1 || waitResult > SIGNAL_63;
+ return !timedout && (waitResult < SIGNAL_1 || waitResult > SIGNAL_63);
+ }
+
+ /**
+ * Returns true if the process timed out.
+ */
+ public boolean timedout() {
+ return timedout;
}
/**
@@ -128,7 +137,7 @@
* if exited() returns true.
*/
public int getTerminatingSignal() {
- if (exited()) {
+ if (exited() || timedout) {
throw new IllegalStateException("getTerminatingSignal() not defined");
}
return waitResult - SIGNAL_1 + 1;
@@ -139,16 +148,20 @@
* e.g. "Exit 1" or "Hangup".
*/
public String toShortString() {
- return exited()
- ? ("Exit " + getExitCode())
- : (getSignalString(getTerminatingSignal()));
+ return exited() ? "Exit " + getExitCode()
+ : timedout ? "Timeout"
+ : getSignalString(getTerminatingSignal());
}
@Override
public String toString() {
- return exited()
- ? ("Process exited with status " + getExitCode())
- : ("Process terminated by signal " + getTerminatingSignal());
+ if (exited()) {
+ return "Process exited with status " + getExitCode();
+ } else if (timedout) {
+ return "Timed out";
+ } else {
+ return "Process terminated by signal " + getTerminatingSignal();
+ }
}
@Override