| // Copyright 2014 The Bazel Authors. All rights reserved. | |
| // | |
| // Licensed under the Apache License, Version 2.0 (the "License"); | |
| // you may not use this file except in compliance with the License. | |
| // You may obtain a copy of the License at | |
| // | |
| // http://www.apache.org/licenses/LICENSE-2.0 | |
| // | |
| // Unless required by applicable law or agreed to in writing, software | |
| // distributed under the License is distributed on an "AS IS" BASIS, | |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| // See the License for the specific language governing permissions and | |
| // limitations under the License. | |
| package com.google.devtools.build.lib.shell; | |
| import java.time.Duration; | |
| import java.util.Optional; | |
| /** | |
| * Represents the termination status of a command. {@link Process#waitFor} is | |
| * not very precisely specified, so this class encapsulates the interpretation | |
| * of values returned by it. | |
| * | |
| * Caveat: due to the lossy encoding, it's not always possible to accurately | |
| * distinguish signal and exit cases. In particular, processes that exit with | |
| * a value within the interval [129, 191] will be mistaken for having been | |
| * terminated by a signal. | |
| * | |
| * Instances are immutable. | |
| */ | |
| public final class TerminationStatus { | |
| private final int waitResult; | |
| private final boolean timedOut; | |
| private final Optional<Duration> wallExecutionTime; | |
| private final Optional<Duration> userExecutionTime; | |
| private final Optional<Duration> systemExecutionTime; | |
| /** | |
| * Values taken from the glibc strsignal(3) function. | |
| */ | |
| private static final String[] SIGNAL_STRINGS = { | |
| null, | |
| "Hangup", | |
| "Interrupt", | |
| "Quit", | |
| "Illegal instruction", | |
| "Trace/breakpoint trap", | |
| "Aborted", | |
| "Bus error", | |
| "Floating point exception", | |
| "Killed", | |
| "User defined signal 1", | |
| "Segmentation fault", | |
| "User defined signal 2", | |
| "Broken pipe", | |
| "Alarm clock", | |
| "Terminated", | |
| "Stack fault", | |
| "Child exited", | |
| "Continued", | |
| "Stopped (signal)", | |
| "Stopped", | |
| "Stopped (tty input)", | |
| "Stopped (tty output)", | |
| "Urgent I/O condition", | |
| "CPU time limit exceeded", | |
| "File size limit exceeded", | |
| "Virtual timer expired", | |
| "Profiling timer expired", | |
| "Window changed", | |
| "I/O possible", | |
| "Power failure", | |
| "Bad system call", | |
| }; | |
| private static String getSignalString(int signum) { | |
| return signum > 0 && signum < SIGNAL_STRINGS.length | |
| ? SIGNAL_STRINGS[signum] | |
| : "Signal " + signum; | |
| } | |
| /** | |
| * Construct a TerminationStatus instance from a Process waitFor code. | |
| * | |
| * @param waitResult the value returned by {@link java.lang.Process#waitFor}. | |
| * @param timedOut whether the execution timed out | |
| */ | |
| public TerminationStatus(int waitResult, boolean timedOut) { | |
| this(waitResult, timedOut, Optional.empty(), Optional.empty(), Optional.empty()); | |
| } | |
| /** | |
| * Construct a TerminationStatus instance from a Process waitFor code. | |
| * | |
| * <p>TerminationStatus objects are considered equal if they have the same waitResult. | |
| * | |
| * @param waitResult the value returned by {@link java.lang.Process#waitFor}. | |
| * @param timedOut whether the execution timed out | |
| * @param wallExecutionTime the wall execution time of the command, if available | |
| * @param userExecutionTime the user execution time of the command, if available | |
| * @param systemExecutionTime the system execution time of the command, if available | |
| */ | |
| public TerminationStatus( | |
| int waitResult, | |
| boolean timedOut, | |
| Optional<Duration> wallExecutionTime, | |
| Optional<Duration> userExecutionTime, | |
| Optional<Duration> systemExecutionTime) { | |
| this.waitResult = waitResult; | |
| this.timedOut = timedOut; | |
| this.wallExecutionTime = wallExecutionTime; | |
| this.userExecutionTime = userExecutionTime; | |
| this.systemExecutionTime = systemExecutionTime; | |
| } | |
| /** | |
| * Returns the exit code returned by the subprocess. | |
| */ | |
| public int getRawExitCode() { | |
| return waitResult; | |
| } | |
| /** | |
| * Returns true iff the process exited with code 0. | |
| */ | |
| public boolean success() { | |
| return exited() && getExitCode() == 0; | |
| } | |
| // We're relying on undocumented behaviour of Process.waitFor, specifically | |
| // that waitResult is the exit status when the process returns normally, or | |
| // 128+signalnumber when the process is terminated by a signal. We further | |
| // assume that value signal numbers fall in the interval [1, 63]. | |
| private static final int SIGNAL_1 = 128 + 1; | |
| private static final int SIGNAL_63 = 128 + 63; | |
| /** | |
| * Returns true iff the process exited normally. | |
| */ | |
| public boolean exited() { | |
| return !timedOut && (waitResult < SIGNAL_1 || waitResult > SIGNAL_63); | |
| } | |
| /** Returns true if the process timed out. */ | |
| public boolean timedOut() { | |
| return timedOut; | |
| } | |
| /** | |
| * Returns the exit code of the subprocess. Undefined if exited() is false. | |
| */ | |
| public int getExitCode() { | |
| if (!exited()) { | |
| throw new IllegalStateException("getExitCode() not defined"); | |
| } | |
| return waitResult; | |
| } | |
| /** | |
| * Returns the number of the signal that terminated the process. Undefined | |
| * if exited() returns true. | |
| */ | |
| public int getTerminatingSignal() { | |
| if (exited() || timedOut) { | |
| throw new IllegalStateException("getTerminatingSignal() not defined"); | |
| } | |
| return waitResult - SIGNAL_1 + 1; | |
| } | |
| /** | |
| * Returns the wall execution time. | |
| * | |
| * @return the measurement, or empty in case of execution errors or when the measurement is not | |
| * implemented for the current platform | |
| */ | |
| public Optional<Duration> getWallExecutionTime() { | |
| return wallExecutionTime; | |
| } | |
| /** | |
| * Returns the user execution time. | |
| * | |
| * @return the measurement, or empty in case of execution errors or when the measurement is not | |
| * implemented for the current platform | |
| */ | |
| public Optional<Duration> getUserExecutionTime() { | |
| return userExecutionTime; | |
| } | |
| /** | |
| * Returns the system execution time. | |
| * | |
| * @return the measurement, or empty in case of execution errors or when the measurement is not | |
| * implemented for the current platform | |
| */ | |
| public Optional<Duration> getSystemExecutionTime() { | |
| return systemExecutionTime; | |
| } | |
| /** | |
| * Returns a short string describing the termination status. | |
| * e.g. "Exit 1" or "Hangup". | |
| */ | |
| public String toShortString() { | |
| return exited() | |
| ? "Exit " + getExitCode() | |
| : (timedOut ? "Timeout" : getSignalString(getTerminatingSignal())); | |
| } | |
| @Override | |
| public String toString() { | |
| if (exited()) { | |
| return "Process exited with status " + getExitCode(); | |
| } else if (timedOut) { | |
| return "Timed out"; | |
| } else { | |
| return "Process terminated by signal " + getTerminatingSignal(); | |
| } | |
| } | |
| @Override | |
| public int hashCode() { | |
| return waitResult; | |
| } | |
| @Override | |
| public boolean equals(Object other) { | |
| return other instanceof TerminationStatus | |
| && ((TerminationStatus) other).waitResult == this.waitResult; | |
| } | |
| /** Returns a new {@link TerminationStatus.Builder}. */ | |
| public static Builder builder() { | |
| return new TerminationStatus.Builder(); | |
| } | |
| /** Builder for {@link TerminationStatus} objects. */ | |
| public static class Builder { | |
| // We use nullness here instead of Optional to avoid confusion between fields that may not | |
| // yet have been set from fields that can legitimately hold an Optional value in the built | |
| // object. | |
| private Integer waitResponse = null; | |
| private Boolean timedOut = null; | |
| private Optional<Duration> wallExecutionTime = Optional.empty(); | |
| private Optional<Duration> userExecutionTime = Optional.empty(); | |
| private Optional<Duration> systemExecutionTime = Optional.empty(); | |
| /** Sets the value returned by {@link java.lang.Process#waitFor}. */ | |
| public Builder setWaitResponse(int waitResponse) { | |
| this.waitResponse = waitResponse; | |
| return this; | |
| } | |
| /** Sets whether the action timed out or not. */ | |
| public Builder setTimedOut(boolean timedOut) { | |
| this.timedOut = timedOut; | |
| return this; | |
| } | |
| /** Sets the wall execution time. */ | |
| public Builder setWallExecutionTime(Duration wallExecutionTime) { | |
| this.wallExecutionTime = Optional.of(wallExecutionTime); | |
| return this; | |
| } | |
| /** Sets the user execution time. */ | |
| public Builder setUserExecutionTime(Duration userExecutionTime) { | |
| this.userExecutionTime = Optional.of(userExecutionTime); | |
| return this; | |
| } | |
| /** Sets the system execution time. */ | |
| public Builder setSystemExecutionTime(Duration systemExecutionTime) { | |
| this.systemExecutionTime = Optional.of(systemExecutionTime); | |
| return this; | |
| } | |
| /** Sets or clears the wall execution time. */ | |
| public Builder setWallExecutionTime(Optional<Duration> wallExecutionTime) { | |
| this.wallExecutionTime = wallExecutionTime; | |
| return this; | |
| } | |
| /** Sets or clears the user execution time. */ | |
| public Builder setUserExecutionTime(Optional<Duration> userExecutionTime) { | |
| this.userExecutionTime = userExecutionTime; | |
| return this; | |
| } | |
| /** Sets or clears the system execution time. */ | |
| public Builder setSystemExecutionTime(Optional<Duration> systemExecutionTime) { | |
| this.systemExecutionTime = systemExecutionTime; | |
| return this; | |
| } | |
| /** Builds a {@link TerminationStatus} object. */ | |
| public TerminationStatus build() { | |
| if (waitResponse == null) { | |
| throw new IllegalStateException("waitResponse never set"); | |
| } | |
| if (timedOut == null) { | |
| throw new IllegalStateException("timedOut never set"); | |
| } | |
| return new TerminationStatus( | |
| waitResponse, timedOut, wallExecutionTime, userExecutionTime, systemExecutionTime); | |
| } | |
| } | |
| } |