mirror of
https://github.com/OrchardCMS/Orchard.git
synced 2026-02-09 09:16:41 +08:00
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:
@@ -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 "";
|
||||
|
||||
@@ -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...");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
using System;
|
||||
using Orchard.Caching;
|
||||
|
||||
namespace Orchard.Environment.Extensions {
|
||||
public interface IExtensionMonitoringCoordinator {
|
||||
void MonitorExtensions(Action<IVolatileToken> monitor);
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
|
||||
@@ -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" />
|
||||
|
||||
Reference in New Issue
Block a user