Adding a background task sweep generator. Updated cms page scheduler processing as a background task.

--HG--
extra : convert_revision : svn%3A5ff7c347-ad56-4c35-b696-ccb81de16e03/trunk%4042426
This commit is contained in:
loudej
2009-11-27 08:52:38 +00:00
parent 72dd76a36d
commit 7a2d4c4a03
14 changed files with 313 additions and 8 deletions

View File

@@ -38,7 +38,8 @@ namespace Orchard.Tests.Environment {
new[] { modelBinderProvider1, modelBinderProvider2 }, new[] { modelBinderProvider1, modelBinderProvider2 },
modelBinderPublisher, modelBinderPublisher,
new ViewEngineCollection(), new ViewEngineCollection(),
new Moq.Mock<IPackageManager>().Object); new Moq.Mock<IPackageManager>().Object,
Enumerable.Empty<IOrchardShellEvents>());
runtime.Activate(); runtime.Activate();

View File

@@ -0,0 +1,77 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NUnit.Framework;
using Orchard.Logging;
namespace Orchard.Tests {
[TestFixture]
public class EventsTests {
[Test]
public void AllEventsAreCalled() {
var events = new ITestEvents[] { new FooSink(), new BarSink() };
events.Invoke(x => x.Hello("world"), NullLogger.Instance);
Assert.That(events.OfType<FooSink>().Single().Name, Is.EqualTo("world"));
Assert.That(events.OfType<BarSink>().Single().Name, Is.EqualTo("world"));
}
[Test]
public void AnExceptionShouldBeLoggedAndOtherEventsWillBeFired() {
var events = new ITestEvents[] { new FooSink(), new CrashSink(), new BarSink() };
var logger = new TestLogger();
events.Invoke(x => x.Hello("world"), logger);
Assert.That(events.OfType<FooSink>().Single().Name, Is.EqualTo("world"));
Assert.That(events.OfType<BarSink>().Single().Name, Is.EqualTo("world"));
Assert.That(logger.LogException, Is.TypeOf<ApplicationException>());
Assert.That(logger.LogException, Has.Property("Message").EqualTo("Illegal name 'world'"));
}
private class TestLogger : ILogger {
public bool IsEnabled(LogLevel level) {
return true;
}
public void Log(LogLevel level, Exception exception, string format, params object[] args) {
LogException = exception;
LogFormat = format;
LogArgs = args;
}
public Exception LogException { get; set; }
public string LogFormat { get; set; }
public object[] LogArgs { get; set; }
}
private interface ITestEvents : IEvents {
void Hello(string name);
}
private class FooSink : ITestEvents {
void ITestEvents.Hello(string name) {
Name = name;
}
public string Name { get; set; }
}
private class BarSink : ITestEvents {
void ITestEvents.Hello(string name) {
Name = name;
}
public string Name { get; set; }
}
private class CrashSink : ITestEvents {
void ITestEvents.Hello(string name) {
throw new ApplicationException("Illegal name '" + name + "'");
}
}
}
}

View File

@@ -104,6 +104,7 @@
<Compile Include="Environment\DefaultOrchardHostTests.cs" /> <Compile Include="Environment\DefaultOrchardHostTests.cs" />
<Compile Include="Environment\DefaultOrchardShellTests.cs" /> <Compile Include="Environment\DefaultOrchardShellTests.cs" />
<Compile Include="Environment\OrchardStarterTests.cs" /> <Compile Include="Environment\OrchardStarterTests.cs" />
<Compile Include="EventsTests.cs" />
<Compile Include="Localization\NullLocalizerTests.cs" /> <Compile Include="Localization\NullLocalizerTests.cs" />
<Compile Include="Logging\LoggingModuleTests.cs" /> <Compile Include="Logging\LoggingModuleTests.cs" />
<Compile Include="Models\ContentQueryTests.cs" /> <Compile Include="Models\ContentQueryTests.cs" />
@@ -127,6 +128,7 @@
<Compile Include="Mvc\OrchardControllerIdentificationStrategyTests.cs" /> <Compile Include="Mvc\OrchardControllerIdentificationStrategyTests.cs" />
<Compile Include="Mvc\RouteCollectionPublisherTests.cs" /> <Compile Include="Mvc\RouteCollectionPublisherTests.cs" />
<Compile Include="Mvc\Routes\StandardPackageRouteProviderTests.cs" /> <Compile Include="Mvc\Routes\StandardPackageRouteProviderTests.cs" />
<Compile Include="Tasks\SweepGeneratorTests.cs" />
<Compile Include="UI\Notify\NotifierTests.cs" /> <Compile Include="UI\Notify\NotifierTests.cs" />
<Compile Include="UI\Notify\NotifyFilterTests.cs" /> <Compile Include="UI\Notify\NotifyFilterTests.cs" />
<Compile Include="Packages\PackageFoldersTests.cs" /> <Compile Include="Packages\PackageFoldersTests.cs" />

