Merge pull request #9408 from umbraco/v8/bugfix/task-scheduler-maindom-logging

Ensure that TaskScheduler.Default is used anywhere that ContinueWith is used, adds more debug logging to MainDom operations

(cherry picked from commit 9a1b456949)

# Conflicts:
#	src/Umbraco.Core/Runtime/SqlMainDomLock.cs
This commit is contained in:
Bjarke Berg
2020-11-23 07:43:24 +01:00
committed by Sebastiaan Janssen
parent 9c4dce564e
commit cd1120e062
8 changed files with 79 additions and 11 deletions

View File

@@ -1,4 +1,5 @@
using System;
using System.Threading;
using System.Threading.Tasks;
namespace Umbraco.Core.Logging
@@ -14,7 +15,12 @@ namespace Umbraco.Core.Logging
/// </summary>
public static Task LogErrors(this Task task, Action<string, Exception> logMethod)
{
return task.ContinueWith(t => LogErrorsInner(t, logMethod), TaskContinuationOptions.OnlyOnFaulted);
return task.ContinueWith(
t => LogErrorsInner(t, logMethod),
CancellationToken.None,
TaskContinuationOptions.OnlyOnFaulted,
// Must explicitly specify this, see https://blog.stephencleary.com/2013/10/continuewith-is-dangerous-too.html
TaskScheduler.Default);
}
/// <summary>
@@ -26,7 +32,10 @@ namespace Umbraco.Core.Logging
/// </summary>
public static Task LogErrorsWaitable(this Task task, Action<string, Exception> logMethod)
{
return task.ContinueWith(t => LogErrorsInner(t, logMethod));
return task.ContinueWith(
t => LogErrorsInner(t, logMethod),
// Must explicitly specify this, see https://blog.stephencleary.com/2013/10/continuewith-is-dangerous-too.html
TaskScheduler.Default);
}
private static void LogErrorsInner(Task task, Action<string, Exception> logAction)

View File

@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using System.Threading;
using System.Threading.Tasks;
using System.Web.Hosting;
using Umbraco.Core;
using Umbraco.Core.Logging;
@@ -38,6 +39,9 @@ namespace Umbraco.Core.Runtime
private const int LockTimeoutMilliseconds = 40000; // 40 seconds
private Task _listenTask;
private Task _listenCompleteTask;
#endregion
#region Ctor
@@ -172,7 +176,13 @@ namespace Umbraco.Core.Runtime
try
{
// Listen for the signal from another AppDomain coming online to release the lock
_mainDomLock.ListenAsync().ContinueWith(_ => OnSignal("signal"));
_listenTask = _mainDomLock.ListenAsync();
_listenCompleteTask = _listenTask.ContinueWith(t =>
{
_logger.Debug<MainDom>("Listening task completed with {TaskStatus}", _listenTask.Status);
OnSignal("signal");
}, TaskScheduler.Default); // Must explicitly specify this, see https://blog.stephencleary.com/2013/10/continuewith-is-dangerous-too.html
}
catch (OperationCanceledException ex)
{

View File

@@ -124,7 +124,12 @@ namespace Umbraco.Core.Runtime
// Create a long running task (dedicated thread)
// to poll to check if we are still the MainDom registered in the DB
return Task.Factory.StartNew(ListeningLoop, _cancellationTokenSource.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default);
return Task.Factory.StartNew(
ListeningLoop,
_cancellationTokenSource.Token,
TaskCreationOptions.LongRunning,
// Must explicitly specify this, see https://blog.stephencleary.com/2013/10/continuewith-is-dangerous-too.html
TaskScheduler.Default);
}
@@ -159,7 +164,11 @@ namespace Umbraco.Core.Runtime
// the other MainDom is taking to startup. In this case the db row will just be deleted and the
// new MainDom will just take over.
if (_cancellationTokenSource.IsCancellationRequested)
{
_logger.Debug<SqlMainDomLock>("Task canceled, exiting loop");
return;
}
IUmbracoDatabase db = null;
try
{
@@ -183,8 +192,10 @@ namespace Umbraco.Core.Runtime
// We need to keep on listening unless we've been notified by our own AppDomain to shutdown since
// we don't want to shutdown resources controlled by MainDom inadvertently. We'll just keep listening otherwise.
if (_cancellationTokenSource.IsCancellationRequested)
{
_logger.Debug<SqlMainDomLock>("Task canceled, exiting loop");
return;
}
}
finally
{
@@ -389,6 +400,8 @@ namespace Umbraco.Core.Runtime
{
lock (_locker)
{
_logger.Debug<SqlMainDomLock>($"{nameof(SqlMainDomLock)} Disposing...");
// immediately cancel all sub-tasks, we don't want them to keep querying
_cancellationTokenSource.Cancel();
_cancellationTokenSource.Dispose();

View File

@@ -1335,7 +1335,11 @@ namespace Umbraco.Web.PublishedCache.NuCache
{
_collectTask = null;
}
}, TaskContinuationOptions.ExecuteSynchronously);
},
CancellationToken.None,
TaskContinuationOptions.ExecuteSynchronously,
// Must explicitly specify this, see https://blog.stephencleary.com/2013/10/continuewith-is-dangerous-too.html
TaskScheduler.Default);
// ReSharper restore InconsistentlySynchronizedField
return task;

