private void Serialize(string filename) { AnalysisCacheData fromOtherProcess = null; Diagnostics.Assert(_saveCacheToDisk != false, "Serialize should never be called without going through QueueSerialization which has a check"); try { if (Utils.NativeFileExists(filename)) { var fileLastWriteTime = new FileInfo(filename).LastWriteTime; if (fileLastWriteTime > this.LastReadTime) { fromOtherProcess = Deserialize(filename); } } else { // Make sure the folder exists var folder = Path.GetDirectoryName(filename); if (!Directory.Exists(folder)) { try { Directory.CreateDirectory(folder); } catch (UnauthorizedAccessException) { // service accounts won't be able to create directory _saveCacheToDisk = false; return; } } } } catch (Exception e) { ModuleIntrinsics.Tracer.WriteLine("Exception checking module analysis cache {0}: {1} ", filename, e.Message); } if (fromOtherProcess != null) { // We should merge with what another process wrote so we don't clobber useful analysis foreach (var otherEntryPair in fromOtherProcess.Entries) { var otherModuleName = otherEntryPair.Key; var otherEntry = otherEntryPair.Value; ModuleCacheEntry thisEntry; if (Entries.TryGetValue(otherModuleName, out thisEntry)) { if (otherEntry.LastWriteTime > thisEntry.LastWriteTime) { // The other entry is newer, take it over ours Entries[otherModuleName] = otherEntry; } } else { Entries[otherModuleName] = otherEntry; } } } // "PSMODULECACHE" -> 13 bytes // byte ( 1 byte) -> version // int ( 4 bytes) -> count of entries // entries (?? bytes) -> all entries // // each entry is // DateTime ( 8 bytes) -> last write time for module file // int ( 4 bytes) -> path length // string (?? bytes) -> utf8 encoded path // int ( 4 bytes) -> count of commands // commands (?? bytes) -> all commands // int ( 4 bytes) -> count of types, -1 means unanalyzed (and 0 items serialized) // types (?? bytes) -> all types // // each command is // int ( 4 bytes) -> command name length // string (?? bytes) -> utf8 encoded command name // int ( 4 bytes) -> CommandTypes enum // // each type is // int ( 4 bytes) -> type name length // string (?? bytes) -> utf8 encoded type name // int ( 4 bytes) -> type attributes try { var bytes = new byte[8]; using (var stream = File.Create(filename)) { var headerBytes = GetHeader(); stream.Write(headerBytes, 0, headerBytes.Length); // Count of entries Write(Entries.Count, bytes, stream); foreach (var pair in Entries.ToArray()) { var path = pair.Key; var entry = pair.Value; // Module last write time Write(entry.LastWriteTime.Ticks, bytes, stream); // Module path Write(path, bytes, stream); // Commands var commandPairs = entry.Commands.ToArray(); Write(commandPairs.Length, bytes, stream); foreach (var command in commandPairs) { Write(command.Key, bytes, stream); Write((int)command.Value, bytes, stream); } // Types var typePairs = entry.Types.ToArray(); Write(entry.TypesAnalyzed ? typePairs.Length : -1, bytes, stream); foreach (var type in typePairs) { Write(type.Key, bytes, stream); Write((int)type.Value, bytes, stream); } } } // We just wrote the file, note this so we can detect writes from another process LastReadTime = new FileInfo(filename).LastWriteTime; } catch (Exception e) { ModuleIntrinsics.Tracer.WriteLine("Exception writing module analysis cache {0}: {1} ", filename, e.Message); } // Reset our counter so we can write again if asked. Interlocked.Exchange(ref _saveCacheToDiskQueued, 0); }
public static AnalysisCacheData Deserialize(string filename) { using (var stream = File.OpenRead(filename)) { var result = new AnalysisCacheData { LastReadTime = DateTime.Now }; var bytes = new byte[1024]; // Header // "PSMODULECACHE" -> 13 bytes // byte ( 1 byte) -> version ReadHeader(stream, bytes); // int ( 4 bytes) -> count of entries int entries = ReadInt(stream, bytes); if (entries > 20 * 1024) throw new Exception(PossibleCorruptionErrorMessage); result.Entries = new ConcurrentDictionary<string, ModuleCacheEntry>(/*concurrency*/3, entries, StringComparer.OrdinalIgnoreCase); // entries (?? bytes) -> all entries while (entries > 0) { // DateTime ( 8 bytes) -> last write time for module file var lastWriteTime = new DateTime(ReadLong(stream, bytes)); // int ( 4 bytes) -> path length // string (?? bytes) -> utf8 encoded path var path = ReadString(stream, ref bytes); // int ( 4 bytes) -> count of commands var countItems = ReadInt(stream, bytes); if (countItems > 20 * 1024) throw new Exception(PossibleCorruptionErrorMessage); var commands = new ConcurrentDictionary<string, CommandTypes>(/*concurrency*/3, countItems, StringComparer.OrdinalIgnoreCase); // commands (?? bytes) -> all commands while (countItems > 0) { // int ( 4 bytes) -> command name length // string (?? bytes) -> utf8 encoded command name var commandName = ReadString(stream, ref bytes); // int ( 4 bytes) -> CommandTypes enum var commandTypes = (CommandTypes)ReadInt(stream, bytes); // Ignore empty entries (possible corruption in the cache or bug?) if (!string.IsNullOrWhiteSpace(commandName)) commands[commandName] = commandTypes; countItems -= 1; } // int ( 4 bytes) -> count of types countItems = ReadInt(stream, bytes); bool typesAnalyzed = countItems != -1; if (!typesAnalyzed) countItems = 0; if (countItems > 20 * 1024) throw new Exception(PossibleCorruptionErrorMessage); var types = new ConcurrentDictionary<string, TypeAttributes>(1, countItems, StringComparer.OrdinalIgnoreCase); // types (?? bytes) -> all types while (countItems > 0) { // int ( 4 bytes) -> type name length // string (?? bytes) -> utf8 encoded type name var typeName = ReadString(stream, ref bytes); // int ( 4 bytes) -> type attributes var typeAttributes = (TypeAttributes)ReadInt(stream, bytes); // Ignore empty entries (possible corruption in the cache or bug?) if (!string.IsNullOrWhiteSpace(typeName)) types[typeName] = typeAttributes; countItems -= 1; } var entry = new ModuleCacheEntry { ModulePath = path, LastWriteTime = lastWriteTime, Commands = commands, TypesAnalyzed = typesAnalyzed, Types = types }; result.Entries[path] = entry; entries -= 1; } if (Environment.GetEnvironmentVariable("PSDisableModuleAnalysisCacheCleanup") == null) { Task.Delay(10000).ContinueWith(_ => result.Cleanup()); } return result; } }