View File

@@ -0,0 +1,43 @@
using System;
using Autofac.Builder;
using Moq;
using NUnit.Framework;
using Orchard.Tasks;
namespace Orchard.Tests.Tasks {
[TestFixture]
public class SweepGeneratorTests {
[Test]
public void DoWorkShouldSendHeartbeatToTaskManager() {
var taskManager = new Mock<IBackgroundService>();
var builder = new ContainerBuilder();
builder.Register(taskManager.Object);
var container = builder.Build();
var heartbeatSource = new SweepGenerator(container);
heartbeatSource.DoWork();
taskManager.Verify(x => x.Sweep(), Times.Once());
}
[Test]
public void ActivatedEventShouldStartTimer() {
var taskManager = new Mock<IBackgroundService>();
var builder = new ContainerBuilder();
builder.Register(taskManager.Object);
var container = builder.Build();
var heartbeatSource = new SweepGenerator(container) {
Interval = TimeSpan.FromMilliseconds(25)
};
taskManager.Verify(x => x.Sweep(), Times.Never());
heartbeatSource.Activated();
System.Threading.Thread.Sleep(TimeSpan.FromMilliseconds(80));
heartbeatSource.Terminating();
taskManager.Verify(x => x.Sweep(), Times.AtLeastOnce());
}
}
}

View File

