| // Copyright 2019 The Bazel Authors. All rights reserved. | 
 | // | 
 | // Licensed under the Apache License, Version 2.0 (the "License"); | 
 | // you may not use this file except in compliance with the License. | 
 | // You may obtain a copy of the License at | 
 | // | 
 | // http://www.apache.org/licenses/LICENSE-2.0 | 
 | // | 
 | // Unless required by applicable law or agreed to in writing, software | 
 | // distributed under the License is distributed on an "AS IS" BASIS, | 
 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
 | // See the License for the specific language governing permissions and | 
 | // limitations under the License. | 
 | package com.google.devtools.build.lib.exec; | 
 |  | 
 | import com.google.common.collect.ImmutableMap; | 
 | import com.google.devtools.build.lib.actions.Artifact; | 
 | import com.google.devtools.build.lib.actions.ArtifactPathResolver; | 
 | import com.google.devtools.build.lib.actions.ExecException; | 
 | import com.google.devtools.build.lib.actions.RunfilesSupplier; | 
 | import com.google.devtools.build.lib.analysis.RunfilesSupport; | 
 | import com.google.devtools.build.lib.profiler.Profiler; | 
 | import com.google.devtools.build.lib.profiler.ProfilerTask; | 
 | import com.google.devtools.build.lib.util.io.OutErr; | 
 | import com.google.devtools.build.lib.vfs.Path; | 
 | import com.google.devtools.build.lib.vfs.PathFragment; | 
 | import java.io.IOException; | 
 | import java.util.Arrays; | 
 | import java.util.HashMap; | 
 | import java.util.Map; | 
 | import javax.annotation.concurrent.GuardedBy; | 
 | import javax.annotation.concurrent.ThreadSafe; | 
 |  | 
 | /** | 
 |  * Utility used in local execution to create a runfiles tree if {@code --nobuild_runfile_links} has | 
 |  * been specified. | 
 |  * | 
 |  * <p>It is safe to call {@link #updateRunfilesDirectory} concurrently. | 
 |  */ | 
 | @ThreadSafe | 
 | public class RunfilesTreeUpdater { | 
 |  | 
 |   public static final RunfilesTreeUpdater INSTANCE = new RunfilesTreeUpdater(); | 
 |  | 
 |   private final Object lock = new Object(); | 
 |  | 
 |   private static final class LockWithRefcnt { | 
 |     int refcnt = 1; | 
 |   } | 
 |  | 
 |   /** | 
 |    * Poor man's reference counted object pool. | 
 |    * | 
 |    * <p>Maintains a mapping of runfiles directory to a monitor. The monitor maintains a counter that | 
 |    * tracks how many threads it is acquired by at the moment. If the count drops to zero the mapping | 
 |    * is removed. | 
 |    */ | 
 |   @GuardedBy("lock") | 
 |   private final Map<PathFragment, LockWithRefcnt> locksWithRefcnt = new HashMap<>(); | 
 |  | 
 |   private RunfilesTreeUpdater() {} | 
 |  | 
 |   private static void updateRunfilesTree( | 
 |       Path execRoot, | 
 |       PathFragment runfilesDir, | 
 |       BinTools binTools, | 
 |       ImmutableMap<String, String> env, | 
 |       OutErr outErr, | 
 |       boolean enableRunfiles) | 
 |       throws IOException, ExecException { | 
 |     Path runfilesDirPath = execRoot.getRelative(runfilesDir); | 
 |     Path inputManifest = RunfilesSupport.inputManifestPath(runfilesDirPath); | 
 |     if (!inputManifest.exists()) { | 
 |       return; | 
 |     } | 
 |  | 
 |     Path outputManifest = RunfilesSupport.outputManifestPath(runfilesDirPath); | 
 |     try { | 
 |       // Avoid rebuilding the runfiles directory if the manifest in it matches the input manifest, | 
 |       // implying the symlinks exist and are already up to date. If the output manifest is a | 
 |       // symbolic link, it is likely a symbolic link to the input manifest, so we cannot trust it as | 
 |       // an up-to-date check. | 
 |       if (!outputManifest.isSymbolicLink() | 
 |           && Arrays.equals(outputManifest.getDigest(), inputManifest.getDigest())) { | 
 |         return; | 
 |       } | 
 |     } catch (IOException e) { | 
 |       // Ignore it - we will just try to create runfiles directory. | 
 |     } | 
 |  | 
 |     if (!runfilesDirPath.exists()) { | 
 |       runfilesDirPath.createDirectoryAndParents(); | 
 |     } | 
 |  | 
 |     SymlinkTreeHelper helper = | 
 |         new SymlinkTreeHelper(inputManifest, runfilesDirPath, /* filesetTree= */ false); | 
 |     helper.createSymlinks(execRoot, outErr, binTools, env, enableRunfiles); | 
 |   } | 
 |  | 
 |   private LockWithRefcnt getLockAndIncrementRefcnt(PathFragment runfilesDirectory) { | 
 |     synchronized (lock) { | 
 |       LockWithRefcnt lock = locksWithRefcnt.get(runfilesDirectory); | 
 |       if (lock != null) { | 
 |         lock.refcnt++; | 
 |         return lock; | 
 |       } | 
 |       lock = new LockWithRefcnt(); | 
 |       locksWithRefcnt.put(runfilesDirectory, lock); | 
 |       return lock; | 
 |     } | 
 |   } | 
 |  | 
 |   private void decrementRefcnt(PathFragment runfilesDirectory) { | 
 |     synchronized (lock) { | 
 |       LockWithRefcnt lock = locksWithRefcnt.get(runfilesDirectory); | 
 |       lock.refcnt--; | 
 |       if (lock.refcnt == 0) { | 
 |         if (!locksWithRefcnt.remove(runfilesDirectory, lock)) { | 
 |           throw new IllegalStateException( | 
 |               String.format( | 
 |                   "Failed to remove lock for dir '%s'." + " This is a bug.", runfilesDirectory)); | 
 |         } | 
 |       } | 
 |     } | 
 |   } | 
 |  | 
 |   public void updateRunfilesDirectory( | 
 |       Path execRoot, | 
 |       RunfilesSupplier runfilesSupplier, | 
 |       ArtifactPathResolver pathResolver, | 
 |       BinTools binTools, | 
 |       ImmutableMap<String, String> env, | 
 |       OutErr outErr) | 
 |       throws ExecException, IOException { | 
 |     for (Map.Entry<PathFragment, Map<PathFragment, Artifact>> runfiles : | 
 |         runfilesSupplier.getMappings(pathResolver).entrySet()) { | 
 |       PathFragment runfilesDir = runfiles.getKey(); | 
 |       if (runfilesSupplier.isBuildRunfileLinks(runfilesDir)) { | 
 |         continue; | 
 |       } | 
 |  | 
 |       try { | 
 |         long startTime = Profiler.nanoTimeMaybe(); | 
 |         // Synchronize runfiles tree generation on the runfiles directory in order to prevent | 
 |         // concurrent modifications of the runfiles tree. In particular this can happen for sharded | 
 |         // tests and --runs_per_test > 1 in which case multiple actions use the same runfiles tree. | 
 |         synchronized (getLockAndIncrementRefcnt(runfilesDir)) { | 
 |           Profiler.instance() | 
 |               .logSimpleTask(startTime, ProfilerTask.WAIT, "Waiting to create runfiles tree"); | 
 |           updateRunfilesTree( | 
 |               execRoot, | 
 |               runfilesDir, | 
 |               binTools, | 
 |               env, | 
 |               outErr, | 
 |               runfilesSupplier.isRunfileLinksEnabled(runfilesDir)); | 
 |         } | 
 |       } finally { | 
 |         decrementRefcnt(runfilesDir); | 
 |       } | 
 |     } | 
 |   } | 
 | } |