88using System . IO ;
99using System . Linq ;
1010using System . Text ;
11- using System . Threading ;
1211using System . Threading . Tasks ;
12+ using BitFaster . Caching . Lru ;
1313using Microsoft . AspNetCore . Http ;
1414using Microsoft . Extensions . Logging ;
1515using Microsoft . Extensions . Options ;
@@ -33,6 +33,24 @@ public class ImageSharpMiddleware
3333 /// </summary>
3434 private static readonly AsyncKeyLock AsyncLock = new AsyncKeyLock ( ) ;
3535
36+ /// <summary>
37+ /// Used to temporarily store source metadata reads to reduce the overhead of cache lookups.
38+ /// </summary>
39+ private static readonly ConcurrentTLru < string , ImageMetadata > SourceMetadataLru
40+ = new ConcurrentTLru < string , ImageMetadata > ( 1024 , TimeSpan . FromMinutes ( 5 ) ) ;
41+
42+ /// <summary>
43+ /// Used to temporarily store cache resolver reads to reduce the overhead of cache lookups.
44+ /// </summary>
45+ private static readonly ConcurrentTLru < string , IImageCacheResolver > CacheResolverLru
46+ = new ConcurrentTLru < string , IImageCacheResolver > ( 1024 , TimeSpan . FromMinutes ( 5 ) ) ;
47+
48+ /// <summary>
49+ /// Used to temporarily store cache metadata reads to reduce the overhead of cache lookups.
50+ /// </summary>
51+ private static readonly ConcurrentTLru < string , ImageCacheMetadata > CacheMetadataLru
52+ = new ConcurrentTLru < string , ImageCacheMetadata > ( 1024 , TimeSpan . FromMinutes ( 5 ) ) ;
53+
3654 /// <summary>
3755 /// The function processing the Http request.
3856 /// </summary>
@@ -304,6 +322,14 @@ private async Task ProcessRequestAsync(
304322
305323 // Save the image to the cache and send the response to the caller.
306324 await this . cache . SetAsync ( key , outStream , cachedImageMetadata ) ;
325+
326+ // Remove the resolver from the cache so we always resolve next request.
327+ CacheResolverLru . TryRemove ( key ) ;
328+
329+ // Replace cache metadata item value.
330+ CacheMetadataLru . TryRemove ( key ) ;
331+ CacheMetadataLru . GetOrAdd ( key , _ => cachedImageMetadata ) ;
332+
307333 await this . SendResponseAsync ( imageContext , key , cachedImageMetadata , outStream , null ) ;
308334 }
309335 catch ( Exception ex )
@@ -351,14 +377,20 @@ private async Task<ValueTuple<bool, ImageMetadata>> IsNewOrUpdatedAsync(
351377 using ( await AsyncLock . ReaderLockAsync ( key ) )
352378 {
353379 // Check to see if the cache contains this image.
354- sourceImageMetadata = await sourceImageResolver . GetMetaDataAsync ( ) ;
355- IImageCacheResolver cachedImageResolver = await this . cache . GetAsync ( key ) ;
380+ IImageCacheResolver cachedImageResolver
381+ = await CacheResolverLru . GetOrAddAsync ( key , async k => await this . cache . GetAsync ( k ) ) ;
382+
356383 if ( cachedImageResolver != null )
357384 {
358- ImageCacheMetadata cachedImageMetadata = await cachedImageResolver . GetMetaDataAsync ( ) ;
385+ ImageCacheMetadata cachedImageMetadata =
386+ await CacheMetadataLru . GetOrAddAsync ( key , async _ => await cachedImageResolver . GetMetaDataAsync ( ) ) ;
387+
359388 if ( cachedImageMetadata != default )
360389 {
361390 // Has the cached image expired or has the source image been updated?
391+ sourceImageMetadata =
392+ await SourceMetadataLru . GetOrAddAsync ( key , async _ => await sourceImageResolver . GetMetaDataAsync ( ) ) ;
393+
362394 if ( cachedImageMetadata . SourceLastWriteTimeUtc == sourceImageMetadata . LastWriteTimeUtc
363395 && cachedImageMetadata . ContentLength > 0 // Fix for old cache without length property
364396 && cachedImageMetadata . CacheLastWriteTimeUtc > ( DateTimeOffset . UtcNow - this . options . CacheMaxAge ) )
0 commit comments