aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/main/java/com/google/devtools/build/lib/runtime/CommandEnvironment.java
diff options
context:
space:
mode:
authorGravatar Michael Staib <mstaib@google.com>2016-10-04 21:26:37 +0000
committerGravatar Damien Martin-Guillerez <dmarting@google.com>2016-10-05 12:26:44 +0000
commit6e5e8fb01e536d46ce789bc8e4e0ca167651cb74 (patch)
treea991658be5aa986758216c22926f2990fb8f5dcd /src/main/java/com/google/devtools/build/lib/runtime/CommandEnvironment.java
parent930e89c2d2880f3342eea90e9b1160989f852ba6 (diff)
Enable Bazel commands to exit at any time.
This is the first step on a journey toward allowing commands to AbruptExit wherever they please, similar to how the user can press Ctrl+C at any time and we (should) bail out as fast as we can. By interrupting the command's main thread, we at least offer the command the ability to see that an error requiring a bail has happened, and it should trigger at potentially more locations, rather than just between phases. -- MOS_MIGRATED_REVID=135152330
Diffstat (limited to 'src/main/java/com/google/devtools/build/lib/runtime/CommandEnvironment.java')
-rw-r--r--src/main/java/com/google/devtools/build/lib/runtime/CommandEnvironment.java71
1 files changed, 65 insertions, 6 deletions
diff --git a/src/main/java/com/google/devtools/build/lib/runtime/CommandEnvironment.java b/src/main/java/com/google/devtools/build/lib/runtime/CommandEnvironment.java
index 1208995695..a68226d446 100644
--- a/src/main/java/com/google/devtools/build/lib/runtime/CommandEnvironment.java
+++ b/src/main/java/com/google/devtools/build/lib/runtime/CommandEnvironment.java
@@ -64,6 +64,7 @@ import java.util.TreeMap;
import java.util.TreeSet;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicReference;
+import javax.annotation.Nullable;
/**
* Encapsulates the state needed for a single command. The environment is dropped when the current
@@ -81,6 +82,7 @@ public final class CommandEnvironment {
private final Map<String, String> clientEnv = new TreeMap<>();
private final Set<String> visibleClientEnv = new TreeSet<>();
private final TimestampGranularityMonitor timestampGranularityMonitor;
+ private final Thread commandThread;
private String[] crashData;
@@ -104,17 +106,29 @@ public final class CommandEnvironment {
@Override
public void exit(AbruptExitException exception) {
- pendingException.compareAndSet(null, exception);
+ Preconditions.checkNotNull(exception);
+ Preconditions.checkNotNull(exception.getExitCode());
+ if (pendingException.compareAndSet(null, exception)) {
+ // There was no exception, so we're the first one to ask for an exit. Interrupt the command.
+ commandThread.interrupt();
+ }
}
}
- CommandEnvironment(BlazeRuntime runtime, BlazeWorkspace workspace, EventBus eventBus) {
+ /**
+ * Creates a new command environment which can be used for executing commands for the given
+ * runtime in the given workspace, which will publish events on the given eventBus. The
+ * commandThread passed is interrupted when a module requests an early exit.
+ */
+ CommandEnvironment(
+ BlazeRuntime runtime, BlazeWorkspace workspace, EventBus eventBus, Thread commandThread) {
this.runtime = runtime;
this.workspace = workspace;
this.directories = workspace.getDirectories();
this.commandId = null; // Will be set once we get the client environment
this.reporter = new Reporter();
this.eventBus = eventBus;
+ this.commandThread = commandThread;
this.blazeModuleEnvironment = new BlazeModuleEnvironment();
this.timestampGranularityMonitor = new TimestampGranularityMonitor(runtime.getClock());
// Record the command's starting time again, for use by
@@ -377,6 +391,29 @@ public final class CommandEnvironment {
}
/**
+ * Prevents any further interruption of this command by modules, and returns the final exit code
+ * from modules, or null if no modules requested an abrupt exit.
+ *
+ * <p>Always returns the same value on subsequent calls.
+ */
+ @Nullable
+ private ExitCode finalizeExitCode() {
+ // Set the pending exception so that further calls to exit(AbruptExitException) don't lead to
+ // unwanted thread interrupts.
+ if (pendingException.compareAndSet(null, new AbruptExitException(null))) {
+ return null;
+ }
+ if (Thread.currentThread() == commandThread) {
+ // We may have interrupted the thread in the process, so clear the interrupted bit.
+ // Whether the command was interrupted or not, it's about to be over, so don't interrupt later
+ // things happening on this thread.
+ Thread.interrupted();
+ }
+ // Extract the exit code (it can be null if someone has already called finalizeExitCode()).
+ return getPendingExitCode();
+ }
+
+ /**
* Hook method called by the BlazeCommandDispatcher right before the dispatch
* of each command ends (while its outcome can still be modified).
*/
@@ -384,14 +421,32 @@ public final class CommandEnvironment {
eventBus.post(new CommandPrecompleteEvent(originalExit));
// If Blaze did not suffer an infrastructure failure, check for errors in modules.
ExitCode exitCode = originalExit;
- AbruptExitException exception = pendingException.get();
- if (!originalExit.isInfrastructureFailure() && exception != null) {
- exitCode = exception.getExitCode();
+ ExitCode newExitCode = finalizeExitCode();
+ if (!originalExit.isInfrastructureFailure() && newExitCode != null) {
+ exitCode = newExitCode;
}
return exitCode;
}
/**
+ * Returns the current exit code requested by modules, or null if no exit has been requested.
+ */
+ @Nullable
+ public ExitCode getPendingExitCode() {
+ AbruptExitException exception = getPendingException();
+ return exception == null ? null : exception.getExitCode();
+ }
+
+ /**
+ * Retrieves the exception currently queued by a Blaze module.
+ *
+ * <p>Prefer getPendingExitCode or throwPendingException where appropriate.
+ */
+ public AbruptExitException getPendingException() {
+ return pendingException.get();
+ }
+
+ /**
* Throws the exception currently queued by a Blaze module.
*
* <p>This should be called as often as is practical so that errors are reported as soon as
@@ -399,8 +454,12 @@ public final class CommandEnvironment {
* the exception this way.
*/
public void throwPendingException() throws AbruptExitException {
- AbruptExitException exception = pendingException.get();
+ AbruptExitException exception = getPendingException();
if (exception != null) {
+ if (Thread.currentThread() == commandThread) {
+ // Throwing this exception counts as the requested interruption. Clear the interrupted bit.
+ Thread.interrupted();
+ }
throw exception;
}
}