Merge branch 'netcore/dev' into netcore/feature/AB3677-remove-current-from-core

# Conflicts:
#	src/Umbraco.Abstractions/UriExtensions.cs
#	src/Umbraco.Tests/Web/Controllers/ContentControllerTests.cs
#	src/Umbraco.Web/Editors/Filters/MemberSaveModelValidator.cs
#	src/Umbraco.Web/Editors/MediaController.cs
#	src/Umbraco.Web/Editors/TemplateController.cs
#	src/Umbraco.Web/Models/Mapping/PropertyTypeGroupMapper.cs
#	src/Umbraco.Web/PropertyEditors/FileUploadPropertyEditor.cs
#	src/Umbraco.Web/PropertyEditors/ListViewPropertyEditor.cs
#	src/Umbraco.Web/PropertyEditors/UserPickerPropertyEditor.cs
#	src/Umbraco.Web/UmbracoDefaultOwinStartup.cs
This commit is contained in:
Bjarke Berg
2020-01-07 09:46:42 +01:00
319 changed files with 3845 additions and 2844 deletions

View File

@@ -199,7 +199,7 @@ namespace Umbraco.Web.Scheduling
{
lock (_locker)
{
var task = _runningTask ?? Task.FromResult(0);
var task = _runningTask ?? Task.CompletedTask;
return new ThreadingTaskImmutable(task);
}
}
@@ -211,8 +211,9 @@ namespace Umbraco.Web.Scheduling
/// <returns>An awaitable object.</returns>
/// <remarks>
/// <para>Used to wait until the runner has terminated.</para>
/// <para>This is for unit tests and should not be used otherwise. In most cases when the runner
/// has terminated, the application domain is going down and it is not the right time to do things.</para>
/// <para>
/// The only time the runner will be terminated is by the Hosting Environment when the application is being shutdown.
/// </para>
/// </remarks>
internal ThreadingTaskImmutable TerminatedAwaitable
{
@@ -338,29 +339,37 @@ namespace Umbraco.Web.Scheduling
if (_isRunning == false) return; // done already
}
var hasTasks = TaskCount > 0;
if (!force && hasTasks)
_logger.Info<BackgroundTaskRunner>("{LogPrefix} Waiting for tasks to complete", _logPrefix);
// complete the queue
// will stop waiting on the queue or on a latch
_tasks.Complete();
if (force)
{
// we must bring everything down, now
Thread.Sleep(100); // give time to Complete()
// we must bring everything down, now
lock (_locker)
{
// was Complete() enough?
if (_isRunning == false) return;
// if _tasks.Complete() ended up triggering code to stop the runner and reset
// the _isRunning flag, then there's no need to initiate a cancel on the cancelation token.
if (_isRunning == false)
return;
}
// try to cancel running async tasks (cannot do much about sync tasks)
// break latched tasks
// stop processing the queue
_shutdownTokenSource.Cancel(false); // false is the default
_shutdownTokenSource.Dispose();
_shutdownTokenSource?.Cancel(false); // false is the default
_shutdownTokenSource?.Dispose();
_shutdownTokenSource = null;
}
// tasks in the queue will be executed...
if (wait == false) return;
if (!wait) return;
_runningTask?.Wait(CancellationToken.None); // wait for whatever is running to end...
}
@@ -428,7 +437,7 @@ namespace Umbraco.Web.Scheduling
lock (_locker)
{
// deal with race condition
if (_shutdownToken.IsCancellationRequested == false && _tasks.Count > 0) continue;
if (_shutdownToken.IsCancellationRequested == false && TaskCount > 0) continue;
// if we really have nothing to do, stop
_logger.Debug<BackgroundTaskRunner>("{LogPrefix} Stopping", _logPrefix);
@@ -453,7 +462,7 @@ namespace Umbraco.Web.Scheduling
// if KeepAlive is false then don't block, exit if there is
// no task in the buffer - yes, there is a race condition, which
// we'll take care of
if (_options.KeepAlive == false && _tasks.Count == 0)
if (_options.KeepAlive == false && TaskCount == 0)
return null;
try
@@ -503,15 +512,19 @@ namespace Umbraco.Web.Scheduling
// returns the task that completed
// - latched.Latch completes when the latch releases
// - _tasks.Completion completes when the runner completes
// - tokenTaskSource.Task completes when this task, or the whole runner, is cancelled
// - tokenTaskSource.Task completes when this task, or the whole runner is cancelled
var task = await Task.WhenAny(latched.Latch, _tasks.Completion, tokenTaskSource.Task);
// ok to run now
if (task == latched.Latch)
return bgTask;
// we are shutting down if the _tasks.Complete(); was called or the shutdown token was cancelled
var isShuttingDown = _shutdownToken.IsCancellationRequested || task == _tasks.Completion;
// if shutting down, return the task only if it runs on shutdown
if (_shutdownToken.IsCancellationRequested == false && latched.RunsOnShutdown) return bgTask;
if (isShuttingDown && latched.RunsOnShutdown)
return bgTask;
// else, either it does not run on shutdown or it's been cancelled, dispose
latched.Dispose();
@@ -578,17 +591,18 @@ namespace Umbraco.Web.Scheduling
// triggers when the hosting environment requests that the runner terminates
internal event TypedEventHandler<BackgroundTaskRunner<T>, EventArgs> Terminating;
// triggers when the runner has terminated (no task can be added, no task is running)
// triggers when the hosting environment has terminated (no task can be added, no task is running)
internal event TypedEventHandler<BackgroundTaskRunner<T>, EventArgs> Terminated;
private void OnEvent(TypedEventHandler<BackgroundTaskRunner<T>, EventArgs> handler, string name)
{
if (handler == null) return;
OnEvent(handler, name, EventArgs.Empty);
}
private void OnEvent<TArgs>(TypedEventHandler<BackgroundTaskRunner<T>, TArgs> handler, string name, TArgs e)
{
_logger.Debug<BackgroundTaskRunner>("{LogPrefix} OnEvent {EventName}", _logPrefix, name);
if (handler == null) return;
try
@@ -664,17 +678,16 @@ namespace Umbraco.Web.Scheduling
#endregion
#region IRegisteredObject.Stop
/// <summary>
/// Requests a registered object to un-register.
/// Used by IRegisteredObject.Stop and shutdown on threadpool threads to not block shutdown times.
/// </summary>
/// <param name="immediate">true to indicate the registered object should un-register from the hosting
/// environment before returning; otherwise, false.</param>
/// <remarks>
/// <para>"When the application manager needs to stop a registered object, it will call the Stop method."</para>
/// <para>The application manager will call the Stop method to ask a registered object to un-register. During
/// processing of the Stop method, the registered object must call the HostingEnvironment.UnregisterObject method.</para>
/// </remarks>
public void Stop(bool immediate)
/// <param name="immediate"></param>
/// <returns>
/// An awaitable Task that is used to handle the shutdown.
/// </returns>
internal Task StopInternal(bool immediate)
{
// the first time the hosting environment requests that the runner terminates,
// raise the Terminating event - that could be used to prevent any process that
@@ -693,33 +706,90 @@ namespace Umbraco.Web.Scheduling
if (onTerminating)
OnEvent(Terminating, "Terminating");
if (immediate == false)
// Run the Stop commands on another thread since IRegisteredObject.Stop calls are called sequentially
// with a single aspnet thread during shutdown and we don't want to delay other calls to IRegisteredObject.Stop.
if (!immediate)
{
// The Stop method is first called with the immediate parameter set to false. The object can either complete
// processing, call the UnregisterObject method, and then return or it can return immediately and complete
// processing asynchronously before calling the UnregisterObject method.
return Task.Run(StopInitial, CancellationToken.None);
}
else
{
lock (_locker)
{
if (_terminated) return Task.CompletedTask;
return Task.Run(StopImmediate, CancellationToken.None);
}
}
}
_logger.Info<BackgroundTaskRunner>("{LogPrefix} Waiting for tasks to complete", _logPrefix);
/// <summary>
/// Requests a registered object to un-register.
/// </summary>
/// <param name="immediate">true to indicate the registered object should un-register from the hosting
/// environment before returning; otherwise, false.</param>
/// <remarks>
/// <para>"When the application manager needs to stop a registered object, it will call the Stop method."</para>
/// <para>The application manager will call the Stop method to ask a registered object to un-register. During
/// processing of the Stop method, the registered object must call the HostingEnvironment.UnregisterObject method.</para>
/// </remarks>
public void Stop(bool immediate) => StopInternal(immediate);
/// <summary>
/// Called when immediate == false for IRegisteredObject.Stop(bool immediate)
/// </summary>
/// <remarks>
/// Called on a threadpool thread
/// </remarks>
private void StopInitial()
{
// immediate == false when the app is trying to wind down, immediate == true will be called either:
// after a call with immediate == false or if the app is not trying to wind down and needs to immediately stop.
// So Stop may be called twice or sometimes only once.
try
{
Shutdown(false, false); // do not accept any more tasks, flush the queue, do not wait
}
finally
{
// raise the completed event only after the running threading task has completed
lock (_locker)
{
if (_runningTask != null)
_runningTask.ContinueWith(_ => Terminate(false));
_runningTask.ContinueWith(_ => StopImmediate());
else
Terminate(false);
StopImmediate();
}
}
else
{
// If the registered object does not complete processing before the application manager's time-out
// period expires, the Stop method is called again with the immediate parameter set to true. When the
// immediate parameter is true, the registered object must call the UnregisterObject method before returning;
// otherwise, its registration will be removed by the application manager.
_logger.Info<BackgroundTaskRunner>("{LogPrefix} Canceling tasks", _logPrefix);
// If the shutdown token was not canceled in the Shutdown call above, it means there was still tasks
// being processed, in which case we'll give it a couple seconds
if (!_shutdownToken.IsCancellationRequested)
{
// If we are called with immediate == false, wind down above and then shutdown within 2 seconds,
// we want to shut down the app as quick as possible, if we wait until immediate == true, this can
// take a very long time since immediate will only be true when a new request is received on the new
// appdomain (or another iis timeout occurs ... which can take some time).
Thread.Sleep(2000); //we are already on a threadpool thread
StopImmediate();
}
}
/// <summary>
/// Called when immediate == true for IRegisteredObject.Stop(bool immediate)
/// </summary>
/// <remarks>
/// Called on a threadpool thread
/// </remarks>
private void StopImmediate()
{
_logger.Info<BackgroundTaskRunner>("{LogPrefix} Canceling tasks", _logPrefix);
try
{
Shutdown(true, true); // cancel all tasks, wait for the current one to end
}
finally
{
Terminate(true);
}
}
@@ -732,7 +802,13 @@ namespace Umbraco.Web.Scheduling
// raise the Terminated event
// complete the awaitable completion source, if any
HostingEnvironment.UnregisterObject(this);
if (immediate)
{
//only unregister when it's the final call, else we won't be notified of the final call
HostingEnvironment.UnregisterObject(this);
}
if (_terminated) return; // already taken care of
TaskCompletionSource<int> terminatedSource;
lock (_locker)
@@ -747,7 +823,9 @@ namespace Umbraco.Web.Scheduling
OnEvent(Terminated, "Terminated");
terminatedSource.SetResult(0);
terminatedSource.TrySetResult(0);
}
#endregion
}
}

