diff --git a/src/ImageProcessor.Web/NET4/ImageProcessor.Web_NET4.csproj b/src/ImageProcessor.Web/NET4/ImageProcessor.Web_NET4.csproj index 82d3986a7..601953a5e 100644 --- a/src/ImageProcessor.Web/NET4/ImageProcessor.Web_NET4.csproj +++ b/src/ImageProcessor.Web/NET4/ImageProcessor.Web_NET4.csproj @@ -101,6 +101,9 @@ StringExtensions.cs + + AsyncDeDuperLock.cs + CommonParameterParserUtility.cs diff --git a/src/ImageProcessor.Web/NET45/Helpers/AsyncDeDuperLock.cs b/src/ImageProcessor.Web/NET45/Helpers/AsyncDeDuperLock.cs new file mode 100644 index 000000000..7d6fbf0cd --- /dev/null +++ b/src/ImageProcessor.Web/NET45/Helpers/AsyncDeDuperLock.cs @@ -0,0 +1,134 @@ +// -------------------------------------------------------------------------------------------------------------------- +// +// Copyright (c) James South. +// Licensed under the Apache License, Version 2.0. +// +// +// Throttles duplicate requests. +// +// -------------------------------------------------------------------------------------------------------------------- +namespace ImageProcessor.Web.Helpers +{ + using System; + using System.Collections.Concurrent; + using System.Threading; + using System.Threading.Tasks; + + /// + /// Throttles duplicate requests. + /// Based loosely on + /// + public sealed class AsyncDeDuperLock + { + /// + /// The semaphore slims. + /// + private static readonly ConcurrentDictionary SemaphoreSlims = new ConcurrentDictionary(); + + /// + /// The lock. + /// + /// + /// The hash. + /// + /// + /// The . + /// + public IDisposable Lock(string key) + { + DisposableScope releaser = new DisposableScope( + key, + s => + { + SemaphoreSlim locker; + if (SemaphoreSlims.TryRemove(s, out locker)) + { + locker.Release(); + locker.Dispose(); + } + }); + + SemaphoreSlim semaphore = SemaphoreSlims.GetOrAdd(key, new SemaphoreSlim(1, 1)); + semaphore.Wait(); + return releaser; + } + +#if NET45 && !__MonoCS__ + /// + /// The lock async. + /// + /// + /// The key. + /// + /// + /// The . + /// + public Task LockAsync(string key) + { + DisposableScope releaser = new DisposableScope( + key, + s => + { + SemaphoreSlim locker; + if (SemaphoreSlims.TryRemove(s, out locker)) + { + locker.Release(); + locker.Dispose(); + } + }); + + Task releaserTask = Task.FromResult(releaser as IDisposable); + SemaphoreSlim semaphore = SemaphoreSlims.GetOrAdd(key, new SemaphoreSlim(1, 1)); + + Task waitTask = semaphore.WaitAsync(); + + return waitTask.IsCompleted + ? releaserTask + : waitTask.ContinueWith( + (_, r) => (IDisposable)r, + releaser, + CancellationToken.None, + TaskContinuationOptions.ExecuteSynchronously, + TaskScheduler.Default); + } +#endif + /// + /// The disposable scope. + /// + internal sealed class DisposableScope : IDisposable + { + /// + /// The key + /// + private readonly string key; + + /// + /// The close scope action. + /// + private readonly Action closeScopeAction; + + /// + /// Initializes a new instance of the class. + /// + /// + /// The key. + /// + /// + /// The close scope action. + /// + public DisposableScope(string key, Action closeScopeAction) + { + this.key = key; + this.closeScopeAction = closeScopeAction; + } + + /// + /// The dispose. + /// + public void Dispose() + { + this.closeScopeAction(this.key); + } + } + } +} \ No newline at end of file diff --git a/src/ImageProcessor.Web/NET45/HttpModules/ImageProcessingModule.cs b/src/ImageProcessor.Web/NET45/HttpModules/ImageProcessingModule.cs index 0f4db86f3..2fe0a275d 100644 --- a/src/ImageProcessor.Web/NET45/HttpModules/ImageProcessingModule.cs +++ b/src/ImageProcessor.Web/NET45/HttpModules/ImageProcessingModule.cs @@ -12,7 +12,6 @@ namespace ImageProcessor.Web.HttpModules { #region Using using System; - using System.Collections.Concurrent; using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; @@ -22,7 +21,6 @@ namespace ImageProcessor.Web.HttpModules using System.Security.Permissions; using System.Security.Principal; using System.Text.RegularExpressions; - using System.Threading; using System.Threading.Tasks; using System.Web; using System.Web.Hosting; @@ -56,9 +54,9 @@ namespace ImageProcessor.Web.HttpModules private static readonly string AssemblyVersion = Assembly.GetExecutingAssembly().GetName().Version.ToString(); /// - /// The collection of SemaphoreSlims for identifying given locking individual queries. + /// The locker for preventing duplicate requests. /// - private static readonly ConcurrentDictionary SemaphoreSlims = new ConcurrentDictionary(); + private static readonly AsyncDeDuperLock Locker = new AsyncDeDuperLock(); /// /// The value to prefix any remote image requests with to ensure they get captured. @@ -149,20 +147,6 @@ namespace ImageProcessor.Web.HttpModules GC.SuppressFinalize(this); } - /// - /// Gets the specific for the given id. - /// - /// - /// The id representing the . - /// - /// - /// The for the given id. - /// - private static SemaphoreSlim GetSemaphoreSlim(string id) - { - return SemaphoreSlims.GetOrAdd(id, new SemaphoreSlim(1, 1)); - } - /// /// Disposes the object and frees resources for the Garbage Collector. /// @@ -377,13 +361,11 @@ namespace ImageProcessor.Web.HttpModules { if (isRemote) { - SemaphoreSlim semaphore = GetSemaphoreSlim(cachedPath); #if NET45 && !__MonoCS__ - await semaphore.WaitAsync(); + using (await Locker.LockAsync(cachedPath)) #else - semaphore.Wait(); + using (Locker.Lock(cachedPath)) #endif - try { Uri uri = new Uri(requestPath + "?" + urlParameters); RemoteFile remoteFile = new RemoteFile(uri, false); @@ -422,20 +404,14 @@ namespace ImageProcessor.Web.HttpModules } } } - finally - { - semaphore.Release(); - } } else { - SemaphoreSlim semaphore = GetSemaphoreSlim(cachedPath); #if NET45 && !__MonoCS__ - await semaphore.WaitAsync(); + using (await Locker.LockAsync(cachedPath)) #else - semaphore.Wait(); + using (Locker.Lock(cachedPath)) #endif - try { // Check to see if the file exists. // ReSharper disable once AssignNullToNotNullAttribute @@ -460,10 +436,6 @@ namespace ImageProcessor.Web.HttpModules // Trim the cache. await cache.TrimCachedFolderAsync(cachedPath); } - finally - { - semaphore.Release(); - } } } } diff --git a/src/ImageProcessor.Web/NET45/ImageProcessor.Web_NET45.csproj b/src/ImageProcessor.Web/NET45/ImageProcessor.Web_NET45.csproj index 40bd16bc7..6d4694400 100644 --- a/src/ImageProcessor.Web/NET45/ImageProcessor.Web_NET45.csproj +++ b/src/ImageProcessor.Web/NET45/ImageProcessor.Web_NET45.csproj @@ -56,6 +56,7 @@ +