private async Task <MemoryContentDirectoryHeader> DeserializeHeaderAsync(Context context, AbsolutePath path) { try { var directoryHeader = new MemoryContentDirectoryHeader(); using (var stream = await _fileSystem.OpenSafeAsync(path, FileAccess.Read, FileMode.Open, FileShare.Read)) { var header = new byte[BinaryHeaderSizeV2]; stream.Read(header, 0, header.Length); var headerContext = new BufferSerializeContext(header); directoryHeader.MagicFlag = headerContext.DeserializeByte(); if (directoryHeader.MagicFlag != BinaryFormatMagicFlag) { throw new CacheException("{0} binary format missing magic flag", Name); } directoryHeader.Version = headerContext.DeserializeByte(); directoryHeader.EntryCount = headerContext.DeserializeInt32(); if (directoryHeader.Version == BinaryFormatVersion) { header = new byte[BinaryHeaderExtraSizeV3]; stream.Read(header, 0, header.Length); headerContext = new BufferSerializeContext(header); directoryHeader.ContentSize = headerContext.DeserializeInt64(); directoryHeader.ReplicaCount = headerContext.DeserializeInt64(); directoryHeader.HeaderSize = BinaryHeaderSizeV2 + BinaryHeaderExtraSizeV3; } else { throw new CacheException("{0} expected {1} but read binary format version {2}", Name, BinaryFormatVersion, directoryHeader.Version); } return(directoryHeader); } } catch (Exception exception) { context.Warning($"{Name} failed to deserialize header of {FilePath} - starting with empty directory: {exception}"); return(new MemoryContentDirectoryHeader()); } }
/// <inheritdoc /> protected override async Task <BoolResult> StartupCoreAsync(OperationContext context) { Tracer.Info(context, $"{Name} startup"); if (_fileSystem.FileExists(_filePath)) { _header = await DeserializeHeaderAsync(context, _filePath); _initializeContentDirectory = InitializeContentDirectoryAsync(context, _header, _filePath, isLoadingBackup: false); Tracer.Info(context, $"{Name} starting with {_header.EntryCount} entries."); } else if (_fileSystem.FileExists(_backupFilePath)) { var backupHeader = await DeserializeHeaderAsync(context, _backupFilePath); _header = new MemoryContentDirectoryHeader(); _initializeContentDirectory = InitializeContentDirectoryAsync(context, backupHeader, _backupFilePath, isLoadingBackup: true); Tracer.Info(context, $"{Name} starting with {backupHeader.EntryCount} entries from backup file."); } else { _header = new MemoryContentDirectoryHeader(); _initializeContentDirectory = InitializeContentDirectoryAsync(context, _header, path: null, isLoadingBackup: false); Tracer.Info(context, $"{Name} starting with {ContentDirectory.Count} entries from no file."); } // Tracing initialization asynchronously. _initializeContentDirectory.ContinueWith( t => { Tracer.Info(context, $"{Name} started with {ContentDirectory.Count} entries."); }).FireAndForget(context); return(BoolResult.Success); }
private async Task <ContentMap> DeserializeBodyAsync(Context context, MemoryContentDirectoryHeader header, AbsolutePath path, bool isLoadingBackup) { var contentDirectory = new ContentMap(); try { var sw = Stopwatch.StartNew(); using (var stream = await _fileSystem.OpenSafeAsync(path, FileAccess.Read, FileMode.Open, FileShare.Read)) { byte[] headerBuffer = new byte[header.HeaderSize]; stream.Read(headerBuffer, 0, header.HeaderSize); var streamSync = new object(); var entriesSync = new object(); var entries = new List <KeyValuePair <ContentHash, ContentFileInfo> >(header.EntryCount); var entriesRemaining = header.EntryCount; var tasks = new List <Task>(); var nowFileTimeUtc = DateTime.UtcNow.ToFileTimeUtc(); long totalSize = 0; long totalUniqueSize = 0; long oldestContentAccessTimeUtc = nowFileTimeUtc; long totalReplicaCount = 0; var statsLock = new object(); Action <int> readChunk = count => { var bufferLength = count * BinaryEntrySize; var buffer = new byte[bufferLength]; int bytesRead; lock (streamSync) { bytesRead = stream.Read(buffer, 0, bufferLength); } if (bytesRead != buffer.Length) { return; } var serializeContext = new BufferSerializeContext(buffer); var partitionEntries = new List <KeyValuePair <ContentHash, ContentFileInfo> >(count); for (var i = 0; i < count; i++) { var contentHash = serializeContext.DeserializeFullContentHash(); var fileSize = serializeContext.DeserializeInt64(); var lastAccessedFileTimeUtc = serializeContext.DeserializeInt64(); // Guard against corruption of serialized timestamps which affect LRU. If we get something out of range, // force it to now. if (lastAccessedFileTimeUtc < 0 || lastAccessedFileTimeUtc > nowFileTimeUtc) { lastAccessedFileTimeUtc = nowFileTimeUtc; } // ReSharper disable once UnusedVariable var accessCount = serializeContext.DeserializeInt32(); var replicaCount = serializeContext.DeserializeInt32(); var contentFileInfo = new ContentFileInfo(fileSize, lastAccessedFileTimeUtc, replicaCount); Interlocked.Add(ref totalSize, fileSize * replicaCount); Interlocked.Add(ref totalUniqueSize, fileSize); Interlocked.Add(ref totalReplicaCount, replicaCount); if (oldestContentAccessTimeUtc > lastAccessedFileTimeUtc) { lock (statsLock) { if (oldestContentAccessTimeUtc > lastAccessedFileTimeUtc) { oldestContentAccessTimeUtc = lastAccessedFileTimeUtc; } } } partitionEntries.Add(new KeyValuePair <ContentHash, ContentFileInfo>(contentHash, contentFileInfo)); } lock (entriesSync) { entries.AddRange(partitionEntries); } }; while (entriesRemaining > 0) { var chunkCount = Math.Min(entriesRemaining, BinaryMaxEntriesPerChunk); tasks.Add(Task.Run(() => readChunk(chunkCount))); entriesRemaining -= chunkCount; } await TaskSafetyHelpers.WhenAll(tasks); context.Debug($"{Name}: Loaded content directory with {entries.Count} entries by {sw.ElapsedMilliseconds}ms: TotalContentSize={totalSize}, TotalUniqueSize={totalUniqueSize}, TotalReplicaCount={totalReplicaCount}, OldestContentTime={DateTime.FromFileTimeUtc(oldestContentAccessTimeUtc)}."); if (entries.Count == header.EntryCount) { contentDirectory = new ContentMap(entries); } else { throw new CacheException($"Failed to read expected number of entries. Entries.Count={entries.Count}, Header.EntryCount={header.EntryCount}."); } } if (!isLoadingBackup) { // At this point, we've either successfully read the file or tried and failed. Either way, the existing file should now be // deleted. On a clean shutdown, it will be regenerated. On a dirty shutdown, we want it to already be gone otherwise // we'll read in a file that is out-of-date. _fileSystem.MoveFile(path, _backupFilePath, true); } } catch (Exception exception) { context.Warning($"{Name} failed to deserialize {FilePath} - starting with empty directory: {exception}"); contentDirectory.Clear(); } return(contentDirectory); }
private async Task <ContentMap> InitializeContentDirectoryAsync(Context context, MemoryContentDirectoryHeader header, AbsolutePath path, bool isLoadingBackup) { var operationContext = new OperationContext(context); var result = await operationContext.PerformOperationAsync <Result <ContentMap> >( Tracer, async() => { // Making this method asynchronous to initialize _initializeContentDirectory field as fast as possible // even if the initialization is synchronous. // This will enforce the invariant that ContentDirectory property is not null. await Task.Yield(); bool canLoadContentDirectory = path != null && header.Version == BinaryFormatVersion; var loadedContentDirectory = canLoadContentDirectory ? await DeserializeBodyAsync(context, header, path, isLoadingBackup) : new ContentMap(); if (!isLoadingBackup && loadedContentDirectory.Count != 0) { // Successfully loaded primary content directory return(loadedContentDirectory); } // Primary content directory is empty, missing, or failed to load // Reconstruct to ensure content is populated. ContentMap contentDirectory = new ContentMap(); var backupContentDirectory = loadedContentDirectory; if (_host != null) { await AddBulkAsync( contentDirectory: contentDirectory, backupContentDirectory: backupContentDirectory, hashInfoPairs: _host.Reconstruct(context)); } else { // Host may be null in tests. Warn? } return(contentDirectory); }, _counters[MemoryContentDirectoryCounters.InitializeContentDirectory]).ThrowIfFailure(); return(result.Value); }