@@ -6,11 +6,11 @@ using JetBrains.Annotations;
using Orchard.CmsPages.Models; using Orchard.CmsPages.Models;
using Orchard.Data; using Orchard.Data;
using Orchard.Services; using Orchard.Services;
using Orchard.Tasks;
namespace Orchard.CmsPages.Services { namespace Orchard.CmsPages.Services {
public interface IPageScheduler : IDependency { public interface IPageScheduler : IBackgroundTask {
void AddPublishTask(PageRevision revision, DateTime moment); void AddPublishTask(PageRevision revision, DateTime moment);
void Sweep();
void ClearTasks(Page page); void ClearTasks(Page page);
} }

View File

@@ -4,6 +4,7 @@ using System.Linq;
using System.Reflection; using System.Reflection;
using System.Threading; using System.Threading;
using System.Web; using System.Web;
using System.Web.Hosting;
using FluentNHibernate.Automapping; using FluentNHibernate.Automapping;
using FluentNHibernate.Automapping.Alterations; using FluentNHibernate.Automapping.Alterations;
using FluentNHibernate.Cfg; using FluentNHibernate.Cfg;
@@ -29,8 +30,9 @@ namespace Orchard.Data {
// that would eventually imply the need for configuration against one or more actual sources // that would eventually imply the need for configuration against one or more actual sources
// and a means to enlist record types from active packages into correct session factory // and a means to enlist record types from active packages into correct session factory
var database = var hackPath = HostingEnvironment.MapPath("~/App_Data/hack.db");
SQLiteConfiguration.Standard.UsingFile(HttpContext.Current.Server.MapPath("~/App_Data/hack.db"));
var database = SQLiteConfiguration.Standard.UsingFile(hackPath);
var recordTypes = _compositionStrategy.GetRecordTypes(); var recordTypes = _compositionStrategy.GetRecordTypes();

View File

@@ -3,9 +3,11 @@ using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Web.Mvc; using System.Web.Mvc;
using Orchard.Logging;
using Orchard.Mvc.ModelBinders; using Orchard.Mvc.ModelBinders;
using Orchard.Mvc.Routes; using Orchard.Mvc.Routes;
using Orchard.Packages; using Orchard.Packages;
using Orchard.Tasks;
namespace Orchard.Environment { namespace Orchard.Environment {
public class DefaultOrchardShell : IOrchardShell { public class DefaultOrchardShell : IOrchardShell {
@@ -15,6 +17,7 @@ namespace Orchard.Environment {
private readonly IModelBinderPublisher _modelBinderPublisher; private readonly IModelBinderPublisher _modelBinderPublisher;
private readonly ViewEngineCollection _viewEngines; private readonly ViewEngineCollection _viewEngines;
private readonly IPackageManager _packageManager; private readonly IPackageManager _packageManager;
private readonly IEnumerable<IOrchardShellEvents> _events;
public DefaultOrchardShell( public DefaultOrchardShell(
IEnumerable<IRouteProvider> routeProviders, IEnumerable<IRouteProvider> routeProviders,
@@ -22,15 +25,21 @@ namespace Orchard.Environment {
IEnumerable<IModelBinderProvider> modelBinderProviders, IEnumerable<IModelBinderProvider> modelBinderProviders,
IModelBinderPublisher modelBinderPublisher, IModelBinderPublisher modelBinderPublisher,
ViewEngineCollection viewEngines, ViewEngineCollection viewEngines,
IPackageManager packageManager) { IPackageManager packageManager,
IEnumerable<IOrchardShellEvents> events) {
_routeProviders = routeProviders; _routeProviders = routeProviders;
_routePublisher = routePublisher; _routePublisher = routePublisher;
_modelBinderProviders = modelBinderProviders; _modelBinderProviders = modelBinderProviders;
_modelBinderPublisher = modelBinderPublisher; _modelBinderPublisher = modelBinderPublisher;
_viewEngines = viewEngines; _viewEngines = viewEngines;
_packageManager = packageManager; _packageManager = packageManager;
_events = events;
Logger = NullLogger.Instance;
} }
public ILogger Logger { get; set; }
static IEnumerable<string> OrchardLocationFormats() { static IEnumerable<string> OrchardLocationFormats() {
return new[] { return new[] {
@@ -49,8 +58,8 @@ namespace Orchard.Environment {
_routePublisher.Publish(_routeProviders.SelectMany(provider => provider.GetRoutes())); _routePublisher.Publish(_routeProviders.SelectMany(provider => provider.GetRoutes()));
_modelBinderPublisher.Publish(_modelBinderProviders.SelectMany(provider => provider.GetModelBinders())); _modelBinderPublisher.Publish(_modelBinderProviders.SelectMany(provider => provider.GetModelBinders()));
var viewEngine = ViewEngines.Engines.OfType<VirtualPathProviderViewEngine>().Single(); var viewEngine = _viewEngines.OfType<VirtualPathProviderViewEngine>().Single();
viewEngine.AreaViewLocationFormats = OrchardLocationFormats() viewEngine.AreaViewLocationFormats = OrchardLocationFormats()
.Concat(viewEngine.AreaViewLocationFormats) .Concat(viewEngine.AreaViewLocationFormats)
.Distinct() .Distinct()
@@ -66,8 +75,16 @@ namespace Orchard.Environment {
.Concat(viewEngine.PartialViewLocationFormats) .Concat(viewEngine.PartialViewLocationFormats)
.Distinct() .Distinct()
.ToArray(); .ToArray();
_events.Invoke(x => x.Activated(), Logger);
} }
public void Terminate() {
_events.Invoke(x => x.Terminating(), Logger);
}
private static string ModelsLocationFormat(PackageDescriptor descriptor) { private static string ModelsLocationFormat(PackageDescriptor descriptor) {
return Path.Combine(Path.Combine(descriptor.Location, descriptor.Name), "Views/Models/{0}.ascx"); return Path.Combine(Path.Combine(descriptor.Location, descriptor.Name), "Views/Models/{0}.ascx");
} }

View File

@@ -1,5 +1,6 @@
namespace Orchard.Environment { namespace Orchard.Environment {
public interface IOrchardShell { public interface IOrchardShell {
void Activate(); void Activate();
void Terminate();
} }
} }

View File

@@ -0,0 +1,6 @@
namespace Orchard.Environment {
public interface IOrchardShellEvents : IEvents {
void Activated();
void Terminating();
}
}

24
src/Orchard/IEvents.cs Normal file
View File

@@ -0,0 +1,24 @@
using System;
using System.Collections.Generic;
using Orchard.Logging;
namespace Orchard {
public interface IEvents : IDependency {
}
public static class EventsExtensions {
public static void Invoke<TEvents>(this IEnumerable<TEvents> events, Action<TEvents> dispatch, ILogger logger) where TEvents : IEvents {
foreach (var sink in events) {
try {
dispatch(sink);
}
catch (Exception ex) {
logger.Error(ex, "{2} thrown from {0} by {1}",
typeof(TEvents).Name,
sink.GetType().FullName,
ex.GetType().Name);
}
}
}
}
}

View File

@@ -118,6 +118,8 @@
<Compile Include="Controllers\HomeController.cs" /> <Compile Include="Controllers\HomeController.cs" />
<Compile Include="Data\Conventions\AttributeCollectionConvention.cs" /> <Compile Include="Data\Conventions\AttributeCollectionConvention.cs" />
<Compile Include="Data\Conventions\CascadeAllDeleteOrphanAttribute.cs" /> <Compile Include="Data\Conventions\CascadeAllDeleteOrphanAttribute.cs" />
<Compile Include="Environment\IOrchardShellEvents.cs" />
<Compile Include="IEvents.cs" />
<Compile Include="Localization\IText.cs" /> <Compile Include="Localization\IText.cs" />
<Compile Include="Localization\LocalizationModule.cs" /> <Compile Include="Localization\LocalizationModule.cs" />
<Compile Include="Localization\LocalizationUtilities.cs" /> <Compile Include="Localization\LocalizationUtilities.cs" />
@@ -194,6 +196,9 @@
<Compile Include="Settings\ISite.cs" /> <Compile Include="Settings\ISite.cs" />
<Compile Include="Settings\ISiteService.cs" /> <Compile Include="Settings\ISiteService.cs" />
<Compile Include="Settings\SettingsModule.cs" /> <Compile Include="Settings\SettingsModule.cs" />
<Compile Include="Tasks\BackgroundService.cs" />
<Compile Include="Tasks\IBackgroundTask.cs" />
<Compile Include="Tasks\SweepGenerator.cs" />
<Compile Include="UI\Menus\AdminMenuFilter.cs" /> <Compile Include="UI\Menus\AdminMenuFilter.cs" />
<Compile Include="UI\Models\ModelTemplate.cs" /> <Compile Include="UI\Models\ModelTemplate.cs" />
<Compile Include="UI\Navigation\NavigationBuilder.cs" /> <Compile Include="UI\Navigation\NavigationBuilder.cs" />

View File

@@ -0,0 +1,25 @@
using System.Collections.Generic;
using Orchard.Logging;
namespace Orchard.Tasks {
public interface IBackgroundService : IDependency {
void Sweep();
}
public class BackgroundService : IBackgroundService {
private readonly IEnumerable<IBackgroundTask> _tasks;
public BackgroundService(IEnumerable<IBackgroundTask> tasks) {
_tasks = tasks;
Logger = NullLogger.Instance;
}
public ILogger Logger { get; set; }
public void Sweep() {
_tasks.Invoke(task => task.Sweep(), Logger);
}
}
}

View File

@@ -0,0 +1,5 @@
namespace Orchard.Tasks {
public interface IBackgroundTask : IEvents {
void Sweep();
}
}

View File

@@ -0,0 +1,97 @@
using System;
using System.Timers;
using Autofac;
using Autofac.Integration.Web;
using Orchard.Environment;
using Orchard.Logging;
namespace Orchard.Tasks {
public class SweepGenerator : IOrchardShellEvents {
private readonly IContainer _container;
private Timer _timer;
public SweepGenerator(IContainer container) {
_container = container;
_timer = new Timer();
_timer.Elapsed += Elapsed;
Logger = NullLogger.Instance;
}
public ILogger Logger { get; set; }
public TimeSpan Interval {
get { return TimeSpan.FromMilliseconds(_timer.Interval); }
set { _timer.Interval = value.Milliseconds; }
}
public void Activated() {
lock (_timer) {
_timer.Start();
}
}
public void Terminating() {
lock (_timer) {
_timer.Stop();
}
}
void Elapsed(object sender, ElapsedEventArgs e) {
// current implementation disallows re-entrancy
if (!System.Threading.Monitor.TryEnter(_timer))
return;
try {
if (_timer.Enabled) {
DoWork();
}
}
catch (Exception ex) {
Logger.Warning(ex, "Problem in background tasks");
}
finally {
System.Threading.Monitor.Exit(_timer);
}
}
public void DoWork() {
// makes an inner container, similar to the per-request container
var containerProvider = new ContainerProvider(_container);
try {
var requestContainer = containerProvider.RequestContainer;
// also inject this instance in case anyone asks for the container provider
requestContainer.Build(builder => builder.Register(containerProvider).As<IContainerProvider>());
// resolve the manager and invoke it
var manager = requestContainer.Resolve<IBackgroundService>();
manager.Sweep();
}
finally{
// shut everything down again
containerProvider.DisposeRequestContainer();
}
}
class ContainerProvider : IContainerProvider {
public ContainerProvider(IContainer applicationContainer) {
// explicitly create a request container for the life of this object
ApplicationContainer = applicationContainer;
RequestContainer = applicationContainer.CreateInnerContainer();
}
public void DisposeRequestContainer() {
var disposeContainer = RequestContainer;
RequestContainer = null;
if (disposeContainer != null)
disposeContainer.Dispose();
}
public IContainer ApplicationContainer { get; private set; }
public IContainer RequestContainer { get; private set; }
}
}
}