public CacheResult Find(SymCacheKey key) { string path = GetPath(key); if (File.Exists(path)) { return(new CacheResult(CacheStatus.PositivelyCached, path, key.Version)); } string negativeCachePath = GetNegativeCachePath(key); if (File.Exists(negativeCachePath)) { try { DateTimeOffset negativeCacheExpires = DateTimeOffset.Parse(File.ReadAllText(negativeCachePath)); if (negativeCacheExpires.UtcDateTime < DateTimeOffset.UtcNow) { File.Delete(negativeCachePath); } return(new CacheResult(CacheStatus.NegativelyCached, null, null)); } catch { // Concurrent requests could mean the file read or delete above fails. Treat the negative cache entry as // non-existent in that case. } } return(new CacheResult(CacheStatus.NotCached, null, null)); }
public void MarkNegativelyCached(SymCacheKey key) { // Treat negative cache entry as valid for 1 day. string contents = DateTimeOffset.UtcNow.AddDays(1).ToString("o"); string relativePath = GetNegativeCacheRelativePath(key); string path = Path.Combine(directory, relativePath); // Ensure parent directories exist. Directory.CreateDirectory(Path.GetDirectoryName(path)); File.WriteAllText(path, contents); }
public async Task <IActionResult> Get(CancellationToken cancellationToken, ushort major, byte minor, byte patch, string pdbName, Guid pdbId, uint pdbAge) { SemanticVersion version = new SemanticVersion(major, minor, patch); if (version <= SymCacheVersion.MinVersion) { return(NotFound()); } if (version.Major < transcoderVersion.Major) { // The client is using an older major version than the server's transcoder, so it wouldn't understand // the format of any SymCache files this server would return, because they have breaking changes // compared to the format the client understands. return(NotFound()); } SemanticVersion ifVersionExceedsVersion; string errorMessage; if (!TryParseIfVersionExceedsHeader(version, out ifVersionExceedsVersion, out errorMessage)) { return(BadRequest(errorMessage)); } SymCacheKey key = new SymCacheKey(version, pdbName, pdbId, pdbAge); CacheResult cacheResult = repository.Find(key); if (cacheResult.Status == CacheStatus.PositivelyCached) { if (ifVersionExceedsVersion != null && ifVersionExceedsVersion >= cacheResult.Version) { // The client wants only a newer version than this server can offer; don't send the server's file. return(NotModified()); } return(Success(cacheResult.Path, cacheResult.Version)); } else if (cacheResult.Status == CacheStatus.NegativelyCached) { return(NotFound()); } else { Debug.Assert(cacheResult.Status == CacheStatus.NotCached); } if (ifVersionExceedsVersion != null && ifVersionExceedsVersion >= transcoderVersion) { // The client wants only a newer version than this server could transcoder will produce. Don't try to // transcode. return(NotModified()); } if (ShouldTranscodeAsynchronously(version)) { // Queue the item to be transcoded on a background thread. transcodeQueue.Enqueue(key); // Ask the client to check back again in 1 second to see if we have a result available. (It is up to // the client to decide how many times to retry, and whether to use the suggested delay, or have // some static value it always uses, or use the suggested value within a min/max range). return(NotFoundRetryAfter(TimeSpan.FromSeconds(1))); } // Older clients expected the symcache file transcoded before sending a response to the HTTP GET request. string transcodedPath = await transcoder.TryTranscodeAsync(key, cancellationToken); if (transcodedPath == null) { repository.MarkNegativelyCached(key); return(NotFound()); } return(Success(transcodedPath, transcoderVersion)); }
static string GetNegativeCacheRelativePath(SymCacheKey key) { return(Path.ChangeExtension(GetRelativePath(key), "negativesymcache")); }
internal static string GetRelativePath(SymCacheKey key) { return($@"{key.PdbName}\{key.PdbId:N}{key.PdbAge:X}\{key.PdbName}-v{key.Version}.symcache"); }
string GetNegativeCachePath(SymCacheKey key) { return(Path.Combine(directory, GetNegativeCacheRelativePath(key))); }
public string GetPath(SymCacheKey key) { return(Path.Combine(directory, GetRelativePath(key))); }
public async Task <string> TryTranscodeAsync(SymCacheKey key, CancellationToken cancellationToken) { // Check whether a result already exists. If so, don't try to transcode again. CacheResult result = repository.Find(key); if (result.Status == CacheStatus.NegativelyCached) { return(null); } else if (result.Status == CacheStatus.PositivelyCached) { return(result.Path); } Debug.Assert(result.Status == CacheStatus.NotCached); string pdbPath = await symbolServer.TryGetPdbPathAsync(key.PdbName, key.PdbId, key.PdbAge, cancellationToken); if (pdbPath == null) { repository.MarkNegativelyCached(key); return(null); } // Use a separate, random directory under the temp directory so that concurrent transcodes do not collide. using (TempDirectory randomDirectory = new TempDirectory(Path.Combine(tempDirectory, Guid.NewGuid().ToString()))) { string pdbDirectory = Path.Combine(randomDirectory.FullName, "pdb"); Directory.CreateDirectory(pdbDirectory); // Cache the PDB from the sybol server locally, so transcoding (which is expensive) is accessing a local // file. string localPdbPath = Path.Combine(pdbDirectory, Path.GetFileName(pdbPath)); try { File.Copy(pdbPath, localPdbPath); } catch { // The symbol server may report a PDB path that does not exist or is not accessible. repository.MarkNegativelyCached(key); return(null); } string expectedOutputPath = Path.Combine(randomDirectory.FullName, SymCacheRepository.GetRelativePath(key)); await RunTranscoderAsync(localPdbPath, randomDirectory.FullName, cancellationToken); if (!File.Exists(expectedOutputPath)) { // Transcoding failed for some reason. repository.MarkNegativelyCached(key); return(null); } string finalOutputPath = repository.GetPath(key); Directory.CreateDirectory(Path.GetDirectoryName(finalOutputPath)); try { File.Move(expectedOutputPath, finalOutputPath); } catch { // In case of concurrency, the file may already exist. If so, the transcode is done. if (!File.Exists(finalOutputPath)) { // But if not, the transcode failed. repository.MarkNegativelyCached(key); return(null); } } return(finalOutputPath); } }
public void Enqueue(SymCacheKey key) { queue.Enqueue(key); itemAvailable.Set(); }
Task ProcessAsync(SymCacheKey key, CancellationToken cancellationToken) { Debug.Assert(key != null); return(transcoder.TryTranscodeAsync(key, cancellationToken)); }