blob: 8e5a0f868dabc5696e9d208a2ac0da80ed001e02 [file] [log] [blame]
ulfjack9274cba2017-08-11 23:19:48 +02001// 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.remote;
15
Jakob Buchgraber321138f2018-05-07 05:24:53 -070016import static com.google.common.base.Strings.isNullOrEmpty;
17
olaolaf0aa55d2018-08-16 08:51:06 -070018import build.bazel.remote.execution.v2.Action;
19import build.bazel.remote.execution.v2.ActionResult;
20import build.bazel.remote.execution.v2.Command;
John Cater3afb7b02019-01-18 11:44:51 -080021import build.bazel.remote.execution.v2.Platform;
ulfjack9274cba2017-08-11 23:19:48 +020022import com.google.devtools.build.lib.actions.ActionInput;
23import com.google.devtools.build.lib.actions.ExecException;
24import com.google.devtools.build.lib.actions.ExecutionStrategy;
shahan602cc852018-06-06 20:09:57 -070025import com.google.devtools.build.lib.actions.FileArtifactValue;
ulfjack9274cba2017-08-11 23:19:48 +020026import com.google.devtools.build.lib.actions.Spawn;
rupertsda40fbf2017-09-22 05:59:42 +020027import com.google.devtools.build.lib.actions.SpawnResult;
28import com.google.devtools.build.lib.actions.SpawnResult.Status;
olaolaa22d0e92017-12-11 07:53:15 -080029import com.google.devtools.build.lib.actions.Spawns;
Mike Moreartyd8ac06a2018-04-12 01:59:34 -070030import com.google.devtools.build.lib.actions.cache.VirtualActionInput;
ulfjack9274cba2017-08-11 23:19:48 +020031import com.google.devtools.build.lib.concurrent.ThreadSafety.ThreadSafe;
Benjamin Peterson3ff87f72017-08-21 18:41:45 +020032import com.google.devtools.build.lib.events.Event;
33import com.google.devtools.build.lib.events.Reporter;
ulfjack9274cba2017-08-11 23:19:48 +020034import com.google.devtools.build.lib.exec.SpawnCache;
Benjamin Peterson7e1c7bc2018-05-03 04:30:19 -070035import com.google.devtools.build.lib.exec.SpawnRunner.ProgressStatus;
tomlu29e306d2018-04-19 05:41:44 -070036import com.google.devtools.build.lib.exec.SpawnRunner.SpawnExecutionContext;
ulfjack5f436f32018-11-06 05:17:23 -080037import com.google.devtools.build.lib.profiler.Profiler;
38import com.google.devtools.build.lib.profiler.SilentCloseable;
ulfjack9274cba2017-08-11 23:19:48 +020039import com.google.devtools.build.lib.remote.TreeNodeRepository.TreeNode;
Googler922d1e62018-03-05 14:49:00 -080040import com.google.devtools.build.lib.remote.util.DigestUtil;
41import com.google.devtools.build.lib.remote.util.DigestUtil.ActionKey;
42import com.google.devtools.build.lib.remote.util.TracingMetadataUtils;
ulfjack9274cba2017-08-11 23:19:48 +020043import com.google.devtools.build.lib.vfs.Path;
44import com.google.devtools.build.lib.vfs.PathFragment;
olaola6f32d5a2017-09-20 17:12:19 +020045import io.grpc.Context;
ulfjack9274cba2017-08-11 23:19:48 +020046import java.io.IOException;
47import java.util.Collection;
Jakob Buchgraber321138f2018-05-07 05:24:53 -070048import java.util.HashSet;
ulfjack9274cba2017-08-11 23:19:48 +020049import java.util.NoSuchElementException;
Jakob Buchgraber321138f2018-05-07 05:24:53 -070050import java.util.Set;
ulfjack9274cba2017-08-11 23:19:48 +020051import java.util.SortedMap;
Benjamin Peterson3ff87f72017-08-21 18:41:45 +020052import javax.annotation.Nullable;
ulfjack9274cba2017-08-11 23:19:48 +020053
Jakob Buchgraber321138f2018-05-07 05:24:53 -070054/** A remote {@link SpawnCache} implementation. */
ulfjack9274cba2017-08-11 23:19:48 +020055@ThreadSafe // If the RemoteActionCache implementation is thread-safe.
56@ExecutionStrategy(
Jakob Buchgraber321138f2018-05-07 05:24:53 -070057 name = {"remote-cache"},
58 contextType = SpawnCache.class)
ulfjack9274cba2017-08-11 23:19:48 +020059final class RemoteSpawnCache implements SpawnCache {
60 private final Path execRoot;
61 private final RemoteOptions options;
ulfjack9274cba2017-08-11 23:19:48 +020062
Hadrien Chauvin3d0a04d2017-12-20 08:45:45 -080063 private final AbstractRemoteActionCache remoteCache;
olaola6f32d5a2017-09-20 17:12:19 +020064 private final String buildRequestId;
65 private final String commandId;
ulfjack9274cba2017-08-11 23:19:48 +020066
Benjamin Peterson3ff87f72017-08-21 18:41:45 +020067 @Nullable private final Reporter cmdlineReporter;
68
Jakob Buchgraber321138f2018-05-07 05:24:53 -070069 private final Set<String> reportedErrors = new HashSet<>();
Benjamin Peterson3ff87f72017-08-21 18:41:45 +020070
buchgr559a07d2017-11-30 11:09:35 -080071 private final DigestUtil digestUtil;
72
olaola6f32d5a2017-09-20 17:12:19 +020073 RemoteSpawnCache(
74 Path execRoot,
75 RemoteOptions options,
Hadrien Chauvin3d0a04d2017-12-20 08:45:45 -080076 AbstractRemoteActionCache remoteCache,
olaola6f32d5a2017-09-20 17:12:19 +020077 String buildRequestId,
78 String commandId,
buchgr559a07d2017-11-30 11:09:35 -080079 @Nullable Reporter cmdlineReporter,
80 DigestUtil digestUtil) {
ulfjack9274cba2017-08-11 23:19:48 +020081 this.execRoot = execRoot;
82 this.options = options;
ulfjack9274cba2017-08-11 23:19:48 +020083 this.remoteCache = remoteCache;
Benjamin Peterson3ff87f72017-08-21 18:41:45 +020084 this.cmdlineReporter = cmdlineReporter;
olaola6f32d5a2017-09-20 17:12:19 +020085 this.buildRequestId = buildRequestId;
86 this.commandId = commandId;
buchgr559a07d2017-11-30 11:09:35 -080087 this.digestUtil = digestUtil;
ulfjack9274cba2017-08-11 23:19:48 +020088 }
89
90 @Override
tomlu29e306d2018-04-19 05:41:44 -070091 public CacheHandle lookup(Spawn spawn, SpawnExecutionContext context)
ulfjack9274cba2017-08-11 23:19:48 +020092 throws InterruptedException, IOException, ExecException {
Benjamin Petersonbb1c2a12019-01-22 02:12:19 -080093 if (!Spawns.mayBeCached(spawn)) {
94 return SpawnCache.NO_RESULT_NO_STORE;
95 }
96 boolean checkCache = options.remoteAcceptCached;
Benjamin Peterson7e1c7bc2018-05-03 04:30:19 -070097
98 if (checkCache) {
99 context.report(ProgressStatus.CHECKING_CACHE, "remote-cache");
100 }
101
ulfjack5f436f32018-11-06 05:17:23 -0800102 SortedMap<PathFragment, ActionInput> inputMap = context.getInputMapping(true);
ulfjack9274cba2017-08-11 23:19:48 +0200103 // Temporary hack: the TreeNodeRepository should be created and maintained upstream!
104 TreeNodeRepository repository =
Jakob Buchgraber12ab12e2019-01-23 07:42:30 -0800105 new TreeNodeRepository(execRoot, context.getMetadataProvider(), digestUtil);
ulfjack5f436f32018-11-06 05:17:23 -0800106 TreeNode inputRoot;
107 try (SilentCloseable c = Profiler.instance().profile("RemoteCache.computeMerkleDigests")) {
108 inputRoot = repository.buildFromActionInputs(inputMap);
109 repository.computeMerkleDigests(inputRoot);
110 }
John Cater3afb7b02019-01-18 11:44:51 -0800111
112 // Get the remote platform properties.
113 Platform platform =
114 RemoteSpawnRunner.parsePlatform(
115 spawn.getExecutionPlatform(), options.remoteDefaultPlatformProperties);
116
olaolaf0aa55d2018-08-16 08:51:06 -0700117 Command command =
118 RemoteSpawnRunner.buildCommand(
John Cater3afb7b02019-01-18 11:44:51 -0800119 spawn.getOutputFiles(), spawn.getArguments(), spawn.getEnvironment(), platform);
ulfjack5f436f32018-11-06 05:17:23 -0800120 Action action;
121 final ActionKey actionKey;
122 try (SilentCloseable c = Profiler.instance().profile("RemoteCache.buildAction")) {
123 action =
124 RemoteSpawnRunner.buildAction(
125 digestUtil.compute(command),
126 repository.getMerkleDigest(inputRoot),
127 context.getTimeout(),
Benjamin Petersonbb1c2a12019-01-22 02:12:19 -0800128 true);
ulfjack5f436f32018-11-06 05:17:23 -0800129 // Look up action cache, and reuse the action output if it is found.
130 actionKey = digestUtil.computeActionKey(action);
131 }
Benjamin Peterson7e1c7bc2018-05-03 04:30:19 -0700132
olaola6f32d5a2017-09-20 17:12:19 +0200133 Context withMetadata =
134 TracingMetadataUtils.contextWithMetadata(buildRequestId, commandId, actionKey);
Benjamin Peterson7e1c7bc2018-05-03 04:30:19 -0700135
136 if (checkCache) {
137 // Metadata will be available in context.current() until we detach.
138 // This is done via a thread-local variable.
139 Context previous = withMetadata.attach();
140 try {
ulfjack5f436f32018-11-06 05:17:23 -0800141 ActionResult result;
142 try (SilentCloseable c = Profiler.instance().profile("RemoteCache.getCachedActionResult")) {
143 result = remoteCache.getCachedActionResult(actionKey);
144 }
Benjamin Peterson7e1c7bc2018-05-03 04:30:19 -0700145 if (result != null) {
146 // We don't cache failed actions, so we know the outputs exist.
147 // For now, download all outputs locally; in the future, we can reuse the digests to
148 // just update the TreeNodeRepository and continue the build.
ulfjack5f436f32018-11-06 05:17:23 -0800149 try (SilentCloseable c = Profiler.instance().profile("RemoteCache.download")) {
150 remoteCache.download(result, execRoot, context.getFileOutErr());
151 }
Benjamin Peterson7e1c7bc2018-05-03 04:30:19 -0700152 SpawnResult spawnResult =
153 new SpawnResult.Builder()
154 .setStatus(Status.SUCCESS)
155 .setExitCode(result.getExitCode())
156 .setCacheHit(true)
157 .setRunnerName("remote cache hit")
158 .build();
159 return SpawnCache.success(spawnResult);
160 }
Benjamin Peterson7e1c7bc2018-05-03 04:30:19 -0700161 } catch (IOException e) {
olaolabf6a63d2018-09-26 09:14:05 -0700162 if (!AbstractRemoteActionCache.causedByCacheMiss(e)) {
163 String errorMsg = e.getMessage();
164 if (isNullOrEmpty(errorMsg)) {
165 errorMsg = e.getClass().getSimpleName();
166 }
167 errorMsg = "Error reading from the remote cache:\n" + errorMsg;
168 report(Event.warn(errorMsg));
Jakob Buchgraber321138f2018-05-07 05:24:53 -0700169 }
Benjamin Peterson7e1c7bc2018-05-03 04:30:19 -0700170 } finally {
171 withMetadata.detach(previous);
ulfjack9274cba2017-08-11 23:19:48 +0200172 }
ulfjack9274cba2017-08-11 23:19:48 +0200173 }
olaolaba8b0b32017-10-20 09:48:56 +0200174 if (options.remoteUploadLocalResults) {
175 return new CacheHandle() {
176 @Override
177 public boolean hasResult() {
178 return false;
179 }
180
181 @Override
182 public SpawnResult getResult() {
183 throw new NoSuchElementException();
184 }
185
186 @Override
187 public boolean willStore() {
188 return true;
189 }
190
191 @Override
Benjamin Petersondd3ddb02018-05-03 09:20:08 -0700192 public void store(SpawnResult result)
193 throws ExecException, InterruptedException, IOException {
ulfjack8896d2e2018-01-19 02:55:21 -0800194 if (options.experimentalGuardAgainstConcurrentChanges) {
ulfjack5f436f32018-11-06 05:17:23 -0800195 try (SilentCloseable c =
196 Profiler.instance().profile("RemoteCache.checkForConcurrentModifications")) {
ulfjack8896d2e2018-01-19 02:55:21 -0800197 checkForConcurrentModifications();
198 } catch (IOException e) {
199 report(Event.warn(e.getMessage()));
200 return;
201 }
202 }
Benjamin Petersonbb1c2a12019-01-22 02:12:19 -0800203 boolean uploadAction = Status.SUCCESS.equals(result.status()) && result.exitCode() == 0;
olaolaba8b0b32017-10-20 09:48:56 +0200204 Context previous = withMetadata.attach();
Benjamin Petersondd3ddb02018-05-03 09:20:08 -0700205 Collection<Path> files =
206 RemoteSpawnRunner.resolveActionInputs(execRoot, spawn.getOutputFiles());
ulfjack5f436f32018-11-06 05:17:23 -0800207 try (SilentCloseable c = Profiler.instance().profile("RemoteCache.upload")) {
olaolaf0aa55d2018-08-16 08:51:06 -0700208 remoteCache.upload(
209 actionKey, action, command, execRoot, files, context.getFileOutErr(), uploadAction);
olaolaba8b0b32017-10-20 09:48:56 +0200210 } catch (IOException e) {
Jakob Buchgraber321138f2018-05-07 05:24:53 -0700211 String errorMsg = e.getMessage();
212 if (isNullOrEmpty(errorMsg)) {
213 errorMsg = e.getClass().getSimpleName();
olaolaba8b0b32017-10-20 09:48:56 +0200214 }
Jakob Buchgraber321138f2018-05-07 05:24:53 -0700215 errorMsg = "Error writing to the remote cache:\n" + errorMsg;
216 report(Event.warn(errorMsg));
olaolaba8b0b32017-10-20 09:48:56 +0200217 } finally {
218 withMetadata.detach(previous);
219 }
220 }
221
222 @Override
223 public void close() {}
ulfjack8896d2e2018-01-19 02:55:21 -0800224
225 private void checkForConcurrentModifications() throws IOException {
226 for (ActionInput input : inputMap.values()) {
Mike Moreartyd8ac06a2018-04-12 01:59:34 -0700227 if (input instanceof VirtualActionInput) {
228 continue;
229 }
shahan499503b2018-06-07 18:57:07 -0700230 FileArtifactValue metadata = context.getMetadataProvider().getMetadata(input);
olaolaf0aa55d2018-08-16 08:51:06 -0700231 Path path = execRoot.getRelative(input.getExecPath());
232 if (metadata.wasModifiedSinceDigest(path)) {
233 throw new IOException(path + " was modified during execution");
ulfjack8896d2e2018-01-19 02:55:21 -0800234 }
235 }
236 }
olaolaba8b0b32017-10-20 09:48:56 +0200237 };
238 } else {
239 return SpawnCache.NO_RESULT_NO_STORE;
240 }
ulfjack9274cba2017-08-11 23:19:48 +0200241 }
Benjamin Peterson3ff87f72017-08-21 18:41:45 +0200242
Benjamin Peterson3ff87f72017-08-21 18:41:45 +0200243 private void report(Event evt) {
Jakob Buchgraber321138f2018-05-07 05:24:53 -0700244 if (cmdlineReporter == null) {
245 return;
246 }
247
248 synchronized (this) {
249 if (reportedErrors.contains(evt.getMessage())) {
250 return;
251 }
252 reportedErrors.add(evt.getMessage());
Benjamin Peterson3ff87f72017-08-21 18:41:45 +0200253 cmdlineReporter.handle(evt);
254 }
255 }
ulfjack9274cba2017-08-11 23:19:48 +0200256}