|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
#nullable disable
using System.Diagnostics;
using System.Reflection.PortableExecutable;
using Microsoft.CodeAnalysis;
namespace Microsoft.NET.Sdk.Razor.Tool
{
internal class MetadataCache
{
// Store 1000 entries -- arbitrary number
private const int CacheSize = 1000;
private readonly ConcurrentLruCache<string, MetadataCacheEntry> _metadataCache =
new(CacheSize, StringComparer.OrdinalIgnoreCase);
// For testing purposes only.
internal ConcurrentLruCache<string, MetadataCacheEntry> Cache => _metadataCache;
internal Metadata GetMetadata(string fullPath)
{
var timestamp = GetFileTimeStamp(fullPath);
// Check if we have an entry in the dictionary.
if (_metadataCache.TryGetValue(fullPath, out var entry))
{
if (timestamp.HasValue && timestamp.Value == entry.Timestamp)
{
// The file has not changed since we cached it. Return the cached entry.
return entry.Metadata;
}
else
{
// The file has changed recently. Remove the cache entry.
_metadataCache.Remove(fullPath);
}
}
Metadata metadata;
using (var fileStream = File.OpenRead(fullPath))
{
metadata = AssemblyMetadata.CreateFromStream(fileStream, PEStreamOptions.PrefetchMetadata);
}
_metadataCache.GetOrAdd(fullPath, new MetadataCacheEntry(timestamp.Value, metadata));
return metadata;
}
private static DateTime? GetFileTimeStamp(string fullPath)
{
try
{
Debug.Assert(Path.IsPathRooted(fullPath));
return File.GetLastWriteTimeUtc(fullPath);
}
catch (Exception e)
{
// There are several exceptions that can occur here: NotSupportedException or PathTooLongException
// for a bad path, UnauthorizedAccessException for access denied, etc. Rather than listing them all,
// just catch all exceptions and log.
ServerLogger.LogException(e, $"Error getting timestamp of file {fullPath}.");
return null;
}
}
internal struct MetadataCacheEntry
{
public MetadataCacheEntry(DateTime timestamp, Metadata metadata)
{
Debug.Assert(timestamp.Kind == DateTimeKind.Utc);
Timestamp = timestamp;
Metadata = metadata;
}
public DateTime Timestamp { get; }
public Metadata Metadata { get; }
}
}
}
|