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)); }
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); } }