ulfjack | 81940bd | 2017-03-29 15:38:28 +0000 | [diff] [blame] | 1 | // Copyright 2017 The Bazel Authors. All rights reserved. |
| 2 | // |
| 3 | // Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 | // you may not use this file except in compliance with the License. |
| 5 | // You may obtain a copy of the License at |
| 6 | // |
| 7 | // http://www.apache.org/licenses/LICENSE-2.0 |
| 8 | // |
| 9 | // Unless required by applicable law or agreed to in writing, software |
| 10 | // distributed under the License is distributed on an "AS IS" BASIS, |
| 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 | // See the License for the specific language governing permissions and |
| 13 | // limitations under the License. |
| 14 | package com.google.devtools.build.lib.exec; |
| 15 | |
ulfjack | 340490a | 2017-06-07 03:43:20 -0400 | [diff] [blame] | 16 | import com.google.common.base.Preconditions; |
ulfjack | 81940bd | 2017-03-29 15:38:28 +0000 | [diff] [blame] | 17 | import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable; |
| 18 | import com.google.devtools.build.lib.concurrent.ThreadSafety.ThreadSafe; |
ulfjack | fc040ce | 2017-07-24 10:42:20 +0200 | [diff] [blame] | 19 | import com.google.devtools.build.lib.shell.TerminationStatus; |
| 20 | import java.util.Locale; |
ulfjack | 72143fe | 2017-04-05 14:04:30 +0000 | [diff] [blame] | 21 | import javax.annotation.Nullable; |
ulfjack | 81940bd | 2017-03-29 15:38:28 +0000 | [diff] [blame] | 22 | |
| 23 | /** |
| 24 | * The result of a spawn execution. |
ulfjack | 72143fe | 2017-04-05 14:04:30 +0000 | [diff] [blame] | 25 | * |
| 26 | * <p>DO NOT IMPLEMENT THIS INTERFACE! Use {@link SpawnResult.Builder} to create instances instead. |
| 27 | * This is a temporary situation as long as we still have separate internal and external |
| 28 | * implementations - the plan is to merge the two into a single immutable, final class. |
ulfjack | 81940bd | 2017-03-29 15:38:28 +0000 | [diff] [blame] | 29 | */ |
ulfjack | 72143fe | 2017-04-05 14:04:30 +0000 | [diff] [blame] | 30 | // TODO(ulfjack): Change this from an interface to an immutable, final class. |
ulfjack | 81940bd | 2017-03-29 15:38:28 +0000 | [diff] [blame] | 31 | public interface SpawnResult { |
ulfjack | 72143fe | 2017-04-05 14:04:30 +0000 | [diff] [blame] | 32 | /** The status of the attempted Spawn execution. */ |
| 33 | public enum Status { |
| 34 | /** |
| 35 | * Subprocess executed successfully, but may have returned a non-zero exit code. See |
| 36 | * {@link #exitCode} for the actual exit code. |
| 37 | */ |
ulfjack | 340490a | 2017-06-07 03:43:20 -0400 | [diff] [blame] | 38 | SUCCESS(true), |
ulfjack | 72143fe | 2017-04-05 14:04:30 +0000 | [diff] [blame] | 39 | |
| 40 | /** Subprocess execution timed out. */ |
ulfjack | 340490a | 2017-06-07 03:43:20 -0400 | [diff] [blame] | 41 | TIMEOUT(true), |
| 42 | |
| 43 | /** |
| 44 | * The subprocess ran out of memory. On Linux systems, the kernel may kill processes in |
| 45 | * low-memory situations, and this status is intended to report such a case back to Bazel. |
| 46 | */ |
| 47 | OUT_OF_MEMORY(true), |
ulfjack | 72143fe | 2017-04-05 14:04:30 +0000 | [diff] [blame] | 48 | |
| 49 | /** |
| 50 | * Subprocess did not execute for an unknown reason - only use this if none of the more specific |
| 51 | * status codes apply. |
| 52 | */ |
| 53 | EXECUTION_FAILED, |
| 54 | |
| 55 | /** The attempted subprocess was disallowed by a user setting. */ |
ulfjack | 340490a | 2017-06-07 03:43:20 -0400 | [diff] [blame] | 56 | LOCAL_ACTION_NOT_ALLOWED(true), |
ulfjack | 72143fe | 2017-04-05 14:04:30 +0000 | [diff] [blame] | 57 | |
| 58 | /** The Spawn referred to an non-existent absolute or relative path. */ |
| 59 | COMMAND_NOT_FOUND, |
| 60 | |
| 61 | /** |
| 62 | * One of the Spawn inputs was a directory. For backwards compatibility, some |
| 63 | * {@link SpawnRunner} implementations may attempt to run the subprocess anyway. Note that this |
| 64 | * leads to incremental correctness issues, as Bazel does not track dependencies on directories. |
| 65 | */ |
ulfjack | 340490a | 2017-06-07 03:43:20 -0400 | [diff] [blame] | 66 | DIRECTORY_AS_INPUT_DISALLOWED(true), |
ulfjack | 72143fe | 2017-04-05 14:04:30 +0000 | [diff] [blame] | 67 | |
| 68 | /** |
| 69 | * Too many input files - remote execution systems may refuse to execute subprocesses with an |
| 70 | * excessive number of input files. |
| 71 | */ |
ulfjack | 340490a | 2017-06-07 03:43:20 -0400 | [diff] [blame] | 72 | TOO_MANY_INPUT_FILES(true), |
ulfjack | 72143fe | 2017-04-05 14:04:30 +0000 | [diff] [blame] | 73 | |
| 74 | /** |
| 75 | * Total size of inputs is too large - remote execution systems may refuse to execute |
| 76 | * subprocesses if the total size of all inputs exceeds a limit. |
| 77 | */ |
ulfjack | 340490a | 2017-06-07 03:43:20 -0400 | [diff] [blame] | 78 | INPUTS_TOO_LARGE(true), |
ulfjack | 72143fe | 2017-04-05 14:04:30 +0000 | [diff] [blame] | 79 | |
| 80 | /** |
| 81 | * One of the input files to the Spawn was modified during the build - some {@link SpawnRunner} |
| 82 | * implementations cache checksums and may detect such modifications on a best effort basis. |
| 83 | */ |
ulfjack | 340490a | 2017-06-07 03:43:20 -0400 | [diff] [blame] | 84 | FILE_MODIFIED_DURING_BUILD(true), |
ulfjack | 72143fe | 2017-04-05 14:04:30 +0000 | [diff] [blame] | 85 | |
| 86 | /** |
| 87 | * The {@link SpawnRunner} was unable to establish a required network connection. |
| 88 | */ |
| 89 | CONNECTION_FAILED, |
| 90 | |
| 91 | /** |
| 92 | * The remote execution system is overloaded and had to refuse execution for this Spawn. |
| 93 | */ |
| 94 | REMOTE_EXECUTOR_OVERLOADED, |
| 95 | |
| 96 | /** |
| 97 | * The remote execution system did not allow the request due to missing authorization or |
| 98 | * authentication. |
| 99 | */ |
| 100 | NOT_AUTHORIZED, |
| 101 | |
| 102 | /** |
| 103 | * The Spawn was malformed. |
| 104 | */ |
| 105 | INVALID_ARGUMENT; |
ulfjack | 340490a | 2017-06-07 03:43:20 -0400 | [diff] [blame] | 106 | |
| 107 | private final boolean isUserError; |
| 108 | |
| 109 | private Status(boolean isUserError) { |
| 110 | this.isUserError = isUserError; |
| 111 | } |
| 112 | |
| 113 | private Status() { |
| 114 | this(false); |
| 115 | } |
| 116 | |
| 117 | public boolean isConsideredUserError() { |
| 118 | return isUserError; |
| 119 | } |
ulfjack | 72143fe | 2017-04-05 14:04:30 +0000 | [diff] [blame] | 120 | } |
| 121 | |
ulfjack | 81940bd | 2017-03-29 15:38:28 +0000 | [diff] [blame] | 122 | /** |
ulfjack | 72143fe | 2017-04-05 14:04:30 +0000 | [diff] [blame] | 123 | * Returns whether the spawn was actually run, regardless of the exit code. I.e., returns if |
ulfjack | 340490a | 2017-06-07 03:43:20 -0400 | [diff] [blame] | 124 | * status == SUCCESS || status == TIMEOUT || status == OUT_OF_MEMORY. Returns false if there were |
| 125 | * errors that prevented the spawn from being run, such as network errors, missing local files, |
| 126 | * errors setting up sandboxing, etc. |
ulfjack | 81940bd | 2017-03-29 15:38:28 +0000 | [diff] [blame] | 127 | */ |
| 128 | boolean setupSuccess(); |
| 129 | |
ulfjack | fc040ce | 2017-07-24 10:42:20 +0200 | [diff] [blame] | 130 | boolean isCatastrophe(); |
| 131 | |
ulfjack | 72143fe | 2017-04-05 14:04:30 +0000 | [diff] [blame] | 132 | /** The status of the attempted Spawn execution. */ |
| 133 | Status status(); |
| 134 | |
ulfjack | 81940bd | 2017-03-29 15:38:28 +0000 | [diff] [blame] | 135 | /** |
ulfjack | 72143fe | 2017-04-05 14:04:30 +0000 | [diff] [blame] | 136 | * The exit code of the subprocess if the subprocess was executed. Check {@link #status} for |
| 137 | * {@link Status#SUCCESS} before calling this method. |
ulfjack | 81940bd | 2017-03-29 15:38:28 +0000 | [diff] [blame] | 138 | */ |
| 139 | int exitCode(); |
| 140 | |
| 141 | /** |
ulfjack | 72143fe | 2017-04-05 14:04:30 +0000 | [diff] [blame] | 142 | * The host name of the executor or {@code null}. This information is intended for debugging |
| 143 | * purposes, especially for remote execution systems. Remote caches usually do not store the |
| 144 | * original host name, so this is generally {@code null} for cache hits. |
| 145 | */ |
| 146 | @Nullable String getExecutorHostName(); |
| 147 | |
| 148 | long getWallTimeMillis(); |
| 149 | |
ulfjack | 340490a | 2017-06-07 03:43:20 -0400 | [diff] [blame] | 150 | /** Whether the spawn result was a cache hit. */ |
| 151 | boolean isCacheHit(); |
| 152 | |
ulfjack | fc040ce | 2017-07-24 10:42:20 +0200 | [diff] [blame] | 153 | String getDetailMessage( |
| 154 | String messagePrefix, String message, boolean catastrophe, boolean forciblyRunRemotely); |
| 155 | |
ulfjack | 72143fe | 2017-04-05 14:04:30 +0000 | [diff] [blame] | 156 | /** |
ulfjack | 81940bd | 2017-03-29 15:38:28 +0000 | [diff] [blame] | 157 | * Basic implementation of {@link SpawnResult}. |
| 158 | */ |
| 159 | @Immutable @ThreadSafe |
| 160 | public static final class SimpleSpawnResult implements SpawnResult { |
ulfjack | 81940bd | 2017-03-29 15:38:28 +0000 | [diff] [blame] | 161 | private final int exitCode; |
ulfjack | 72143fe | 2017-04-05 14:04:30 +0000 | [diff] [blame] | 162 | private final Status status; |
| 163 | private final String executorHostName; |
| 164 | private final long wallTimeMillis; |
ulfjack | 340490a | 2017-06-07 03:43:20 -0400 | [diff] [blame] | 165 | private final boolean cacheHit; |
ulfjack | 81940bd | 2017-03-29 15:38:28 +0000 | [diff] [blame] | 166 | |
| 167 | SimpleSpawnResult(Builder builder) { |
ulfjack | 81940bd | 2017-03-29 15:38:28 +0000 | [diff] [blame] | 168 | this.exitCode = builder.exitCode; |
ulfjack | 340490a | 2017-06-07 03:43:20 -0400 | [diff] [blame] | 169 | this.status = Preconditions.checkNotNull(builder.status); |
ulfjack | 72143fe | 2017-04-05 14:04:30 +0000 | [diff] [blame] | 170 | this.executorHostName = builder.executorHostName; |
| 171 | this.wallTimeMillis = builder.wallTimeMillis; |
ulfjack | 340490a | 2017-06-07 03:43:20 -0400 | [diff] [blame] | 172 | this.cacheHit = builder.cacheHit; |
ulfjack | 81940bd | 2017-03-29 15:38:28 +0000 | [diff] [blame] | 173 | } |
| 174 | |
| 175 | @Override |
| 176 | public boolean setupSuccess() { |
ulfjack | 340490a | 2017-06-07 03:43:20 -0400 | [diff] [blame] | 177 | return status == Status.SUCCESS || status == Status.TIMEOUT || status == Status.OUT_OF_MEMORY; |
ulfjack | 81940bd | 2017-03-29 15:38:28 +0000 | [diff] [blame] | 178 | } |
| 179 | |
| 180 | @Override |
ulfjack | fc040ce | 2017-07-24 10:42:20 +0200 | [diff] [blame] | 181 | public boolean isCatastrophe() { |
| 182 | return false; |
| 183 | } |
| 184 | |
| 185 | @Override |
ulfjack | 81940bd | 2017-03-29 15:38:28 +0000 | [diff] [blame] | 186 | public int exitCode() { |
| 187 | return exitCode; |
| 188 | } |
ulfjack | 72143fe | 2017-04-05 14:04:30 +0000 | [diff] [blame] | 189 | |
| 190 | @Override |
| 191 | public Status status() { |
| 192 | return status; |
| 193 | } |
| 194 | |
| 195 | @Override |
| 196 | public String getExecutorHostName() { |
| 197 | return executorHostName; |
| 198 | } |
| 199 | |
| 200 | @Override |
| 201 | public long getWallTimeMillis() { |
| 202 | return wallTimeMillis; |
| 203 | } |
ulfjack | 340490a | 2017-06-07 03:43:20 -0400 | [diff] [blame] | 204 | |
| 205 | @Override |
| 206 | public boolean isCacheHit() { |
| 207 | return cacheHit; |
| 208 | } |
ulfjack | fc040ce | 2017-07-24 10:42:20 +0200 | [diff] [blame] | 209 | |
| 210 | @Override |
| 211 | public String getDetailMessage( |
| 212 | String messagePrefix, String message, boolean catastrophe, boolean forciblyRunRemotely) { |
| 213 | TerminationStatus status = new TerminationStatus( |
| 214 | exitCode(), status() == Status.TIMEOUT); |
| 215 | String reason = " (" + status.toShortString() + ")"; // e.g " (Exit 1)" |
| 216 | String explanation = status.exited() ? "" : ": " + message; |
| 217 | |
| 218 | if (!status().isConsideredUserError()) { |
| 219 | String errorDetail = status().name().toLowerCase(Locale.US) |
| 220 | .replace('_', ' '); |
| 221 | explanation += ". Note: Remote connection/protocol failed with: " + errorDetail; |
| 222 | } |
| 223 | if (status() == Status.TIMEOUT) { |
| 224 | explanation += |
| 225 | String.format( |
| 226 | " (failed due to timeout after %.2f seconds.)", |
| 227 | getWallTimeMillis() / 1000.0f); |
| 228 | } else if (status() == Status.OUT_OF_MEMORY) { |
| 229 | explanation += " (Remote action was terminated due to Out of Memory.)"; |
| 230 | } |
| 231 | if (status() != Status.TIMEOUT && forciblyRunRemotely) { |
| 232 | explanation += " Action tagged as local was forcibly run remotely and failed - it's " |
| 233 | + "possible that the action simply doesn't work remotely"; |
| 234 | } |
| 235 | return messagePrefix + " failed" + reason + explanation; |
| 236 | } |
ulfjack | 81940bd | 2017-03-29 15:38:28 +0000 | [diff] [blame] | 237 | } |
| 238 | |
| 239 | /** |
| 240 | * Builder class for {@link SpawnResult}. |
| 241 | */ |
| 242 | public static final class Builder { |
ulfjack | 81940bd | 2017-03-29 15:38:28 +0000 | [diff] [blame] | 243 | private int exitCode; |
ulfjack | 72143fe | 2017-04-05 14:04:30 +0000 | [diff] [blame] | 244 | private Status status; |
| 245 | private String executorHostName; |
| 246 | private long wallTimeMillis; |
ulfjack | 340490a | 2017-06-07 03:43:20 -0400 | [diff] [blame] | 247 | private boolean cacheHit; |
ulfjack | 81940bd | 2017-03-29 15:38:28 +0000 | [diff] [blame] | 248 | |
| 249 | public SpawnResult build() { |
| 250 | return new SimpleSpawnResult(this); |
| 251 | } |
| 252 | |
ulfjack | 81940bd | 2017-03-29 15:38:28 +0000 | [diff] [blame] | 253 | public Builder setExitCode(int exitCode) { |
| 254 | this.exitCode = exitCode; |
| 255 | return this; |
| 256 | } |
ulfjack | 72143fe | 2017-04-05 14:04:30 +0000 | [diff] [blame] | 257 | |
| 258 | public Builder setStatus(Status status) { |
| 259 | this.status = status; |
| 260 | return this; |
| 261 | } |
| 262 | |
| 263 | public Builder setExecutorHostname(String executorHostName) { |
| 264 | this.executorHostName = executorHostName; |
| 265 | return this; |
| 266 | } |
| 267 | |
| 268 | public Builder setWallTimeMillis(long wallTimeMillis) { |
| 269 | this.wallTimeMillis = wallTimeMillis; |
| 270 | return this; |
| 271 | } |
ulfjack | 340490a | 2017-06-07 03:43:20 -0400 | [diff] [blame] | 272 | |
| 273 | public Builder setCacheHit(boolean cacheHit) { |
| 274 | this.cacheHit = cacheHit; |
| 275 | return this; |
| 276 | } |
ulfjack | 81940bd | 2017-03-29 15:38:28 +0000 | [diff] [blame] | 277 | } |
ulfjack | 72143fe | 2017-04-05 14:04:30 +0000 | [diff] [blame] | 278 | } |