U4-7810 DatabaseServerRegistrar should not execute as soon as a request is made, this slows down app start

This commit is contained in:
Shannon
2016-01-21 18:36:04 +01:00
parent b15f0a9c54
commit d86db6b2cd
2 changed files with 106 additions and 34 deletions

View File

@@ -1,4 +1,5 @@
using System;
using System.ComponentModel;
namespace Umbraco.Core.Sync
{
@@ -14,13 +15,18 @@ namespace Umbraco.Core.Sync
{
StaleServerTimeout = TimeSpan.FromMinutes(2); // 2 minutes
ThrottleSeconds = 30; // 30 seconds
RecurringSeconds = 60; // do it every minute
}
/// <summary>
/// The number of seconds to wait between each updates to the database.
/// </summary>
[Obsolete("This is no longer used")]
[EditorBrowsable(EditorBrowsableState.Never)]
public int ThrottleSeconds { get; set; }
/// <summary>
/// The amount of seconds to wait between calls to the database on the background thread
/// </summary>
public int RecurringSeconds { get; set; }
/// <summary>
/// The time span to wait before considering a server stale, after it has last been accessed.
/// </summary>

View File

@@ -1,4 +1,6 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using System.Web;
using Newtonsoft.Json;
using Umbraco.Core;
@@ -6,6 +8,7 @@ using Umbraco.Core.Logging;
using Umbraco.Core.Services;
using Umbraco.Core.Sync;
using Umbraco.Web.Routing;
using Umbraco.Web.Scheduling;
namespace Umbraco.Web.Strategies
{
@@ -22,74 +25,137 @@ namespace Umbraco.Web.Strategies
/// </remarks>
public sealed class ServerRegistrationEventHandler : ApplicationEventHandler
{
private readonly object _locko = new object();
private DatabaseServerRegistrar _registrar;
private DateTime _lastUpdated = DateTime.MinValue;
private BackgroundTaskRunner<IBackgroundTask> _backgroundTaskRunner;
private bool _started = false;
// bind to events
protected override void ApplicationStarted(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext)
{
_registrar = ServerRegistrarResolver.Current.Registrar as DatabaseServerRegistrar;
_backgroundTaskRunner = new BackgroundTaskRunner<IBackgroundTask>(
new BackgroundTaskRunnerOptions { AutoStart = true },
applicationContext.ProfilingLogger.Logger);
// only for the DatabaseServerRegistrar
if (_registrar == null) return;
//We will start the whole process when a successful request is made
UmbracoModule.RouteAttempt += UmbracoModuleRouteAttempt;
}
// handles route attempts.
/// <summary>
/// Handle when a request is made
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
/// <remarks>
/// We require this because:
/// - ApplicationContext.UmbracoApplicationUrl is initialized by UmbracoModule in BeginRequest
/// - RegisterServer is called on UmbracoModule.RouteAttempt which is triggered in ProcessRequest
/// we are safe, UmbracoApplicationUrl has been initialized
/// </remarks>
private void UmbracoModuleRouteAttempt(object sender, RoutableAttemptEventArgs e)
{
if (e.HttpContext.Request == null || e.HttpContext.Request.Url == null) return;
switch (e.Outcome)
{
case EnsureRoutableOutcome.IsRoutable:
// front-end request
RegisterServer(e);
//remove handler, we're done
UmbracoModule.RouteAttempt -= UmbracoModuleRouteAttempt;
break;
case EnsureRoutableOutcome.NotDocumentRequest:
// anything else (back-end request, service...)
//so it's not a document request, we'll check if it's a back office request
if (e.HttpContext.Request.Url.IsBackOfficeRequest(HttpRuntime.AppDomainAppVirtualPath))
{
RegisterServer(e);
break;
/*
case EnsureRoutableOutcome.NotReady:
case EnsureRoutableOutcome.NotConfigured:
case EnsureRoutableOutcome.NoContent:
default:
// otherwise, do nothing
break;
*/
//remove handler, we're done
UmbracoModule.RouteAttempt -= UmbracoModuleRouteAttempt;
}
break;
}
}
// register current server (throttled).
private void RegisterServer(UmbracoRequestEventArgs e)
{
lock (_locko) // ensure we trigger only once
{
var secondsSinceLastUpdate = DateTime.Now.Subtract(_lastUpdated).TotalSeconds;
if (secondsSinceLastUpdate < _registrar.Options.ThrottleSeconds) return;
_lastUpdated = DateTime.Now;
}
//only process once
if (_started) return;
_started = true;
var serverAddress = e.UmbracoContext.Application.UmbracoApplicationUrl;
var svc = e.UmbracoContext.Application.Services.ServerRegistrationService;
// because
// - ApplicationContext.UmbracoApplicationUrl is initialized by UmbracoModule in BeginRequest
// - RegisterServer is called on UmbracoModule.RouteAttempt which is triggered in ProcessRequest
// we are safe, UmbracoApplicationUrl has been initialized
var serverAddress = e.UmbracoContext.Application.UmbracoApplicationUrl;
//Perform the rest async, we don't want to block the startup sequence
// this will just reoccur on a background thread
_backgroundTaskRunner.Add(new TouchServerTask(_backgroundTaskRunner,
5, //delay before first execution
_registrar.Options.RecurringSeconds * 1000, //amount of ms between executions
svc, _registrar, serverAddress));
}
try
private class TouchServerTask : RecurringTaskBase
{
private readonly IServerRegistrationService _svc;
private readonly DatabaseServerRegistrar _registrar;
private readonly string _serverAddress;
/// <summary>
/// Initializes a new instance of the <see cref="RecurringTaskBase"/> class.
/// </summary>
/// <param name="runner">The task runner.</param>
/// <param name="delayMilliseconds">The delay.</param>
/// <param name="periodMilliseconds">The period.</param>
/// <param name="svc"></param>
/// <param name="registrar"></param>
/// <param name="serverAddress"></param>
/// <remarks>The task will repeat itself periodically. Use this constructor to create a new task.</remarks>
public TouchServerTask(IBackgroundTaskRunner<RecurringTaskBase> runner, int delayMilliseconds, int periodMilliseconds,
IServerRegistrationService svc, DatabaseServerRegistrar registrar, string serverAddress)
: base(runner, delayMilliseconds, periodMilliseconds)
{
svc.TouchServer(serverAddress, svc.CurrentServerIdentity, _registrar.Options.StaleServerTimeout);
if (svc == null) throw new ArgumentNullException("svc");
_svc = svc;
_registrar = registrar;
_serverAddress = serverAddress;
}
catch (Exception ex)
public override bool IsAsync
{
LogHelper.Error<ServerRegistrationEventHandler>("Failed to update server record in database.", ex);
get { return false; }
}
public override bool RunsOnShutdown
{
get { return false; }
}
/// <summary>
/// Runs the background task.
/// </summary>
/// <returns>A value indicating whether to repeat the task.</returns>
public override bool PerformRun()
{
try
{
_svc.TouchServer(_serverAddress, _svc.CurrentServerIdentity, _registrar.Options.StaleServerTimeout);
return true; // repeat
}
catch (Exception ex)
{
LogHelper.Error<ServerRegistrationEventHandler>("Failed to update server record in database.", ex);
return false; // probably stop if we have an error
}
}
public override Task<bool> PerformRunAsync(CancellationToken token)
{
throw new NotImplementedException();
}
}
}