PERF: Re-use cached filehash for dynamic module hash

Before recompiling a dynamic module (.csproj), the BuildManager
verifies that the file hash (returned from the VPP) has changed
from the previous compilation. Instead of returning of the file
hash of the .csproj file and all its virtual dependencies (source
files and references), use the file hash value stored in the file
managed by IExtensionDependenciesManager.

This improves startup time by 10+ seconds on file i/o bound machines.

--HG--
branch : 1.x
This commit is contained in:
Renaud Paquay
2011-05-28 22:16:14 -07:00
parent 21f1658f48
commit 933b625bf0
5 changed files with 104 additions and 56 deletions

View File

@@ -85,14 +85,37 @@ namespace Orchard.Environment.Extensions {
}
}
private string GetFileHash(ExtensionLoadingContext context, string path) {
private string GetFileHash(ExtensionLoadingContext context, string extensionId) {
var hash = new Hash();
hash.AddString(path);
hash.AddString(extensionId);
DateTime dateTime;
if (context.VirtualPathModficationDates.TryGetValue(path, out dateTime)) {
hash.AddDateTime(dateTime);
{
ExtensionProbeEntry extensionProbe;
if (context.ProcessedExtensions.TryGetValue(extensionId, out extensionProbe)) {
if (extensionProbe != null) {
var virtualPathDependencies = extensionProbe.VirtualPathDependencies;
foreach (var virtualpathDependency in virtualPathDependencies) {
DateTime dateTime;
if (context.VirtualPathModficationDates.TryGetValue(virtualpathDependency, out dateTime)) {
hash.AddDateTime(dateTime);
}
}
}
}
}
{
ExtensionReferenceProbeEntry extensionReferenceProbe;
if (context.ProcessedReferences.TryGetValue(extensionId, out extensionReferenceProbe)) {
if (extensionReferenceProbe != null) {
DateTime dateTime;
if (context.VirtualPathModficationDates.TryGetValue(extensionReferenceProbe.VirtualPath, out dateTime)) {
hash.AddDateTime(dateTime);
}
}
}
}
return hash.Value;
}
@@ -309,8 +332,8 @@ namespace Orchard.Environment.Extensions {
// Activate the binary ref
if (bestBinaryReference != null) {
if (!context.ProcessedReferences.Contains(bestBinaryReference.Entry.Name)) {
context.ProcessedReferences.Add(bestBinaryReference.Entry.Name);
if (!context.ProcessedReferences.ContainsKey(bestBinaryReference.Entry.Name)) {
context.ProcessedReferences.Add(bestBinaryReference.Entry.Name, bestBinaryReference.Entry);
bestBinaryReference.Entry.Loader.ReferenceActivated(context, bestBinaryReference.Entry);
}
activatedReferences.Add(new DependencyReferenceDescriptor {

View File

@@ -9,14 +9,14 @@ namespace Orchard.Environment.Extensions {
public class ExtensionLoadingContext {
public ExtensionLoadingContext() {
ProcessedExtensions = new Dictionary<string, ExtensionProbeEntry>(StringComparer.OrdinalIgnoreCase);
ProcessedReferences = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
ProcessedReferences = new Dictionary<string, ExtensionReferenceProbeEntry>(StringComparer.OrdinalIgnoreCase);
DeleteActions = new List<Action>();
CopyActions = new List<Action>();
NewDependencies = new List<DependencyDescriptor>();
}
public IDictionary<string, ExtensionProbeEntry> ProcessedExtensions { get; private set; }
public ISet<string> ProcessedReferences { get; private set; }
public IDictionary<string, ExtensionReferenceProbeEntry> ProcessedReferences { get; private set; }
public IList<DependencyDescriptor> NewDependencies { get; private set; }

View File

@@ -4,7 +4,6 @@ using System.Linq;
using System.Xml.Linq;
using Orchard.Caching;
using Orchard.FileSystems.AppData;
using Orchard.FileSystems.VirtualPath;
using Orchard.Logging;
namespace Orchard.FileSystems.Dependencies {
@@ -14,12 +13,17 @@ namespace Orchard.FileSystems.Dependencies {
/// the file stored by this component will also change.
/// </summary>
public class DefaultExtensionDependenciesManager : IExtensionDependenciesManager {
private readonly IAppDataFolder _appDataFolder;
private const string BasePath = "Dependencies";
private const string FileName = "Dependencies.ModuleCompilation.xml";
private readonly ICacheManager _cacheManager;
private readonly IAppDataFolder _appDataFolder;
private readonly InvalidationToken _writeThroughToken;
public DefaultExtensionDependenciesManager(IAppDataFolder appDataFolder) {
public DefaultExtensionDependenciesManager(ICacheManager cacheManager, IAppDataFolder appDataFolder) {
_cacheManager = cacheManager;
_appDataFolder = appDataFolder;
_writeThroughToken = new InvalidationToken();
Logger = NullLogger.Instance;
}
@@ -53,6 +57,23 @@ namespace Orchard.FileSystems.Dependencies {
}
}
public ActivatedExtensionDescriptor GetDescriptor(string extensionId) {
return LoadDescriptors().FirstOrDefault(d => d.ExtensionId.Equals(extensionId, StringComparison.OrdinalIgnoreCase));
}
public IEnumerable<ActivatedExtensionDescriptor> LoadDescriptors() {
return _cacheManager.Get(PersistencePath,
ctx => {
_appDataFolder.CreateDirectory(BasePath);
ctx.Monitor(_appDataFolder.WhenPathChanges(ctx.Key));
_writeThroughToken.IsCurrent = true;
ctx.Monitor(_writeThroughToken);
return ReadDescriptors(ctx.Key).ToList();
});
}
private XDocument CreateDocument(IEnumerable<DependencyDescriptor> dependencies, Func<string, string> fileHashProvider) {
Func<string, XName> ns = (name => XName.Get(name));
@@ -60,21 +81,42 @@ namespace Orchard.FileSystems.Dependencies {
document.Add(new XElement(ns("Dependencies")));
var elements = FilterDependencies(dependencies).Select(
d => new XElement("Dependency",
new XElement(ns("ModuleName"), d.Name),
new XElement(ns("ExtensionId"), d.Name),
new XElement(ns("LoaderName"), d.LoaderName),
new XElement(ns("VirtualPath"), d.VirtualPath),
new XElement(ns("FileHash"), fileHashProvider(d.VirtualPath)),
new XElement(ns("FileHash"), fileHashProvider(d.Name)),
new XElement(ns("References"), FilterReferences(d.References)
.Select(r => new XElement(ns("Reference"),
new XElement(ns("Name"), r.Name),
new XElement(ns("ReferenceId"), r.Name),
new XElement(ns("LoaderName"), r.LoaderName),
new XElement(ns("VirtualPath"), r.VirtualPath),
new XElement(ns("FileHash"), fileHashProvider(r.VirtualPath)))).ToArray())));
new XElement(ns("FileHash"), fileHashProvider(r.Name)))).ToArray())));
document.Root.Add(elements);
return document;
}
private IEnumerable<ActivatedExtensionDescriptor> ReadDescriptors(string persistancePath) {
Func<string, XName> ns = (name => XName.Get(name));
Func<XElement, string, string> elem = (e, name) => e.Element(ns(name)).Value;
XDocument document = ReadDocument(persistancePath);
return document
.Elements(ns("Dependencies"))
.Elements(ns("Dependency"))
.Select(e => new ActivatedExtensionDescriptor {
ExtensionId = elem(e, "ExtensionId"),
VirtualPath = elem(e, "VirtualPath"),
LoaderName = elem(e, "LoaderName"),
FileHash = elem(e, "FileHash"),
//References = e.Elements(ns("References")).Elements(ns("Reference")).Select(r => new DependencyReferenceDescriptor {
// Name = elem(r, "Name"),
// LoaderName = elem(r, "LoaderName"),
// VirtualPath = elem(r, "VirtualPath")
//})
}).ToList();
}
private IEnumerable<DependencyDescriptor> FilterDependencies(IEnumerable<DependencyDescriptor> dependencies) {
return dependencies.Where(dep => IsSupportedLoader(dep.LoaderName));
}
@@ -87,11 +129,12 @@ namespace Orchard.FileSystems.Dependencies {
//Note: this is hard-coded for now, to avoid adding more responsibilities to the IExtensionLoader
// implementations.
return
loaderName == "DynamicExtensionLoader" ||
loaderName == "DynamicExtensionLoader" ||
loaderName == "PrecompiledExtensionLoader";
}
private void WriteDocument(string persistancePath, XDocument document) {
_writeThroughToken.IsCurrent = false;
using (var stream = _appDataFolder.CreateFile(persistancePath)) {
document.Save(stream, SaveOptions.None);
stream.Close();
@@ -107,7 +150,7 @@ namespace Orchard.FileSystems.Dependencies {
return XDocument.Load(stream);
}
}
catch(Exception e) {
catch (Exception e) {
Logger.Information(e, "Error reading file '{0}'", persistancePath);
return new XDocument();
}

View File

@@ -14,25 +14,17 @@ namespace Orchard.FileSystems.Dependencies {
/// served from the "~/Modules" or "~/Themes" directory.
/// </summary>
public class DynamicModuleVirtualPathProvider : VirtualPathProvider, ICustomVirtualPathProvider {
private readonly IDependenciesFolder _dependenciesFolder;
private readonly IExtensionDependenciesManager _extensionDependenciesManager;
private readonly IEnumerable<IExtensionLoader> _loaders;
public DynamicModuleVirtualPathProvider(IDependenciesFolder dependenciesFolder, IEnumerable<IExtensionLoader> loaders) {
_dependenciesFolder = dependenciesFolder;
public DynamicModuleVirtualPathProvider(IExtensionDependenciesManager extensionDependenciesManager, IEnumerable<IExtensionLoader> loaders) {
_extensionDependenciesManager = extensionDependenciesManager;
_loaders = loaders;
Logger = NullLogger.Instance;
}
public ILogger Logger { get; set; }
public override bool DirectoryExists(string virtualDir) {
return Previous.DirectoryExists(virtualDir);
}
public override bool FileExists(string virtualPath) {
return Previous.FileExists(virtualPath);
}
public override string GetFileHash(string virtualPath, IEnumerable virtualPathDependencies) {
var result = GetFileHashWorker(virtualPath, virtualPathDependencies);
Logger.Debug("GetFileHash(\"{0}\"): {1}", virtualPath, result);
@@ -42,48 +34,29 @@ namespace Orchard.FileSystems.Dependencies {
private string GetFileHashWorker(string virtualPath, IEnumerable virtualPathDependencies) {
virtualPath = VirtualPathUtility.ToAppRelative(virtualPath);
var desc = GetDependencyDescriptor(virtualPath);
var desc = GetExtensionDescriptor(virtualPath);
if (desc != null) {
// We are only interested in ".csproj" files loaded from "DynamicExtensionLoader"
var dynamicExtensionLoader = _loaders.Where(l => l.Name == desc.LoaderName).FirstOrDefault() as DynamicExtensionLoader;
if (dynamicExtensionLoader != null) {
if (virtualPath.Equals(desc.VirtualPath, StringComparison.OrdinalIgnoreCase)) {
var otherDependencies = dynamicExtensionLoader.GetFileHashDependencies(desc);
if (otherDependencies.Any()) {
var allDependencies = virtualPathDependencies.OfType<string>().Concat(otherDependencies).ToList();
if (Logger.IsEnabled(LogLevel.Debug)) {
Logger.Debug("GetFileHash(\"{0}\") - virtual path dependencies:", virtualPath);
foreach (var dependency in allDependencies) {
Logger.Debug(" Dependency: \"{0}\"", dependency);
}
}
return base.GetFileHash(virtualPath, allDependencies);
}
return desc.FileHash;
}
}
}
return base.GetFileHash(virtualPath, virtualPathDependencies);
}
public override VirtualFile GetFile(string virtualPath) {
return Previous.GetFile(virtualPath);
}
private DependencyDescriptor GetDependencyDescriptor(string virtualPath) {
private ActivatedExtensionDescriptor GetExtensionDescriptor(string virtualPath) {
var prefix = PrefixMatch(virtualPath, DynamicExtensionLoader.ExtensionsVirtualPathPrefixes);
if (prefix == null)
return null;
var moduleName = ModuleMatch(virtualPath, prefix);
if (moduleName == null)
var moduleId = ModuleMatch(virtualPath, prefix);
if (moduleId == null)
return null;
return _dependenciesFolder.GetDescriptor(moduleName);
return _extensionDependenciesManager.GetDescriptor(moduleId);
}
private static string ModuleMatch(string virtualPath, string prefix) {
@@ -91,8 +64,8 @@ namespace Orchard.FileSystems.Dependencies {
if (index < 0)
return null;
var moduleName = virtualPath.Substring(prefix.Length, index - prefix.Length);
return (string.IsNullOrEmpty(moduleName) ? null : moduleName);
var moduleId = virtualPath.Substring(prefix.Length, index - prefix.Length);
return (string.IsNullOrEmpty(moduleId) ? null : moduleId);
}
private static string PrefixMatch(string virtualPath, params string[] prefixes) {

View File

@@ -3,8 +3,17 @@ using System.Collections.Generic;
using Orchard.Caching;
namespace Orchard.FileSystems.Dependencies {
public class ActivatedExtensionDescriptor {
public string ExtensionId { get; set; }
public string LoaderName { get; set; }
public string VirtualPath { get; set; }
public string FileHash { get; set; }
}
public interface IExtensionDependenciesManager : IVolatileProvider {
void StoreDependencies(IEnumerable<DependencyDescriptor> dependencyDescriptors, Func<string, string> fileHashProvider);
IEnumerable<string> GetVirtualPathDependencies(DependencyDescriptor descriptor);
ActivatedExtensionDescriptor GetDescriptor(string extensionId);
}
}