271 lines
12 KiB
C#
271 lines
12 KiB
C#
using System;
|
|
using System.Threading;
|
|
using Umbraco.Core;
|
|
using Umbraco.Core.Composing;
|
|
using Umbraco.Core.Logging;
|
|
using Umbraco.Core.Services;
|
|
using Umbraco.Core.Services.Changes;
|
|
using Umbraco.Core.Sync;
|
|
using Umbraco.Examine;
|
|
using Umbraco.Web.Cache;
|
|
using Umbraco.Web.Routing;
|
|
using Umbraco.Web.Scheduling;
|
|
using Umbraco.Web.Search;
|
|
using Current = Umbraco.Web.Composing.Current;
|
|
|
|
namespace Umbraco.Web.Compose
|
|
{
|
|
/// <summary>
|
|
/// Ensures that servers are automatically registered in the database, when using the database server registrar.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// <para>At the moment servers are automatically registered upon first request and then on every
|
|
/// request but not more than once per (configurable) period. This really is "for information & debug" purposes so
|
|
/// we can look at the table and see what servers are registered - but the info is not used anywhere.</para>
|
|
/// <para>Should we actually want to use this, we would need a better and more deterministic way of figuring
|
|
/// out the "server address" ie the address to which server-to-server requests should be sent - because it
|
|
/// probably is not the "current request address" - especially in multi-domains configurations.</para>
|
|
/// </remarks>
|
|
[RuntimeLevel(MinLevel = RuntimeLevel.Run)]
|
|
|
|
// during Initialize / Startup, we end up checking Examine, which needs to be initialized beforehand
|
|
// TODO: should not be a strong dependency on "examine" but on an "indexing component"
|
|
[ComposeAfter(typeof(ExamineComposer))]
|
|
|
|
public sealed class DatabaseServerRegistrarAndMessengerComposer : ComponentComposer<DatabaseServerRegistrarAndMessengerComponent>, ICoreComposer
|
|
{
|
|
public static DatabaseServerMessengerOptions GetDefaultOptions(IFactory factory)
|
|
{
|
|
var logger = factory.GetInstance<ILogger>();
|
|
var indexRebuilder = factory.GetInstance<IndexRebuilder>();
|
|
|
|
return new DatabaseServerMessengerOptions
|
|
{
|
|
//These callbacks will be executed if the server has not been synced
|
|
// (i.e. it is a new server or the lastsynced.txt file has been removed)
|
|
InitializingCallbacks = new Action[]
|
|
{
|
|
//rebuild the xml cache file if the server is not synced
|
|
() =>
|
|
{
|
|
// rebuild the published snapshot caches entirely, if the server is not synced
|
|
// this is equivalent to DistributedCache RefreshAll... but local only
|
|
// (we really should have a way to reuse RefreshAll... locally)
|
|
// note: refresh all content & media caches does refresh content types too
|
|
var svc = Current.PublishedSnapshotService;
|
|
svc.Notify(new[] { new DomainCacheRefresher.JsonPayload(0, DomainChangeTypes.RefreshAll) });
|
|
svc.Notify(new[] { new ContentCacheRefresher.JsonPayload(0, TreeChangeTypes.RefreshAll) }, out _, out _);
|
|
svc.Notify(new[] { new MediaCacheRefresher.JsonPayload(0, TreeChangeTypes.RefreshAll) }, out _);
|
|
},
|
|
|
|
//rebuild indexes if the server is not synced
|
|
// NOTE: This will rebuild ALL indexes including the members, if developers want to target specific
|
|
// indexes then they can adjust this logic themselves.
|
|
() => { ExamineComponent.RebuildIndexes(indexRebuilder, logger, false, 5000); }
|
|
}
|
|
};
|
|
}
|
|
|
|
public override void Compose(Composition composition)
|
|
{
|
|
base.Compose(composition);
|
|
|
|
composition.SetDatabaseServerMessengerOptions(GetDefaultOptions);
|
|
composition.SetServerMessenger<BatchedDatabaseServerMessenger>();
|
|
}
|
|
}
|
|
|
|
public sealed class DatabaseServerRegistrarAndMessengerComponent : IComponent
|
|
{
|
|
private object _locker = new object();
|
|
private readonly DatabaseServerRegistrar _registrar;
|
|
private readonly BatchedDatabaseServerMessenger _messenger;
|
|
private readonly IRuntimeState _runtime;
|
|
private readonly ILogger _logger;
|
|
private readonly IServerRegistrationService _registrationService;
|
|
private readonly BackgroundTaskRunner<IBackgroundTask> _touchTaskRunner;
|
|
private readonly BackgroundTaskRunner<IBackgroundTask> _processTaskRunner;
|
|
private bool _started;
|
|
private IBackgroundTask[] _tasks;
|
|
private IndexRebuilder _indexRebuilder;
|
|
|
|
public DatabaseServerRegistrarAndMessengerComponent(IRuntimeState runtime, IServerRegistrar serverRegistrar, IServerMessenger serverMessenger, IServerRegistrationService registrationService, ILogger logger, IndexRebuilder indexRebuilder)
|
|
{
|
|
_runtime = runtime;
|
|
_logger = logger;
|
|
_registrationService = registrationService;
|
|
_indexRebuilder = indexRebuilder;
|
|
|
|
// create task runner for DatabaseServerRegistrar
|
|
_registrar = serverRegistrar as DatabaseServerRegistrar;
|
|
if (_registrar != null)
|
|
{
|
|
_touchTaskRunner = new BackgroundTaskRunner<IBackgroundTask>("ServerRegistration",
|
|
new BackgroundTaskRunnerOptions { AutoStart = true }, logger);
|
|
}
|
|
|
|
// create task runner for BatchedDatabaseServerMessenger
|
|
_messenger = serverMessenger as BatchedDatabaseServerMessenger;
|
|
if (_messenger != null)
|
|
{
|
|
_processTaskRunner = new BackgroundTaskRunner<IBackgroundTask>("ServerInstProcess",
|
|
new BackgroundTaskRunnerOptions { AutoStart = true }, logger);
|
|
}
|
|
}
|
|
|
|
public void Initialize()
|
|
{
|
|
//We will start the whole process when a successful request is made
|
|
if (_registrar != null || _messenger != null)
|
|
UmbracoModule.RouteAttempt += RegisterBackgroundTasksOnce;
|
|
|
|
// must come last, as it references some _variables
|
|
_messenger?.Startup();
|
|
}
|
|
|
|
public void Terminate()
|
|
{ }
|
|
|
|
/// <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 RegisterBackgroundTasksOnce(object sender, RoutableAttemptEventArgs e)
|
|
{
|
|
switch (e.Outcome)
|
|
{
|
|
case EnsureRoutableOutcome.IsRoutable:
|
|
case EnsureRoutableOutcome.NotDocumentRequest:
|
|
UmbracoModule.RouteAttempt -= RegisterBackgroundTasksOnce;
|
|
RegisterBackgroundTasks();
|
|
break;
|
|
}
|
|
}
|
|
|
|
private void RegisterBackgroundTasks()
|
|
{
|
|
// only perform this one time ever
|
|
LazyInitializer.EnsureInitialized(ref _tasks, ref _started, ref _locker, () =>
|
|
{
|
|
var serverAddress = _runtime.ApplicationUrl.ToString();
|
|
|
|
return new[]
|
|
{
|
|
RegisterInstructionProcess(),
|
|
RegisterTouchServer(_registrationService, serverAddress)
|
|
};
|
|
});
|
|
}
|
|
|
|
private IBackgroundTask RegisterInstructionProcess()
|
|
{
|
|
if (_messenger == null)
|
|
return null;
|
|
|
|
var task = new InstructionProcessTask(_processTaskRunner,
|
|
60000, //delay before first execution
|
|
_messenger.Options.ThrottleSeconds*1000, //amount of ms between executions
|
|
_messenger,
|
|
_logger);
|
|
_processTaskRunner.TryAdd(task);
|
|
return task;
|
|
}
|
|
|
|
private IBackgroundTask RegisterTouchServer(IServerRegistrationService registrationService, string serverAddress)
|
|
{
|
|
if (_registrar == null)
|
|
return null;
|
|
|
|
var task = new TouchServerTask(_touchTaskRunner,
|
|
15000, //delay before first execution
|
|
_registrar.Options.RecurringSeconds*1000, //amount of ms between executions
|
|
registrationService, _registrar, serverAddress, _logger);
|
|
_touchTaskRunner.TryAdd(task);
|
|
return task;
|
|
}
|
|
|
|
private class InstructionProcessTask : RecurringTaskBase
|
|
{
|
|
private readonly DatabaseServerMessenger _messenger;
|
|
private readonly ILogger _logger;
|
|
|
|
public InstructionProcessTask(IBackgroundTaskRunner<RecurringTaskBase> runner, int delayMilliseconds, int periodMilliseconds,
|
|
DatabaseServerMessenger messenger, ILogger logger)
|
|
: base(runner, delayMilliseconds, periodMilliseconds)
|
|
{
|
|
_messenger = messenger;
|
|
_logger = logger;
|
|
}
|
|
|
|
public override bool IsAsync => false;
|
|
|
|
/// <summary>
|
|
/// Runs the background task.
|
|
/// </summary>
|
|
/// <returns>A value indicating whether to repeat the task.</returns>
|
|
public override bool PerformRun()
|
|
{
|
|
try
|
|
{
|
|
_messenger.Sync();
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
_logger.Error<InstructionProcessTask>("Failed (will repeat).", e);
|
|
}
|
|
return true; // repeat
|
|
}
|
|
}
|
|
|
|
private class TouchServerTask : RecurringTaskBase
|
|
{
|
|
private readonly IServerRegistrationService _svc;
|
|
private readonly DatabaseServerRegistrar _registrar;
|
|
private readonly string _serverAddress;
|
|
private readonly ILogger _logger;
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="TouchServerTask"/> class.
|
|
/// </summary>
|
|
public TouchServerTask(IBackgroundTaskRunner<RecurringTaskBase> runner, int delayMilliseconds, int periodMilliseconds,
|
|
IServerRegistrationService svc, DatabaseServerRegistrar registrar, string serverAddress, ILogger logger)
|
|
: base(runner, delayMilliseconds, periodMilliseconds)
|
|
{
|
|
_svc = svc ?? throw new ArgumentNullException(nameof(svc));
|
|
_registrar = registrar;
|
|
_serverAddress = serverAddress;
|
|
_logger = logger;
|
|
}
|
|
|
|
public override bool IsAsync => false;
|
|
|
|
/// <summary>
|
|
/// Runs the background task.
|
|
/// </summary>
|
|
/// <returns>A value indicating whether to repeat the task.</returns>
|
|
public override bool PerformRun()
|
|
{
|
|
try
|
|
{
|
|
// TouchServer uses a proper unit of work etc underneath so even in a
|
|
// background task it is safe to call it without dealing with any scope
|
|
_svc.TouchServer(_serverAddress, _svc.CurrentServerIdentity, _registrar.Options.StaleServerTimeout);
|
|
return true; // repeat
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.Error<DatabaseServerRegistrarAndMessengerComponent>(ex, "Failed to update server record in database.");
|
|
return false; // probably stop if we have an error
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|