From 4becc488c8b6bca0a40739d15deddb08914205cc Mon Sep 17 00:00:00 2001 From: Renaud Paquay Date: Wed, 9 Jun 2010 18:55:35 -0700 Subject: [PATCH] Couple of fixed with dynamic extension loader Perf: Use the cache manager to keep the list of dependency descriptors in memory, with invalidation when "dependencies.xml" changes on disk When loading an assembly from the list of references assemblies, notify the dependencies folder component, so it removes the entry. --HG-- branch : dev --- src/Orchard.Tests/Stubs/StubWebSiteFolder.cs | 5 + .../Loaders/ReferencedExtensionLoader.cs | 8 ++ .../Dependencies/IDependenciesFolder.cs | 91 +++++++++++-------- .../FileSystems/WebSite/IWebSiteFolder.cs | 4 +- .../FileSystems/WebSite/WebSiteFolder.cs | 11 ++- 5 files changed, 80 insertions(+), 39 deletions(-) diff --git a/src/Orchard.Tests/Stubs/StubWebSiteFolder.cs b/src/Orchard.Tests/Stubs/StubWebSiteFolder.cs index 82e0446ee..211824fea 100644 --- a/src/Orchard.Tests/Stubs/StubWebSiteFolder.cs +++ b/src/Orchard.Tests/Stubs/StubWebSiteFolder.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.IO; using System.Linq; @@ -23,5 +24,9 @@ namespace Orchard.Tests.Stubs { public IVolatileToken WhenPathChanges(string path) { return new WebSiteFolder.Token(path); } + + public void WhenPathChanges(string path, Action action) { + throw new NotImplementedException(); + } } } \ No newline at end of file diff --git a/src/Orchard/Environment/Extensions/Loaders/ReferencedExtensionLoader.cs b/src/Orchard/Environment/Extensions/Loaders/ReferencedExtensionLoader.cs index 39b8a19f0..4ce622ef7 100644 --- a/src/Orchard/Environment/Extensions/Loaders/ReferencedExtensionLoader.cs +++ b/src/Orchard/Environment/Extensions/Loaders/ReferencedExtensionLoader.cs @@ -3,14 +3,20 @@ using System.Reflection; using System.Web.Compilation; using System.Web.Hosting; using Orchard.Environment.Extensions.Models; +using Orchard.FileSystems.Dependencies; namespace Orchard.Environment.Extensions.Loaders { /// /// Load an extension by looking through the BuildManager referenced assemblies /// public class ReferencedExtensionLoader : IExtensionLoader { + private readonly IDependenciesFolder _dependenciesFolder; public int Order { get { return 20; } } + public ReferencedExtensionLoader(IDependenciesFolder dependenciesFolder) { + _dependenciesFolder = dependenciesFolder; + } + public ExtensionEntry Load(ExtensionDescriptor descriptor) { if (HostingEnvironment.IsHosted == false) return null; @@ -22,6 +28,8 @@ namespace Orchard.Environment.Extensions.Loaders { if (assembly == null) return null; + _dependenciesFolder.StoreReferencedAssembly(descriptor.Name); + return new ExtensionEntry { Descriptor = descriptor, Assembly = assembly, diff --git a/src/Orchard/FileSystems/Dependencies/IDependenciesFolder.cs b/src/Orchard/FileSystems/Dependencies/IDependenciesFolder.cs index 321f74e16..d3d87d60a 100644 --- a/src/Orchard/FileSystems/Dependencies/IDependenciesFolder.cs +++ b/src/Orchard/FileSystems/Dependencies/IDependenciesFolder.cs @@ -3,13 +3,11 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; -using System.Web.Caching; -using System.Web.Hosting; using System.Xml.Linq; using Orchard.Caching; using Orchard.Environment; using Orchard.Environment.Extensions; -using Orchard.Environment.Topology; +using Orchard.FileSystems.WebSite; namespace Orchard.FileSystems.Dependencies { public class DependencyDescriptor { @@ -20,21 +18,27 @@ namespace Orchard.FileSystems.Dependencies { } public interface IDependenciesFolder : IVolatileProvider { - void StoreBuildProviderAssembly(string moduleName, string virtualPath, Assembly assembly); + void StoreReferencedAssembly(string moduleName); void StorePrecompiledAssembly(string moduleName, string virtualPath); + void StoreBuildProviderAssembly(string moduleName, string virtualPath, Assembly assembly); DependencyDescriptor GetDescriptor(string moduleName); Assembly LoadAssembly(string assemblyName); } public class DefaultDependenciesFolder : IDependenciesFolder { - private readonly string _prefix = Guid.NewGuid().ToString("n"); - private const string _basePath = "~/App_Data/Dependencies"; + private readonly string _basePath = "~/App_Data/Dependencies"; + private readonly ICacheManager _cacheManager; + private readonly IWebSiteFolder _webSiteFolder; private readonly IVirtualPathProvider _virtualPathProvider; private readonly IExtensionManagerEvents _events; + private readonly InvalidationToken _token; - public DefaultDependenciesFolder(IVirtualPathProvider virtualPathProvider, IExtensionManagerEvents events) { + public DefaultDependenciesFolder(ICacheManager cacheManager, IWebSiteFolder webSiteFolder, IVirtualPathProvider virtualPathProvider, IExtensionManagerEvents events) { + _cacheManager = cacheManager; + _webSiteFolder = webSiteFolder; _virtualPathProvider = virtualPathProvider; _events = events; + _token = new InvalidationToken(); } private string BasePath { @@ -49,9 +53,33 @@ namespace Orchard.FileSystems.Dependencies { } } - public void StoreBuildProviderAssembly(string moduleName, string virtualPath, Assembly assembly) { - _virtualPathProvider.CreateDirectory(BasePath); + private IList Descriptors { + get { + return _cacheManager.Get(PersistencePath, + ctx => { + ctx.Monitor(_webSiteFolder.WhenPathChanges(ctx.Key)); + ctx.Monitor(_token); + _virtualPathProvider.CreateDirectory(BasePath); + return ReadDependencies(ctx.Key); + }); + } + } + + public class InvalidationToken : IVolatileToken { + public bool IsCurrent { get; set; } + } + + public void StoreReferencedAssembly(string moduleName) { + if (Descriptors.Any(d => d.ModuleName == moduleName)) { + // Remove the moduleName from the list of assemblies in the dependency folder + var newDescriptors = Descriptors.Where(d => d.ModuleName != moduleName); + + WriteDependencies(PersistencePath, newDescriptors); + } + } + + public void StoreBuildProviderAssembly(string moduleName, string virtualPath, Assembly assembly) { var descriptor = new DependencyDescriptor { ModuleName = moduleName, IsFromBuildProvider = true, @@ -61,21 +89,7 @@ namespace Orchard.FileSystems.Dependencies { StoreDepencyInformation(descriptor); -#if true - var cacheDependency = HostingEnvironment.VirtualPathProvider.GetCacheDependency( - virtualPath, - new[] { virtualPath }, - DateTime.UtcNow); - - HostingEnvironment.Cache.Add( - _prefix + virtualPath, - moduleName, - cacheDependency, - Cache.NoAbsoluteExpiration, - Cache.NoSlidingExpiration, - CacheItemPriority.NotRemovable, - (key, value, reason) => _events.ModuleChanged((string) value)); -#endif + _webSiteFolder.WhenPathChanges(virtualPath, () => _events.ModuleChanged(moduleName)); } public void StorePrecompiledAssembly(string moduleName, string virtualPath) { @@ -98,11 +112,11 @@ namespace Orchard.FileSystems.Dependencies { } public DependencyDescriptor GetDescriptor(string moduleName) { - return ReadDependencies().SingleOrDefault(d => d.ModuleName == moduleName); + return Descriptors.SingleOrDefault(d => d.ModuleName == moduleName); } private bool IsNewerAssembly(string moduleName, string assemblyFileName) { - var dependency = ReadDependencies().SingleOrDefault(d => d.ModuleName == moduleName); + var dependency = Descriptors.SingleOrDefault(d => d.ModuleName == moduleName); if (dependency == null) { return true; } @@ -116,7 +130,7 @@ namespace Orchard.FileSystems.Dependencies { } private void StoreDepencyInformation(DependencyDescriptor descriptor) { - var dependencies = ReadDependencies().ToList(); + var dependencies = Descriptors.ToList(); int index = dependencies.FindIndex(d => d.ModuleName == descriptor.ModuleName); if (index < 0) { dependencies.Add(descriptor); @@ -125,13 +139,13 @@ namespace Orchard.FileSystems.Dependencies { dependencies[index] = descriptor; } - WriteDependencies(dependencies); + WriteDependencies(PersistencePath, dependencies); } public Assembly LoadAssembly(string assemblyName) { _virtualPathProvider.CreateDirectory(BasePath); - var dependency = ReadDependencies().SingleOrDefault(d => d.ModuleName == assemblyName); + var dependency = Descriptors.SingleOrDefault(d => d.ModuleName == assemblyName); if (dependency == null) return null; @@ -141,11 +155,13 @@ namespace Orchard.FileSystems.Dependencies { return Assembly.Load(Path.GetFileNameWithoutExtension(dependency.FileName)); } - private IEnumerable ReadDependencies() { - if (!_virtualPathProvider.FileExists(PersistencePath)) + private IEnumerable ReadDependencies(string persistancePath) { + Func ns = (name => XName.Get(name)); + + if (!_virtualPathProvider.FileExists(persistancePath)) return Enumerable.Empty(); - using (var stream = _virtualPathProvider.OpenFile(PersistencePath)) { + using (var stream = _virtualPathProvider.OpenFile(persistancePath)) { XDocument document = XDocument.Load(stream); return document .Elements(ns("Dependencies")) @@ -160,7 +176,9 @@ namespace Orchard.FileSystems.Dependencies { } } - private void WriteDependencies(IEnumerable dependencies) { + private void WriteDependencies(string persistancePath, IEnumerable dependencies) { + Func ns = (name => XName.Get(name)); + var document = new XDocument(); document.Add(new XElement(ns("Dependencies"))); var elements = dependencies.Select(d => new XElement("Dependency", @@ -170,13 +188,12 @@ namespace Orchard.FileSystems.Dependencies { new XElement(ns("FileName"), d.FileName))); document.Root.Add(elements); - using (var stream = _virtualPathProvider.CreateText(PersistencePath)) { + using (var stream = _virtualPathProvider.CreateText(persistancePath)) { document.Save(stream, SaveOptions.None); } - } - private static XName ns(string name) { - return XName.Get(name/*, "http://schemas.microsoft.com/developer/msbuild/2003"*/); + // Ensure cache is invalidated right away, not waiting for file change notification to happen + _token.IsCurrent = false; } } } diff --git a/src/Orchard/FileSystems/WebSite/IWebSiteFolder.cs b/src/Orchard/FileSystems/WebSite/IWebSiteFolder.cs index a7dc73925..8836d2009 100644 --- a/src/Orchard/FileSystems/WebSite/IWebSiteFolder.cs +++ b/src/Orchard/FileSystems/WebSite/IWebSiteFolder.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using Orchard.Caching; namespace Orchard.FileSystems.WebSite { @@ -7,5 +8,6 @@ namespace Orchard.FileSystems.WebSite { string ReadFile(string path); IVolatileToken WhenPathChanges(string path); + void WhenPathChanges(string path, Action action); } } \ No newline at end of file diff --git a/src/Orchard/FileSystems/WebSite/WebSiteFolder.cs b/src/Orchard/FileSystems/WebSite/WebSiteFolder.cs index d82eec4f9..d5b2ddd4b 100644 --- a/src/Orchard/FileSystems/WebSite/WebSiteFolder.cs +++ b/src/Orchard/FileSystems/WebSite/WebSiteFolder.cs @@ -49,6 +49,10 @@ namespace Orchard.FileSystems.WebSite { return token; } + public void WhenPathChanges(string virtualPath, Action action) { + BindSignal(virtualPath, (key, value, reason) => action()); + } + private Token BindToken(string virtualPath) { lock (_tokens) { Weak weak; @@ -80,6 +84,11 @@ namespace Orchard.FileSystems.WebSite { } private void BindSignal(string virtualPath) { + BindSignal(virtualPath, _thunk.Signal); + + } + + private void BindSignal(string virtualPath, CacheItemRemovedCallback callback) { var cacheDependency = HostingEnvironment.VirtualPathProvider.GetCacheDependency( virtualPath, new[] { virtualPath }, @@ -92,7 +101,7 @@ namespace Orchard.FileSystems.WebSite { Cache.NoAbsoluteExpiration, Cache.NoSlidingExpiration, CacheItemPriority.NotRemovable, - _thunk.Signal); + callback); } public void Signal(string key, object value, CacheItemRemovedReason reason) {