ulfjack | 9274cba | 2017-08-11 23:19:48 +0200 | [diff] [blame] | 1 | // 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 | package com.google.devtools.build.lib.remote; |
| 15 | |
Jakob Buchgraber | 321138f | 2018-05-07 05:24:53 -0700 | [diff] [blame] | 16 | import static com.google.common.base.Strings.isNullOrEmpty; |
| 17 | |
olaola | f0aa55d | 2018-08-16 08:51:06 -0700 | [diff] [blame] | 18 | import build.bazel.remote.execution.v2.Action; |
| 19 | import build.bazel.remote.execution.v2.ActionResult; |
| 20 | import build.bazel.remote.execution.v2.Command; |
John Cater | 3afb7b0 | 2019-01-18 11:44:51 -0800 | [diff] [blame] | 21 | import build.bazel.remote.execution.v2.Platform; |
ulfjack | 9274cba | 2017-08-11 23:19:48 +0200 | [diff] [blame] | 22 | import com.google.devtools.build.lib.actions.ActionInput; |
| 23 | import com.google.devtools.build.lib.actions.ExecException; |
| 24 | import com.google.devtools.build.lib.actions.ExecutionStrategy; |
shahan | 602cc85 | 2018-06-06 20:09:57 -0700 | [diff] [blame] | 25 | import com.google.devtools.build.lib.actions.FileArtifactValue; |
ulfjack | 9274cba | 2017-08-11 23:19:48 +0200 | [diff] [blame] | 26 | import com.google.devtools.build.lib.actions.Spawn; |
ruperts | da40fbf | 2017-09-22 05:59:42 +0200 | [diff] [blame] | 27 | import com.google.devtools.build.lib.actions.SpawnResult; |
| 28 | import com.google.devtools.build.lib.actions.SpawnResult.Status; |
olaola | a22d0e9 | 2017-12-11 07:53:15 -0800 | [diff] [blame] | 29 | import com.google.devtools.build.lib.actions.Spawns; |
Mike Morearty | d8ac06a | 2018-04-12 01:59:34 -0700 | [diff] [blame] | 30 | import com.google.devtools.build.lib.actions.cache.VirtualActionInput; |
ulfjack | 9274cba | 2017-08-11 23:19:48 +0200 | [diff] [blame] | 31 | import com.google.devtools.build.lib.concurrent.ThreadSafety.ThreadSafe; |
Benjamin Peterson | 3ff87f7 | 2017-08-21 18:41:45 +0200 | [diff] [blame] | 32 | import com.google.devtools.build.lib.events.Event; |
| 33 | import com.google.devtools.build.lib.events.Reporter; |
ulfjack | 9274cba | 2017-08-11 23:19:48 +0200 | [diff] [blame] | 34 | import com.google.devtools.build.lib.exec.SpawnCache; |
Benjamin Peterson | 7e1c7bc | 2018-05-03 04:30:19 -0700 | [diff] [blame] | 35 | import com.google.devtools.build.lib.exec.SpawnRunner.ProgressStatus; |
tomlu | 29e306d | 2018-04-19 05:41:44 -0700 | [diff] [blame] | 36 | import com.google.devtools.build.lib.exec.SpawnRunner.SpawnExecutionContext; |
ulfjack | 5f436f3 | 2018-11-06 05:17:23 -0800 | [diff] [blame] | 37 | import com.google.devtools.build.lib.profiler.Profiler; |
| 38 | import com.google.devtools.build.lib.profiler.SilentCloseable; |
ulfjack | 9274cba | 2017-08-11 23:19:48 +0200 | [diff] [blame] | 39 | import com.google.devtools.build.lib.remote.TreeNodeRepository.TreeNode; |
Googler | 922d1e6 | 2018-03-05 14:49:00 -0800 | [diff] [blame] | 40 | import com.google.devtools.build.lib.remote.util.DigestUtil; |
| 41 | import com.google.devtools.build.lib.remote.util.DigestUtil.ActionKey; |
| 42 | import com.google.devtools.build.lib.remote.util.TracingMetadataUtils; |
ulfjack | 9274cba | 2017-08-11 23:19:48 +0200 | [diff] [blame] | 43 | import com.google.devtools.build.lib.vfs.Path; |
| 44 | import com.google.devtools.build.lib.vfs.PathFragment; |
olaola | 6f32d5a | 2017-09-20 17:12:19 +0200 | [diff] [blame] | 45 | import io.grpc.Context; |
ulfjack | 9274cba | 2017-08-11 23:19:48 +0200 | [diff] [blame] | 46 | import java.io.IOException; |
| 47 | import java.util.Collection; |
Jakob Buchgraber | 321138f | 2018-05-07 05:24:53 -0700 | [diff] [blame] | 48 | import java.util.HashSet; |
ulfjack | 9274cba | 2017-08-11 23:19:48 +0200 | [diff] [blame] | 49 | import java.util.NoSuchElementException; |
Jakob Buchgraber | 321138f | 2018-05-07 05:24:53 -0700 | [diff] [blame] | 50 | import java.util.Set; |
ulfjack | 9274cba | 2017-08-11 23:19:48 +0200 | [diff] [blame] | 51 | import java.util.SortedMap; |
Benjamin Peterson | 3ff87f7 | 2017-08-21 18:41:45 +0200 | [diff] [blame] | 52 | import javax.annotation.Nullable; |
ulfjack | 9274cba | 2017-08-11 23:19:48 +0200 | [diff] [blame] | 53 | |
Jakob Buchgraber | 321138f | 2018-05-07 05:24:53 -0700 | [diff] [blame] | 54 | /** A remote {@link SpawnCache} implementation. */ |
ulfjack | 9274cba | 2017-08-11 23:19:48 +0200 | [diff] [blame] | 55 | @ThreadSafe // If the RemoteActionCache implementation is thread-safe. |
| 56 | @ExecutionStrategy( |
Jakob Buchgraber | 321138f | 2018-05-07 05:24:53 -0700 | [diff] [blame] | 57 | name = {"remote-cache"}, |
| 58 | contextType = SpawnCache.class) |
ulfjack | 9274cba | 2017-08-11 23:19:48 +0200 | [diff] [blame] | 59 | final class RemoteSpawnCache implements SpawnCache { |
| 60 | private final Path execRoot; |
| 61 | private final RemoteOptions options; |
ulfjack | 9274cba | 2017-08-11 23:19:48 +0200 | [diff] [blame] | 62 | |
Hadrien Chauvin | 3d0a04d | 2017-12-20 08:45:45 -0800 | [diff] [blame] | 63 | private final AbstractRemoteActionCache remoteCache; |
olaola | 6f32d5a | 2017-09-20 17:12:19 +0200 | [diff] [blame] | 64 | private final String buildRequestId; |
| 65 | private final String commandId; |
ulfjack | 9274cba | 2017-08-11 23:19:48 +0200 | [diff] [blame] | 66 | |
Benjamin Peterson | 3ff87f7 | 2017-08-21 18:41:45 +0200 | [diff] [blame] | 67 | @Nullable private final Reporter cmdlineReporter; |
| 68 | |
Jakob Buchgraber | 321138f | 2018-05-07 05:24:53 -0700 | [diff] [blame] | 69 | private final Set<String> reportedErrors = new HashSet<>(); |
Benjamin Peterson | 3ff87f7 | 2017-08-21 18:41:45 +0200 | [diff] [blame] | 70 | |
buchgr | 559a07d | 2017-11-30 11:09:35 -0800 | [diff] [blame] | 71 | private final DigestUtil digestUtil; |
| 72 | |
olaola | 6f32d5a | 2017-09-20 17:12:19 +0200 | [diff] [blame] | 73 | RemoteSpawnCache( |
| 74 | Path execRoot, |
| 75 | RemoteOptions options, |
Hadrien Chauvin | 3d0a04d | 2017-12-20 08:45:45 -0800 | [diff] [blame] | 76 | AbstractRemoteActionCache remoteCache, |
olaola | 6f32d5a | 2017-09-20 17:12:19 +0200 | [diff] [blame] | 77 | String buildRequestId, |
| 78 | String commandId, |
buchgr | 559a07d | 2017-11-30 11:09:35 -0800 | [diff] [blame] | 79 | @Nullable Reporter cmdlineReporter, |
| 80 | DigestUtil digestUtil) { |
ulfjack | 9274cba | 2017-08-11 23:19:48 +0200 | [diff] [blame] | 81 | this.execRoot = execRoot; |
| 82 | this.options = options; |
ulfjack | 9274cba | 2017-08-11 23:19:48 +0200 | [diff] [blame] | 83 | this.remoteCache = remoteCache; |
Benjamin Peterson | 3ff87f7 | 2017-08-21 18:41:45 +0200 | [diff] [blame] | 84 | this.cmdlineReporter = cmdlineReporter; |
olaola | 6f32d5a | 2017-09-20 17:12:19 +0200 | [diff] [blame] | 85 | this.buildRequestId = buildRequestId; |
| 86 | this.commandId = commandId; |
buchgr | 559a07d | 2017-11-30 11:09:35 -0800 | [diff] [blame] | 87 | this.digestUtil = digestUtil; |
ulfjack | 9274cba | 2017-08-11 23:19:48 +0200 | [diff] [blame] | 88 | } |
| 89 | |
| 90 | @Override |
tomlu | 29e306d | 2018-04-19 05:41:44 -0700 | [diff] [blame] | 91 | public CacheHandle lookup(Spawn spawn, SpawnExecutionContext context) |
ulfjack | 9274cba | 2017-08-11 23:19:48 +0200 | [diff] [blame] | 92 | throws InterruptedException, IOException, ExecException { |
Benjamin Peterson | bb1c2a1 | 2019-01-22 02:12:19 -0800 | [diff] [blame] | 93 | if (!Spawns.mayBeCached(spawn)) { |
| 94 | return SpawnCache.NO_RESULT_NO_STORE; |
| 95 | } |
| 96 | boolean checkCache = options.remoteAcceptCached; |
Benjamin Peterson | 7e1c7bc | 2018-05-03 04:30:19 -0700 | [diff] [blame] | 97 | |
| 98 | if (checkCache) { |
| 99 | context.report(ProgressStatus.CHECKING_CACHE, "remote-cache"); |
| 100 | } |
| 101 | |
ulfjack | 5f436f3 | 2018-11-06 05:17:23 -0800 | [diff] [blame] | 102 | SortedMap<PathFragment, ActionInput> inputMap = context.getInputMapping(true); |
ulfjack | 9274cba | 2017-08-11 23:19:48 +0200 | [diff] [blame] | 103 | // Temporary hack: the TreeNodeRepository should be created and maintained upstream! |
| 104 | TreeNodeRepository repository = |
Jakob Buchgraber | 12ab12e | 2019-01-23 07:42:30 -0800 | [diff] [blame^] | 105 | new TreeNodeRepository(execRoot, context.getMetadataProvider(), digestUtil); |
ulfjack | 5f436f3 | 2018-11-06 05:17:23 -0800 | [diff] [blame] | 106 | TreeNode inputRoot; |
| 107 | try (SilentCloseable c = Profiler.instance().profile("RemoteCache.computeMerkleDigests")) { |
| 108 | inputRoot = repository.buildFromActionInputs(inputMap); |
| 109 | repository.computeMerkleDigests(inputRoot); |
| 110 | } |
John Cater | 3afb7b0 | 2019-01-18 11:44:51 -0800 | [diff] [blame] | 111 | |
| 112 | // Get the remote platform properties. |
| 113 | Platform platform = |
| 114 | RemoteSpawnRunner.parsePlatform( |
| 115 | spawn.getExecutionPlatform(), options.remoteDefaultPlatformProperties); |
| 116 | |
olaola | f0aa55d | 2018-08-16 08:51:06 -0700 | [diff] [blame] | 117 | Command command = |
| 118 | RemoteSpawnRunner.buildCommand( |
John Cater | 3afb7b0 | 2019-01-18 11:44:51 -0800 | [diff] [blame] | 119 | spawn.getOutputFiles(), spawn.getArguments(), spawn.getEnvironment(), platform); |
ulfjack | 5f436f3 | 2018-11-06 05:17:23 -0800 | [diff] [blame] | 120 | 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 Peterson | bb1c2a1 | 2019-01-22 02:12:19 -0800 | [diff] [blame] | 128 | true); |
ulfjack | 5f436f3 | 2018-11-06 05:17:23 -0800 | [diff] [blame] | 129 | // Look up action cache, and reuse the action output if it is found. |
| 130 | actionKey = digestUtil.computeActionKey(action); |
| 131 | } |
Benjamin Peterson | 7e1c7bc | 2018-05-03 04:30:19 -0700 | [diff] [blame] | 132 | |
olaola | 6f32d5a | 2017-09-20 17:12:19 +0200 | [diff] [blame] | 133 | Context withMetadata = |
| 134 | TracingMetadataUtils.contextWithMetadata(buildRequestId, commandId, actionKey); |
Benjamin Peterson | 7e1c7bc | 2018-05-03 04:30:19 -0700 | [diff] [blame] | 135 | |
| 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 { |
ulfjack | 5f436f3 | 2018-11-06 05:17:23 -0800 | [diff] [blame] | 141 | ActionResult result; |
| 142 | try (SilentCloseable c = Profiler.instance().profile("RemoteCache.getCachedActionResult")) { |
| 143 | result = remoteCache.getCachedActionResult(actionKey); |
| 144 | } |
Benjamin Peterson | 7e1c7bc | 2018-05-03 04:30:19 -0700 | [diff] [blame] | 145 | 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. |
ulfjack | 5f436f3 | 2018-11-06 05:17:23 -0800 | [diff] [blame] | 149 | try (SilentCloseable c = Profiler.instance().profile("RemoteCache.download")) { |
| 150 | remoteCache.download(result, execRoot, context.getFileOutErr()); |
| 151 | } |
Benjamin Peterson | 7e1c7bc | 2018-05-03 04:30:19 -0700 | [diff] [blame] | 152 | 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 Peterson | 7e1c7bc | 2018-05-03 04:30:19 -0700 | [diff] [blame] | 161 | } catch (IOException e) { |
olaola | bf6a63d | 2018-09-26 09:14:05 -0700 | [diff] [blame] | 162 | 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 Buchgraber | 321138f | 2018-05-07 05:24:53 -0700 | [diff] [blame] | 169 | } |
Benjamin Peterson | 7e1c7bc | 2018-05-03 04:30:19 -0700 | [diff] [blame] | 170 | } finally { |
| 171 | withMetadata.detach(previous); |
ulfjack | 9274cba | 2017-08-11 23:19:48 +0200 | [diff] [blame] | 172 | } |
ulfjack | 9274cba | 2017-08-11 23:19:48 +0200 | [diff] [blame] | 173 | } |
olaola | ba8b0b3 | 2017-10-20 09:48:56 +0200 | [diff] [blame] | 174 | 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 Peterson | dd3ddb0 | 2018-05-03 09:20:08 -0700 | [diff] [blame] | 192 | public void store(SpawnResult result) |
| 193 | throws ExecException, InterruptedException, IOException { |
ulfjack | 8896d2e | 2018-01-19 02:55:21 -0800 | [diff] [blame] | 194 | if (options.experimentalGuardAgainstConcurrentChanges) { |
ulfjack | 5f436f3 | 2018-11-06 05:17:23 -0800 | [diff] [blame] | 195 | try (SilentCloseable c = |
| 196 | Profiler.instance().profile("RemoteCache.checkForConcurrentModifications")) { |
ulfjack | 8896d2e | 2018-01-19 02:55:21 -0800 | [diff] [blame] | 197 | checkForConcurrentModifications(); |
| 198 | } catch (IOException e) { |
| 199 | report(Event.warn(e.getMessage())); |
| 200 | return; |
| 201 | } |
| 202 | } |
Benjamin Peterson | bb1c2a1 | 2019-01-22 02:12:19 -0800 | [diff] [blame] | 203 | boolean uploadAction = Status.SUCCESS.equals(result.status()) && result.exitCode() == 0; |
olaola | ba8b0b3 | 2017-10-20 09:48:56 +0200 | [diff] [blame] | 204 | Context previous = withMetadata.attach(); |
Benjamin Peterson | dd3ddb0 | 2018-05-03 09:20:08 -0700 | [diff] [blame] | 205 | Collection<Path> files = |
| 206 | RemoteSpawnRunner.resolveActionInputs(execRoot, spawn.getOutputFiles()); |
ulfjack | 5f436f3 | 2018-11-06 05:17:23 -0800 | [diff] [blame] | 207 | try (SilentCloseable c = Profiler.instance().profile("RemoteCache.upload")) { |
olaola | f0aa55d | 2018-08-16 08:51:06 -0700 | [diff] [blame] | 208 | remoteCache.upload( |
| 209 | actionKey, action, command, execRoot, files, context.getFileOutErr(), uploadAction); |
olaola | ba8b0b3 | 2017-10-20 09:48:56 +0200 | [diff] [blame] | 210 | } catch (IOException e) { |
Jakob Buchgraber | 321138f | 2018-05-07 05:24:53 -0700 | [diff] [blame] | 211 | String errorMsg = e.getMessage(); |
| 212 | if (isNullOrEmpty(errorMsg)) { |
| 213 | errorMsg = e.getClass().getSimpleName(); |
olaola | ba8b0b3 | 2017-10-20 09:48:56 +0200 | [diff] [blame] | 214 | } |
Jakob Buchgraber | 321138f | 2018-05-07 05:24:53 -0700 | [diff] [blame] | 215 | errorMsg = "Error writing to the remote cache:\n" + errorMsg; |
| 216 | report(Event.warn(errorMsg)); |
olaola | ba8b0b3 | 2017-10-20 09:48:56 +0200 | [diff] [blame] | 217 | } finally { |
| 218 | withMetadata.detach(previous); |
| 219 | } |
| 220 | } |
| 221 | |
| 222 | @Override |
| 223 | public void close() {} |
ulfjack | 8896d2e | 2018-01-19 02:55:21 -0800 | [diff] [blame] | 224 | |
| 225 | private void checkForConcurrentModifications() throws IOException { |
| 226 | for (ActionInput input : inputMap.values()) { |
Mike Morearty | d8ac06a | 2018-04-12 01:59:34 -0700 | [diff] [blame] | 227 | if (input instanceof VirtualActionInput) { |
| 228 | continue; |
| 229 | } |
shahan | 499503b | 2018-06-07 18:57:07 -0700 | [diff] [blame] | 230 | FileArtifactValue metadata = context.getMetadataProvider().getMetadata(input); |
olaola | f0aa55d | 2018-08-16 08:51:06 -0700 | [diff] [blame] | 231 | Path path = execRoot.getRelative(input.getExecPath()); |
| 232 | if (metadata.wasModifiedSinceDigest(path)) { |
| 233 | throw new IOException(path + " was modified during execution"); |
ulfjack | 8896d2e | 2018-01-19 02:55:21 -0800 | [diff] [blame] | 234 | } |
| 235 | } |
| 236 | } |
olaola | ba8b0b3 | 2017-10-20 09:48:56 +0200 | [diff] [blame] | 237 | }; |
| 238 | } else { |
| 239 | return SpawnCache.NO_RESULT_NO_STORE; |
| 240 | } |
ulfjack | 9274cba | 2017-08-11 23:19:48 +0200 | [diff] [blame] | 241 | } |
Benjamin Peterson | 3ff87f7 | 2017-08-21 18:41:45 +0200 | [diff] [blame] | 242 | |
Benjamin Peterson | 3ff87f7 | 2017-08-21 18:41:45 +0200 | [diff] [blame] | 243 | private void report(Event evt) { |
Jakob Buchgraber | 321138f | 2018-05-07 05:24:53 -0700 | [diff] [blame] | 244 | 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 Peterson | 3ff87f7 | 2017-08-21 18:41:45 +0200 | [diff] [blame] | 253 | cmdlineReporter.handle(evt); |
| 254 | } |
| 255 | } |
ulfjack | 9274cba | 2017-08-11 23:19:48 +0200 | [diff] [blame] | 256 | } |