From 316fd23041b8054543c78a7281cc569fea201bf4 Mon Sep 17 00:00:00 2001 From: James South Date: Wed, 16 Jul 2014 18:35:49 +0100 Subject: [PATCH 1/4] Fixing threading issue? Former-commit-id: 00715678f8231c6f329b0c88bc104ba26164fa32 --- .../HttpModules/ImageProcessingModule.cs | 63 ++++++++++++------- 1 file changed, 39 insertions(+), 24 deletions(-) diff --git a/src/ImageProcessor.Web/NET45/HttpModules/ImageProcessingModule.cs b/src/ImageProcessor.Web/NET45/HttpModules/ImageProcessingModule.cs index db378fae4..a91d6e07a 100644 --- a/src/ImageProcessor.Web/NET45/HttpModules/ImageProcessingModule.cs +++ b/src/ImageProcessor.Web/NET45/HttpModules/ImageProcessingModule.cs @@ -157,12 +157,11 @@ namespace ImageProcessor.Web.HttpModules /// The id representing the . /// /// - /// The for the given id. + /// The for the given id. /// private static SemaphoreSlim GetSemaphoreSlim(string id) { - SemaphoreSlim semaphore = SemaphoreSlims.GetOrAdd(id, new SemaphoreSlim(1, 1)); - return semaphore; + return SemaphoreSlims.GetOrAdd(id, new SemaphoreSlim(1, 1)); } /// @@ -362,7 +361,7 @@ namespace ImageProcessor.Web.HttpModules PermissionSet permission = new PermissionSet(PermissionState.None); permission.AddPermission(new AspNetHostingPermission(AspNetHostingPermissionLevel.Unrestricted)); bool hasPermission = permission.IsSubsetOf(AppDomain.CurrentDomain.PermissionSet); - + bool isAllowed = true; // Run the rewritten path past the authorization system again. @@ -385,17 +384,21 @@ namespace ImageProcessor.Web.HttpModules { if (isRemote) { - Uri uri = new Uri(requestPath + "?" + urlParameters); - - RemoteFile remoteFile = new RemoteFile(uri, false); - - // Prevent response blocking. - WebResponse webResponse = await remoteFile.GetWebResponseAsync().ConfigureAwait(false); - SemaphoreSlim semaphore = GetSemaphoreSlim(cachedPath); try { - semaphore.Wait(); + // This should not happen :( + if (semaphore != null) + { + semaphore.Wait(); + } + + Uri uri = new Uri(requestPath + "?" + urlParameters); + + RemoteFile remoteFile = new RemoteFile(uri, false); + + // Prevent response blocking. + WebResponse webResponse = await remoteFile.GetWebResponseAsync().ConfigureAwait(false); using (MemoryStream memoryStream = new MemoryStream()) { @@ -430,24 +433,32 @@ namespace ImageProcessor.Web.HttpModules } finally { - semaphore.Release(); + // This should not happen :( + if (semaphore != null) + { + semaphore.Release(); + } } } else { - // Check to see if the file exists. - // ReSharper disable once AssignNullToNotNullAttribute - FileInfo fileInfo = new FileInfo(requestPath); - - if (!fileInfo.Exists) - { - throw new HttpException(404, "No image exists at " + fullPath); - } - SemaphoreSlim semaphore = GetSemaphoreSlim(cachedPath); try { - semaphore.Wait(); + // This should not happen :( + if (semaphore != null) + { + semaphore.Wait(); + } + + // Check to see if the file exists. + // ReSharper disable once AssignNullToNotNullAttribute + FileInfo fileInfo = new FileInfo(requestPath); + + if (!fileInfo.Exists) + { + throw new HttpException(404, "No image exists at " + fullPath); + } // Process the Image imageFactory.Load(requestPath) @@ -465,7 +476,11 @@ namespace ImageProcessor.Web.HttpModules } finally { - semaphore.Release(); + // This should not happen :( + if (semaphore != null) + { + semaphore.Release(); + } } } } From 2b13e87c198cba03648408f341162cc9d652343a Mon Sep 17 00:00:00 2001 From: James South Date: Thu, 17 Jul 2014 13:00:14 +0100 Subject: [PATCH 2/4] Better Semaphore fix Former-commit-id: 420a9831a79083d397feb75e03bb9540efa3f6e3 --- .../HttpModules/ImageProcessingModule.cs | 35 +++++++------------ .../Imaging/Formats/GifFormat.cs | 2 +- .../NET45/Test_Website_NET45/Web.config | 8 ++--- 3 files changed, 17 insertions(+), 28 deletions(-) diff --git a/src/ImageProcessor.Web/NET45/HttpModules/ImageProcessingModule.cs b/src/ImageProcessor.Web/NET45/HttpModules/ImageProcessingModule.cs index a91d6e07a..d93f8ab77 100644 --- a/src/ImageProcessor.Web/NET45/HttpModules/ImageProcessingModule.cs +++ b/src/ImageProcessor.Web/NET45/HttpModules/ImageProcessingModule.cs @@ -385,16 +385,14 @@ namespace ImageProcessor.Web.HttpModules if (isRemote) { SemaphoreSlim semaphore = GetSemaphoreSlim(cachedPath); +#if NET45 + await semaphore.WaitAsync(); +#else + semaphore.Wait(); +#endif try { - // This should not happen :( - if (semaphore != null) - { - semaphore.Wait(); - } - Uri uri = new Uri(requestPath + "?" + urlParameters); - RemoteFile remoteFile = new RemoteFile(uri, false); // Prevent response blocking. @@ -433,24 +431,19 @@ namespace ImageProcessor.Web.HttpModules } finally { - // This should not happen :( - if (semaphore != null) - { - semaphore.Release(); - } + semaphore.Release(); } } else { SemaphoreSlim semaphore = GetSemaphoreSlim(cachedPath); +#if NET45 + await semaphore.WaitAsync(); +#else + semaphore.Wait(); +#endif try { - // This should not happen :( - if (semaphore != null) - { - semaphore.Wait(); - } - // Check to see if the file exists. // ReSharper disable once AssignNullToNotNullAttribute FileInfo fileInfo = new FileInfo(requestPath); @@ -476,11 +469,7 @@ namespace ImageProcessor.Web.HttpModules } finally { - // This should not happen :( - if (semaphore != null) - { - semaphore.Release(); - } + semaphore.Release(); } } } diff --git a/src/ImageProcessor/Imaging/Formats/GifFormat.cs b/src/ImageProcessor/Imaging/Formats/GifFormat.cs index 83904225b..dffccb99b 100644 --- a/src/ImageProcessor/Imaging/Formats/GifFormat.cs +++ b/src/ImageProcessor/Imaging/Formats/GifFormat.cs @@ -86,7 +86,7 @@ namespace ImageProcessor.Imaging.Formats foreach (GifFrame frame in info.GifFrames) { factory.Image = frame.Image; - frame.Image = quantizer.Quantize(processor.Invoke(factory)); + frame.Image = quantizer.Quantize(processor.Invoke(factory)); encoder.AddFrame(frame); } } diff --git a/src/TestWebsites/NET45/Test_Website_NET45/Web.config b/src/TestWebsites/NET45/Test_Website_NET45/Web.config index 67e336777..8cfc303d3 100644 --- a/src/TestWebsites/NET45/Test_Website_NET45/Web.config +++ b/src/TestWebsites/NET45/Test_Website_NET45/Web.config @@ -6,17 +6,17 @@ - + - + From d008abb4102d1350f7c329acf452ca52eb2e198d Mon Sep 17 00:00:00 2001 From: James South Date: Thu, 17 Jul 2014 17:20:38 +0100 Subject: [PATCH 3/4] Removing disposal code. Former-commit-id: 9a6d027a47ae7828d0064a5bd743a9dc3815f8df --- .../NET45/HttpModules/ImageProcessingModule.cs | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/src/ImageProcessor.Web/NET45/HttpModules/ImageProcessingModule.cs b/src/ImageProcessor.Web/NET45/HttpModules/ImageProcessingModule.cs index d93f8ab77..c1ee8355e 100644 --- a/src/ImageProcessor.Web/NET45/HttpModules/ImageProcessingModule.cs +++ b/src/ImageProcessor.Web/NET45/HttpModules/ImageProcessingModule.cs @@ -13,7 +13,6 @@ namespace ImageProcessor.Web.HttpModules #region Using using System; using System.Collections.Concurrent; - using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; @@ -178,12 +177,6 @@ namespace ImageProcessor.Web.HttpModules if (disposing) { // Dispose of any managed resources here. - foreach (KeyValuePair semaphore in SemaphoreSlims) - { - semaphore.Value.Dispose(); - } - - SemaphoreSlims.Clear(); } // Call the appropriate methods to clean up @@ -385,7 +378,7 @@ namespace ImageProcessor.Web.HttpModules if (isRemote) { SemaphoreSlim semaphore = GetSemaphoreSlim(cachedPath); -#if NET45 +#if NET45 && !__MonoCS__ await semaphore.WaitAsync(); #else semaphore.Wait(); @@ -437,7 +430,7 @@ namespace ImageProcessor.Web.HttpModules else { SemaphoreSlim semaphore = GetSemaphoreSlim(cachedPath); -#if NET45 +#if NET45 && !__MonoCS__ await semaphore.WaitAsync(); #else semaphore.Wait(); From 889c9401b1e0e372d6de7c46871db744a3ececed Mon Sep 17 00:00:00 2001 From: James South Date: Fri, 1 Aug 2014 15:42:50 +0100 Subject: [PATCH 4/4] Fixing virtual directory mapping issue Former-commit-id: a36a95380e0cdebf4569dfe20fd233ecdc71747e --- .../NET45/Caching/DiskCache.cs | 77 +++++++++---------- .../HttpModules/ImageProcessingModule.cs | 6 +- 2 files changed, 38 insertions(+), 45 deletions(-) diff --git a/src/ImageProcessor.Web/NET45/Caching/DiskCache.cs b/src/ImageProcessor.Web/NET45/Caching/DiskCache.cs index be6b88744..c6300c64d 100644 --- a/src/ImageProcessor.Web/NET45/Caching/DiskCache.cs +++ b/src/ImageProcessor.Web/NET45/Caching/DiskCache.cs @@ -49,14 +49,14 @@ namespace ImageProcessor.Web.Caching private const int MaxFilesCount = 100; /// - /// The absolute path to virtual cache path on the server. + /// The virtual cache path. /// - private static readonly string AbsoluteCachePath = HostingEnvironment.MapPath(ImageProcessorConfiguration.Instance.VirtualCachePath); + private static readonly string VirtualCachePath = ImageProcessorConfiguration.Instance.VirtualCachePath; /// - /// The request for the image. + /// The absolute path to virtual cache path on the server. /// - private readonly HttpRequest request; + private static readonly string AbsoluteCachePath = HostingEnvironment.MapPath(ImageProcessorConfiguration.Instance.VirtualCachePath); /// /// The request path for the image. @@ -72,15 +72,22 @@ namespace ImageProcessor.Web.Caching /// The image name /// private readonly string imageName; + + /// + /// The physical cached path. + /// + private string physicalCachedPath; + + /// + /// The virtual cached path. + /// + private string virtualCachedPath; #endregion #region Constructors /// /// Initializes a new instance of the class. /// - /// - /// The request for the image. - /// /// /// The request path for the image. /// @@ -90,53 +97,41 @@ namespace ImageProcessor.Web.Caching /// /// The image name. /// - public DiskCache(HttpRequest request, string requestPath, string fullPath, string imageName) + public DiskCache(string requestPath, string fullPath, string imageName) { - this.request = request; this.requestPath = requestPath; this.fullPath = fullPath; this.imageName = imageName; + + // Get the physical and virtual paths. + this.GetCachePaths(); } #endregion - #region Methods - #region Internal /// - /// Gets the full transformed cached path for the image. - /// The images are stored in paths that are based upon the SHA1 of their full request path - /// taking the individual characters of the hash to determine their location. - /// This allows us to store millions of images. + /// Gets the cached path. /// - /// The full cached path for the image. - internal Task GetCachePathAsync() + public string CachedPath { - return TaskHelpers.Run(this.GetCachePath); + get + { + return this.physicalCachedPath; + } } /// - /// Gets the virtual path to the cached processed image. + /// Gets the cached path. /// - /// - /// The path to the cached image. - /// - /// - /// The virtual path to the cached processed image. - /// - internal string GetVirtualCachedPath(string cachedPath) + public string VirtualCachedPath { - string applicationPath = this.request.PhysicalApplicationPath; - string virtualDir = this.request.ApplicationPath; - virtualDir = virtualDir == "/" ? virtualDir : (virtualDir + "/"); - - if (applicationPath != null) + get { - return cachedPath.Replace(applicationPath, virtualDir).Replace(@"\", "/"); + return this.virtualCachedPath; } - - throw new InvalidOperationException( - "We can only map an absolute back to a relative path if the application path is available."); } + #region Methods + #region Internal /// /// Adds an image to the cache. /// @@ -256,15 +251,13 @@ namespace ImageProcessor.Web.Caching } /// - /// Gets the full transformed cached path for the image. + /// Gets the full transformed cached paths for the image. /// The images are stored in paths that are based upon the SHA1 of their full request path /// taking the individual characters of the hash to determine their location. /// This allows us to store millions of images. /// - /// The full cached path for the image. - private string GetCachePath() + private void GetCachePaths() { - string cachedPath = string.Empty; string streamHash = string.Empty; if (AbsoluteCachePath != null) @@ -302,16 +295,16 @@ namespace ImageProcessor.Web.Caching // Collision rate of about 1 in 10000 for the folder structure. string pathFromKey = string.Join("\\", encryptedName.ToCharArray().Take(6)); + string virtualPathFromKey = pathFromKey.Replace(@"\", "/"); string cachedFileName = string.Format( "{0}.{1}", encryptedName, !string.IsNullOrWhiteSpace(parsedExtension) ? parsedExtension.Replace(".", string.Empty) : fallbackExtension); - cachedPath = Path.Combine(AbsoluteCachePath, pathFromKey, cachedFileName); + this.physicalCachedPath = Path.Combine(AbsoluteCachePath, pathFromKey, cachedFileName); + this.virtualCachedPath = Path.Combine(VirtualCachePath, virtualPathFromKey, cachedFileName).Replace(@"\", "/"); } - - return cachedPath; } /// diff --git a/src/ImageProcessor.Web/NET45/HttpModules/ImageProcessingModule.cs b/src/ImageProcessor.Web/NET45/HttpModules/ImageProcessingModule.cs index c1ee8355e..0f4db86f3 100644 --- a/src/ImageProcessor.Web/NET45/HttpModules/ImageProcessingModule.cs +++ b/src/ImageProcessor.Web/NET45/HttpModules/ImageProcessingModule.cs @@ -338,15 +338,15 @@ namespace ImageProcessor.Web.HttpModules } // Create a new cache to help process and cache the request. - DiskCache cache = new DiskCache(request, requestPath, fullPath, imageName); - string cachedPath = await cache.GetCachePathAsync(); + DiskCache cache = new DiskCache(requestPath, fullPath, imageName); + string cachedPath = cache.CachedPath; // Since we are now rewriting the path we need to check again that the current user has access // to the rewritten path. // Get the user for the current request // If the user is anonymous or authentication doesn't work for this suffix avoid a NullReferenceException // in the UrlAuthorizationModule by creating a generic identity. - string virtualCachedPath = cache.GetVirtualCachedPath(cachedPath); + string virtualCachedPath = cache.VirtualCachedPath; IPrincipal user = context.User ?? new GenericPrincipal(new GenericIdentity(string.Empty, string.Empty), new string[0]);