From 01e0d2398bd029e38d89b7759495bfda47f9676c Mon Sep 17 00:00:00 2001 From: mschaller Date: Tue, 19 May 2020 14:39:43 -0700 Subject: [PATCH] Encode CleanCommand failures with FailureDetails RELNOTES: None. PiperOrigin-RevId: 312355942 --- .../build/lib/runtime/BlazeWorkspace.java | 4 +- .../lib/runtime/commands/CleanCommand.java | 95 ++++++++++++++----- src/main/protobuf/failure_details.proto | 19 ++++ 3 files changed, 93 insertions(+), 25 deletions(-) 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 3007d8edfc1341..7421a9f8f321c3 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 @@ -221,9 +221,7 @@ public void resetEvaluator() { skyframeExecutor.resetEvaluator(); } - /** - * Removes in-memory caches. - */ + /** Removes in-memory and on-disk action caches. */ public void clearCaches() throws IOException { if (actionCache != null) { actionCache.clear(); diff --git a/src/main/java/com/google/devtools/build/lib/runtime/commands/CleanCommand.java b/src/main/java/com/google/devtools/build/lib/runtime/commands/CleanCommand.java index edb7c540eadefd..ecb191541de7bc 100644 --- a/src/main/java/com/google/devtools/build/lib/runtime/commands/CleanCommand.java +++ b/src/main/java/com/google/devtools/build/lib/runtime/commands/CleanCommand.java @@ -14,6 +14,7 @@ package com.google.devtools.build.lib.runtime.commands; import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Strings; import com.google.common.flogger.GoogleLogger; import com.google.devtools.build.lib.actions.ExecException; import com.google.devtools.build.lib.analysis.NoBuildEvent; @@ -25,9 +26,12 @@ import com.google.devtools.build.lib.runtime.BlazeRuntime; import com.google.devtools.build.lib.runtime.Command; import com.google.devtools.build.lib.runtime.CommandEnvironment; +import com.google.devtools.build.lib.server.FailureDetails; +import com.google.devtools.build.lib.server.FailureDetails.CleanCommand.Code; +import com.google.devtools.build.lib.server.FailureDetails.FailureDetail; import com.google.devtools.build.lib.shell.CommandException; import com.google.devtools.build.lib.util.CommandBuilder; -import com.google.devtools.build.lib.util.ExitCode; +import com.google.devtools.build.lib.util.InterruptedFailureDetails; import com.google.devtools.build.lib.util.OS; import com.google.devtools.build.lib.util.ProcessUtils; import com.google.devtools.build.lib.util.ShellEscaper; @@ -160,15 +164,15 @@ public BlazeCommandResult exec(CommandEnvironment env, OptionsParsingResult opti .getOptions(BuildRequestOptions.class) .getSymlinkPrefix(env.getRuntime().getProductName()); return actuallyClean(env, env.getOutputBase(), cleanOptions.expunge, async, symlinkPrefix); - } catch (IOException e) { - env.getReporter().handle(Event.error(e.getMessage())); - return BlazeCommandResult.exitCode(ExitCode.LOCAL_ENVIRONMENTAL_ERROR); - } catch (CommandException | ExecException e) { + } catch (CleanException e) { env.getReporter().handle(Event.error(e.getMessage())); - return BlazeCommandResult.exitCode(ExitCode.RUN_FAILURE); + return BlazeCommandResult.failureDetail(e.getFailureDetail()); } catch (InterruptedException e) { - env.getReporter().handle(Event.error("clean interrupted")); - return BlazeCommandResult.exitCode(ExitCode.INTERRUPTED); + String message = "clean interrupted"; + env.getReporter().handle(Event.error(message)); + return BlazeCommandResult.detailedExitCode( + InterruptedFailureDetails.detailedExitCode( + message, FailureDetails.Interrupted.Code.CLEAN_COMMAND)); } } @@ -203,25 +207,36 @@ private static void asyncClean(CommandEnvironment env, Path path, String pathIte private BlazeCommandResult actuallyClean( CommandEnvironment env, Path outputBase, boolean expunge, boolean async, String symlinkPrefix) - throws IOException, CommandException, ExecException, - InterruptedException { + throws CleanException, InterruptedException { BlazeRuntime runtime = env.getRuntime(); String workspaceDirectory = env.getWorkspace().getBaseName(); if (env.getOutputService() != null) { - env.getOutputService().clean(); + try { + env.getOutputService().clean(); + } catch (ExecException e) { + throw new CleanException(Code.OUTPUT_SERVICE_CLEAN_FAILURE, e); + } + } + try { + env.getBlazeWorkspace().clearCaches(); + } catch (IOException e) { + throw new CleanException(Code.ACTION_CACHE_CLEAN_FAILURE, e); } - env.getBlazeWorkspace().clearCaches(); if (expunge && !async) { logger.atInfo().log("Expunging..."); runtime.prepareForAbruptShutdown(); // Close java.log. LogManager.getLogManager().reset(); // Close the default stdout/stderr. - if (FileDescriptor.out.valid()) { - new FileOutputStream(FileDescriptor.out).close(); - } - if (FileDescriptor.err.valid()) { - new FileOutputStream(FileDescriptor.err).close(); + try { + if (FileDescriptor.out.valid()) { + new FileOutputStream(FileDescriptor.out).close(); + } + if (FileDescriptor.err.valid()) { + new FileOutputStream(FileDescriptor.err).close(); + } + } catch (IOException e) { + throw new CleanException(Code.OUT_ERR_CLOSE_FAILURE, e); } // Close the redirected stdout/stderr. System.out.close(); @@ -231,12 +246,22 @@ private BlazeCommandResult actuallyClean( // and links right before we exit. Once the lock file is gone there will // be a small possibility of a server race if a client is waiting, but // all significant files will be gone by then. - outputBase.deleteTreesBelow(); - outputBase.deleteTree(); + try { + outputBase.deleteTreesBelow(); + outputBase.deleteTree(); + } catch (IOException e) { + throw new CleanException(Code.OUTPUT_BASE_DELETE_FAILURE, e); + } } else if (expunge && async) { logger.atInfo().log("Expunging asynchronously..."); runtime.prepareForAbruptShutdown(); - asyncClean(env, outputBase, "Output base"); + try { + asyncClean(env, outputBase, "Output base"); + } catch (IOException e) { + throw new CleanException(Code.OUTPUT_BASE_TEMP_MOVE_FAILURE, e); + } catch (CommandException e) { + throw new CleanException(Code.ASYNC_OUTPUT_BASE_DELETE_FAILURE, e); + } } else { logger.atInfo().log("Output cleaning..."); env.getBlazeWorkspace().resetEvaluator(); @@ -244,9 +269,19 @@ private BlazeCommandResult actuallyClean( if (execroot.exists()) { logger.atFinest().log("Cleaning %s%s", execroot, async ? " asynchronously..." : ""); if (async) { - asyncClean(env, execroot, "Output tree"); + try { + asyncClean(env, execroot, "Output tree"); + } catch (IOException e) { + throw new CleanException(Code.EXECROOT_TEMP_MOVE_FAILURE, e); + } catch (CommandException e) { + throw new CleanException(Code.ASYNC_EXECROOT_DELETE_FAILURE, e); + } } else { - execroot.deleteTreesBelow(); + try { + execroot.deleteTreesBelow(); + } catch (IOException e) { + throw new CleanException(Code.EXECROOT_DELETE_FAILURE, e); + } } } } @@ -266,4 +301,20 @@ private BlazeCommandResult actuallyClean( System.gc(); return BlazeCommandResult.success(); } + + private static class CleanException extends Exception { + private final FailureDetails.CleanCommand.Code detailedCode; + + private CleanException(FailureDetails.CleanCommand.Code detailedCode, Exception e) { + super(Strings.nullToEmpty(e.getMessage()), e); + this.detailedCode = detailedCode; + } + + private FailureDetail getFailureDetail() { + return FailureDetail.newBuilder() + .setMessage(getMessage()) + .setCleanCommand(FailureDetails.CleanCommand.newBuilder().setCode(detailedCode)) + .build(); + } + } } diff --git a/src/main/protobuf/failure_details.proto b/src/main/protobuf/failure_details.proto index 3cc2cc30cb19f6..2db09ecf914c40 100644 --- a/src/main/protobuf/failure_details.proto +++ b/src/main/protobuf/failure_details.proto @@ -118,6 +118,7 @@ message FailureDetail { TestCommand test_command = 140; ActionQuery action_query = 141; TargetPatterns target_patterns = 142; + CleanCommand clean_command = 144; } reserved 102; // For internal use @@ -145,6 +146,7 @@ message Interrupted { AFTER_QUERY = 10 [(metadata) = { exit_code: 8 }]; FETCH_COMMAND = 17 [(metadata) = { exit_code: 8 }]; SYNC_COMMAND = 18 [(metadata) = { exit_code: 8 }]; + CLEAN_COMMAND = 20 [(metadata) = { exit_code: 8 }]; reserved 1 to 3; // For internal use reserved 11 to 16; // For internal use reserved 19; // For internal use @@ -616,3 +618,20 @@ message TargetPatterns { Code code = 1; } + +message CleanCommand { + enum Code { + CLEAN_COMMAND_UNKNOWN = 0 [(metadata) = { exit_code: 37 }]; + OUTPUT_SERVICE_CLEAN_FAILURE = 1 [(metadata) = { exit_code: 6 }]; + ACTION_CACHE_CLEAN_FAILURE = 2 [(metadata) = { exit_code: 36 }]; + OUT_ERR_CLOSE_FAILURE = 3 [(metadata) = { exit_code: 36 }]; + OUTPUT_BASE_DELETE_FAILURE = 4 [(metadata) = { exit_code: 36 }]; + OUTPUT_BASE_TEMP_MOVE_FAILURE = 5 [(metadata) = { exit_code: 36 }]; + ASYNC_OUTPUT_BASE_DELETE_FAILURE = 6 [(metadata) = { exit_code: 6 }]; + EXECROOT_DELETE_FAILURE = 7 [(metadata) = { exit_code: 36 }]; + EXECROOT_TEMP_MOVE_FAILURE = 8 [(metadata) = { exit_code: 36 }]; + ASYNC_EXECROOT_DELETE_FAILURE = 9 [(metadata) = { exit_code: 6 }]; + } + + Code code = 1; +}