diff --git a/src/Tasks.UnitTests/RARPrecomputedCache_Tests.cs b/src/Tasks.UnitTests/RARPrecomputedCache_Tests.cs index 11c0a395500..1156b77ead8 100644 --- a/src/Tasks.UnitTests/RARPrecomputedCache_Tests.cs +++ b/src/Tasks.UnitTests/RARPrecomputedCache_Tests.cs @@ -86,7 +86,8 @@ public void StandardCacheTakesPrecedence() // When we read the state file, it should read from the caches produced in a normal build. In this case, // the normal cache does not have dll.dll, whereas the precomputed cache does, so it should not be // present when we read it. - rarReaderTask.ReadStateFile(p => true); + rarReaderTask.InitializeStateFile(p => true); + rarReaderTask._cache.InitializeSystemState(); rarReaderTask._cache.instanceLocalFileStateCache.ShouldNotContainKey(dllName); } } @@ -128,7 +129,8 @@ public void TestPreComputedCacheInputMatchesOutput() // At this point, the standard cache does not exist, so it defaults to reading the "precomputed" cache. // Then we verify that the information contained in that cache matches what we'd expect. - rarReaderTask.ReadStateFile(p => true); + rarReaderTask.InitializeStateFile(p => true); + rarReaderTask._cache.InitializeSystemState(); rarReaderTask._cache.instanceLocalFileStateCache.ShouldContainKey(dllName); SystemState.FileState assembly3 = rarReaderTask._cache.instanceLocalFileStateCache[dllName]; assembly3.Assembly.ShouldBeNull(); diff --git a/src/Tasks/AssemblyDependency/ReferenceTable.cs b/src/Tasks/AssemblyDependency/ReferenceTable.cs index 196a70b8747..3b214c55a9f 100644 --- a/src/Tasks/AssemblyDependency/ReferenceTable.cs +++ b/src/Tasks/AssemblyDependency/ReferenceTable.cs @@ -741,13 +741,8 @@ out string redistName /// private static void TryConvertToAssemblyName(string itemSpec, string fusionName, ref AssemblyNameExtension assemblyName) { - // FusionName is used if available. - string finalName = fusionName; - if (string.IsNullOrEmpty(finalName)) - { - // Otherwise, its itemSpec. - finalName = itemSpec; - } + // FusionName is used if available; otherwise use itemspec. + string finalName = string.IsNullOrEmpty(fusionName) ? itemSpec : fusionName; bool pathRooted = false; try diff --git a/src/Tasks/AssemblyDependency/ResolveAssemblyReference.cs b/src/Tasks/AssemblyDependency/ResolveAssemblyReference.cs index 2cea34c71bb..35b71ea6a17 100644 --- a/src/Tasks/AssemblyDependency/ResolveAssemblyReference.cs +++ b/src/Tasks/AssemblyDependency/ResolveAssemblyReference.cs @@ -2005,20 +2005,9 @@ private void LogConflict(Reference reference, string fusionName, StringBuilder l /// /// Reads the state file (if present) into the cache. /// - internal void ReadStateFile(FileExists fileExists) + internal void InitializeStateFile(FileExists fileExists) { - _cache = SystemState.DeserializeCache(_stateFile, Log, typeof(SystemState)) as SystemState; - - // Construct the cache only if we can't find any caches. - if (_cache == null && AssemblyInformationCachePaths != null && AssemblyInformationCachePaths.Length > 0) - { - _cache = SystemState.DeserializePrecomputedCaches(AssemblyInformationCachePaths, Log, fileExists); - } - - if (_cache == null) - { - _cache = new SystemState(); - } + _cache = new SystemState(_stateFile, AssemblyInformationCachePaths, Log, fileExists); } /// @@ -2026,9 +2015,14 @@ internal void ReadStateFile(FileExists fileExists) /// internal void WriteStateFile() { + if (!_cache.deserializedFromCache) + { + // The cache is empty; don't bother serializing it. + return; + } if (!String.IsNullOrEmpty(AssemblyInformationCacheOutputPath)) { - _cache.SerializePrecomputedCache(AssemblyInformationCacheOutputPath, Log); + _cache.SerializePrecomputedCache(AssemblyInformationCacheOutputPath); } else if (!String.IsNullOrEmpty(_stateFile) && _cache.IsDirty) { @@ -2262,7 +2256,7 @@ ReadMachineTypeFromPEHeader readMachineTypeFromPEHeader } // Load any prior saved state. - ReadStateFile(fileExists); + InitializeStateFile(fileExists); _cache.SetGetLastWriteTime(getLastWriteTime); _cache.SetInstalledAssemblyInformation(installedAssemblyTableInfo); diff --git a/src/Tasks/SystemState.cs b/src/Tasks/SystemState.cs index 2992e07bc73..8e722f4595a 100644 --- a/src/Tasks/SystemState.cs +++ b/src/Tasks/SystemState.cs @@ -101,6 +101,12 @@ internal sealed class SystemState : StateFileBase, ITranslatable /// private GetAssemblyRuntimeVersion getAssemblyRuntimeVersion; + internal bool deserializedFromCache = false; + private readonly string stateFile; + private readonly ITaskItem[] assemblyInformationCachePaths; + private readonly TaskLoggingHelper log; + private readonly FileExists fileExists; + /// /// Class that holds the current file state. /// @@ -217,6 +223,14 @@ public SystemState() { } + public SystemState(string stateFile, ITaskItem[] assemblyInformationCachePaths, TaskLoggingHelper log, FileExists fileExists) + { + this.stateFile = stateFile; + this.assemblyInformationCachePaths = assemblyInformationCachePaths; + this.log = log; + this.fileExists = fileExists; + } + public SystemState(ITranslator translator) { Translate(translator); @@ -357,35 +371,42 @@ private FileState GetFileState(string path) private FileState ComputeFileStateFromCachesAndDisk(string path) { DateTime lastModified = GetAndCacheLastModified(path); - bool isCachedInInstance = instanceLocalFileStateCache.TryGetValue(path, out FileState cachedInstanceFileState); bool isCachedInProcess = s_processWideFileStateCache.TryGetValue(path, out FileState cachedProcessFileState); - - bool isInstanceFileStateUpToDate = isCachedInInstance && lastModified == cachedInstanceFileState.LastModified; bool isProcessFileStateUpToDate = isCachedInProcess && lastModified == cachedProcessFileState.LastModified; // If the process-wide cache contains an up-to-date FileState, always use it if (isProcessFileStateUpToDate) { - // If a FileState already exists in this instance cache due to deserialization, remove it; - // another instance has taken responsibility for serialization, and keeping this would - // result in multiple instances serializing the same data to disk - if (isCachedInInstance) + if (deserializedFromCache) { - instanceLocalFileStateCache.Remove(path); - isDirty = true; + bool isCachedInInstance = instanceLocalFileStateCache.TryGetValue(path, out FileState cachedInstanceFileState); + bool isInstanceFileStateUpToDate = isCachedInInstance && lastModified == cachedInstanceFileState.LastModified; + // For the next build, we may be using a different process. Update the file cache. + if (!isInstanceFileStateUpToDate) + { + instanceLocalFileStateCache[path] = cachedProcessFileState; + isDirty = true; + } } return cachedProcessFileState; } - // If the process-wide FileState is missing or out-of-date, this instance owns serialization; - // sync the process-wide cache and signal other instances to avoid data duplication - if (isInstanceFileStateUpToDate) + else { - return s_processWideFileStateCache[path] = cachedInstanceFileState; - } + if (!deserializedFromCache) + { + InitializeSystemState(); + } + bool isCachedInInstance = instanceLocalFileStateCache.TryGetValue(path, out FileState cachedInstanceFileState); + bool isInstanceFileStateUpToDate = isCachedInInstance && lastModified == cachedInstanceFileState.LastModified; + if (isInstanceFileStateUpToDate) + { + return s_processWideFileStateCache[path] = cachedInstanceFileState; + } - // If no up-to-date FileState exists at this point, create one and take ownership - return InitializeFileState(path, lastModified); + // If no up-to-date FileState exists at this point, create one and take ownership + return InitializeFileState(path, lastModified); + } } private DateTime GetAndCacheLastModified(string path) @@ -520,20 +541,32 @@ out fileState.frameworkName frameworkName = fileState.frameworkName; } + internal void InitializeSystemState() + { + SystemState cache = SystemState.DeserializeCache(stateFile, log, typeof(SystemState)) as SystemState; + if (cache is null && assemblyInformationCachePaths?.Length > 0) + { + cache = DeserializePrecomputedCaches(); + } + + if (cache is not null) + { + instanceLocalFileStateCache = cache.instanceLocalFileStateCache; + } + deserializedFromCache = true; + } + /// /// Reads in cached data from stateFiles to build an initial cache. Avoids logging warnings or errors. /// - /// List of locations of caches on disk. - /// How to log - /// Whether a file exists /// A cache representing key aspects of file states. - internal static SystemState DeserializePrecomputedCaches(ITaskItem[] stateFiles, TaskLoggingHelper log, FileExists fileExists) + internal SystemState DeserializePrecomputedCaches() { SystemState retVal = new SystemState(); - retVal.isDirty = stateFiles.Length > 0; + retVal.isDirty = assemblyInformationCachePaths.Length > 0; HashSet assembliesFound = new HashSet(); - foreach (ITaskItem stateFile in stateFiles) + foreach (ITaskItem stateFile in assemblyInformationCachePaths) { // Verify that it's a real stateFile. Log message but do not error if not. SystemState sysState = DeserializeCache(stateFile.ToString(), log, typeof(SystemState)) as SystemState; @@ -565,8 +598,7 @@ internal static SystemState DeserializePrecomputedCaches(ITaskItem[] stateFiles, /// Modifies this object to be more portable across machines, then writes it to filePath. /// /// Path to which to write the precomputed cache - /// How to log - internal void SerializePrecomputedCache(string stateFile, TaskLoggingHelper log) + internal void SerializePrecomputedCache(string stateFile) { // Save a copy of instanceLocalFileStateCache so we can restore it later. SerializeCacheByTranslator serializes // instanceLocalFileStateCache by default, so change that to the relativized form, then change it back.