From 29953451a5d1df9b2d699f4d6572b014b59556fe Mon Sep 17 00:00:00 2001 From: James South Date: Tue, 14 Jan 2014 21:14:07 +0000 Subject: [PATCH] Further attempt at protecting the Save method Former-commit-id: b2768040730f1934e0fecf74d4b304828c5a587d --- .../NET45/Caching/DiskCache.cs | 1 + .../NET45/Helpers/TaskHelpers.cs | 2 +- .../HttpModules/ImageProcessingModule.cs | 163 +++++++++++++++--- .../NET45/Settings.StyleCop | 5 + src/ImageProcessor/ImageFactory.cs | 1 - 5 files changed, 149 insertions(+), 23 deletions(-) diff --git a/src/ImageProcessor.Web/NET45/Caching/DiskCache.cs b/src/ImageProcessor.Web/NET45/Caching/DiskCache.cs index 24d46dc28..dbc1493a8 100644 --- a/src/ImageProcessor.Web/NET45/Caching/DiskCache.cs +++ b/src/ImageProcessor.Web/NET45/Caching/DiskCache.cs @@ -301,6 +301,7 @@ namespace ImageProcessor.Web.Caching { DateTime dateTime = imageFileInfo.LastWriteTimeUtc; creationTime = cachedFileInfo.CreationTimeUtc; + cachedFileInfo.LastWriteTimeUtc = dateTime; lastWriteTime = dateTime; diff --git a/src/ImageProcessor.Web/NET45/Helpers/TaskHelpers.cs b/src/ImageProcessor.Web/NET45/Helpers/TaskHelpers.cs index f06a14195..b4f339794 100644 --- a/src/ImageProcessor.Web/NET45/Helpers/TaskHelpers.cs +++ b/src/ImageProcessor.Web/NET45/Helpers/TaskHelpers.cs @@ -12,7 +12,7 @@ namespace ImageProcessor.Web.Helpers { #region Using using System; - using System.Threading.Tasks; + using System.Threading.Tasks; #endregion /// diff --git a/src/ImageProcessor.Web/NET45/HttpModules/ImageProcessingModule.cs b/src/ImageProcessor.Web/NET45/HttpModules/ImageProcessingModule.cs index a6a86f01d..6cf7df761 100644 --- a/src/ImageProcessor.Web/NET45/HttpModules/ImageProcessingModule.cs +++ b/src/ImageProcessor.Web/NET45/HttpModules/ImageProcessingModule.cs @@ -12,6 +12,7 @@ namespace ImageProcessor.Web.HttpModules { #region Using using System; + using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; @@ -20,6 +21,7 @@ namespace ImageProcessor.Web.HttpModules using System.Security; using System.Security.Permissions; using System.Security.Principal; + using System.Threading; using System.Threading.Tasks; using System.Web; using System.Web.Hosting; @@ -51,6 +53,44 @@ namespace ImageProcessor.Web.HttpModules /// The assembly version. /// private static readonly string AssemblyVersion = Assembly.GetExecutingAssembly().GetName().Version.ToString(); + + /// + /// The collection of Semaphores for identifying given locking individual queries. + /// + private static readonly Dictionary Semaphores = new Dictionary(); + + /// + /// A value indicating whether this instance of the given entity has been disposed. + /// + /// if this instance has been disposed; otherwise, . + /// + /// If the entity is disposed, it must not be disposed a second + /// time. The isDisposed field is set the first time the entity + /// is disposed. If the isDisposed field is true, then the Dispose() + /// method will not dispose again. This help not to prolong the entity's + /// life in the Garbage Collector. + /// + private bool isDisposed; + #endregion + + #region Destructors + /// + /// Finalizes an instance of the class. + /// + /// + /// Use C# destructor syntax for finalization code. + /// This destructor will run only if the Dispose method + /// does not get called. + /// It gives your base class the opportunity to finalize. + /// Do not provide destructors in types derived from this class. + /// + ~ImageProcessingModule() + { + // Do not re-create Dispose clean-up code here. + // Calling Dispose(false) is optimal in terms of + // readability and maintainability. + this.Dispose(false); + } #endregion #region IHttpModule Members @@ -83,7 +123,65 @@ namespace ImageProcessor.Web.HttpModules /// public void Dispose() { - // Nothing to dispose. + this.Dispose(true); + + // This object will be cleaned up by the Dispose method. + // Therefore, you should call GC.SupressFinalize to + // take this object off the finalization queue + // and prevent finalization code for this object + // from executing a second time. + GC.SuppressFinalize(this); + } + + /// + /// Gets the specific for the given id. + /// + /// + /// The id representing the . + /// + /// + /// The for the given id. + /// + private static Semaphore GetSemaphore(string id) + { + id = id.ToMD5Fingerprint(); + + if (Semaphores.ContainsKey(id)) + { + return Semaphores[id]; + } + + Semaphore semaphore = new Semaphore(1, 1, id); + Semaphores.Add(id, semaphore); + return semaphore; + } + + /// + /// Disposes the object and frees resources for the Garbage Collector. + /// + /// If true, the object gets disposed. + private void Dispose(bool disposing) + { + if (this.isDisposed) + { + return; + } + + if (disposing) + { + // Dispose of any managed resources here. + foreach (KeyValuePair semaphore in Semaphores) + { + semaphore.Value.Dispose(); + } + + Semaphores.Clear(); + } + + // Call the appropriate methods to clean up + // unmanaged resources here. + // Note disposing is done. + this.isDisposed = true; } #endregion @@ -294,19 +392,31 @@ namespace ImageProcessor.Web.HttpModules { responseStream.CopyTo(memoryStream); - imageFactory.Load(memoryStream) - .AddQueryString(queryString) - .Format(ImageUtils.GetImageFormat(imageName)) - .AutoProcess().Save(cachedPath); - - // Ensure that the LastWriteTime property of the source and cached file match. - Tuple creationAndLastWriteDateTimes = await cache.SetCachedLastWriteTimeAsync(); - - // Add to the cache. - cache.AddImageToCache(creationAndLastWriteDateTimes); - - // Trim the cache. - await cache.TrimCachedFolderAsync(cachedPath); + Semaphore semaphore = GetSemaphore(cachedPath); + try + { + semaphore.WaitOne(); + + // Process the Image + imageFactory.Load(memoryStream) + .AddQueryString(queryString) + .Format(ImageUtils.GetImageFormat(imageName)) + .AutoProcess() + .Save(cachedPath); + + // Ensure that the LastWriteTime property of the source and cached file match. + Tuple creationAndLastWriteDateTimes = await cache.SetCachedLastWriteTimeAsync(); + + // Add to the cache. + cache.AddImageToCache(creationAndLastWriteDateTimes); + + // Trim the cache. + await cache.TrimCachedFolderAsync(cachedPath); + } + finally + { + semaphore.Release(); + } } } } @@ -323,16 +433,27 @@ namespace ImageProcessor.Web.HttpModules throw new HttpException(404, "No image exists at " + fullPath); } - imageFactory.Load(fullPath).AutoProcess().Save(cachedPath); + Semaphore semaphore = GetSemaphore(cachedPath); + try + { + semaphore.WaitOne(); + + // Process the Image + imageFactory.Load(fullPath).AutoProcess().Save(cachedPath); - // Ensure that the LastWriteTime property of the source and cached file match. - Tuple creationAndLastWriteDateTimes = await cache.SetCachedLastWriteTimeAsync(); + // Ensure that the LastWriteTime property of the source and cached file match. + Tuple creationAndLastWriteDateTimes = await cache.SetCachedLastWriteTimeAsync(); - // Add to the cache. - cache.AddImageToCache(creationAndLastWriteDateTimes); + // Add to the cache. + cache.AddImageToCache(creationAndLastWriteDateTimes); - // Trim the cache. - await cache.TrimCachedFolderAsync(cachedPath); + // Trim the cache. + await cache.TrimCachedFolderAsync(cachedPath); + } + finally + { + semaphore.Release(); + } } } } diff --git a/src/ImageProcessor.Web/NET45/Settings.StyleCop b/src/ImageProcessor.Web/NET45/Settings.StyleCop index 253825567..578d6f188 100644 --- a/src/ImageProcessor.Web/NET45/Settings.StyleCop +++ b/src/ImageProcessor.Web/NET45/Settings.StyleCop @@ -1,4 +1,9 @@ + + + Mutexes + + diff --git a/src/ImageProcessor/ImageFactory.cs b/src/ImageProcessor/ImageFactory.cs index 91d011d7d..3a0ccf6cd 100644 --- a/src/ImageProcessor/ImageFactory.cs +++ b/src/ImageProcessor/ImageFactory.cs @@ -77,7 +77,6 @@ namespace ImageProcessor // readability and maintainability. this.Dispose(false); } - #endregion #region Properties