blob: b3a03839d380f7f516144eb01b0e9da7258bfdce [file] [log] [blame]
ulfjack81940bd2017-03-29 15:38:28 +00001// 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.
14package com.google.devtools.build.lib.exec;
15
ulfjack340490a2017-06-07 03:43:20 -040016import com.google.common.base.Preconditions;
ulfjack81940bd2017-03-29 15:38:28 +000017import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable;
18import com.google.devtools.build.lib.concurrent.ThreadSafety.ThreadSafe;
ulfjackfc040ce2017-07-24 10:42:20 +020019import com.google.devtools.build.lib.shell.TerminationStatus;
20import java.util.Locale;
ulfjack72143fe2017-04-05 14:04:30 +000021import javax.annotation.Nullable;
ulfjack81940bd2017-03-29 15:38:28 +000022
23/**
24 * The result of a spawn execution.
ulfjack72143fe2017-04-05 14:04:30 +000025 *
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.
ulfjack81940bd2017-03-29 15:38:28 +000029 */
ulfjack72143fe2017-04-05 14:04:30 +000030// TODO(ulfjack): Change this from an interface to an immutable, final class.
ulfjack81940bd2017-03-29 15:38:28 +000031public interface SpawnResult {
ulfjack72143fe2017-04-05 14:04:30 +000032 /** 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 */
ulfjack340490a2017-06-07 03:43:20 -040038 SUCCESS(true),
ulfjack72143fe2017-04-05 14:04:30 +000039
40 /** Subprocess execution timed out. */
ulfjack340490a2017-06-07 03:43:20 -040041 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),
ulfjack72143fe2017-04-05 14:04:30 +000048
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. */
ulfjack340490a2017-06-07 03:43:20 -040056 LOCAL_ACTION_NOT_ALLOWED(true),
ulfjack72143fe2017-04-05 14:04:30 +000057
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 */
ulfjack340490a2017-06-07 03:43:20 -040066 DIRECTORY_AS_INPUT_DISALLOWED(true),
ulfjack72143fe2017-04-05 14:04:30 +000067
68 /**
69 * Too many input files - remote execution systems may refuse to execute subprocesses with an
70 * excessive number of input files.
71 */
ulfjack340490a2017-06-07 03:43:20 -040072 TOO_MANY_INPUT_FILES(true),
ulfjack72143fe2017-04-05 14:04:30 +000073
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 */
ulfjack340490a2017-06-07 03:43:20 -040078 INPUTS_TOO_LARGE(true),
ulfjack72143fe2017-04-05 14:04:30 +000079
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 */
ulfjack340490a2017-06-07 03:43:20 -040084 FILE_MODIFIED_DURING_BUILD(true),
ulfjack72143fe2017-04-05 14:04:30 +000085
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;
ulfjack340490a2017-06-07 03:43:20 -0400106
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 }
ulfjack72143fe2017-04-05 14:04:30 +0000120 }
121
ulfjack81940bd2017-03-29 15:38:28 +0000122 /**
ulfjack72143fe2017-04-05 14:04:30 +0000123 * Returns whether the spawn was actually run, regardless of the exit code. I.e., returns if
ulfjack340490a2017-06-07 03:43:20 -0400124 * 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.
ulfjack81940bd2017-03-29 15:38:28 +0000127 */
128 boolean setupSuccess();
129
ulfjackfc040ce2017-07-24 10:42:20 +0200130 boolean isCatastrophe();
131
ulfjack72143fe2017-04-05 14:04:30 +0000132 /** The status of the attempted Spawn execution. */
133 Status status();
134
ulfjack81940bd2017-03-29 15:38:28 +0000135 /**
ulfjack72143fe2017-04-05 14:04:30 +0000136 * The exit code of the subprocess if the subprocess was executed. Check {@link #status} for
137 * {@link Status#SUCCESS} before calling this method.
ulfjack81940bd2017-03-29 15:38:28 +0000138 */
139 int exitCode();
140
141 /**
ulfjack72143fe2017-04-05 14:04:30 +0000142 * 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
ulfjack340490a2017-06-07 03:43:20 -0400150 /** Whether the spawn result was a cache hit. */
151 boolean isCacheHit();
152
ulfjackfc040ce2017-07-24 10:42:20 +0200153 String getDetailMessage(
154 String messagePrefix, String message, boolean catastrophe, boolean forciblyRunRemotely);
155
ulfjack72143fe2017-04-05 14:04:30 +0000156 /**
ulfjack81940bd2017-03-29 15:38:28 +0000157 * Basic implementation of {@link SpawnResult}.
158 */
159 @Immutable @ThreadSafe
160 public static final class SimpleSpawnResult implements SpawnResult {
ulfjack81940bd2017-03-29 15:38:28 +0000161 private final int exitCode;
ulfjack72143fe2017-04-05 14:04:30 +0000162 private final Status status;
163 private final String executorHostName;
164 private final long wallTimeMillis;
ulfjack340490a2017-06-07 03:43:20 -0400165 private final boolean cacheHit;
ulfjack81940bd2017-03-29 15:38:28 +0000166
167 SimpleSpawnResult(Builder builder) {
ulfjack81940bd2017-03-29 15:38:28 +0000168 this.exitCode = builder.exitCode;
ulfjack340490a2017-06-07 03:43:20 -0400169 this.status = Preconditions.checkNotNull(builder.status);
ulfjack72143fe2017-04-05 14:04:30 +0000170 this.executorHostName = builder.executorHostName;
171 this.wallTimeMillis = builder.wallTimeMillis;
ulfjack340490a2017-06-07 03:43:20 -0400172 this.cacheHit = builder.cacheHit;
ulfjack81940bd2017-03-29 15:38:28 +0000173 }
174
175 @Override
176 public boolean setupSuccess() {
ulfjack340490a2017-06-07 03:43:20 -0400177 return status == Status.SUCCESS || status == Status.TIMEOUT || status == Status.OUT_OF_MEMORY;
ulfjack81940bd2017-03-29 15:38:28 +0000178 }
179
180 @Override
ulfjackfc040ce2017-07-24 10:42:20 +0200181 public boolean isCatastrophe() {
182 return false;
183 }
184
185 @Override
ulfjack81940bd2017-03-29 15:38:28 +0000186 public int exitCode() {
187 return exitCode;
188 }
ulfjack72143fe2017-04-05 14:04:30 +0000189
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 }
ulfjack340490a2017-06-07 03:43:20 -0400204
205 @Override
206 public boolean isCacheHit() {
207 return cacheHit;
208 }
ulfjackfc040ce2017-07-24 10:42:20 +0200209
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 }
ulfjack81940bd2017-03-29 15:38:28 +0000237 }
238
239 /**
240 * Builder class for {@link SpawnResult}.
241 */
242 public static final class Builder {
ulfjack81940bd2017-03-29 15:38:28 +0000243 private int exitCode;
ulfjack72143fe2017-04-05 14:04:30 +0000244 private Status status;
245 private String executorHostName;
246 private long wallTimeMillis;
ulfjack340490a2017-06-07 03:43:20 -0400247 private boolean cacheHit;
ulfjack81940bd2017-03-29 15:38:28 +0000248
249 public SpawnResult build() {
250 return new SimpleSpawnResult(this);
251 }
252
ulfjack81940bd2017-03-29 15:38:28 +0000253 public Builder setExitCode(int exitCode) {
254 this.exitCode = exitCode;
255 return this;
256 }
ulfjack72143fe2017-04-05 14:04:30 +0000257
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 }
ulfjack340490a2017-06-07 03:43:20 -0400272
273 public Builder setCacheHit(boolean cacheHit) {
274 this.cacheHit = cacheHit;
275 return this;
276 }
ulfjack81940bd2017-03-29 15:38:28 +0000277 }
ulfjack72143fe2017-04-05 14:04:30 +0000278}