/// <summary> /// Checks if the cache contains stored outputs for a given builder with any dependency fingerprint /// </summary> /// <param name="builder">Builder key</param> /// <returns>Returns <c>true</c> if there are stored outputs for the given builder.</returns> public bool ContainsAny(BuildKey builder) { lock (cache) { return(cache.ContainsKey(builder)); } }
/// <summary> /// Store build outputs in the cache by reading them from the file system /// </summary> /// <param name="builder">Builder key (first part of the key)</param> /// <param name="fingerprint">Dependency fingerprint created when the builder was executed (second part of the key)</param> /// <param name="outputs">Target-relative path of the build outputs to be cached</param> /// <param name="targetRoot">File system abstraction of the root target directory</param> public void Store(BuildKey builder, IDependencyFingerprint fingerprint, IEnumerable <TargetRelativePath> outputs, IFileSystemDirectory targetRoot) { MemoryCacheItem item = GetOrCreate(builder); var map = new ConcurrentDictionary <TargetRelativePath, byte[]>(); Parallel.ForEach(outputs, outputPath => { if (targetRoot.Exists(outputPath)) { using (var stream = targetRoot.ReadBinaryFile(outputPath)) { var buf = new byte[stream.Length]; stream.Read(buf, 0, buf.Length); map.TryAdd(outputPath, buf); } } else { map.TryAdd(outputPath, null); } }); item.Update(fingerprint, map); }
/// <summary> /// Runs this builder /// </summary> /// <param name="context"> </param> /// <returns>Returns a set of generated files, in suite relative paths</returns> public ISet <TargetRelativePath> Run(IBuildContext context) { var currentFingerprint = wrappedBuilder.Dependencies.CreateFingerprint(); var buildKey = new BuildKey(wrappedBuilder.GetType(), wrappedBuilder.Uid); cache.LockForBuilder(buildKey); try { if (cache.Contains(buildKey, currentFingerprint)) { log.DebugFormat("Restoring cached build outputs for {0}", buildKey); return(cache.Restore(buildKey, targetDir)); } else { log.DebugFormat("Running builder {0}", buildKey); var files = wrappedBuilder.Run(context); log.DebugFormat("Storing build outputs of {0}", buildKey); cache.Store(buildKey, currentFingerprint, files, targetDir); return(files); } } finally { cache.UnlockForBuilder(buildKey); } }
/// <summary> /// Restores the stored files for a given builder to a file system directory /// /// <para>The cache only stores the latest stored results and this is what will be restored /// to the target directory. To verify if it was generated with the correct dependency fingerprint, /// use <see cref="IBuildCache.Contains"/>.</para> /// <para>To ensure thread safety, use <see cref="IBuildCache.LockForBuilder"/>.</para> /// </summary> /// <param name="builder">Builder key</param> /// <param name="targetRoot">Target file system directory</param> /// <returns>Returns the target root relative paths of all the restored files</returns> public ISet <TargetRelativePath> Restore(BuildKey builder, IFileSystemDirectory targetRoot) { MemoryCacheItem item; lock (cache) cache.TryGetValue(builder, out item); if (item != null) { var outputs = item.Outputs; var paths = new HashSet <TargetRelativePath>(); foreach (var pair in outputs) { if (pair.Value != null) { using (var stream = targetRoot.CreateBinaryFile(pair.Key)) stream.Write(pair.Value, 0, pair.Value.Length); } paths.Add(pair.Key); } return(paths); } else { return(new HashSet <TargetRelativePath>()); } }
/// <summary> /// Removes the lock put by the <see cref="IBuildCache.LockForBuilder"/> method. /// </summary> /// <param name="builder">Builder key</param> public void UnlockForBuilder(BuildKey builder) { ReaderWriterLockSlim lck; if (locks.TryGetValue(builder, out lck)) { lck.ExitUpgradeableReadLock(); } }
/// <summary> /// Gets an existing lock or creates a new one /// </summary> /// <param name="builder">Builder key used as a key to get locks</param> /// <returns>Returns a reader-writer lock</returns> private ReaderWriterLockSlim GetOrCreateLock(BuildKey builder) { ReaderWriterLockSlim lck; if (!locks.TryGetValue(builder, out lck)) { locks.Add(builder, lck = new ReaderWriterLockSlim()); } return(lck); }
/// <summary> /// Restores the stored files for a given builder to a file system directory /// /// <para>The cache only stores the latest stored results and this is what will be restored /// to the target directory. To verify if it was generated with the correct dependency fingerprint, /// use <see cref="IBuildCache.Contains"/>.</para> /// <para>To ensure thread safety, use <see cref="IBuildCache.LockForBuilder"/>.</para> /// </summary> /// <param name="builder">Builder key</param> /// <param name="targetRoot">Target file system directory</param> /// <returns>Returns the target root relative paths of all the restored files</returns> public ISet <TargetRelativePath> Restore(BuildKey builder, IFileSystemDirectory targetRoot) { var lck = GetOrCreateLock(builder); lck.EnterReadLock(); try { var dirName = GetCacheDirectoryName(builder); if (cacheRoot.ChildDirectories.Contains(dirName)) { var cacheDir = cacheRoot.GetChildDirectory(dirName); if (cacheDir.Files.Contains(NamesFileName)) { using (var reader = cacheDir.ReadTextFile(NamesFileName)) { var result = new HashSet <TargetRelativePath>(); int idx = 0; string line; while ((line = reader.ReadLine()) != null) { var parts = line.Split(';'); if (parts.Length == 2) { var relativeRoot = parts[0]; var relativePath = parts[1]; var fullPath = Path.Combine(relativeRoot, relativePath); var cacheFileName = idx.ToString(CultureInfo.InvariantCulture); // It is possible that only a file name (a virtual file) was cached without any contents: if (cacheDir.Exists(cacheFileName)) { CopyIfDifferent(cacheDir, cacheFileName, targetRoot, fullPath); } result.Add(new TargetRelativePath(relativeRoot, relativePath)); } idx++; } return(result); } } } return(new HashSet <TargetRelativePath>()); } finally { lck.ExitReadLock(); } }
/// <summary> /// Verifies if the builder is able to run. Can be used to fallback to cached results without getting en error. /// </summary> /// <returns>If <c>true</c>, the builder thinks it can run.</returns> public bool CanRun() { var buildKey = new BuildKey(wrappedBuilder.BuilderType, wrappedBuilder.Uid); cache.LockForBuilder(buildKey); try { return(wrappedBuilder.CanRun() || cache.ContainsAny(buildKey)); } finally { cache.UnlockForBuilder(buildKey); } }
/// <summary> /// Checks if the cache contains stored outputs for a given builder with a given dependency fingerprint /// /// <para>If <see cref="IBuildCache.Restore"/> will be also called, the cache must be locked first using /// the <see cref="IBuildCache.LockForBuilder"/> method.</para> /// </summary> /// <param name="builder">Builder key</param> /// <param name="fingerprint">Current dependency fingerprint</param> /// <returns>Returns <c>true</c> if there are stored outputs for the given builder and fingerprint combination.</returns> public bool Contains(BuildKey builder, IDependencyFingerprint fingerprint) { lock (cache) { MemoryCacheItem item; if (cache.TryGetValue(builder, out item)) { return(item.MatchesFingerprint(fingerprint)); } else { return(false); } } }
/// <summary> /// Checks if the cache contains stored outputs for a given builder with a given dependency fingerprint /// /// <para>If <see cref="IBuildCache.Restore"/> will be also called, the cache must be locked first using /// the <see cref="IBuildCache.LockForBuilder"/> method.</para> /// </summary> /// <param name="builder">Builder key</param> /// <param name="fingerprint">Current dependency fingerprint</param> /// <returns>Returns <c>true</c> if there are stored outputs for the given builder and fingerprint combination.</returns> public bool Contains(BuildKey builder, IDependencyFingerprint fingerprint) { var lck = GetOrCreateLock(builder); lck.EnterReadLock(); try { var dirName = GetCacheDirectoryName(builder); if (cacheRoot.Value.ChildDirectories.Contains(dirName)) { var cacheDir = cacheRoot.Value.GetChildDirectory(dirName); if (cacheDir.Files.Contains(DepsFileName)) { using (var depsStream = cacheDir.ReadBinaryFile(DepsFileName)) using (var memStream = new MemoryStream()) { fingerprint.Save(protocolSerializer, memStream); if (depsStream.Length != memStream.Length) { return(false); } var buf1 = memStream.ToArray(); var buf2 = new byte[depsStream.Length]; depsStream.Read(buf2, 0, (int)depsStream.Length); for (int i = 0; i < buf1.Length; i++) { if (buf1[i] != buf2[i]) { return(false); } } return(true); } } } return(false); } finally { lck.ExitReadLock(); } }
/// <summary> /// Store build outputs in the cache by reading them from the file system /// </summary> /// <param name="builder">Builder key (first part of the key)</param> /// <param name="fingerprint">Dependency fingerprint created when the builder was executed (second part of the key)</param> /// <param name="outputs">Target-relative path of the build outputs to be cached</param> /// <param name="targetRoot">File system abstraction of the root target directory</param> public void Store(BuildKey builder, IDependencyFingerprint fingerprint, IEnumerable <TargetRelativePath> outputs, IFileSystemDirectory targetRoot) { var lck = GetOrCreateLock(builder); lck.EnterWriteLock(); try { var cacheDir = cacheRoot.Value.GetChildDirectory(GetCacheDirectoryName(builder), createIfMissing: true); SaveDependencyFingerprint(fingerprint, cacheDir); SaveOutputs(outputs, targetRoot, cacheDir); } finally { lck.ExitWriteLock(); } }
private MemoryCacheItem GetOrCreate(BuildKey builder) { Contract.Ensures(Contract.Result <MemoryCacheItem>() != null); lock (cache) { MemoryCacheItem item; if (!cache.TryGetValue(builder, out item)) { item = new MemoryCacheItem(); cache.Add(builder, item); } Contract.Assume(item != null); return(item); } }
/// <summary> /// Checks if the cache contains stored outputs for a given builder with a given dependency fingerprint /// /// <para>If <see cref="IBuildCache.Restore"/> will be also called, the cache must be locked first using /// the <see cref="IBuildCache.LockForBuilder"/> method.</para> /// </summary> /// <param name="builder">Builder key</param> /// <param name="fingerprint">Current dependency fingerprint</param> /// <returns>Returns <c>true</c> if there are stored outputs for the given builder and fingerprint combination.</returns> public bool Contains(BuildKey builder, IDependencyFingerprint fingerprint) { var lck = GetOrCreateLock(builder); lck.EnterReadLock(); try { var dirName = GetCacheDirectoryName(builder); if (cacheRoot.ChildDirectories.Contains(dirName)) { var cacheDir = cacheRoot.GetChildDirectory(dirName); if (cacheDir.Files.Contains(DepsFileName)) { using (var depsStream = cacheDir.ReadBinaryFile(DepsFileName)) { var fpType = fingerprint.GetType(); var storedFp = Activator.CreateInstance(fpType, protocolSerializer, depsStream); bool fingerprintEquals = fingerprint.Equals(storedFp); if (!fingerprintEquals && EnableFingerprintDiff) { log.DebugFormat("[{0}] Fingerprint differs", dirName); log.DebugFormat("[{1}] Cached: {0}", storedFp, dirName); log.DebugFormat("[{1}] Current: {0}", fingerprint, dirName); } return(fingerprintEquals); } } } return(false); } finally { lck.ExitReadLock(); } }
/// <summary> /// Checks if the cache contains stored outputs for a given builder with any dependency fingerprint /// </summary> /// <param name="builder">Builder key</param> /// <returns>Returns <c>true</c> if there are stored outputs for the given builder.</returns> public bool ContainsAny(BuildKey builder) { var lck = GetOrCreateLock(builder); lck.EnterReadLock(); try { var dirName = GetCacheDirectoryName(builder); if (cacheRoot.Value.ChildDirectories.Contains(dirName)) { var cacheDir = cacheRoot.Value.GetChildDirectory(dirName); if (cacheDir.Files.Contains(DepsFileName)) { return(true); } } return(false); } finally { lck.ExitReadLock(); } }
/// <summary> /// Locks the cache for a given builder. /// /// <para>Until calling <see cref="IBuildCache.UnlockForBuilder"/>, it is guaranteed that no /// <see cref="IBuildCache.Store"/> operation will be ran for the given builder from other /// threads.</para> /// </summary> /// <param name="builder">Builder key</param> public void LockForBuilder(BuildKey builder) { var lck = GetOrCreateLock(builder); lck.EnterUpgradeableReadLock(); }
/// <summary> /// Gets the directory name associated with a given Builder key /// </summary> /// <param name="builder">Builder key</param> /// <returns>Returns a valid directory name</returns> private static string GetCacheDirectoryName(BuildKey builder) { return(builder.ToString().Replace("/", "___")); }
/// <summary> /// Removes the lock put by the <see cref="IBuildCache.LockForBuilder"/> method. /// </summary> /// <param name="builder">Builder key</param> public void UnlockForBuilder(BuildKey builder) { GetOrCreate(builder).ExitUpgradeableLock(); }
/// <summary> /// Locks the cache for a given builder. /// /// <para>Until calling <see cref="IBuildCache.UnlockForBuilder"/>, it is guaranteed that no /// <see cref="IBuildCache.Store"/> operation will be ran for the given builder from other /// threads.</para> /// </summary> /// <param name="builder">Builder key</param> public void LockForBuilder(BuildKey builder) { GetOrCreate(builder).EnterUpgradeableLock(); }
/// <summary> /// Runs this builder /// </summary> /// <param name="context"> </param> /// <returns>Returns a set of generated files, in suite relative paths</returns> public ISet <TargetRelativePath> Run(IBuildContext context) { var buildKey = new BuildKey(wrappedBuilder.BuilderType, wrappedBuilder.Uid); cache.LockForBuilder(buildKey); try { if (wrappedBuilder.CanRun()) { try { var currentFingerprint = wrappedBuilder.Dependencies.Fingerprint; if (cache.Contains(buildKey, currentFingerprint)) { log.DebugFormat("Restoring cached build outputs for {0}", buildKey); return(cache.Restore(buildKey, targetDir, aggressive, agressiveModeExceptions)); } else { log.DebugFormat("Running builder {0}", buildKey); var files = wrappedBuilder.Run(context); log.DebugFormat("Storing build outputs of {0}", buildKey); cache.Store(buildKey, currentFingerprint, files, targetDir); return(files); } } catch (Exception ex) { log.ErrorFormat("Failed to run builder {0}: {1}", wrappedBuilder.Uid, ex); // Fallback to any cached value if (SupportsFallback && cache.ContainsAny(buildKey)) { log.DebugFormat("Restoring cached build outputs for {0} without fingerprint check", buildKey); return(cache.Restore(buildKey, targetDir, aggressive, agressiveModeExceptions)); } else { throw; } } } else { // Fallback to any cached value if (cache.ContainsAny(buildKey)) { log.DebugFormat("Restoring cached build outputs for {0} without fingerprint check", buildKey); return(cache.Restore(buildKey, targetDir, aggressive, agressiveModeExceptions)); } else { throw new BuilderCantRunException(wrappedBuilder.Uid); } } } finally { cache.UnlockForBuilder(buildKey); } }