blob: 75cbf7397ab5102970540868d98da0f096d21b58 [file] [log] [blame]
Jakob Buchgraber6ec5e872019-03-29 03:53:17 -07001// Copyright 2019 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
buchgr357cb1e2019-04-03 06:15:02 -070016import build.bazel.remote.execution.v2.Digest;
George Gensure3ef8fb92020-05-06 09:49:48 -070017import build.bazel.remote.execution.v2.RequestMetadata;
buchgr357cb1e2019-04-03 06:15:02 -070018import com.google.common.annotations.VisibleForTesting;
Jakob Buchgraber6ec5e872019-03-29 03:53:17 -070019import com.google.common.base.Preconditions;
20import com.google.common.collect.ImmutableSet;
janakrc3bcb982020-04-14 06:50:08 -070021import com.google.common.flogger.GoogleLogger;
buchgr357cb1e2019-04-03 06:15:02 -070022import com.google.common.util.concurrent.FutureCallback;
23import com.google.common.util.concurrent.Futures;
Jakob Buchgraber6ec5e872019-03-29 03:53:17 -070024import com.google.common.util.concurrent.ListenableFuture;
buchgr357cb1e2019-04-03 06:15:02 -070025import com.google.common.util.concurrent.MoreExecutors;
Jakob Buchgraber6ec5e872019-03-29 03:53:17 -070026import com.google.devtools.build.lib.actions.ActionInput;
27import com.google.devtools.build.lib.actions.ActionInputPrefetcher;
28import com.google.devtools.build.lib.actions.FileArtifactValue;
29import com.google.devtools.build.lib.actions.MetadataProvider;
30import com.google.devtools.build.lib.actions.cache.VirtualActionInput;
31import com.google.devtools.build.lib.profiler.Profiler;
Jakob Buchgraber4b3c2eb2019-04-04 02:01:48 -070032import com.google.devtools.build.lib.profiler.ProfilerTask;
Jakob Buchgraber6ec5e872019-03-29 03:53:17 -070033import com.google.devtools.build.lib.profiler.SilentCloseable;
Jakob Buchgraber18462692019-11-06 04:34:16 -080034import com.google.devtools.build.lib.remote.common.CacheNotFoundException;
Jakob Buchgraber6ec5e872019-03-29 03:53:17 -070035import com.google.devtools.build.lib.remote.util.DigestUtil;
George Gensure3ef8fb92020-05-06 09:49:48 -070036import com.google.devtools.build.lib.remote.util.TracingMetadataUtils;
Jakob Buchgraber6ec5e872019-03-29 03:53:17 -070037import com.google.devtools.build.lib.vfs.Path;
38import io.grpc.Context;
39import java.io.IOException;
40import java.io.OutputStream;
41import java.util.HashMap;
42import java.util.HashSet;
43import java.util.Map;
44import java.util.Set;
buchgr357cb1e2019-04-03 06:15:02 -070045import java.util.concurrent.ExecutionException;
Jakob Buchgraber6ec5e872019-03-29 03:53:17 -070046import javax.annotation.concurrent.GuardedBy;
47
48/**
49 * Stages output files that are stored remotely to the local filesystem.
50 *
51 * <p>This is necessary for remote caching/execution when {@code
buchgrd480c5f2019-04-03 00:53:34 -070052 * --experimental_remote_download_outputs=minimal} is specified.
Jakob Buchgraber6ec5e872019-03-29 03:53:17 -070053 */
54class RemoteActionInputFetcher implements ActionInputPrefetcher {
55
janakrc3bcb982020-04-14 06:50:08 -070056 private static final GoogleLogger logger = GoogleLogger.forEnclosingClass();
buchgr357cb1e2019-04-03 06:15:02 -070057
Jakob Buchgraber6ec5e872019-03-29 03:53:17 -070058 private final Object lock = new Object();
59
buchgr357cb1e2019-04-03 06:15:02 -070060 /** Set of successfully downloaded output files. */
Jakob Buchgraber6ec5e872019-03-29 03:53:17 -070061 @GuardedBy("lock")
62 private final Set<Path> downloadedPaths = new HashSet<>();
63
buchgr357cb1e2019-04-03 06:15:02 -070064 @VisibleForTesting
Jakob Buchgraber6ec5e872019-03-29 03:53:17 -070065 @GuardedBy("lock")
buchgr357cb1e2019-04-03 06:15:02 -070066 final Map<Path, ListenableFuture<Void>> downloadsInProgress = new HashMap<>();
Jakob Buchgraber6ec5e872019-03-29 03:53:17 -070067
Jakob Buchgraber60566092019-11-11 06:28:22 -080068 private final RemoteCache remoteCache;
Jakob Buchgraber6ec5e872019-03-29 03:53:17 -070069 private final Path execRoot;
George Gensure3ef8fb92020-05-06 09:49:48 -070070 private final RequestMetadata requestMetadata;
Jakob Buchgraber6ec5e872019-03-29 03:53:17 -070071
George Gensure3ef8fb92020-05-06 09:49:48 -070072 RemoteActionInputFetcher(
73 RemoteCache remoteCache, Path execRoot, RequestMetadata requestMetadata) {
Jakob Buchgraber6ec5e872019-03-29 03:53:17 -070074 this.remoteCache = Preconditions.checkNotNull(remoteCache);
75 this.execRoot = Preconditions.checkNotNull(execRoot);
George Gensure3ef8fb92020-05-06 09:49:48 -070076 this.requestMetadata = Preconditions.checkNotNull(requestMetadata);
Jakob Buchgraber6ec5e872019-03-29 03:53:17 -070077 }
78
79 /**
80 * Fetches remotely stored action outputs, that are inputs to this spawn, and stores them under
81 * their path in the output base.
82 *
83 * <p>This method blocks until all downloads have finished.
84 *
85 * <p>This method is safe to be called concurrently from spawn runners before running any local
86 * spawn.
87 */
88 @Override
89 public void prefetchFiles(
90 Iterable<? extends ActionInput> inputs, MetadataProvider metadataProvider)
91 throws IOException, InterruptedException {
Jakob Buchgraber4b3c2eb2019-04-04 02:01:48 -070092 try (SilentCloseable c =
93 Profiler.instance().profile(ProfilerTask.REMOTE_DOWNLOAD, "stage remote inputs")) {
Jakob Buchgraber6ec5e872019-03-29 03:53:17 -070094 Map<Path, ListenableFuture<Void>> downloadsToWaitFor = new HashMap<>();
95 for (ActionInput input : inputs) {
96 if (input instanceof VirtualActionInput) {
97 VirtualActionInput paramFileActionInput = (VirtualActionInput) input;
98 Path outputPath = execRoot.getRelative(paramFileActionInput.getExecPath());
99 outputPath.getParentDirectory().createDirectoryAndParents();
100 try (OutputStream out = outputPath.getOutputStream()) {
101 paramFileActionInput.writeTo(out);
102 }
103 } else {
104 FileArtifactValue metadata = metadataProvider.getMetadata(input);
105 if (metadata == null || !metadata.isRemote()) {
106 continue;
107 }
108
109 Path path = execRoot.getRelative(input.getExecPath());
110 synchronized (lock) {
111 if (downloadedPaths.contains(path)) {
112 continue;
113 }
buchgr357cb1e2019-04-03 06:15:02 -0700114 ListenableFuture<Void> download = downloadFileAsync(path, metadata);
Jakob Buchgraber6ec5e872019-03-29 03:53:17 -0700115 downloadsToWaitFor.putIfAbsent(path, download);
116 }
117 }
118 }
119
George Gensure7cf5a5e2020-04-28 08:48:19 -0700120 try {
121 RemoteCache.waitForBulkTransfer(
122 downloadsToWaitFor.values(), /* cancelRemainingOnInterrupt=*/ true);
123 } catch (BulkTransferException e) {
124 if (e.onlyCausedByCacheNotFoundException()) {
125 BulkTransferException bulkAnnotatedException = new BulkTransferException();
126 for (Throwable t : e.getSuppressed()) {
127 IOException annotatedException =
buchgr357cb1e2019-04-03 06:15:02 -0700128 new IOException(
129 String.format(
130 "Failed to fetch file with hash '%s' because it does not exist remotely."
utsav-dbx79d9ef22020-06-09 03:44:26 -0700131 + " --remote_download_outputs=minimal does not work if"
buchgr357cb1e2019-04-03 06:15:02 -0700132 + " your remote cache evicts files during builds.",
George Gensure7cf5a5e2020-04-28 08:48:19 -0700133 ((CacheNotFoundException) t).getMissingDigest().getHash()));
134 bulkAnnotatedException.add(annotatedException);
Jakob Buchgraber6ec5e872019-03-29 03:53:17 -0700135 }
George Gensure7cf5a5e2020-04-28 08:48:19 -0700136 e = bulkAnnotatedException;
Jakob Buchgraber6ec5e872019-03-29 03:53:17 -0700137 }
George Gensure7cf5a5e2020-04-28 08:48:19 -0700138 throw e;
Jakob Buchgraber6ec5e872019-03-29 03:53:17 -0700139 }
140 }
141 }
142
143 ImmutableSet<Path> downloadedFiles() {
144 synchronized (lock) {
145 return ImmutableSet.copyOf(downloadedPaths);
146 }
147 }
buchgr357cb1e2019-04-03 06:15:02 -0700148
149 void downloadFile(Path path, FileArtifactValue metadata)
150 throws IOException, InterruptedException {
151 try {
152 downloadFileAsync(path, metadata).get();
153 } catch (ExecutionException e) {
154 if (e.getCause() instanceof IOException) {
155 throw (IOException) e.getCause();
156 }
157 throw new IOException(e.getCause());
158 }
159 }
160
161 private ListenableFuture<Void> downloadFileAsync(Path path, FileArtifactValue metadata)
162 throws IOException {
163 synchronized (lock) {
164 if (downloadedPaths.contains(path)) {
165 return Futures.immediateFuture(null);
166 }
167
168 ListenableFuture<Void> download = downloadsInProgress.get(path);
169 if (download == null) {
George Gensure3ef8fb92020-05-06 09:49:48 -0700170 Context ctx =
171 TracingMetadataUtils.contextWithMetadata(
172 requestMetadata.toBuilder().setActionId(metadata.getActionId()).build());
buchgr357cb1e2019-04-03 06:15:02 -0700173 Context prevCtx = ctx.attach();
174 try {
175 Digest digest = DigestUtil.buildDigest(metadata.getDigest(), metadata.getSize());
176 download = remoteCache.downloadFile(path, digest);
177 downloadsInProgress.put(path, download);
178 Futures.addCallback(
179 download,
180 new FutureCallback<Void>() {
181 @Override
182 public void onSuccess(Void v) {
183 synchronized (lock) {
184 downloadsInProgress.remove(path);
185 downloadedPaths.add(path);
186 }
187
188 try {
Jakob Buchgraberc7e8a922019-06-19 10:04:26 -0700189 path.chmod(0755);
buchgr357cb1e2019-04-03 06:15:02 -0700190 } catch (IOException e) {
janakrc3bcb982020-04-14 06:50:08 -0700191 logger.atWarning().withCause(e).log("Failed to chmod 755 on %s", path);
buchgr357cb1e2019-04-03 06:15:02 -0700192 }
193 }
194
195 @Override
196 public void onFailure(Throwable t) {
197 synchronized (lock) {
198 downloadsInProgress.remove(path);
199 }
200 try {
201 path.delete();
202 } catch (IOException e) {
janakrc3bcb982020-04-14 06:50:08 -0700203 logger.atWarning().withCause(e).log(
204 "Failed to delete output file after incomplete download: %s", path);
buchgr357cb1e2019-04-03 06:15:02 -0700205 }
206 }
207 },
208 MoreExecutors.directExecutor());
209 } finally {
210 ctx.detach(prevCtx);
211 }
212 }
213 return download;
214 }
215 }
Jakob Buchgraber6ec5e872019-03-29 03:53:17 -0700216}