blob: 82754f85a48e9db28b9ad38ec8b168347210767c [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.
14
15package com.google.devtools.build.lib.remote;
16
Jakob Buchgraber4b3c2eb2019-04-04 02:01:48 -070017import static com.google.devtools.build.lib.profiler.ProfilerTask.REMOTE_DOWNLOAD;
18import static com.google.devtools.build.lib.profiler.ProfilerTask.REMOTE_EXECUTION;
19import static com.google.devtools.build.lib.profiler.ProfilerTask.UPLOAD_TIME;
buchgrd480c5f2019-04-03 00:53:34 -070020import static com.google.devtools.build.lib.remote.util.Utils.createSpawnResult;
buchgrff008f42018-06-02 14:13:43 -070021import static com.google.devtools.build.lib.remote.util.Utils.getFromFuture;
buchgrd480c5f2019-04-03 00:53:34 -070022import static com.google.devtools.build.lib.remote.util.Utils.getInMemoryOutputPath;
Jakob Buchgraber50c10042019-04-11 02:11:19 -070023import static com.google.devtools.build.lib.remote.util.Utils.hasTopLevelOutputs;
24import static com.google.devtools.build.lib.remote.util.Utils.shouldDownloadAllSpawnOutputs;
buchgrff008f42018-06-02 14:13:43 -070025
olaolaf0aa55d2018-08-16 08:51:06 -070026import build.bazel.remote.execution.v2.Action;
27import build.bazel.remote.execution.v2.ActionResult;
28import build.bazel.remote.execution.v2.Command;
29import build.bazel.remote.execution.v2.Digest;
30import build.bazel.remote.execution.v2.ExecuteRequest;
31import build.bazel.remote.execution.v2.ExecuteResponse;
32import build.bazel.remote.execution.v2.LogFile;
33import build.bazel.remote.execution.v2.Platform;
Jakob Buchgraber562fcf92017-07-27 12:51:13 +020034import com.google.common.annotations.VisibleForTesting;
Jakob Buchgraber716e4e32019-01-21 07:59:23 -080035import com.google.common.base.Preconditions;
olaola1cbba982017-07-27 02:52:37 +020036import com.google.common.base.Throwables;
Benjamin Petersondd3ddb02018-05-03 09:20:08 -070037import com.google.common.collect.ImmutableList;
ulfjack81940bd2017-03-29 15:38:28 +000038import com.google.common.collect.ImmutableMap;
Jakob Buchgraber50c10042019-04-11 02:11:19 -070039import com.google.common.collect.ImmutableSet;
buchgr7f725442019-03-08 03:20:25 -080040import com.google.common.collect.Maps;
ulfjack81940bd2017-03-29 15:38:28 +000041import com.google.devtools.build.lib.actions.ActionInput;
olaola2838dd92018-03-08 09:56:12 -080042import com.google.devtools.build.lib.actions.Artifact;
tomlu09fe0622018-06-19 12:55:39 -070043import com.google.devtools.build.lib.actions.CommandLines.ParamFileActionInput;
olaola460a1052017-06-09 04:33:25 +020044import com.google.devtools.build.lib.actions.ExecException;
ulfjack81940bd2017-03-29 15:38:28 +000045import com.google.devtools.build.lib.actions.Spawn;
rupertsda40fbf2017-09-22 05:59:42 +020046import com.google.devtools.build.lib.actions.SpawnResult;
47import com.google.devtools.build.lib.actions.SpawnResult.Status;
olaola460a1052017-06-09 04:33:25 +020048import com.google.devtools.build.lib.actions.Spawns;
Ola Rozenfeldccbe40a2017-07-17 10:05:59 +020049import com.google.devtools.build.lib.actions.cache.VirtualActionInput;
Googler77ef8d62019-07-23 11:55:53 -070050import com.google.devtools.build.lib.analysis.platform.PlatformUtils;
ulfjack72143fe2017-04-05 14:04:30 +000051import com.google.devtools.build.lib.concurrent.ThreadSafety.ThreadSafe;
Benjamin Peterson3ff87f72017-08-21 18:41:45 +020052import com.google.devtools.build.lib.events.Event;
53import com.google.devtools.build.lib.events.Reporter;
tomlu09fe0622018-06-19 12:55:39 -070054import com.google.devtools.build.lib.exec.ExecutionOptions;
ulfjack2ce304a2017-04-04 08:04:01 +000055import com.google.devtools.build.lib.exec.SpawnRunner;
ulfjack278a84b2018-11-05 08:44:22 -080056import com.google.devtools.build.lib.profiler.Profiler;
Jakob Buchgraber4b3c2eb2019-04-04 02:01:48 -070057import com.google.devtools.build.lib.profiler.ProfilerTask;
ulfjack278a84b2018-11-05 08:44:22 -080058import com.google.devtools.build.lib.profiler.SilentCloseable;
buchgr7f725442019-03-08 03:20:25 -080059import com.google.devtools.build.lib.remote.merkletree.MerkleTree;
Jakob Buchgraber75b7ed42019-03-27 10:27:13 -070060import com.google.devtools.build.lib.remote.options.RemoteOptions;
buchgrd480c5f2019-04-03 00:53:34 -070061import com.google.devtools.build.lib.remote.options.RemoteOutputsMode;
Googler922d1e62018-03-05 14:49:00 -080062import com.google.devtools.build.lib.remote.util.DigestUtil;
63import com.google.devtools.build.lib.remote.util.DigestUtil.ActionKey;
64import com.google.devtools.build.lib.remote.util.TracingMetadataUtils;
Jakob Buchgrabera79a4b62019-06-23 02:06:20 -070065import com.google.devtools.build.lib.remote.util.Utils;
buchgrd480c5f2019-04-03 00:53:34 -070066import com.google.devtools.build.lib.remote.util.Utils.InMemoryOutput;
buchgra6591352017-09-01 13:14:19 +020067import com.google.devtools.build.lib.util.ExitCode;
buchgr9626bb42017-08-11 13:54:04 +020068import com.google.devtools.build.lib.util.io.FileOutErr;
ulfjack81940bd2017-03-29 15:38:28 +000069import com.google.devtools.build.lib.vfs.Path;
70import com.google.devtools.build.lib.vfs.PathFragment;
buchgr7f725442019-03-08 03:20:25 -080071import com.google.protobuf.Message;
olaola6f32d5a2017-09-20 17:12:19 +020072import io.grpc.Context;
olaola1cbba982017-07-27 02:52:37 +020073import io.grpc.Status.Code;
ulfjack81940bd2017-03-29 15:38:28 +000074import java.io.IOException;
tomlu09fe0622018-06-19 12:55:39 -070075import java.io.OutputStream;
ulfjackdeab0cf2017-08-08 13:08:24 +020076import java.time.Duration;
ulfjack29360472017-07-07 09:32:53 -040077import java.util.ArrayList;
ulfjack81940bd2017-03-29 15:38:28 +000078import java.util.Collection;
olaola0d310142017-07-14 10:07:50 +020079import java.util.Collections;
Ola Rozenfeldccbe40a2017-07-17 10:05:59 +020080import java.util.HashMap;
ulfjack81940bd2017-03-29 15:38:28 +000081import java.util.List;
Ola Rozenfeldccbe40a2017-07-17 10:05:59 +020082import java.util.Map;
ulfjack81940bd2017-03-29 15:38:28 +000083import java.util.SortedMap;
84import java.util.TreeSet;
Benjamin Peterson3ff87f72017-08-21 18:41:45 +020085import java.util.concurrent.atomic.AtomicBoolean;
philwod6ce2642018-07-30 05:24:50 -070086import java.util.concurrent.atomic.AtomicReference;
ulfjack3fbd7c42017-07-10 13:19:19 +020087import javax.annotation.Nullable;
ulfjack81940bd2017-03-29 15:38:28 +000088
olaola460a1052017-06-09 04:33:25 +020089/** A client for the remote execution service. */
ulfjack72143fe2017-04-05 14:04:30 +000090@ThreadSafe
buchgrd480c5f2019-04-03 00:53:34 -070091public class RemoteSpawnRunner implements SpawnRunner {
92 private static final int POSIX_TIMEOUT_EXIT_CODE = /*SIGNAL_BASE=*/ 128 + /*SIGALRM=*/ 14;
ulfjack32e7a1c2017-11-28 01:14:34 -080093
ulfjack81940bd2017-03-29 15:38:28 +000094 private final Path execRoot;
tomlu09fe0622018-06-19 12:55:39 -070095 private final RemoteOptions remoteOptions;
96 private final ExecutionOptions executionOptions;
philwod6ce2642018-07-30 05:24:50 -070097 private final AtomicReference<SpawnRunner> fallbackRunner;
olaola1cbba982017-07-27 02:52:37 +020098 private final boolean verboseFailures;
ulfjack81940bd2017-03-29 15:38:28 +000099
Benjamin Peterson3ff87f72017-08-21 18:41:45 +0200100 @Nullable private final Reporter cmdlineReporter;
Jakob Buchgraber716e4e32019-01-21 07:59:23 -0800101 private final GrpcRemoteCache remoteCache;
ulfjack3fbd7c42017-07-10 13:19:19 +0200102 @Nullable private final GrpcRemoteExecutor remoteExecutor;
George Gensuref600b692018-07-10 14:13:50 -0700103 @Nullable private final RemoteRetrier retrier;
olaola6f32d5a2017-09-20 17:12:19 +0200104 private final String buildRequestId;
105 private final String commandId;
buchgr559a07d2017-11-30 11:09:35 -0800106 private final DigestUtil digestUtil;
olaolabf326fa2018-03-21 11:22:12 -0700107 private final Path logDir;
ulfjack81940bd2017-03-29 15:38:28 +0000108
Jakob Buchgraber50c10042019-04-11 02:11:19 -0700109 /**
110 * Set of artifacts that are top level outputs
111 *
112 * <p>This set is empty unless {@link RemoteOutputsMode#TOPLEVEL} is specified. If so, this set is
113 * used to decide whether to download an output.
114 */
Jakob Buchgraber34784c32019-07-22 07:40:14 -0700115 private final ImmutableSet<ActionInput> topLevelOutputs;
Jakob Buchgraber50c10042019-04-11 02:11:19 -0700116
Benjamin Peterson3ff87f72017-08-21 18:41:45 +0200117 // Used to ensure that a warning is reported only once.
118 private final AtomicBoolean warningReported = new AtomicBoolean();
119
ulfjack81940bd2017-03-29 15:38:28 +0000120 RemoteSpawnRunner(
121 Path execRoot,
tomlu09fe0622018-06-19 12:55:39 -0700122 RemoteOptions remoteOptions,
123 ExecutionOptions executionOptions,
philwod6ce2642018-07-30 05:24:50 -0700124 AtomicReference<SpawnRunner> fallbackRunner,
olaola1cbba982017-07-27 02:52:37 +0200125 boolean verboseFailures,
Benjamin Peterson3ff87f72017-08-21 18:41:45 +0200126 @Nullable Reporter cmdlineReporter,
olaola6f32d5a2017-09-20 17:12:19 +0200127 String buildRequestId,
128 String commandId,
Jakob Buchgraber716e4e32019-01-21 07:59:23 -0800129 GrpcRemoteCache remoteCache,
buchgr559a07d2017-11-30 11:09:35 -0800130 @Nullable GrpcRemoteExecutor remoteExecutor,
George Gensuref600b692018-07-10 14:13:50 -0700131 @Nullable RemoteRetrier retrier,
olaolabf326fa2018-03-21 11:22:12 -0700132 DigestUtil digestUtil,
Jakob Buchgraber50c10042019-04-11 02:11:19 -0700133 Path logDir,
Jakob Buchgraber34784c32019-07-22 07:40:14 -0700134 ImmutableSet<ActionInput> topLevelOutputs) {
ulfjack81940bd2017-03-29 15:38:28 +0000135 this.execRoot = execRoot;
tomlu09fe0622018-06-19 12:55:39 -0700136 this.remoteOptions = remoteOptions;
137 this.executionOptions = executionOptions;
ulfjack29360472017-07-07 09:32:53 -0400138 this.fallbackRunner = fallbackRunner;
Jakob Buchgraber716e4e32019-01-21 07:59:23 -0800139 this.remoteCache = Preconditions.checkNotNull(remoteCache, "remoteCache");
ulfjack29360472017-07-07 09:32:53 -0400140 this.remoteExecutor = remoteExecutor;
olaola1cbba982017-07-27 02:52:37 +0200141 this.verboseFailures = verboseFailures;
Benjamin Peterson3ff87f72017-08-21 18:41:45 +0200142 this.cmdlineReporter = cmdlineReporter;
olaola6f32d5a2017-09-20 17:12:19 +0200143 this.buildRequestId = buildRequestId;
144 this.commandId = commandId;
George Gensuref600b692018-07-10 14:13:50 -0700145 this.retrier = retrier;
buchgr559a07d2017-11-30 11:09:35 -0800146 this.digestUtil = digestUtil;
olaolabf326fa2018-03-21 11:22:12 -0700147 this.logDir = logDir;
Jakob Buchgraber50c10042019-04-11 02:11:19 -0700148 this.topLevelOutputs = Preconditions.checkNotNull(topLevelOutputs, "topLevelOutputs");
ulfjack81940bd2017-03-29 15:38:28 +0000149 }
150
ulfjack2ce304a2017-04-04 08:04:01 +0000151 @Override
olaolaec085532018-02-22 10:33:28 -0800152 public String getName() {
153 return "remote";
154 }
155
156 @Override
tomlu29e306d2018-04-19 05:41:44 -0700157 public SpawnResult exec(Spawn spawn, SpawnExecutionContext context)
olaola460a1052017-06-09 04:33:25 +0200158 throws ExecException, InterruptedException, IOException {
Sergio Rodriguez Orellana8860c3e2019-07-25 01:12:58 -0700159
160 boolean spawnCacheableRemotely = Spawns.mayBeCachedRemotely(spawn);
161 boolean uploadLocalResults = remoteOptions.remoteUploadLocalResults && spawnCacheableRemotely;
162 boolean acceptCachedResult = remoteOptions.remoteAcceptCached && spawnCacheableRemotely;
ulfjack81940bd2017-03-29 15:38:28 +0000163
tomlu29e306d2018-04-19 05:41:44 -0700164 context.report(ProgressStatus.EXECUTING, getName());
buchgrd480c5f2019-04-03 00:53:34 -0700165 RemoteOutputsMode remoteOutputsMode = remoteOptions.remoteOutputsMode;
philwo9ed9d8a2018-09-03 07:30:26 -0700166 SortedMap<PathFragment, ActionInput> inputMap = context.getInputMapping(true);
buchgr7f725442019-03-08 03:20:25 -0800167 final MerkleTree merkleTree =
168 MerkleTree.build(inputMap, context.getMetadataProvider(), execRoot, digestUtil);
tomlu09fe0622018-06-19 12:55:39 -0700169 maybeWriteParamFilesLocally(spawn);
John Cater3afb7b02019-01-18 11:44:51 -0800170
171 // Get the remote platform properties.
Googler77ef8d62019-07-23 11:55:53 -0700172 Platform platform = PlatformUtils.getPlatformProto(spawn.getExecutionPlatform(), remoteOptions);
John Cater3afb7b02019-01-18 11:44:51 -0800173
Jakob Buchgraber54d7d612019-03-25 08:30:17 -0700174 Command command =
175 buildCommand(
176 spawn.getOutputFiles(), spawn.getArguments(), spawn.getEnvironment(), platform);
177 Digest commandHash = digestUtil.compute(command);
178 Action action =
179 buildAction(
Sergio Rodriguez Orellana8860c3e2019-07-25 01:12:58 -0700180 commandHash, merkleTree.getRootDigest(), context.getTimeout(), spawnCacheableRemotely);
Jakob Buchgraber54d7d612019-03-25 08:30:17 -0700181 ActionKey actionKey = digestUtil.computeActionKey(action);
ulfjack81940bd2017-03-29 15:38:28 +0000182
Sergio Rodriguez Orellana8860c3e2019-07-25 01:12:58 -0700183 if (!Spawns.mayBeExecutedRemotely(spawn)) {
184 return execLocallyAndUpload(
185 spawn, context, inputMap, actionKey, action, command, uploadLocalResults);
186 }
187
ulfjack29360472017-07-07 09:32:53 -0400188 // Look up action cache, and reuse the action output if it is found.
olaola6f32d5a2017-09-20 17:12:19 +0200189 Context withMetadata =
190 TracingMetadataUtils.contextWithMetadata(buildRequestId, commandId, actionKey);
191 Context previous = withMetadata.attach();
Jakob Buchgraber4b3c2eb2019-04-04 02:01:48 -0700192 Profiler prof = Profiler.instance();
ulfjack29360472017-07-07 09:32:53 -0400193 try {
olaola6f32d5a2017-09-20 17:12:19 +0200194 try {
195 // Try to lookup the action in the action cache.
ulfjack278a84b2018-11-05 08:44:22 -0800196 ActionResult cachedResult;
Jakob Buchgraber4b3c2eb2019-04-04 02:01:48 -0700197 try (SilentCloseable c = prof.profile(ProfilerTask.REMOTE_CACHE_CHECK, "check cache hit")) {
ulfjack278a84b2018-11-05 08:44:22 -0800198 cachedResult = acceptCachedResult ? remoteCache.getCachedActionResult(actionKey) : null;
199 }
olaola6f32d5a2017-09-20 17:12:19 +0200200 if (cachedResult != null) {
201 if (cachedResult.getExitCode() != 0) {
ishikhman62f54582019-03-18 03:42:42 -0700202 // Failed actions are treated as a cache miss mostly in order to avoid caching flaky
203 // actions (tests).
204 // Set acceptCachedResult to false in order to force the action re-execution
olaola6f32d5a2017-09-20 17:12:19 +0200205 acceptCachedResult = false;
ishikhman62f54582019-03-18 03:42:42 -0700206 } else {
buchgrd480c5f2019-04-03 00:53:34 -0700207 try {
208 return downloadAndFinalizeSpawnResult(
209 cachedResult, /* cacheHit= */ true, spawn, context, remoteOutputsMode);
ishikhman62f54582019-03-18 03:42:42 -0700210 } catch (CacheNotFoundException e) {
211 // No cache hit, so we fall through to local or remote execution.
212 // We set acceptCachedResult to false in order to force the action re-execution.
213 acceptCachedResult = false;
214 }
olaola6f32d5a2017-09-20 17:12:19 +0200215 }
buchgr2efea9d2017-08-11 16:19:00 +0200216 }
olaola6f32d5a2017-09-20 17:12:19 +0200217 } catch (IOException e) {
olaolaf0aa55d2018-08-16 08:51:06 -0700218 return execLocallyAndUploadOrFail(
219 spawn, context, inputMap, actionKey, action, command, uploadLocalResults, e);
ulfjack81940bd2017-03-29 15:38:28 +0000220 }
221
olaola6f32d5a2017-09-20 17:12:19 +0200222 if (remoteExecutor == null) {
223 // Remote execution is disabled and so execute the spawn on the local machine.
olaolaf0aa55d2018-08-16 08:51:06 -0700224 return execLocallyAndUpload(
Sergio Rodriguez Orellana8860c3e2019-07-25 01:12:58 -0700225 spawn, context, inputMap, actionKey, action, command, uploadLocalResults);
George Gensure7131af42018-12-11 09:45:41 -0800226 }
ulfjack29360472017-07-07 09:32:53 -0400227
olaolafea6ab42018-12-20 08:47:00 -0800228 ExecuteRequest.Builder requestBuilder =
George Gensuref600b692018-07-10 14:13:50 -0700229 ExecuteRequest.newBuilder()
230 .setInstanceName(remoteOptions.remoteInstanceName)
olaolaf0aa55d2018-08-16 08:51:06 -0700231 .setActionDigest(actionKey.getDigest())
olaolafea6ab42018-12-20 08:47:00 -0800232 .setSkipCacheLookup(!acceptCachedResult);
233 if (remoteOptions.remoteResultCachePriority != 0) {
234 requestBuilder
235 .getResultsCachePolicyBuilder()
236 .setPriority(remoteOptions.remoteResultCachePriority);
237 }
olaola5c97dcb2018-12-21 10:40:43 -0800238 if (remoteOptions.remoteExecutionPriority != 0) {
239 requestBuilder
240 .getExecutionPolicyBuilder()
241 .setPriority(remoteOptions.remoteExecutionPriority);
242 }
olaola6f32d5a2017-09-20 17:12:19 +0200243 try {
George Gensuref600b692018-07-10 14:13:50 -0700244 return retrier.execute(
245 () -> {
George Gensureda146d22019-03-26 03:17:32 -0700246 ExecuteRequest request = requestBuilder.build();
247
Sergio Rodriguez Orellana8860c3e2019-07-25 01:12:58 -0700248 // Upload the command and all the inputs into the remote cache, if remote caching
249 // is enabled and not disabled using tags, {@see Spawns#mayBeCachedRemotely}
250 if (spawnCacheableRemotely) {
251 try (SilentCloseable c = prof.profile(UPLOAD_TIME, "upload missing inputs")) {
252 Map<Digest, Message> additionalInputs = Maps.newHashMapWithExpectedSize(2);
253 additionalInputs.put(actionKey.getDigest(), action);
254 additionalInputs.put(commandHash, command);
255 remoteCache.ensureInputsPresent(merkleTree, additionalInputs, execRoot);
256 }
ulfjack278a84b2018-11-05 08:44:22 -0800257 }
Sergio Rodriguez Orellana8860c3e2019-07-25 01:12:58 -0700258
ulfjack278a84b2018-11-05 08:44:22 -0800259 ExecuteResponse reply;
Jakob Buchgraber4b3c2eb2019-04-04 02:01:48 -0700260 try (SilentCloseable c = prof.profile(REMOTE_EXECUTION, "execute remotely")) {
ulfjack278a84b2018-11-05 08:44:22 -0800261 reply = remoteExecutor.executeRemotely(request);
262 }
Ed Schoutene2132692019-01-24 04:03:08 -0800263
264 FileOutErr outErr = context.getFileOutErr();
265 String message = reply.getMessage();
Jakob Buchgraber1b8cf022019-03-21 09:22:29 -0700266 ActionResult actionResult = reply.getResult();
267 if ((actionResult.getExitCode() != 0
Ed Schoutene2132692019-01-24 04:03:08 -0800268 || reply.getStatus().getCode() != Code.OK.value())
269 && !message.isEmpty()) {
270 outErr.printErr(message + "\n");
271 }
272
Jakob Buchgraber4b3c2eb2019-04-04 02:01:48 -0700273 try (SilentCloseable c = prof.profile(REMOTE_DOWNLOAD, "download server logs")) {
ulfjack278a84b2018-11-05 08:44:22 -0800274 maybeDownloadServerLogs(reply, actionKey);
275 }
buchgr9626bb42017-08-11 13:54:04 +0200276
buchgrd480c5f2019-04-03 00:53:34 -0700277 try {
278 return downloadAndFinalizeSpawnResult(
279 actionResult, reply.getCachedResult(), spawn, context, remoteOutputsMode);
George Gensureda146d22019-03-26 03:17:32 -0700280 } catch (CacheNotFoundException e) {
281 // No cache hit, so if we retry this execution, we must no longer accept
282 // cached results, it must be reexecuted
283 requestBuilder.setSkipCacheLookup(true);
284 throw e;
ulfjack278a84b2018-11-05 08:44:22 -0800285 }
George Gensuref600b692018-07-10 14:13:50 -0700286 });
olaola6f32d5a2017-09-20 17:12:19 +0200287 } catch (IOException e) {
olaolaf0aa55d2018-08-16 08:51:06 -0700288 return execLocallyAndUploadOrFail(
289 spawn, context, inputMap, actionKey, action, command, uploadLocalResults, e);
olaola6f32d5a2017-09-20 17:12:19 +0200290 }
291 } finally {
292 withMetadata.detach(previous);
buchgr9626bb42017-08-11 13:54:04 +0200293 }
294 }
295
buchgrd480c5f2019-04-03 00:53:34 -0700296 private SpawnResult downloadAndFinalizeSpawnResult(
297 ActionResult actionResult,
298 boolean cacheHit,
299 Spawn spawn,
300 SpawnExecutionContext context,
301 RemoteOutputsMode remoteOutputsMode)
302 throws ExecException, IOException, InterruptedException {
Jakob Buchgraber50c10042019-04-11 02:11:19 -0700303 boolean downloadOutputs =
304 shouldDownloadAllSpawnOutputs(
305 remoteOutputsMode,
306 /* exitCode = */ actionResult.getExitCode(),
307 hasTopLevelOutputs(spawn.getOutputFiles(), topLevelOutputs));
buchgrd480c5f2019-04-03 00:53:34 -0700308 InMemoryOutput inMemoryOutput = null;
Jakob Buchgraber50c10042019-04-11 02:11:19 -0700309 if (downloadOutputs) {
310 try (SilentCloseable c = Profiler.instance().profile(REMOTE_DOWNLOAD, "download outputs")) {
Jakob Buchgraberd75b6cf2019-06-19 08:12:49 -0700311 remoteCache.download(
312 actionResult, execRoot, context.getFileOutErr(), context::lockOutputFiles);
Jakob Buchgraber50c10042019-04-11 02:11:19 -0700313 }
314 } else {
315 PathFragment inMemoryOutputPath = getInMemoryOutputPath(spawn);
316 try (SilentCloseable c =
317 Profiler.instance().profile(REMOTE_DOWNLOAD, "download outputs minimal")) {
318 inMemoryOutput =
319 remoteCache.downloadMinimal(
320 actionResult,
321 spawn.getOutputFiles(),
322 inMemoryOutputPath,
323 context.getFileOutErr(),
324 execRoot,
Jakob Buchgraberd75b6cf2019-06-19 08:12:49 -0700325 context.getMetadataInjector(),
326 context::lockOutputFiles);
Jakob Buchgraber50c10042019-04-11 02:11:19 -0700327 }
buchgrd480c5f2019-04-03 00:53:34 -0700328 }
329 return createSpawnResult(actionResult.getExitCode(), cacheHit, getName(), inMemoryOutput);
330 }
331
philwo849113c2019-02-20 15:09:30 -0800332 @Override
333 public boolean canExec(Spawn spawn) {
334 return Spawns.mayBeExecutedRemotely(spawn);
335 }
336
tomlu09fe0622018-06-19 12:55:39 -0700337 private void maybeWriteParamFilesLocally(Spawn spawn) throws IOException {
tomlub5a727a2019-08-03 23:06:35 -0700338 if (!executionOptions.shouldMaterializeParamFiles()) {
tomlu09fe0622018-06-19 12:55:39 -0700339 return;
340 }
341 for (ActionInput actionInput : spawn.getInputFiles()) {
342 if (actionInput instanceof ParamFileActionInput) {
343 ParamFileActionInput paramFileActionInput = (ParamFileActionInput) actionInput;
344 Path outputPath = execRoot.getRelative(paramFileActionInput.getExecPath());
345 if (outputPath.exists()) {
346 outputPath.delete();
347 }
348 outputPath.getParentDirectory().createDirectoryAndParents();
349 try (OutputStream out = outputPath.getOutputStream()) {
350 paramFileActionInput.writeTo(out);
351 }
352 }
353 }
354 }
355
olaolabf326fa2018-03-21 11:22:12 -0700356 private void maybeDownloadServerLogs(ExecuteResponse resp, ActionKey actionKey)
357 throws InterruptedException {
358 ActionResult result = resp.getResult();
359 if (resp.getServerLogsCount() > 0
360 && (result.getExitCode() != 0 || resp.getStatus().getCode() != Code.OK.value())) {
361 Path parent = logDir.getRelative(actionKey.getDigest().getHash());
362 Path logPath = null;
363 int logCount = 0;
364 for (Map.Entry<String, LogFile> e : resp.getServerLogsMap().entrySet()) {
365 if (e.getValue().getHumanReadable()) {
366 logPath = parent.getRelative(e.getKey());
367 logCount++;
368 try {
olaolaf0aa55d2018-08-16 08:51:06 -0700369 getFromFuture(remoteCache.downloadFile(logPath, e.getValue().getDigest()));
olaolabf326fa2018-03-21 11:22:12 -0700370 } catch (IOException ex) {
371 reportOnce(Event.warn("Failed downloading server logs from the remote cache."));
372 }
373 }
374 }
375 if (logCount > 0 && verboseFailures) {
376 report(
377 Event.info("Server logs of failing action:\n " + (logCount > 1 ? parent : logPath)));
378 }
379 }
380 }
381
olaolaf0aa55d2018-08-16 08:51:06 -0700382 private SpawnResult execLocally(Spawn spawn, SpawnExecutionContext context)
383 throws ExecException, InterruptedException, IOException {
384 return fallbackRunner.get().exec(spawn, context);
385 }
386
387 private SpawnResult execLocallyAndUploadOrFail(
olaola6f32d5a2017-09-20 17:12:19 +0200388 Spawn spawn,
tomlu29e306d2018-04-19 05:41:44 -0700389 SpawnExecutionContext context,
olaola6f32d5a2017-09-20 17:12:19 +0200390 SortedMap<PathFragment, ActionInput> inputMap,
391 ActionKey actionKey,
olaolaf0aa55d2018-08-16 08:51:06 -0700392 Action action,
393 Command command,
olaola6f32d5a2017-09-20 17:12:19 +0200394 boolean uploadLocalResults,
395 IOException cause)
buchgr9626bb42017-08-11 13:54:04 +0200396 throws ExecException, InterruptedException, IOException {
olaolae3cc4812018-03-06 14:15:38 -0800397 // Regardless of cause, if we are interrupted, we should stop without displaying a user-visible
398 // failure/stack trace.
399 if (Thread.currentThread().isInterrupted()) {
400 throw new InterruptedException();
401 }
Benjamin Peterson1532df02019-01-24 08:45:44 -0800402 if (remoteOptions.remoteLocalFallback && !RemoteRetrierUtils.causedByExecTimeout(cause)) {
olaolaf0aa55d2018-08-16 08:51:06 -0700403 return execLocallyAndUpload(
Sergio Rodriguez Orellana8860c3e2019-07-25 01:12:58 -0700404 spawn, context, inputMap, actionKey, action, command, uploadLocalResults);
buchgr9626bb42017-08-11 13:54:04 +0200405 }
Jakob Buchgraberd75b6cf2019-06-19 08:12:49 -0700406 return handleError(cause, context.getFileOutErr(), actionKey, context);
buchgra6591352017-09-01 13:14:19 +0200407 }
408
Jakob Buchgraberd75b6cf2019-06-19 08:12:49 -0700409 private SpawnResult handleError(
410 IOException exception, FileOutErr outErr, ActionKey actionKey, SpawnExecutionContext context)
olaola2732df02018-03-16 08:48:13 -0700411 throws ExecException, InterruptedException, IOException {
Benjamin Peterson1532df02019-01-24 08:45:44 -0800412 if (exception.getCause() instanceof ExecutionStatusException) {
413 ExecutionStatusException e = (ExecutionStatusException) exception.getCause();
olaola2732df02018-03-16 08:48:13 -0700414 if (e.getResponse() != null) {
415 ExecuteResponse resp = e.getResponse();
olaolabf326fa2018-03-21 11:22:12 -0700416 maybeDownloadServerLogs(resp, actionKey);
olaola2732df02018-03-16 08:48:13 -0700417 if (resp.hasResult()) {
418 // We try to download all (partial) results even on server error, for debuggability.
Jakob Buchgraberd75b6cf2019-06-19 08:12:49 -0700419 remoteCache.download(resp.getResult(), execRoot, outErr, context::lockOutputFiles);
olaola2732df02018-03-16 08:48:13 -0700420 }
buchgra6591352017-09-01 13:14:19 +0200421 }
olaola2732df02018-03-16 08:48:13 -0700422 if (e.isExecutionTimeout()) {
423 return new SpawnResult.Builder()
424 .setRunnerName(getName())
425 .setStatus(Status.TIMEOUT)
426 .setExitCode(POSIX_TIMEOUT_EXIT_CODE)
427 .build();
428 }
ulfjack32e7a1c2017-11-28 01:14:34 -0800429 }
430 final Status status;
Benjamin Peterson1532df02019-01-24 08:45:44 -0800431 if (RemoteRetrierUtils.causedByStatus(exception, Code.UNAVAILABLE)) {
ulfjack32e7a1c2017-11-28 01:14:34 -0800432 status = Status.EXECUTION_FAILED_CATASTROPHICALLY;
Benjamin Peterson1532df02019-01-24 08:45:44 -0800433 } else if (exception instanceof CacheNotFoundException) {
buchgra6591352017-09-01 13:14:19 +0200434 status = Status.REMOTE_CACHE_FAILED;
buchgr9626bb42017-08-11 13:54:04 +0200435 } else {
buchgra6591352017-09-01 13:14:19 +0200436 status = Status.EXECUTION_FAILED;
buchgr9626bb42017-08-11 13:54:04 +0200437 }
Jakob Buchgrabera79a4b62019-06-23 02:06:20 -0700438
439 final String errorMessage;
440 if (!verboseFailures) {
441 errorMessage = Utils.grpcAwareErrorMessage(exception);
442 } else {
443 // On --verbose_failures print the whole stack trace
444 errorMessage = Throwables.getStackTraceAsString(exception);
445 }
446
447 return new SpawnResult.Builder()
448 .setRunnerName(getName())
449 .setStatus(status)
450 .setExitCode(ExitCode.REMOTE_ERROR.getNumericExitCode())
451 .setFailureMessage(errorMessage)
452 .build();
ulfjack81940bd2017-03-29 15:38:28 +0000453 }
454
Jakob Buchgrabera79a4b62019-06-23 02:06:20 -0700455
buchgrd480c5f2019-04-03 00:53:34 -0700456 static Action buildAction(Digest command, Digest inputRoot, Duration timeout, boolean cacheable) {
John Cater38ba1932018-01-10 19:01:14 -0800457
ulfjack81940bd2017-03-29 15:38:28 +0000458 Action.Builder action = Action.newBuilder();
459 action.setCommandDigest(command);
460 action.setInputRootDigest(inputRoot);
ulfjackdeab0cf2017-08-08 13:08:24 +0200461 if (!timeout.isZero()) {
462 action.setTimeout(com.google.protobuf.Duration.newBuilder().setSeconds(timeout.getSeconds()));
olaolab259cc42017-07-04 09:58:53 -0400463 }
olaolaa22d0e92017-12-11 07:53:15 -0800464 if (!cacheable) {
465 action.setDoNotCache(true);
466 }
ulfjack81940bd2017-03-29 15:38:28 +0000467 return action.build();
468 }
469
olaolaf0aa55d2018-08-16 08:51:06 -0700470 static Command buildCommand(
471 Collection<? extends ActionInput> outputs,
472 List<String> arguments,
473 ImmutableMap<String, String> env,
John Cater3afb7b02019-01-18 11:44:51 -0800474 @Nullable Platform platform) {
ulfjack81940bd2017-03-29 15:38:28 +0000475 Command.Builder command = Command.newBuilder();
olaolaf0aa55d2018-08-16 08:51:06 -0700476 ArrayList<String> outputFiles = new ArrayList<>();
477 ArrayList<String> outputDirectories = new ArrayList<>();
478 for (ActionInput output : outputs) {
479 String pathString = output.getExecPathString();
480 if (output instanceof Artifact && ((Artifact) output).isTreeArtifact()) {
481 outputDirectories.add(pathString);
482 } else {
483 outputFiles.add(pathString);
484 }
485 }
486 Collections.sort(outputFiles);
487 Collections.sort(outputDirectories);
488 command.addAllOutputFiles(outputFiles);
489 command.addAllOutputDirectories(outputDirectories);
490
John Cater3afb7b02019-01-18 11:44:51 -0800491 if (platform != null) {
olaolaf0aa55d2018-08-16 08:51:06 -0700492 command.setPlatform(platform);
493 }
olaola460a1052017-06-09 04:33:25 +0200494 command.addAllArguments(arguments);
ulfjack81940bd2017-03-29 15:38:28 +0000495 // Sorting the environment pairs by variable name.
ulfjack85c533c2017-08-11 12:23:48 +0200496 TreeSet<String> variables = new TreeSet<>(env.keySet());
ulfjack81940bd2017-03-29 15:38:28 +0000497 for (String var : variables) {
ulfjack85c533c2017-08-11 12:23:48 +0200498 command.addEnvironmentVariablesBuilder().setName(var).setValue(env.get(var));
ulfjack81940bd2017-03-29 15:38:28 +0000499 }
500 return command.build();
501 }
ulfjack29360472017-07-07 09:32:53 -0400502
Jakob Buchgraber562fcf92017-07-27 12:51:13 +0200503 private Map<Path, Long> getInputCtimes(SortedMap<PathFragment, ActionInput> inputMap) {
buchgrd480c5f2019-04-03 00:53:34 -0700504 HashMap<Path, Long> ctimes = new HashMap<>();
Ola Rozenfeldccbe40a2017-07-17 10:05:59 +0200505 for (Map.Entry<PathFragment, ActionInput> e : inputMap.entrySet()) {
506 ActionInput input = e.getValue();
ulfjack3e288682018-01-08 09:31:17 -0800507 if (input instanceof VirtualActionInput) {
Ola Rozenfeldccbe40a2017-07-17 10:05:59 +0200508 continue;
509 }
510 Path path = execRoot.getRelative(input.getExecPathString());
511 try {
512 ctimes.put(path, path.stat().getLastChangeTime());
513 } catch (IOException ex) {
514 // Put a token value indicating an exception; this is used so that if the exception
515 // is raised both before and after the execution, it is ignored, but if it is raised only
516 // one of the times, it triggers a remote cache upload skip.
517 ctimes.put(path, -1L);
518 }
519 }
520 return ctimes;
521 }
522
Jakob Buchgraber562fcf92017-07-27 12:51:13 +0200523 @VisibleForTesting
524 SpawnResult execLocallyAndUpload(
525 Spawn spawn,
tomlu29e306d2018-04-19 05:41:44 -0700526 SpawnExecutionContext context,
Jakob Buchgraber562fcf92017-07-27 12:51:13 +0200527 SortedMap<PathFragment, ActionInput> inputMap,
olaolaf0aa55d2018-08-16 08:51:06 -0700528 ActionKey actionKey,
529 Action action,
530 Command command,
531 boolean uploadLocalResults)
olaola6f32d5a2017-09-20 17:12:19 +0200532 throws ExecException, IOException, InterruptedException {
Ola Rozenfeldccbe40a2017-07-17 10:05:59 +0200533 Map<Path, Long> ctimesBefore = getInputCtimes(inputMap);
olaolaf0aa55d2018-08-16 08:51:06 -0700534 SpawnResult result = execLocally(spawn, context);
Ola Rozenfeldccbe40a2017-07-17 10:05:59 +0200535 Map<Path, Long> ctimesAfter = getInputCtimes(inputMap);
Jakob Buchgraberbac30fe2019-01-28 05:24:23 -0800536 uploadLocalResults =
537 uploadLocalResults && Status.SUCCESS.equals(result.status()) && result.exitCode() == 0;
538 if (!uploadLocalResults) {
539 return result;
540 }
541
Ola Rozenfeldccbe40a2017-07-17 10:05:59 +0200542 for (Map.Entry<Path, Long> e : ctimesBefore.entrySet()) {
543 // Skip uploading to remote cache, because an input was modified during execution.
544 if (!ctimesAfter.get(e.getKey()).equals(e.getValue())) {
545 return result;
546 }
547 }
Jakob Buchgraberbac30fe2019-01-28 05:24:23 -0800548
Benjamin Petersondd3ddb02018-05-03 09:20:08 -0700549 Collection<Path> outputFiles = resolveActionInputs(execRoot, spawn.getOutputFiles());
Jakob Buchgraber4b3c2eb2019-04-04 02:01:48 -0700550 try (SilentCloseable c = Profiler.instance().profile(UPLOAD_TIME, "upload outputs")) {
olaolaf0aa55d2018-08-16 08:51:06 -0700551 remoteCache.upload(
Jakob Buchgraberbac30fe2019-01-28 05:24:23 -0800552 actionKey, action, command, execRoot, outputFiles, context.getFileOutErr());
Benjamin Peterson3ff87f72017-08-21 18:41:45 +0200553 } catch (IOException e) {
554 if (verboseFailures) {
555 report(Event.debug("Upload to remote cache failed: " + e.getMessage()));
556 } else {
557 reportOnce(Event.warn("Some artifacts failed be uploaded to the remote cache."));
558 }
559 }
ulfjack29360472017-07-07 09:32:53 -0400560 return result;
561 }
ulfjack3fbd7c42017-07-10 13:19:19 +0200562
Benjamin Peterson3ff87f72017-08-21 18:41:45 +0200563 private void reportOnce(Event evt) {
564 if (warningReported.compareAndSet(false, true)) {
565 report(evt);
566 }
567 }
568
569 private void report(Event evt) {
570 if (cmdlineReporter != null) {
571 cmdlineReporter.handle(evt);
572 }
573 }
574
philwod6ce2642018-07-30 05:24:50 -0700575 /**
576 * Resolve a collection of {@link com.google.devtools.build.lib.actions.ActionInput}s to {@link
577 * Path}s.
578 */
Benjamin Petersondd3ddb02018-05-03 09:20:08 -0700579 static Collection<Path> resolveActionInputs(
580 Path execRoot, Collection<? extends ActionInput> actionInputs) {
buchgrd480c5f2019-04-03 00:53:34 -0700581 return actionInputs.stream()
Benjamin Petersondd3ddb02018-05-03 09:20:08 -0700582 .map((inp) -> execRoot.getRelative(inp.getExecPath()))
583 .collect(ImmutableList.toImmutableList());
ulfjack85c533c2017-08-11 12:23:48 +0200584 }
ulfjack81940bd2017-03-29 15:38:28 +0000585}