aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/main/java/com
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/com')
-rw-r--r--src/main/java/com/google/devtools/build/lib/buildtool/BuildTool.java16
-rw-r--r--src/main/java/com/google/devtools/build/lib/runtime/BlazeModule.java4
-rw-r--r--src/main/java/com/google/devtools/build/lib/runtime/BlazeRuntime.java6
-rw-r--r--src/main/java/com/google/devtools/build/lib/runtime/BlazeWorkspace.java10
-rw-r--r--src/main/java/com/google/devtools/build/lib/runtime/CommandEnvironment.java71
5 files changed, 93 insertions, 14 deletions
diff --git a/src/main/java/com/google/devtools/build/lib/buildtool/BuildTool.java b/src/main/java/com/google/devtools/build/lib/buildtool/BuildTool.java
index 974a63228c..afdde3c365 100644
--- a/src/main/java/com/google/devtools/build/lib/buildtool/BuildTool.java
+++ b/src/main/java/com/google/devtools/build/lib/buildtool/BuildTool.java
@@ -342,9 +342,19 @@ public final class BuildTool {
}
exitCode = e.getExitCode() != null ? e.getExitCode() : ExitCode.BUILD_FAILURE;
} catch (InterruptedException e) {
- exitCode = ExitCode.INTERRUPTED;
- env.getReporter().handle(Event.error("build interrupted"));
- env.getEventBus().post(new BuildInterruptedEvent());
+ // We may have been interrupted by an error, or the user's interruption may have raced with
+ // an error, so check to see if we should report that error code instead.
+ exitCode = env.getPendingExitCode();
+ if (exitCode == null) {
+ exitCode = ExitCode.INTERRUPTED;
+ env.getReporter().handle(Event.error("build interrupted"));
+ env.getEventBus().post(new BuildInterruptedEvent());
+ } else {
+ // Report the exception from the environment - the exception we're handling here is just an
+ // interruption.
+ reportExceptionError(env.getPendingException());
+ result.setCatastrophe();
+ }
} catch (TargetParsingException | LoadingFailedException | ViewCreationFailedException e) {
exitCode = ExitCode.PARSING_FAILURE;
reportExceptionError(e);
diff --git a/src/main/java/com/google/devtools/build/lib/runtime/BlazeModule.java b/src/main/java/com/google/devtools/build/lib/runtime/BlazeModule.java
index 126c1274b1..a2e71cc9a4 100644
--- a/src/main/java/com/google/devtools/build/lib/runtime/BlazeModule.java
+++ b/src/main/java/com/google/devtools/build/lib/runtime/BlazeModule.java
@@ -310,9 +310,7 @@ public abstract class BlazeModule {
throws NoSuchThingException, InterruptedException, IOException;
/**
- * Exits Blaze as early as possible. This is currently a hack and should only be called in
- * event handlers for {@code BuildStartingEvent}, {@code GotOptionsEvent} and
- * {@code LoadingPhaseCompleteEvent}.
+ * Exits Blaze as early as possible by sending an interrupt to the command's main thread.
*/
void exit(AbruptExitException exception);
}
diff --git a/src/main/java/com/google/devtools/build/lib/runtime/BlazeRuntime.java b/src/main/java/com/google/devtools/build/lib/runtime/BlazeRuntime.java
index 2b64b6ed80..e4fad28e57 100644
--- a/src/main/java/com/google/devtools/build/lib/runtime/BlazeRuntime.java
+++ b/src/main/java/com/google/devtools/build/lib/runtime/BlazeRuntime.java
@@ -234,6 +234,12 @@ public final class BlazeRuntime {
}
}
+ /**
+ * Initializes a CommandEnvironment to execute a command in this server.
+ *
+ * <p>This method should be called from the "main" thread on which the command will execute;
+ * that thread will receive interruptions if a module requests an early exit.
+ */
public CommandEnvironment initCommand() {
return workspace.initCommand();
}
diff --git a/src/main/java/com/google/devtools/build/lib/runtime/BlazeWorkspace.java b/src/main/java/com/google/devtools/build/lib/runtime/BlazeWorkspace.java
index f536e937a3..98c56c48c8 100644
--- a/src/main/java/com/google/devtools/build/lib/runtime/BlazeWorkspace.java
+++ b/src/main/java/com/google/devtools/build/lib/runtime/BlazeWorkspace.java
@@ -184,9 +184,15 @@ public final class BlazeWorkspace {
return lastExecutionRange;
}
+ /**
+ * Initializes a CommandEnvironment to execute a command in this workspace.
+ *
+ * <p>This method should be called from the "main" thread on which the command will execute;
+ * that thread will receive interruptions if a module requests an early exit.
+ */
public CommandEnvironment initCommand() {
- CommandEnvironment env
- = new CommandEnvironment(runtime, this, new EventBus(eventBusExceptionHandler));
+ CommandEnvironment env = new CommandEnvironment(
+ runtime, this, new EventBus(eventBusExceptionHandler), Thread.currentThread());
skyframeExecutor.setClientEnv(env.getClientEnv());
return env;
}
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;
}
}