View File

@@ -3,6 +3,8 @@ using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Umbraco.Core;
using Umbraco.Core.Composing;
using Umbraco.Core.Configuration.UmbracoSettings;
using Umbraco.Core.Logging;
using Umbraco.Core.Sync;
@@ -11,14 +13,16 @@ namespace Umbraco.Web.Scheduling
internal class KeepAlive : RecurringTaskBase
{
private readonly IRuntimeState _runtime;
private readonly IKeepAliveSection _keepAliveSection;
private readonly IProfilingLogger _logger;
private static HttpClient _httpClient;
public KeepAlive(IBackgroundTaskRunner<RecurringTaskBase> runner, int delayMilliseconds, int periodMilliseconds,
IRuntimeState runtime, IProfilingLogger logger)
IRuntimeState runtime, IKeepAliveSection keepAliveSection, IProfilingLogger logger)
: base(runner, delayMilliseconds, periodMilliseconds)
{
_runtime = runtime;
_keepAliveSection = keepAliveSection;
_logger = logger;
if (_httpClient == null)
_httpClient = new HttpClient();
@@ -46,25 +50,27 @@ namespace Umbraco.Web.Scheduling
using (_logger.DebugDuration<KeepAlive>("Keep alive executing", "Keep alive complete"))
{
string umbracoAppUrl = null;
var keepAlivePingUrl = _keepAliveSection.KeepAlivePingUrl;
try
{
umbracoAppUrl = _runtime.ApplicationUrl.ToString();
if (umbracoAppUrl.IsNullOrWhiteSpace())
if (keepAlivePingUrl.Contains("{umbracoApplicationUrl}"))
{
_logger.Warn<KeepAlive>("No url for service (yet), skip.");
return true; // repeat
var umbracoAppUrl = _runtime.ApplicationUrl.ToString();
if (umbracoAppUrl.IsNullOrWhiteSpace())
{
_logger.Warn<KeepAlive>("No umbracoApplicationUrl for service (yet), skip.");
return true; // repeat
}
keepAlivePingUrl = keepAlivePingUrl.Replace("{umbracoApplicationUrl}", umbracoAppUrl.TrimEnd('/'));
}
var url = umbracoAppUrl.TrimEnd('/') + "/api/keepalive/ping";
var request = new HttpRequestMessage(HttpMethod.Get, url);
var request = new HttpRequestMessage(HttpMethod.Get, keepAlivePingUrl);
var result = await _httpClient.SendAsync(request, token);
}
catch (Exception ex)
{
_logger.Error<KeepAlive>(ex, "Keep alive failed (at '{UmbracoAppUrl}').", umbracoAppUrl);
_logger.Error<KeepAlive>(ex, "Keep alive failed (at '{keepAlivePingUrl}').", keepAlivePingUrl);
}
}

View File

@@ -98,7 +98,11 @@ namespace Umbraco.Web.Scheduling
var tasks = new List<IBackgroundTask>();
tasks.Add(RegisterKeepAlive());
if (settings.KeepAlive.DisableKeepAliveTask == false)
{
tasks.Add(RegisterKeepAlive(settings.KeepAlive));
}
tasks.Add(RegisterScheduledPublishing());
tasks.Add(RegisterLogScrubber(settings));
tasks.Add(RegisterTempFileCleanup());
@@ -111,11 +115,11 @@ namespace Umbraco.Web.Scheduling
});
}
private IBackgroundTask RegisterKeepAlive()
private IBackgroundTask RegisterKeepAlive(IKeepAliveSection keepAliveSection)
{
// ping/keepalive
// on all servers
var task = new KeepAlive(_keepAliveRunner, DefaultDelayMilliseconds, FiveMinuteMilliseconds, _runtime, _logger);
var task = new KeepAlive(_keepAliveRunner, DefaultDelayMilliseconds, FiveMinuteMilliseconds, _runtime, keepAliveSection, _logger);
_keepAliveRunner.TryAdd(task);
return task;
}