@@ -272,93 +272,93 @@ private async Task ProcessRequestAsync(
272272 // This reduces the overheads of unnecessary processing plus avoids file locks.
273273 await WriteWorkers . GetOrAdd (
274274 key ,
275- x => new Lazy < Task > (
275+ _ => new Lazy < Task > (
276276 async ( ) =>
277- {
278- try
279- {
280- // Prevent a second request from starting a read during write execution.
281- if ( ReadWorkers . TryGetValue ( key , out var readWork ) )
282- {
283- await readWork . Value ;
284- }
285-
286- ImageCacheMetadata cachedImageMetadata = default ;
287- outStream = new RecyclableMemoryStream ( this . options . MemoryStreamManager ) ;
288- IImageFormat format ;
289-
290- // 14.9.3 CacheControl Max-Age
291- // Check to see if the source metadata has a CacheControl Max-Age value
292- // and use it to override the default max age from our options.
293- TimeSpan maxAge = this . options . BrowserMaxAge ;
294- if ( ! sourceImageMetadata . CacheControlMaxAge . Equals ( TimeSpan . MinValue ) )
295- {
296- maxAge = sourceImageMetadata . CacheControlMaxAge ;
297- }
298-
299- using ( Stream inStream = await sourceImageResolver . OpenReadAsync ( ) )
300277 {
301- // No commands? We simply copy the stream across.
302- if ( commands . Count == 0 )
278+ try
303279 {
304- await inStream . CopyToAsync ( outStream ) ;
280+ // Prevent a second request from starting a read during write execution.
281+ if ( ReadWorkers . TryGetValue ( key , out Lazy < Task < ( bool , ImageMetadata ) > > readWork ) )
282+ {
283+ await readWork . Value ;
284+ }
285+
286+ ImageCacheMetadata cachedImageMetadata = default ;
287+ outStream = new RecyclableMemoryStream ( this . options . MemoryStreamManager ) ;
288+ IImageFormat format ;
289+
290+ // 14.9.3 CacheControl Max-Age
291+ // Check to see if the source metadata has a CacheControl Max-Age value
292+ // and use it to override the default max age from our options.
293+ TimeSpan maxAge = this . options . BrowserMaxAge ;
294+ if ( ! sourceImageMetadata . CacheControlMaxAge . Equals ( TimeSpan . MinValue ) )
295+ {
296+ maxAge = sourceImageMetadata . CacheControlMaxAge ;
297+ }
298+
299+ using ( Stream inStream = await sourceImageResolver . OpenReadAsync ( ) )
300+ {
301+ // No commands? We simply copy the stream across.
302+ if ( commands . Count == 0 )
303+ {
304+ await inStream . CopyToAsync ( outStream ) ;
305+ outStream . Position = 0 ;
306+ format = await Image . DetectFormatAsync ( this . options . Configuration , outStream ) ;
307+ }
308+ else
309+ {
310+ using var image = FormattedImage . Load ( this . options . Configuration , inStream ) ;
311+
312+ image . Process (
313+ this . logger ,
314+ this . processors ,
315+ commands ,
316+ this . commandParser ,
317+ this . parserCulture ) ;
318+
319+ await this . options . OnBeforeSaveAsync . Invoke ( image ) ;
320+
321+ image . Save ( outStream ) ;
322+ format = image . Format ;
323+ }
324+ }
325+
326+ // Allow for any further optimization of the image.
327+ outStream . Position = 0 ;
328+ string contentType = format . DefaultMimeType ;
329+ string extension = this . formatUtilities . GetExtensionFromContentType ( contentType ) ;
330+ await this . options . OnProcessedAsync . Invoke ( new ImageProcessingContext ( context , outStream , commands , contentType , extension ) ) ;
305331 outStream . Position = 0 ;
306- format = await Image . DetectFormatAsync ( this . options . Configuration , outStream ) ;
307- }
308- else
309- {
310- using var image = FormattedImage . Load ( this . options . Configuration , inStream ) ;
311-
312- image . Process (
313- this . logger ,
314- this . processors ,
315- commands ,
316- this . commandParser ,
317- this . parserCulture ) ;
318-
319- await this . options . OnBeforeSaveAsync . Invoke ( image ) ;
320-
321- image . Save ( outStream ) ;
322- format = image . Format ;
323- }
324- }
325-
326- // Allow for any further optimization of the image.
327- outStream . Position = 0 ;
328- string contentType = format . DefaultMimeType ;
329- string extension = this . formatUtilities . GetExtensionFromContentType ( contentType ) ;
330- await this . options . OnProcessedAsync . Invoke ( new ImageProcessingContext ( context , outStream , commands , contentType , extension ) ) ;
331- outStream . Position = 0 ;
332332
333- cachedImageMetadata = new ImageCacheMetadata (
334- sourceImageMetadata . LastWriteTimeUtc ,
335- DateTime . UtcNow ,
336- contentType ,
337- maxAge ,
338- outStream . Length ) ;
333+ cachedImageMetadata = new ImageCacheMetadata (
334+ sourceImageMetadata . LastWriteTimeUtc ,
335+ DateTime . UtcNow ,
336+ contentType ,
337+ maxAge ,
338+ outStream . Length ) ;
339339
340- // Save the image to the cache and send the response to the caller.
341- await this . cache . SetAsync ( key , outStream , cachedImageMetadata ) ;
340+ // Save the image to the cache and send the response to the caller.
341+ await this . cache . SetAsync ( key , outStream , cachedImageMetadata ) ;
342342
343- // Remove the resolver from the cache so we always resolve next request
344- // for the same key.
345- CacheResolverLru . TryRemove ( key ) ;
343+ // Remove the resolver from the cache so we always resolve next request
344+ // for the same key.
345+ CacheResolverLru . TryRemove ( key ) ;
346346
347- await this . SendResponseAsync ( imageContext , key , cachedImageMetadata , outStream , null ) ;
348- }
349- catch ( Exception ex )
350- {
351- // Log the error internally then rethrow.
352- // We don't call next here, the pipeline will automatically handle it
353- this . logger . LogImageProcessingFailed ( imageContext . GetDisplayUrl ( ) , ex ) ;
354- throw ;
355- }
356- finally
357- {
358- await this . StreamDisposeAsync ( outStream ) ;
359- WriteWorkers . TryRemove ( key , out var _ ) ;
360- }
361- } , LazyThreadSafetyMode . ExecutionAndPublication ) ) . Value ;
347+ await this . SendResponseAsync ( imageContext , key , cachedImageMetadata , outStream , null ) ;
348+ }
349+ catch ( Exception ex )
350+ {
351+ // Log the error internally then rethrow.
352+ // We don't call next here, the pipeline will automatically handle it
353+ this . logger . LogImageProcessingFailed ( imageContext . GetDisplayUrl ( ) , ex ) ;
354+ throw ;
355+ }
356+ finally
357+ {
358+ await this . StreamDisposeAsync ( outStream ) ;
359+ WriteWorkers . TryRemove ( key , out Lazy < Task > _ ) ;
360+ }
361+ } , LazyThreadSafetyMode . ExecutionAndPublication ) ) . Value ;
362362 }
363363
364364 private ValueTask StreamDisposeAsync ( Stream stream )
@@ -388,69 +388,69 @@ private async Task<ValueTuple<bool, ImageMetadata>> IsNewOrUpdatedAsync(
388388 ImageContext imageContext ,
389389 string key )
390390 {
391- if ( WriteWorkers . TryGetValue ( key , out var writeWork ) )
391+ if ( WriteWorkers . TryGetValue ( key , out Lazy < Task > writeWork ) )
392392 {
393393 await writeWork . Value ;
394394 }
395395
396- if ( ReadWorkers . TryGetValue ( key , out var readWork ) )
396+ if ( ReadWorkers . TryGetValue ( key , out Lazy < Task < ( bool , ImageMetadata ) > > readWork ) )
397397 {
398398 return await readWork . Value ;
399399 }
400400
401401 return await ReadWorkers . GetOrAdd (
402402 key ,
403- x => new Lazy < Task < ValueTuple < bool , ImageMetadata > > > (
403+ _ => new Lazy < Task < ValueTuple < bool , ImageMetadata > > > (
404404 async ( ) =>
405- {
406- try
407- {
408- // Get the source metadata for processing, storing the result for future checks.
409- ImageMetadata sourceImageMetadata = await
410- SourceMetadataLru . GetOrAddAsync (
411- key ,
412- _ => sourceImageResolver . GetMetaDataAsync ( ) ) ;
413-
414- // Check to see if the cache contains this image.
415- // If not, we return early. No further checks necessary.
416- IImageCacheResolver cachedImageResolver = await
417- CacheResolverLru . GetOrAddAsync (
418- key ,
419- k => this . cache . GetAsync ( k ) ) ;
420-
421- if ( cachedImageResolver is null )
422- {
423- // Remove the null resolver from the store.
424- CacheResolverLru . TryRemove ( key ) ;
425- return ( true , sourceImageMetadata ) ;
426- }
427-
428- // Now resolve the cached image metadata storing the result.
429- ImageCacheMetadata cachedImageMetadata = await
430- CacheMetadataLru . GetOrAddAsync (
431- key ,
432- _ => cachedImageResolver . GetMetaDataAsync ( ) ) ;
433-
434- // Has the cached image expired?
435- // Or has the source image changed since the image was last cached?
436- if ( cachedImageMetadata . ContentLength == 0 // Fix for old cache without length property
437- || cachedImageMetadata . CacheLastWriteTimeUtc <= ( DateTimeOffset . UtcNow - this . options . CacheMaxAge )
438- || cachedImageMetadata . SourceLastWriteTimeUtc != sourceImageMetadata . LastWriteTimeUtc )
439405 {
440- // We want to remove the metadata from the store so that the next check gets the updated file.
441- CacheMetadataLru . TryRemove ( key ) ;
442- return ( true , sourceImageMetadata ) ;
443- }
444-
445- // We're pulling the image from the cache.
446- await this . SendResponseAsync ( imageContext , key , cachedImageMetadata , null , cachedImageResolver ) ;
447- return ( false , sourceImageMetadata ) ;
448- }
449- finally
450- {
451- ReadWorkers . TryRemove ( key , out var _ ) ;
452- }
453- } , LazyThreadSafetyMode . ExecutionAndPublication ) ) . Value ;
406+ try
407+ {
408+ // Get the source metadata for processing, storing the result for future checks.
409+ ImageMetadata sourceImageMetadata = await
410+ SourceMetadataLru . GetOrAddAsync (
411+ key ,
412+ _ => sourceImageResolver . GetMetaDataAsync ( ) ) ;
413+
414+ // Check to see if the cache contains this image.
415+ // If not, we return early. No further checks necessary.
416+ IImageCacheResolver cachedImageResolver = await
417+ CacheResolverLru . GetOrAddAsync (
418+ key ,
419+ k => this . cache . GetAsync ( k ) ) ;
420+
421+ if ( cachedImageResolver is null )
422+ {
423+ // Remove the null resolver from the store.
424+ CacheResolverLru . TryRemove ( key ) ;
425+ return ( true , sourceImageMetadata ) ;
426+ }
427+
428+ // Now resolve the cached image metadata storing the result.
429+ ImageCacheMetadata cachedImageMetadata = await
430+ CacheMetadataLru . GetOrAddAsync (
431+ key ,
432+ _ => cachedImageResolver . GetMetaDataAsync ( ) ) ;
433+
434+ // Has the cached image expired?
435+ // Or has the source image changed since the image was last cached?
436+ if ( cachedImageMetadata . ContentLength == 0 // Fix for old cache without length property
437+ || cachedImageMetadata . CacheLastWriteTimeUtc <= ( DateTimeOffset . UtcNow - this . options . CacheMaxAge )
438+ || cachedImageMetadata . SourceLastWriteTimeUtc != sourceImageMetadata . LastWriteTimeUtc )
439+ {
440+ // We want to remove the metadata from the store so that the next check gets the updated file.
441+ CacheMetadataLru . TryRemove ( key ) ;
442+ return ( true , sourceImageMetadata ) ;
443+ }
444+
445+ // We're pulling the image from the cache.
446+ await this . SendResponseAsync ( imageContext , key , cachedImageMetadata , null , cachedImageResolver ) ;
447+ return ( false , sourceImageMetadata ) ;
448+ }
449+ finally
450+ {
451+ ReadWorkers . TryRemove ( key , out Lazy < Task < ( bool , ImageMetadata ) > > _ ) ;
452+ }
453+ } , LazyThreadSafetyMode . ExecutionAndPublication ) ) . Value ;
454454 }
455455
456456 private async Task SendResponseAsync (
0 commit comments