View File

@@ -380,7 +380,11 @@ namespace Umbraco.Web.PublishedCache.NuCache
{
_collectTask = null;
}
}, TaskContinuationOptions.ExecuteSynchronously);
},
CancellationToken.None,
TaskContinuationOptions.ExecuteSynchronously,
// Must explicitly specify this, see https://blog.stephencleary.com/2013/10/continuewith-is-dangerous-too.html
TaskScheduler.Default);
// ReSharper restore InconsistentlySynchronizedField
return task;

View File

@@ -756,9 +756,17 @@ namespace Umbraco.Web.Scheduling
lock (_locker)
{
if (_runningTask != null)
_runningTask.ContinueWith(_ => StopImmediate());
{
_runningTask.ContinueWith(
_ => StopImmediate(),
// Must explicitly specify this, see https://blog.stephencleary.com/2013/10/continuewith-is-dangerous-too.html
TaskScheduler.Default);
}
else
{
StopImmediate();
}
}
}

View File

@@ -8,6 +8,7 @@ namespace Umbraco.Web.Scheduling
{
#region Task Extensions
// TODO: Not used, is this used in Deploy or something?
static void SetCompletionSource<TResult>(TaskCompletionSource<TResult> completionSource, Task task)
{
if (task.IsFaulted)
@@ -16,6 +17,7 @@ namespace Umbraco.Web.Scheduling
completionSource.SetResult(default(TResult));
}
// TODO: Not used, is this used in Deploy or something?
static void SetCompletionSource<TResult>(TaskCompletionSource<TResult> completionSource, Task<TResult> task)
{
if (task.IsFaulted)
@@ -24,17 +26,33 @@ namespace Umbraco.Web.Scheduling
completionSource.SetResult(task.Result);
}
// TODO: Not used, is this used in Deploy or something?
public static Task ContinueWithTask(this Task task, Func<Task, Task> continuation)
{
var completionSource = new TaskCompletionSource<object>();
task.ContinueWith(atask => continuation(atask).ContinueWith(atask2 => SetCompletionSource(completionSource, atask2)));
task.ContinueWith(atask => continuation(atask).ContinueWith(
atask2 => SetCompletionSource(completionSource, atask2),
// Must explicitly specify this, see https://blog.stephencleary.com/2013/10/continuewith-is-dangerous-too.html
TaskScheduler.Default),
// Must explicitly specify this, see https://blog.stephencleary.com/2013/10/continuewith-is-dangerous-too.html
TaskScheduler.Default);
return completionSource.Task;
}
// TODO: Not used, is this used in Deploy or something?
public static Task ContinueWithTask(this Task task, Func<Task, Task> continuation, CancellationToken token)
{
var completionSource = new TaskCompletionSource<object>();
task.ContinueWith(atask => continuation(atask).ContinueWith(atask2 => SetCompletionSource(completionSource, atask2), token), token);
task.ContinueWith(atask => continuation(atask).ContinueWith(
atask2 => SetCompletionSource(completionSource, atask2),
token,
TaskContinuationOptions.None,
// Must explicitly specify this, see https://blog.stephencleary.com/2013/10/continuewith-is-dangerous-too.html
TaskScheduler.Default),
token,
TaskContinuationOptions.None,
// Must explicitly specify this, see https://blog.stephencleary.com/2013/10/continuewith-is-dangerous-too.html
TaskScheduler.Default);
return completionSource.Task;
}

View File

@@ -89,7 +89,9 @@ namespace Umbraco.Web.WebApi
throw x.Exception;
}
result = x.ConfigureAwait(false).GetAwaiter().GetResult();
});
},
// Must explicitly specify this, see https://blog.stephencleary.com/2013/10/continuewith-is-dangerous-too.html
TaskScheduler.Default);
task.Wait();
if (result == null)