From d86db6b2cd3ef511c74c8e6cb30ea4cc7fea03ef Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 21 Jan 2016 18:36:04 +0100 Subject: [PATCH] U4-7810 DatabaseServerRegistrar should not execute as soon as a request is made, this slows down app start --- .../Sync/DatabaseServerRegistrarOptions.cs | 12 +- .../ServerRegistrationEventHandler.cs | 128 +++++++++++++----- 2 files changed, 106 insertions(+), 34 deletions(-) diff --git a/src/Umbraco.Core/Sync/DatabaseServerRegistrarOptions.cs b/src/Umbraco.Core/Sync/DatabaseServerRegistrarOptions.cs index 33ab1c8f57..5b5f6fc457 100644 --- a/src/Umbraco.Core/Sync/DatabaseServerRegistrarOptions.cs +++ b/src/Umbraco.Core/Sync/DatabaseServerRegistrarOptions.cs @@ -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 } - /// - /// The number of seconds to wait between each updates to the database. - /// + [Obsolete("This is no longer used")] + [EditorBrowsable(EditorBrowsableState.Never)] public int ThrottleSeconds { get; set; } + /// + /// The amount of seconds to wait between calls to the database on the background thread + /// + public int RecurringSeconds { get; set; } + /// /// The time span to wait before considering a server stale, after it has last been accessed. /// diff --git a/src/Umbraco.Web/Strategies/ServerRegistrationEventHandler.cs b/src/Umbraco.Web/Strategies/ServerRegistrationEventHandler.cs index 2a61d4177d..9106d43960 100644 --- a/src/Umbraco.Web/Strategies/ServerRegistrationEventHandler.cs +++ b/src/Umbraco.Web/Strategies/ServerRegistrationEventHandler.cs @@ -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 /// public sealed class ServerRegistrationEventHandler : ApplicationEventHandler { - private readonly object _locko = new object(); private DatabaseServerRegistrar _registrar; - private DateTime _lastUpdated = DateTime.MinValue; + private BackgroundTaskRunner _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( + 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. + /// + /// Handle when a request is made + /// + /// + /// + /// + /// 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 + /// 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; + + /// + /// Initializes a new instance of the class. + /// + /// The task runner. + /// The delay. + /// The period. + /// + /// + /// + /// The task will repeat itself periodically. Use this constructor to create a new task. + public TouchServerTask(IBackgroundTaskRunner 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("Failed to update server record in database.", ex); + get { return false; } + } + + public override bool RunsOnShutdown + { + get { return false; } + } + + /// + /// Runs the background task. + /// + /// A value indicating whether to repeat the task. + public override bool PerformRun() + { + try + { + _svc.TouchServer(_serverAddress, _svc.CurrentServerIdentity, _registrar.Options.StaleServerTimeout); + + return true; // repeat + } + catch (Exception ex) + { + LogHelper.Error("Failed to update server record in database.", ex); + + return false; // probably stop if we have an error + } + } + + public override Task PerformRunAsync(CancellationToken token) + { + throw new NotImplementedException(); } } }