|
19 | 19 | import com.google.common.base.Preconditions;
|
20 | 20 | import com.google.common.collect.ImmutableSet;
|
21 | 21 | import com.google.common.flogger.GoogleLogger;
|
22 |
| -import com.google.common.util.concurrent.FutureCallback; |
23 |
| -import com.google.common.util.concurrent.Futures; |
24 | 22 | import com.google.common.util.concurrent.ListenableFuture;
|
25 | 23 | import com.google.common.util.concurrent.MoreExecutors;
|
26 | 24 | import com.google.devtools.build.lib.actions.ActionInput;
|
|
34 | 32 | import com.google.devtools.build.lib.profiler.SilentCloseable;
|
35 | 33 | import com.google.devtools.build.lib.remote.common.CacheNotFoundException;
|
36 | 34 | import com.google.devtools.build.lib.remote.common.RemoteActionExecutionContext;
|
| 35 | +import com.google.devtools.build.lib.remote.util.AsyncTaskCache; |
37 | 36 | import com.google.devtools.build.lib.remote.util.DigestUtil;
|
| 37 | +import com.google.devtools.build.lib.remote.util.RxFutures; |
38 | 38 | import com.google.devtools.build.lib.remote.util.TracingMetadataUtils;
|
39 | 39 | import com.google.devtools.build.lib.remote.util.Utils;
|
40 | 40 | import com.google.devtools.build.lib.sandbox.SandboxHelpers;
|
41 | 41 | import com.google.devtools.build.lib.vfs.Path;
|
| 42 | +import io.reactivex.rxjava3.core.Completable; |
42 | 43 | import java.io.IOException;
|
43 | 44 | import java.util.HashMap;
|
44 |
| -import java.util.HashSet; |
45 | 45 | import java.util.Map;
|
46 |
| -import java.util.Set; |
47 |
| -import javax.annotation.concurrent.GuardedBy; |
48 | 46 |
|
49 | 47 | /**
|
50 | 48 | * Stages output files that are stored remotely to the local filesystem.
|
|
55 | 53 | class RemoteActionInputFetcher implements ActionInputPrefetcher {
|
56 | 54 |
|
57 | 55 | private static final GoogleLogger logger = GoogleLogger.forEnclosingClass();
|
| 56 | + private final AsyncTaskCache.NoResult<Path> downloadCache = AsyncTaskCache.NoResult.create(); |
58 | 57 |
|
59 | 58 | private final Object lock = new Object();
|
60 | 59 |
|
61 |
| - /** Set of successfully downloaded output files. */ |
62 |
| - @GuardedBy("lock") |
63 |
| - private final Set<Path> downloadedPaths = new HashSet<>(); |
64 |
| - |
65 |
| - @VisibleForTesting |
66 |
| - @GuardedBy("lock") |
67 |
| - final Map<Path, ListenableFuture<Void>> downloadsInProgress = new HashMap<>(); |
68 |
| - |
69 | 60 | private final String buildRequestId;
|
70 | 61 | private final String commandId;
|
71 | 62 | private final RemoteCache remoteCache;
|
@@ -110,11 +101,8 @@ public void prefetchFiles(
|
110 | 101 |
|
111 | 102 | Path path = execRoot.getRelative(input.getExecPath());
|
112 | 103 | synchronized (lock) {
|
113 |
| - if (downloadedPaths.contains(path)) { |
114 |
| - continue; |
115 |
| - } |
116 |
| - ListenableFuture<Void> download = downloadFileAsync(path, metadata); |
117 |
| - downloadsToWaitFor.putIfAbsent(path, download); |
| 104 | + downloadsToWaitFor.computeIfAbsent( |
| 105 | + path, key -> RxFutures.toListenableFuture(downloadFileAsync(path, metadata))); |
118 | 106 | }
|
119 | 107 | }
|
120 | 108 | }
|
@@ -143,65 +131,59 @@ public void prefetchFiles(
|
143 | 131 | }
|
144 | 132 |
|
145 | 133 | ImmutableSet<Path> downloadedFiles() {
|
146 |
| - synchronized (lock) { |
147 |
| - return ImmutableSet.copyOf(downloadedPaths); |
148 |
| - } |
| 134 | + return downloadCache.getFinishedTasks(); |
| 135 | + } |
| 136 | + |
| 137 | + ImmutableSet<Path> downloadsInProgress() { |
| 138 | + return downloadCache.getInProgressTasks(); |
| 139 | + } |
| 140 | + |
| 141 | + @VisibleForTesting |
| 142 | + AsyncTaskCache.NoResult<Path> getDownloadCache() { |
| 143 | + return downloadCache; |
149 | 144 | }
|
150 | 145 |
|
151 | 146 | void downloadFile(Path path, FileArtifactValue metadata)
|
152 | 147 | throws IOException, InterruptedException {
|
153 |
| - Utils.getFromFuture(downloadFileAsync(path, metadata)); |
| 148 | + Utils.getFromFuture(RxFutures.toListenableFuture(downloadFileAsync(path, metadata))); |
154 | 149 | }
|
155 | 150 |
|
156 |
| - private ListenableFuture<Void> downloadFileAsync(Path path, FileArtifactValue metadata) |
157 |
| - throws IOException { |
158 |
| - synchronized (lock) { |
159 |
| - if (downloadedPaths.contains(path)) { |
160 |
| - return Futures.immediateFuture(null); |
161 |
| - } |
| 151 | + private Completable downloadFileAsync(Path path, FileArtifactValue metadata) { |
| 152 | + Completable download = |
| 153 | + RxFutures.toCompletable( |
| 154 | + () -> { |
| 155 | + RequestMetadata requestMetadata = |
| 156 | + TracingMetadataUtils.buildMetadata( |
| 157 | + buildRequestId, commandId, metadata.getActionId()); |
| 158 | + RemoteActionExecutionContext context = |
| 159 | + RemoteActionExecutionContext.create(requestMetadata); |
| 160 | + |
| 161 | + Digest digest = DigestUtil.buildDigest(metadata.getDigest(), metadata.getSize()); |
| 162 | + |
| 163 | + return remoteCache.downloadFile(context, path, digest); |
| 164 | + }, |
| 165 | + MoreExecutors.directExecutor()) |
| 166 | + .doOnComplete(() -> finalizeDownload(path)) |
| 167 | + .doOnError(error -> deletePartialDownload(path)) |
| 168 | + .doOnDispose(() -> deletePartialDownload(path)); |
| 169 | + |
| 170 | + return downloadCache.executeIfNot(path, download); |
| 171 | + } |
162 | 172 |
|
163 |
| - ListenableFuture<Void> download = downloadsInProgress.get(path); |
164 |
| - if (download == null) { |
165 |
| - RequestMetadata requestMetadata = |
166 |
| - TracingMetadataUtils.buildMetadata(buildRequestId, commandId, metadata.getActionId()); |
167 |
| - RemoteActionExecutionContext context = RemoteActionExecutionContext.create(requestMetadata); |
168 |
| - |
169 |
| - Digest digest = DigestUtil.buildDigest(metadata.getDigest(), metadata.getSize()); |
170 |
| - download = remoteCache.downloadFile(context, path, digest); |
171 |
| - downloadsInProgress.put(path, download); |
172 |
| - Futures.addCallback( |
173 |
| - download, |
174 |
| - new FutureCallback<Void>() { |
175 |
| - @Override |
176 |
| - public void onSuccess(Void v) { |
177 |
| - synchronized (lock) { |
178 |
| - downloadsInProgress.remove(path); |
179 |
| - downloadedPaths.add(path); |
180 |
| - } |
181 |
| - |
182 |
| - try { |
183 |
| - path.chmod(0755); |
184 |
| - } catch (IOException e) { |
185 |
| - logger.atWarning().withCause(e).log("Failed to chmod 755 on %s", path); |
186 |
| - } |
187 |
| - } |
188 |
| - |
189 |
| - @Override |
190 |
| - public void onFailure(Throwable t) { |
191 |
| - synchronized (lock) { |
192 |
| - downloadsInProgress.remove(path); |
193 |
| - } |
194 |
| - try { |
195 |
| - path.delete(); |
196 |
| - } catch (IOException e) { |
197 |
| - logger.atWarning().withCause(e).log( |
198 |
| - "Failed to delete output file after incomplete download: %s", path); |
199 |
| - } |
200 |
| - } |
201 |
| - }, |
202 |
| - MoreExecutors.directExecutor()); |
203 |
| - } |
204 |
| - return download; |
| 173 | + private void finalizeDownload(Path path) { |
| 174 | + try { |
| 175 | + path.chmod(0755); |
| 176 | + } catch (IOException e) { |
| 177 | + logger.atWarning().withCause(e).log("Failed to chmod 755 on %s", path); |
| 178 | + } |
| 179 | + } |
| 180 | + |
| 181 | + private void deletePartialDownload(Path path) { |
| 182 | + try { |
| 183 | + path.delete(); |
| 184 | + } catch (IOException e) { |
| 185 | + logger.atWarning().withCause(e).log( |
| 186 | + "Failed to delete output file after incomplete download: %s", path); |
205 | 187 | }
|
206 | 188 | }
|
207 | 189 | }
|
0 commit comments