refactor Scheduler to use new BackgroundTaskRunner capabilities

Conflicts:
	src/Umbraco.Web/Scheduling/Scheduler.cs
This commit is contained in:
Stephan
2015-02-06 18:10:19 +01:00
committed by Shannon
parent b7436dc55f
commit a73b7a5849
6 changed files with 113 additions and 118 deletions

View File

@@ -34,12 +34,18 @@ namespace Umbraco.Web.Scheduling
if (_gate != null) return _gate;
_gate = new ManualResetEvent(false);
// note
// must use the single-parameter constructor on Timer to avoid it from being GC'd
// read http://stackoverflow.com/questions/4962172/why-does-a-system-timers-timer-survive-gc-but-not-system-threading-timer
_timer = new Timer(_ =>
{
_timer.Dispose();
_timer = null;
_gate.Set();
}, null, _delayMilliseconds, 0);
});
_timer.Change(_delayMilliseconds, 0);
return _gate;
}
}

View File

@@ -9,17 +9,31 @@ using Umbraco.Core.Logging;
namespace Umbraco.Web.Scheduling
{
internal class LogScrubber : DisposableObject, IBackgroundTask
internal class LogScrubber : DelayedRecurringTaskBase<LogScrubber>
{
private readonly ApplicationContext _appContext;
private readonly IUmbracoSettingsSection _settings;
public LogScrubber(ApplicationContext appContext, IUmbracoSettingsSection settings)
public LogScrubber(IBackgroundTaskRunner<LogScrubber> runner, int delayMilliseconds, int periodMilliseconds,
ApplicationContext appContext, IUmbracoSettingsSection settings)
: base(runner, delayMilliseconds, periodMilliseconds)
{
_appContext = appContext;
_settings = settings;
}
public LogScrubber(LogScrubber source)
: base(source)
{
_appContext = source._appContext;
_settings = source._settings;
}
protected override LogScrubber GetRecurring()
{
return new LogScrubber(this);
}
private int GetLogScrubbingMaximumAge(IUmbracoSettingsSection settings)
{
int maximumAge = 24 * 60 * 60;
@@ -36,14 +50,22 @@ namespace Umbraco.Web.Scheduling
}
/// <summary>
/// Handles the disposal of resources. Derived from abstract class <see cref="DisposableObject"/> which handles common required locking logic.
/// </summary>
protected override void DisposeResources()
public static int GetLogScrubbingInterval(IUmbracoSettingsSection settings)
{
int interval = 24 * 60 * 60; //24 hours
try
{
if (settings.Logging.CleaningMiliseconds > -1)
interval = settings.Logging.CleaningMiliseconds;
}
catch (Exception e)
{
LogHelper.Error<LogScrubber>("Unable to locate a log scrubbing interval. Defaulting to 24 horus", e);
}
return interval;
}
public void Run()
public override void PerformRun()
{
using (DisposableTimer.DebugDuration<LogScrubber>(() => "Log scrubbing executing", () => "Log scrubbing complete"))
{
@@ -51,12 +73,12 @@ namespace Umbraco.Web.Scheduling
}
}
public Task RunAsync()
public override Task PerformRunAsync()
{
throw new NotImplementedException();
}
public bool IsAsync
public override bool IsAsync
{
get { return false; }
}

View File

@@ -67,12 +67,17 @@ namespace Umbraco.Web.Scheduling
var recur = GetRecurring();
if (recur == null) return; // done
// note
// must use the single-parameter constructor on Timer to avoid it from being GC'd
// read http://stackoverflow.com/questions/4962172/why-does-a-system-timers-timer-survive-gc-but-not-system-threading-timer
_timer = new Timer(_ =>
{
_timer.Dispose();
_timer = null;
_runner.TryAdd(recur);
}, null, _periodMilliseconds, 0);
});
_timer.Change(_periodMilliseconds, 0);
}
/// <summary>

View File

