PERF: Monitor modules files asynchronously

Monitoring virtual dependency files for modules takes requires a lot
of file I/O, and can be a significant bottleneck on heavily loaded
disk sub-system (5-10 secs). Making this process asynchronous during
startup decreases startup time by almost that amount of time.

--HG--
branch : 1.x
This commit is contained in:
Renaud Paquay
2011-05-28 19:05:52 -07:00
parent 7a35d03902
commit 23afea8a0e
7 changed files with 119 additions and 23 deletions

View File

@@ -19,6 +19,7 @@ namespace Orchard.Environment {
private readonly IRunningShellTable _runningShellTable;
private readonly IProcessingEngine _processingEngine;
private readonly IExtensionLoaderCoordinator _extensionLoaderCoordinator;
private readonly IExtensionMonitoringCoordinator _extensionMonitoringCoordinator;
private readonly ICacheManager _cacheManager;
private readonly object _syncLock = new object();
@@ -30,6 +31,7 @@ namespace Orchard.Environment {
IRunningShellTable runningShellTable,
IProcessingEngine processingEngine,
IExtensionLoaderCoordinator extensionLoaderCoordinator,
IExtensionMonitoringCoordinator extensionMonitoringCoordinator,
ICacheManager cacheManager,
IHostLocalRestart hostLocalRestart ) {
_shellSettingsManager = shellSettingsManager;
@@ -37,6 +39,7 @@ namespace Orchard.Environment {
_runningShellTable = runningShellTable;
_processingEngine = processingEngine;
_extensionLoaderCoordinator = extensionLoaderCoordinator;
_extensionMonitoringCoordinator = extensionMonitoringCoordinator;
_cacheManager = cacheManager;
_hostLocalRestart = hostLocalRestart;
@@ -151,7 +154,7 @@ namespace Orchard.Environment {
// on disk, and we need to reload new/updated extensions.
_cacheManager.Get("OrchardHost_Extensions",
ctx => {
_extensionLoaderCoordinator.MonitorExtensions(ctx.Monitor);
_extensionMonitoringCoordinator.MonitorExtensions(ctx.Monitor);
_hostLocalRestart.Monitor(ctx.Monitor);
DisposeShellContext();
return "";

View File

@@ -165,7 +165,7 @@ namespace Orchard.Environment.Extensions {
if (duplicates.Count() > 0) {
var sb = new StringBuilder();
sb.Append(T("There are multiple extensions with the same name installed in this instance of Orchard.\r\n"));
foreach(var dup in duplicates) {
foreach (var dup in duplicates) {
sb.Append(T("Extension '{0}' has been found from the following locations: {1}.\r\n", dup.Key, string.Join(", ", dup.Select(e => e.Location + "/" + e.Id))));
}
sb.Append(T("This issue can be usually solved by removing or renaming the conflicting extension."));
@@ -300,21 +300,5 @@ namespace Orchard.Environment.Extensions {
action();
}
}
public void MonitorExtensions(Action<IVolatileToken> monitor) {
Logger.Information("Start monitoring extension files...");
// Monitor add/remove of any module/theme
monitor(_virtualPathMonitor.WhenPathChanges("~/Modules"));
monitor(_virtualPathMonitor.WhenPathChanges("~/Themes"));
// Give loaders a chance to monitor any additional changes
var extensions = _extensionManager.AvailableExtensions().Where(d => DefaultExtensionTypes.IsModule(d.ExtensionType) || DefaultExtensionTypes.IsTheme(d.ExtensionType)).ToList();
foreach (var extension in extensions) {
foreach (var loader in _loaders) {
loader.Monitor(extension, monitor);
}
}
Logger.Information("Done monitoring extension files...");
}
}
}

View File

@@ -0,0 +1,102 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using Orchard.Caching;
using Orchard.Environment.Extensions.Loaders;
using Orchard.Environment.Extensions.Models;
using Orchard.FileSystems.VirtualPath;
using Orchard.Logging;
namespace Orchard.Environment.Extensions {
public class ExtensionMonitoringCoordinator : IExtensionMonitoringCoordinator {
private readonly IVirtualPathMonitor _virtualPathMonitor;
private readonly IExtensionManager _extensionManager;
private readonly IEnumerable<IExtensionLoader> _loaders;
public ExtensionMonitoringCoordinator(IVirtualPathMonitor virtualPathMonitor,
IExtensionManager extensionManager,
IEnumerable<IExtensionLoader> loaders) {
_virtualPathMonitor = virtualPathMonitor;
_extensionManager = extensionManager;
_loaders = loaders;
Logger = NullLogger.Instance;
}
public ILogger Logger { get; set; }
public bool Disabled { get; set; }
public void MonitorExtensions(Action<IVolatileToken> monitor) {
// We may be disabled by custom host configuration for performance reasons
if (Disabled)
return;
//PREF: Monitor extensions asynchronously. IsCurrent will be 'true'
// until all tokens are collected by the work function.
var token = new AsyncVolativeToken(MonitorExtensionsWork, Logger);
monitor(token);
token.QueueWorkItem();
}
public void MonitorExtensionsWork(Action<IVolatileToken> monitor) {
Logger.Information("Start monitoring extension files...");
// Monitor add/remove of any module/theme
monitor(_virtualPathMonitor.WhenPathChanges("~/Modules"));
monitor(_virtualPathMonitor.WhenPathChanges("~/Themes"));
// Give loaders a chance to monitor any additional changes
var extensions = _extensionManager.AvailableExtensions().Where(d => DefaultExtensionTypes.IsModule(d.ExtensionType) || DefaultExtensionTypes.IsTheme(d.ExtensionType)).ToList();
foreach (var extension in extensions) {
foreach (var loader in _loaders) {
loader.Monitor(extension, monitor);
}
}
Logger.Information("Done monitoring extension files...");
}
public class AsyncVolativeToken : IVolatileToken {
private readonly Action<Action<IVolatileToken>> _task;
private readonly List<IVolatileToken> _taskTokens = new List<IVolatileToken>();
private volatile Exception _taskException;
private volatile bool _isTaskFinished;
public AsyncVolativeToken(Action<Action<IVolatileToken>> task, ILogger logger) {
_task = task;
Logger = logger;
}
public ILogger Logger { get; set; }
public void QueueWorkItem() {
// Start a work item to collect tokens in our internal array
ThreadPool.QueueUserWorkItem((state) => {
try {
_task(token => _taskTokens.Add(token));
}
catch (Exception e) {
Logger.Error(e, "Error while monitoring extension files. Assuming extensions are not current.");
_taskException = e;
}
finally {
_isTaskFinished = true;
}
});
}
public bool IsCurrent {
get {
// We are current until the task has finished
if (_taskException != null) {
return false;
}
if (_isTaskFinished) {
return _taskTokens.All(t => t.IsCurrent);
}
return true;
}
}
}
}
}

View File

@@ -1,9 +1,5 @@
using System;
using Orchard.Caching;
namespace Orchard.Environment.Extensions {
namespace Orchard.Environment.Extensions {
public interface IExtensionLoaderCoordinator {
void SetupExtensions();
void MonitorExtensions(Action<IVolatileToken> monitor);
}
}

View File

@@ -0,0 +1,8 @@
using System;
using Orchard.Caching;
namespace Orchard.Environment.Extensions {
public interface IExtensionMonitoringCoordinator {
void MonitorExtensions(Action<IVolatileToken> monitor);
}
}

View File

@@ -77,6 +77,7 @@ namespace Orchard.Environment {
{
builder.RegisterType<ShellContainerRegistrations>().As<IShellContainerRegistrations>().SingleInstance();
builder.RegisterType<ExtensionLoaderCoordinator>().As<IExtensionLoaderCoordinator>().SingleInstance();
builder.RegisterType<ExtensionMonitoringCoordinator>().As<IExtensionMonitoringCoordinator>().SingleInstance();
builder.RegisterType<ExtensionManager>().As<IExtensionManager>().SingleInstance();
{
builder.RegisterType<ModuleFolders>().As<IExtensionFolders>().SingleInstance()

View File

@@ -175,6 +175,8 @@
<Compile Include="DisplayManagement\Shapes\ShapeDebugView.cs" />
<Compile Include="DisplayManagement\Shapes\ITagBuilderFactory.cs" />
<Compile Include="Environment\CollectionOrderModule.cs" />
<Compile Include="Environment\Extensions\ExtensionMonitoringCoordinator.cs" />
<Compile Include="Environment\Extensions\IExtensionMonitoringCoordinator.cs" />
<Compile Include="Environment\Extensions\OrchardSuppressDependencyAttribute.cs" />
<Compile Include="Environment\Features\IFeatureManager.cs" />
<Compile Include="Environment\IAssemblyNameResolver.cs" />