Exemplo n.º 1
0
    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));
    }
Exemplo n.º 2
0
    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);
    }
Exemplo n.º 3
0
        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));
        }
Exemplo n.º 4
0
 static string GetNegativeCacheRelativePath(SymCacheKey key)
 {
     return(Path.ChangeExtension(GetRelativePath(key), "negativesymcache"));
 }
Exemplo n.º 5
0
 internal static string GetRelativePath(SymCacheKey key)
 {
     return($@"{key.PdbName}\{key.PdbId:N}{key.PdbAge:X}\{key.PdbName}-v{key.Version}.symcache");
 }
Exemplo n.º 6
0
 string GetNegativeCachePath(SymCacheKey key)
 {
     return(Path.Combine(directory, GetNegativeCacheRelativePath(key)));
 }
Exemplo n.º 7
0
 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);
        }
    }
Exemplo n.º 9
0
 public void Enqueue(SymCacheKey key)
 {
     queue.Enqueue(key);
     itemAvailable.Set();
 }
Exemplo n.º 10
0
 Task ProcessAsync(SymCacheKey key, CancellationToken cancellationToken)
 {
     Debug.Assert(key != null);
     return(transcoder.TryTranscodeAsync(key, cancellationToken));
 }