@@ -12,30 +12,41 @@ using Umbraco.Web.Mvc;
namespace Umbraco.Web.Scheduling
{
internal class ScheduledPublishing : DisposableObject, IBackgroundTask
internal class ScheduledPublishing : DelayedRecurringTaskBase<ScheduledPublishing>
{
private readonly ApplicationContext _appContext;
private readonly IUmbracoSettingsSection _settings;
private static bool _isPublishingRunning = false;
private static bool _isPublishingRunning;
public ScheduledPublishing(ApplicationContext appContext, IUmbracoSettingsSection settings)
public ScheduledPublishing(IBackgroundTaskRunner<ScheduledPublishing> runner, int delayMilliseconds, int periodMilliseconds,
ApplicationContext appContext, IUmbracoSettingsSection settings)
: base(runner, delayMilliseconds, periodMilliseconds)
{
_appContext = appContext;
_settings = settings;
}
/// <summary>
/// Handles the disposal of resources. Derived from abstract class <see cref="DisposableObject"/> which handles common required locking logic.
/// </summary>
protected override void DisposeResources()
private ScheduledPublishing(ScheduledPublishing source)
: base(source)
{
_appContext = source._appContext;
_settings = source._settings;
}
public void Run()
protected override ScheduledPublishing GetRecurring()
{
return new ScheduledPublishing(this);
}
public override void PerformRun()
{
if (_appContext == null) return;
if (ServerEnvironmentHelper.GetStatus(_settings) == CurrentServerEnvironmentStatus.Slave)
{
LogHelper.Debug<ScheduledPublishing>("Does not run on slave servers.");
return;
}
using (DisposableTimer.DebugDuration<ScheduledPublishing>(() => "Scheduled publishing executing", () => "Scheduled publishing complete"))
{
@@ -77,12 +88,12 @@ namespace Umbraco.Web.Scheduling
}
}
public Task RunAsync()
public override Task PerformRunAsync()
{
throw new NotImplementedException();
}
public bool IsAsync
public override bool IsAsync
{
get { return false; }
}

View File

@@ -17,19 +17,33 @@ namespace Umbraco.Web.Scheduling
// would need to be a publicly available task (URL) which isn't really very good :(
// We should really be using the AdminTokenAuthorizeAttribute for this stuff
internal class ScheduledTasks : DisposableObject, IBackgroundTask
internal class ScheduledTasks : DelayedRecurringTaskBase<ScheduledTasks>
{
private readonly ApplicationContext _appContext;
private readonly IUmbracoSettingsSection _settings;
private static readonly Hashtable ScheduledTaskTimes = new Hashtable();
private static bool _isPublishingRunning = false;
public ScheduledTasks(ApplicationContext appContext, IUmbracoSettingsSection settings)
public ScheduledTasks(IBackgroundTaskRunner<ScheduledTasks> runner, int delayMilliseconds, int periodMilliseconds,
ApplicationContext appContext, IUmbracoSettingsSection settings)
: base(runner, delayMilliseconds, periodMilliseconds)
{
_appContext = appContext;
_settings = settings;
}
public ScheduledTasks(ScheduledTasks source)
: base(source)
{
_appContext = source._appContext;
_settings = source._settings;
}
protected override ScheduledTasks GetRecurring()
{
return new ScheduledTasks(this);
}
private void ProcessTasks()
{
var scheduledTasks = _settings.ScheduledTasks.Tasks;
@@ -78,15 +92,14 @@ namespace Umbraco.Web.Scheduling
return false;
}
/// <summary>
/// Handles the disposal of resources. Derived from abstract class <see cref="DisposableObject"/> which handles common required locking logic.
/// </summary>
protected override void DisposeResources()
public override void PerformRun()
{
}
if (ServerEnvironmentHelper.GetStatus(_settings) == CurrentServerEnvironmentStatus.Slave)
{
LogHelper.Debug<ScheduledTasks>("Does not run on slave servers.");
return;
}
public void Run()
{
using (DisposableTimer.DebugDuration<ScheduledTasks>(() => "Scheduled tasks executing", () => "Scheduled tasks complete"))
{
if (_isPublishingRunning) return;
@@ -108,12 +121,12 @@ namespace Umbraco.Web.Scheduling
}
}
public Task RunAsync()
public override Task PerformRunAsync()
{
throw new NotImplementedException();
}
public bool IsAsync
public override bool IsAsync
{
get { return false; }
}

View File

@@ -19,12 +19,10 @@ namespace Umbraco.Web.Scheduling
internal sealed class Scheduler : ApplicationEventHandler
{
private static Timer _pingTimer;
private static Timer _schedulingTimer;
private static BackgroundTaskRunner<ScheduledPublishing> _publishingRunner;
private static BackgroundTaskRunner<ScheduledTasks> _tasksRunner;
private static BackgroundTaskRunner<LogScrubber> _scrubberRunner;
private static Timer _logScrubberTimer;
private static volatile bool _started = false;
private static BackgroundTaskRunner<IBackgroundTask> _publishingRunner;
private static BackgroundTaskRunner<IBackgroundTask> _tasksRunner;
private static BackgroundTaskRunner<IBackgroundTask> _scrubberRunner;
private static volatile bool _started;
private static readonly object Locker = new object();
protected override void ApplicationStarted(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext)
@@ -49,97 +47,37 @@ namespace Umbraco.Web.Scheduling
_started = true;
LogHelper.Debug<Scheduler>(() => "Initializing the scheduler");
// time to setup the tasks
// backgrounds runners are web aware, if the app domain dies, these tasks will wind down correctly
_publishingRunner = new BackgroundTaskRunner<IBackgroundTask>();
_tasksRunner = new BackgroundTaskRunner<IBackgroundTask>();
_scrubberRunner = new BackgroundTaskRunner<IBackgroundTask>();
//We have 3 background runners that are web aware, if the app domain dies, these tasks will wind down correctly
_publishingRunner = new BackgroundTaskRunner<ScheduledPublishing>();
_tasksRunner = new BackgroundTaskRunner<ScheduledTasks>();
_scrubberRunner = new BackgroundTaskRunner<LogScrubber>();
var settings = UmbracoConfig.For.UmbracoSettings();
//NOTE: It is important to note that we need to use the ctor for a timer without the 'state' object specified, this is in order
// to ensure that the timer itself is not GC'd since internally .net will pass itself in as the state object and that will keep it alive.
// There's references to this here: http://stackoverflow.com/questions/4962172/why-does-a-system-timers-timer-survive-gc-but-not-system-threading-timer
// we also make these timers static to ensure further GC safety.
// note
// must use the single-parameter constructor on Timer to avoid it from being GC'd
// also make the timer static to ensure further GC safety
// read http://stackoverflow.com/questions/4962172/why-does-a-system-timers-timer-survive-gc-but-not-system-threading-timer
// ping/keepalive - NOTE: we don't use a background runner for this because it does not need to be web aware, if the app domain dies, no problem
// ping/keepalive - no need for a background runner - does not need to be web aware, ok if the app domain dies
_pingTimer = new Timer(state => KeepAlive.Start(applicationContext, UmbracoConfig.For.UmbracoSettings()));
_pingTimer.Change(60000, 300000);
// scheduled publishing/unpublishing
_schedulingTimer = new Timer(state => PerformScheduling(applicationContext, UmbracoConfig.For.UmbracoSettings()));
_schedulingTimer.Change(60000, 60000);
// install on all, will only run on non-slaves servers
// both are delayed recurring tasks
_publishingRunner.Add(new ScheduledPublishing(_publishingRunner, 60000, 60000, applicationContext, settings));
_tasksRunner.Add(new ScheduledTasks(_tasksRunner, 60000, 60000, applicationContext, settings));
//log scrubbing
_logScrubberTimer = new Timer(state => PerformLogScrub(applicationContext, UmbracoConfig.For.UmbracoSettings()));
_logScrubberTimer.Change(60000, GetLogScrubbingInterval(UmbracoConfig.For.UmbracoSettings()));
// log scrubbing
// install & run on all servers
// LogScrubber is a delayed recurring task
_scrubberRunner.Add(new LogScrubber(_scrubberRunner, 60000, LogScrubber.GetLogScrubbingInterval(settings), applicationContext, settings));
}
}
}
};
};
}
private int GetLogScrubbingInterval(IUmbracoSettingsSection settings)
{
var interval = 4 * 60 * 60 * 1000; // 4 hours, in milliseconds
try
{
if (settings.Logging.CleaningMiliseconds > -1)
interval = settings.Logging.CleaningMiliseconds;
}
catch (Exception e)
{
LogHelper.Error<Scheduler>("Unable to locate a log scrubbing interval. Defaulting to 4 hours.", e);
}
return interval;
}
private static void PerformLogScrub(ApplicationContext appContext, IUmbracoSettingsSection settings)
{
_scrubberRunner.Add(new LogScrubber(appContext, settings));
}
/// <summary>
/// This performs all of the scheduling on the one timer
/// </summary>
/// <param name="appContext"></param>
/// <param name="settings"></param>
/// <remarks>
/// No processing will be done if this server is a slave
/// </remarks>
private static void PerformScheduling(ApplicationContext appContext, IUmbracoSettingsSection settings)
{
using (DisposableTimer.DebugDuration<Scheduler>(() => "Scheduling interval executing", () => "Scheduling interval complete"))
{
//get the current server status to see if this server should execute the scheduled publishing
var serverStatus = ServerEnvironmentHelper.GetStatus(settings);
switch (serverStatus)
{
case CurrentServerEnvironmentStatus.Single:
case CurrentServerEnvironmentStatus.Master:
case CurrentServerEnvironmentStatus.Unknown:
//if it's a single server install, a master or it cannot be determined
// then we will process the scheduling
//do the scheduled publishing
_publishingRunner.Add(new ScheduledPublishing(appContext, settings));
//do the scheduled tasks
_tasksRunner.Add(new ScheduledTasks(appContext, settings));
break;
case CurrentServerEnvironmentStatus.Slave:
//do not process
LogHelper.Debug<Scheduler>(
() => string.Format("Current server ({0}) detected as a slave, no scheduled processes will execute on this server", NetworkHelper.MachineName));
break;
}
}
}
}
}