U4-6638 - bugfix BackgroundTaskRunner
This commit is contained in:
@@ -1,24 +1,27 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Globalization;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web.Hosting;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Events;
|
||||
using Umbraco.Core.Logging;
|
||||
|
||||
namespace Umbraco.Web.Scheduling
|
||||
{
|
||||
// exists for logging purposes
|
||||
internal class BackgroundTaskRunner
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
/// Manages a queue of tasks of type <typeparamref name="T"/> and runs them in the background.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the managed tasks.</typeparam>
|
||||
/// <remarks>The task runner is web-aware and will ensure that it shuts down correctly when the AppDomain
|
||||
/// shuts down (ie is unloaded).</remarks>
|
||||
internal class BackgroundTaskRunner<T> : IBackgroundTaskRunner<T>
|
||||
internal class BackgroundTaskRunner<T> : BackgroundTaskRunner, IBackgroundTaskRunner<T>
|
||||
where T : class, IBackgroundTask
|
||||
{
|
||||
private readonly string _logPrefix;
|
||||
private readonly BackgroundTaskRunnerOptions _options;
|
||||
private readonly BlockingCollection<T> _tasks = new BlockingCollection<T>();
|
||||
private readonly object _locker = new object();
|
||||
@@ -35,13 +38,26 @@ namespace Umbraco.Web.Scheduling
|
||||
internal event TypedEventHandler<BackgroundTaskRunner<T>, TaskEventArgs<T>> TaskStarting;
|
||||
internal event TypedEventHandler<BackgroundTaskRunner<T>, TaskEventArgs<T>> TaskCompleted;
|
||||
internal event TypedEventHandler<BackgroundTaskRunner<T>, TaskEventArgs<T>> TaskCancelled;
|
||||
|
||||
// triggers when the runner stops (but could start again if a task is added to it)
|
||||
internal event TypedEventHandler<BackgroundTaskRunner<T>, EventArgs> Stopped;
|
||||
|
||||
// triggers when the runner completes (no task can be added to it anymore)
|
||||
internal event TypedEventHandler<BackgroundTaskRunner<T>, EventArgs> Completed;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="BackgroundTaskRunner{T}"/> class.
|
||||
/// </summary>
|
||||
public BackgroundTaskRunner()
|
||||
: this(new BackgroundTaskRunnerOptions())
|
||||
: this(typeof (T).FullName, new BackgroundTaskRunnerOptions())
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="BackgroundTaskRunner{T}"/> class.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the runner.</param>
|
||||
public BackgroundTaskRunner(string name)
|
||||
: this(name, new BackgroundTaskRunnerOptions())
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
@@ -49,9 +65,19 @@ namespace Umbraco.Web.Scheduling
|
||||
/// </summary>
|
||||
/// <param name="options">The set of options.</param>
|
||||
public BackgroundTaskRunner(BackgroundTaskRunnerOptions options)
|
||||
: this(typeof (T).FullName, options)
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="BackgroundTaskRunner{T}"/> class with a set of options.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the runner.</param>
|
||||
/// <param name="options">The set of options.</param>
|
||||
public BackgroundTaskRunner(string name, BackgroundTaskRunnerOptions options)
|
||||
{
|
||||
if (options == null) throw new ArgumentNullException("options");
|
||||
_options = options;
|
||||
_logPrefix = "[" + name + "] ";
|
||||
|
||||
HostingEnvironment.RegisterObject(this);
|
||||
|
||||
@@ -133,7 +159,7 @@ namespace Umbraco.Web.Scheduling
|
||||
throw new InvalidOperationException("The task runner has completed.");
|
||||
|
||||
// add task
|
||||
LogHelper.Debug<BackgroundTaskRunner<T>>("Task added {0}", task.GetType);
|
||||
LogHelper.Debug<BackgroundTaskRunner>(_logPrefix + "Task added {0}", task.GetType);
|
||||
_tasks.Add(task);
|
||||
|
||||
// start
|
||||
@@ -154,7 +180,7 @@ namespace Umbraco.Web.Scheduling
|
||||
if (_isCompleted) return false;
|
||||
|
||||
// add task
|
||||
LogHelper.Debug<BackgroundTaskRunner<T>>("Task added {0}", task.GetType);
|
||||
LogHelper.Debug<BackgroundTaskRunner>(_logPrefix + "Task added {0}", task.GetType);
|
||||
_tasks.Add(task);
|
||||
|
||||
// start
|
||||
@@ -195,7 +221,7 @@ namespace Umbraco.Web.Scheduling
|
||||
// create a new token source since this is a new process
|
||||
_tokenSource = new CancellationTokenSource();
|
||||
_runningTask = PumpIBackgroundTasks(Task.Factory, _tokenSource.Token);
|
||||
LogHelper.Debug<BackgroundTaskRunner<T>>("Starting");
|
||||
LogHelper.Debug<BackgroundTaskRunner>(_logPrefix + "Starting");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -236,7 +262,6 @@ namespace Umbraco.Web.Scheduling
|
||||
// tasks in the queue will be executed...
|
||||
if (wait == false) return;
|
||||
_runningTask.Wait(); // wait for whatever is running to end...
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -260,23 +285,24 @@ namespace Umbraco.Web.Scheduling
|
||||
{
|
||||
if (token.IsCancellationRequested || _tasks.Count == 0)
|
||||
{
|
||||
LogHelper.Debug<BackgroundTaskRunner<T>>("_isRunning = false");
|
||||
LogHelper.Debug<BackgroundTaskRunner>(_logPrefix + "Stopping");
|
||||
|
||||
_isRunning = false; // done
|
||||
if (_options.PreserveRunningTask == false)
|
||||
_runningTask = null;
|
||||
//raise event
|
||||
OnCompleted();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
OnStopped();
|
||||
|
||||
// if _runningTask is taskSource.Task then we must keep continuing it,
|
||||
// not starting a new taskSource, else _runningTask would complete and
|
||||
// something may be waiting on it
|
||||
//PumpIBackgroundTasks(factory, token); // restart
|
||||
// ReSharper disable once MethodSupportsCancellation // always run
|
||||
// ReSharper disable MethodSupportsCancellation // always run
|
||||
t.ContinueWithTask(_ => PumpIBackgroundTasks(factory, token)); // restart
|
||||
// ReSharper restore MethodSupportsCancellation
|
||||
});
|
||||
|
||||
Action<Task> pump = null;
|
||||
@@ -288,7 +314,7 @@ namespace Umbraco.Web.Scheduling
|
||||
if (task != null && task.IsFaulted)
|
||||
{
|
||||
var exception = task.Exception;
|
||||
LogHelper.Error<BackgroundTaskRunner<T>>("Task runner exception.", exception);
|
||||
LogHelper.Error<BackgroundTaskRunner>(_logPrefix + "Task runner exception.", exception);
|
||||
}
|
||||
|
||||
// is it ok to run?
|
||||
@@ -298,6 +324,7 @@ namespace Umbraco.Web.Scheduling
|
||||
// the blocking MoveNext will end if token is cancelled or collection is completed
|
||||
T bgTask;
|
||||
var hasBgTask = _options.KeepAlive
|
||||
// ReSharper disable once PossibleNullReferenceException
|
||||
? (bgTask = enumerator.MoveNext() ? enumerator.Current : null) != null // blocking
|
||||
: _tasks.TryTake(out bgTask); // non-blocking
|
||||
|
||||
@@ -343,7 +370,7 @@ namespace Umbraco.Web.Scheduling
|
||||
return taskSourceContinuing;
|
||||
}
|
||||
|
||||
private bool TaskSourceCanceled(TaskCompletionSource<object> taskSource, CancellationToken token)
|
||||
private static bool TaskSourceCanceled(TaskCompletionSource<object> taskSource, CancellationToken token)
|
||||
{
|
||||
if (token.IsCancellationRequested)
|
||||
{
|
||||
@@ -353,7 +380,7 @@ namespace Umbraco.Web.Scheduling
|
||||
return false;
|
||||
}
|
||||
|
||||
private void TaskSourceCompleted(TaskCompletionSource<object> taskSource, CancellationToken token)
|
||||
private static void TaskSourceCompleted(TaskCompletionSource<object> taskSource, CancellationToken token)
|
||||
{
|
||||
if (token.IsCancellationRequested)
|
||||
taskSource.SetCanceled();
|
||||
@@ -394,83 +421,57 @@ namespace Umbraco.Web.Scheduling
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.Error<BackgroundTaskRunner<T>>("Task has failed.", ex);
|
||||
LogHelper.Error<BackgroundTaskRunner>(_logPrefix + "Task has failed.", ex);
|
||||
}
|
||||
}
|
||||
|
||||
#region Events
|
||||
|
||||
private void OnEvent<TArgs>(TypedEventHandler<BackgroundTaskRunner<T>, TArgs> handler, string name, TArgs e)
|
||||
{
|
||||
if (handler == null) return;
|
||||
|
||||
try
|
||||
{
|
||||
handler(this, e);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.Error<BackgroundTaskRunner>(_logPrefix + name + " exception occurred", ex);
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void OnTaskError(TaskEventArgs<T> e)
|
||||
{
|
||||
var handler = TaskError;
|
||||
if (handler != null) handler(this, e);
|
||||
OnEvent(TaskError, "TaskError", e);
|
||||
}
|
||||
|
||||
protected virtual void OnTaskStarting(TaskEventArgs<T> e)
|
||||
{
|
||||
var handler = TaskStarting;
|
||||
if (handler != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
handler(this, e);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.Error<BackgroundTaskRunner<T>>("TaskStarting exception occurred", ex);
|
||||
}
|
||||
}
|
||||
OnEvent(TaskStarting, "TaskStarting", e);
|
||||
}
|
||||
|
||||
protected virtual void OnTaskCompleted(TaskEventArgs<T> e)
|
||||
{
|
||||
var handler = TaskCompleted;
|
||||
if (handler != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
handler(this, e);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.Error<BackgroundTaskRunner<T>>("TaskCompleted exception occurred", ex);
|
||||
}
|
||||
}
|
||||
OnEvent(TaskCompleted, "TaskCompleted", e);
|
||||
}
|
||||
|
||||
protected virtual void OnTaskCancelled(TaskEventArgs<T> e)
|
||||
{
|
||||
var handler = TaskCancelled;
|
||||
if (handler != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
handler(this, e);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.Error<BackgroundTaskRunner<T>>("TaskCancelled exception occurred", ex);
|
||||
}
|
||||
}
|
||||
OnEvent(TaskCancelled, "TaskCancelled", e);
|
||||
|
||||
//dispose it
|
||||
e.Task.Dispose();
|
||||
}
|
||||
|
||||
protected virtual void OnStopped()
|
||||
{
|
||||
OnEvent(Stopped, "Stopped", EventArgs.Empty);
|
||||
}
|
||||
|
||||
protected virtual void OnCompleted()
|
||||
{
|
||||
var handler = Completed;
|
||||
if (handler != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
handler(this, EventArgs.Empty);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.Error<BackgroundTaskRunner<T>>("OnCompleted exception occurred", ex);
|
||||
}
|
||||
}
|
||||
OnEvent(Completed, "Completed", EventArgs.Empty);
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -482,7 +483,7 @@ namespace Umbraco.Web.Scheduling
|
||||
|
||||
~BackgroundTaskRunner()
|
||||
{
|
||||
this.Dispose(false);
|
||||
Dispose(false);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
@@ -493,7 +494,7 @@ namespace Umbraco.Web.Scheduling
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (this.IsDisposed || disposing == false)
|
||||
if (IsDisposed || disposing == false)
|
||||
return;
|
||||
|
||||
lock (_disposalLocker)
|
||||
@@ -531,23 +532,26 @@ namespace Umbraco.Web.Scheduling
|
||||
// processing, call the UnregisterObject method, and then return or it can return immediately and complete
|
||||
// processing asynchronously before calling the UnregisterObject method.
|
||||
|
||||
LogHelper.Info<BackgroundTaskRunner<T>>("Shutting down, waiting for tasks to complete.");
|
||||
LogHelper.Info<BackgroundTaskRunner>(_logPrefix + "Shutting down, waiting for tasks to complete.");
|
||||
Shutdown(false, false); // do not accept any more tasks, flush the queue, do not wait
|
||||
|
||||
// raise the completed event only after the running task has completed
|
||||
// and there's no more task running
|
||||
|
||||
lock (_locker)
|
||||
{
|
||||
if (_runningTask != null)
|
||||
_runningTask.ContinueWith(_ =>
|
||||
{
|
||||
HostingEnvironment.UnregisterObject(this);
|
||||
LogHelper.Info<BackgroundTaskRunner<T>>("Down, tasks completed.");
|
||||
Stopped.RaiseEvent(new StoppedEventArgs(false), this);
|
||||
LogHelper.Info<BackgroundTaskRunner>(_logPrefix + "Down, tasks completed.");
|
||||
Completed.RaiseEvent(EventArgs.Empty, this);
|
||||
});
|
||||
else
|
||||
{
|
||||
HostingEnvironment.UnregisterObject(this);
|
||||
LogHelper.Info<BackgroundTaskRunner<T>>("Down, tasks completed.");
|
||||
Stopped.RaiseEvent(new StoppedEventArgs(false), this);
|
||||
LogHelper.Info<BackgroundTaskRunner>(_logPrefix + "Down, tasks completed.");
|
||||
Completed.RaiseEvent(EventArgs.Empty, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -558,24 +562,13 @@ namespace Umbraco.Web.Scheduling
|
||||
// immediate parameter is true, the registered object must call the UnregisterObject method before returning;
|
||||
// otherwise, its registration will be removed by the application manager.
|
||||
|
||||
LogHelper.Info<BackgroundTaskRunner<T>>("Shutting down immediately.");
|
||||
LogHelper.Info<BackgroundTaskRunner>(_logPrefix + "Shutting down immediately.");
|
||||
Shutdown(true, true); // cancel all tasks, wait for the current one to end
|
||||
HostingEnvironment.UnregisterObject(this);
|
||||
LogHelper.Info<BackgroundTaskRunner<T>>("Down.");
|
||||
Stopped.RaiseEvent(new StoppedEventArgs(true), this);
|
||||
LogHelper.Info<BackgroundTaskRunner>(_logPrefix + "Down.");
|
||||
// raise the completed event: there's no more task running
|
||||
Completed.RaiseEvent(EventArgs.Empty, this);
|
||||
}
|
||||
}
|
||||
|
||||
public class StoppedEventArgs : EventArgs
|
||||
{
|
||||
public StoppedEventArgs(bool immediate)
|
||||
{
|
||||
Immediate = immediate;
|
||||
}
|
||||
|
||||
public bool Immediate { get; private set; }
|
||||
}
|
||||
|
||||
public event TypedEventHandler<BackgroundTaskRunner<T>, StoppedEventArgs> Stopped;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,8 @@ namespace Umbraco.Web.Scheduling
|
||||
/// A custom awaiter requires implementing INotifyCompletion as well as IsCompleted, OnCompleted and GetResult
|
||||
/// see: http://blogs.msdn.com/b/pfxteam/archive/2011/01/13/10115642.aspx
|
||||
/// </remarks>
|
||||
internal class BackgroundTaskRunnerAwaiter<T> : INotifyCompletion where T : class, IBackgroundTask
|
||||
internal class BackgroundTaskRunnerAwaiter<T> : INotifyCompletion
|
||||
where T : class, IBackgroundTask
|
||||
{
|
||||
private readonly BackgroundTaskRunner<T> _runner;
|
||||
private readonly TaskCompletionSource<int> _tcs;
|
||||
@@ -27,21 +28,20 @@ namespace Umbraco.Web.Scheduling
|
||||
_runner = runner;
|
||||
|
||||
_tcs = new TaskCompletionSource<int>();
|
||||
|
||||
_awaiter = _tcs.Task.GetAwaiter();
|
||||
|
||||
if (_runner.IsRunning)
|
||||
{
|
||||
_runner.Completed += (s, e) =>
|
||||
_runner.Stopped += (s, e) =>
|
||||
{
|
||||
LogHelper.Debug<BackgroundTaskRunnerAwaiter<T>>("Setting result");
|
||||
|
||||
LogHelper.Debug<BackgroundTaskRunnerAwaiter<T>>("Runner has stopped.");
|
||||
_tcs.SetResult(0);
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
//not running, just set the result
|
||||
LogHelper.Debug<BackgroundTaskRunnerAwaiter<T>>("Runner is stopped.");
|
||||
_tcs.SetResult(0);
|
||||
}
|
||||
|
||||
@@ -59,6 +59,7 @@ namespace Umbraco.Web.Scheduling
|
||||
{
|
||||
get
|
||||
{
|
||||
// FIXME I DONT UNDERSTAND
|
||||
LogHelper.Debug<BackgroundTaskRunnerAwaiter<T>>("IsCompleted :: " + _tcs.Task.IsCompleted + ", " + (_runner.IsRunning == false));
|
||||
//Need to check if the task is completed because it might already be done on the ctor and the runner never runs
|
||||
return _tcs.Task.IsCompleted || _runner.IsRunning == false;
|
||||
|
||||
@@ -63,7 +63,7 @@ namespace Umbraco.Web.Scheduling
|
||||
|
||||
public bool IsLatched
|
||||
{
|
||||
get { return _latch != null; }
|
||||
get { return _latch != null && _latch.IsSet == false; }
|
||||
}
|
||||
|
||||
public virtual bool RunsOnShutdown
|
||||
|
||||
@@ -48,9 +48,9 @@ namespace Umbraco.Web.Scheduling
|
||||
LogHelper.Debug<Scheduler>(() => "Initializing the scheduler");
|
||||
|
||||
// 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>();
|
||||
_publishingRunner = new BackgroundTaskRunner<IBackgroundTask>("ScheduledPublishing");
|
||||
_tasksRunner = new BackgroundTaskRunner<IBackgroundTask>("ScheduledTasks");
|
||||
_scrubberRunner = new BackgroundTaskRunner<IBackgroundTask>("LogScrubber");
|
||||
|
||||
var settings = UmbracoConfig.For.UmbracoSettings();
|
||||
|
||||
|
||||
@@ -49,7 +49,7 @@ namespace umbraco
|
||||
// and prepare the persister task
|
||||
// there's always be one task keeping a ref to the runner
|
||||
// so it's safe to just create it as a local var here
|
||||
var runner = new BackgroundTaskRunner<XmlCacheFilePersister>(new BackgroundTaskRunnerOptions
|
||||
var runner = new BackgroundTaskRunner<XmlCacheFilePersister>("XmlCacheFilePersister", new BackgroundTaskRunnerOptions
|
||||
{
|
||||
LongRunning = true,
|
||||
KeepAlive = true
|
||||
@@ -58,7 +58,7 @@ namespace umbraco
|
||||
// when the runner has stopped we know we will not be writing
|
||||
// to the file anymore, so we can release the lock now - and
|
||||
// not wait for the AppDomain unload
|
||||
runner.Stopped += (sender, args) =>
|
||||
runner.Completed += (sender, args) =>
|
||||
{
|
||||
if (_fileLock == null) return; // not locking (testing?)
|
||||
if (_fileLocked == null) return; // not locked
|
||||
|
||||
Reference in New Issue
Block a user