Migrated scheduled publishing task to a hosted service.

This commit is contained in:
Andy Butland
2020-11-02 18:08:25 +01:00
parent 051dcccae7
commit 2f2da679a4
6 changed files with 162 additions and 152 deletions

View File

@@ -1,110 +0,0 @@
using System;
using System.Linq;
using Umbraco.Core;
using Umbraco.Core.Services;
using Umbraco.Core.Sync;
using Microsoft.Extensions.Logging;
namespace Umbraco.Web.Scheduling
{
public class ScheduledPublishing : RecurringTaskBase
{
private readonly IContentService _contentService;
private readonly ILogger<ScheduledPublishing> _logger;
private readonly IMainDom _mainDom;
private readonly IRuntimeState _runtime;
private readonly IServerMessenger _serverMessenger;
private readonly IBackofficeSecurityFactory _backofficeSecurityFactory;
private readonly IServerRegistrar _serverRegistrar;
private readonly IUmbracoContextFactory _umbracoContextFactory;
public ScheduledPublishing(IBackgroundTaskRunner<RecurringTaskBase> runner, int delayMilliseconds,
int periodMilliseconds,
IRuntimeState runtime, IMainDom mainDom, IServerRegistrar serverRegistrar, IContentService contentService,
IUmbracoContextFactory umbracoContextFactory, ILogger<ScheduledPublishing> logger, IServerMessenger serverMessenger, IBackofficeSecurityFactory backofficeSecurityFactory)
: base(runner, delayMilliseconds, periodMilliseconds)
{
_runtime = runtime;
_mainDom = mainDom;
_serverRegistrar = serverRegistrar;
_contentService = contentService;
_umbracoContextFactory = umbracoContextFactory;
_logger = logger;
_serverMessenger = serverMessenger;
_backofficeSecurityFactory = backofficeSecurityFactory;
}
public override bool IsAsync => false;
public override bool PerformRun()
{
if (Suspendable.ScheduledPublishing.CanRun == false)
return true; // repeat, later
switch (_serverRegistrar.GetCurrentServerRole())
{
case ServerRole.Replica:
_logger.LogDebug("Does not run on replica servers.");
return true; // DO repeat, server role can change
case ServerRole.Unknown:
_logger.LogDebug("Does not run on servers with unknown role.");
return true; // DO repeat, server role can change
}
// ensure we do not run if not main domain, but do NOT lock it
if (_mainDom.IsMainDom == false)
{
_logger.LogDebug("Does not run if not MainDom.");
return false; // do NOT repeat, going down
}
// do NOT run publishing if not properly running
if (_runtime.Level != RuntimeLevel.Run)
{
_logger.LogDebug("Does not run if run level is not Run.");
return true; // repeat/wait
}
try
{
// We don't need an explicit scope here because PerformScheduledPublish creates it's own scope
// so it's safe as it will create it's own ambient scope.
// Ensure we run with an UmbracoContext, because this will run in a background task,
// and developers may be using the UmbracoContext in the event handlers.
// TODO: or maybe not, CacheRefresherComponent already ensures a context when handling events
// - UmbracoContext 'current' needs to be refactored and cleaned up
// - batched messenger should not depend on a current HttpContext
// but then what should be its "scope"? could we attach it to scopes?
// - and we should definitively *not* have to flush it here (should be auto)
//
_backofficeSecurityFactory.EnsureBackofficeSecurity();
using (var contextReference = _umbracoContextFactory.EnsureUmbracoContext())
{
try
{
// run
var result = _contentService.PerformScheduledPublish(DateTime.Now);
foreach (var grouped in result.GroupBy(x => x.Result))
_logger.LogInformation(
"Scheduled publishing result: '{StatusCount}' items with status {Status}",
grouped.Count(), grouped.Key);
}
finally
{
// if running on a temp context, we have to flush the messenger
if (contextReference.IsRoot && _serverMessenger is IBatchedDatabaseServerMessenger m)
m.FlushBatch();
}
}
}
catch (Exception ex)
{
// important to catch *everything* to ensure the task repeats
_logger.LogError(ex, "Failed.");
}
return true; // repeat
}
}
}

View File

@@ -1,105 +0,0 @@
using System.Collections.Generic;
using System.Threading;
using Microsoft.Extensions.Logging;
using Umbraco.Core;
using Umbraco.Core.Composing;
using Umbraco.Core.Hosting;
using Umbraco.Core.Services;
using Umbraco.Core.Sync;
using Umbraco.Web.Routing;
namespace Umbraco.Web.Scheduling
{
public sealed class SchedulerComponent : IComponent
{
private const int DefaultDelayMilliseconds = 180000; // 3 mins
private const int OneMinuteMilliseconds = 60000;
private readonly IRuntimeState _runtime;
private readonly IMainDom _mainDom;
private readonly IServerRegistrar _serverRegistrar;
private readonly IContentService _contentService;
private readonly ILogger<SchedulerComponent> _logger;
private readonly ILoggerFactory _loggerFactory;
private readonly IApplicationShutdownRegistry _applicationShutdownRegistry;
private readonly IUmbracoContextFactory _umbracoContextFactory;
private readonly IServerMessenger _serverMessenger;
private readonly IRequestAccessor _requestAccessor;
private readonly IBackofficeSecurityFactory _backofficeSecurityFactory;
private BackgroundTaskRunner<IBackgroundTask> _publishingRunner;
private bool _started;
private object _locker = new object();
private IBackgroundTask[] _tasks;
public SchedulerComponent(IRuntimeState runtime, IMainDom mainDom, IServerRegistrar serverRegistrar,
IContentService contentService, IUmbracoContextFactory umbracoContextFactory, ILoggerFactory loggerFactory,
IApplicationShutdownRegistry applicationShutdownRegistry,
IServerMessenger serverMessenger, IRequestAccessor requestAccessor,
IBackofficeSecurityFactory backofficeSecurityFactory)
{
_runtime = runtime;
_mainDom = mainDom;
_serverRegistrar = serverRegistrar;
_contentService = contentService;
_loggerFactory = loggerFactory;
_logger = loggerFactory.CreateLogger<SchedulerComponent>();
_applicationShutdownRegistry = applicationShutdownRegistry;
_umbracoContextFactory = umbracoContextFactory;
_serverMessenger = serverMessenger;
_requestAccessor = requestAccessor;
_backofficeSecurityFactory = backofficeSecurityFactory;
}
public void Initialize()
{
var logger = _loggerFactory.CreateLogger<BackgroundTaskRunner<IBackgroundTask>>();
// backgrounds runners are web aware, if the app domain dies, these tasks will wind down correctly
_publishingRunner = new BackgroundTaskRunner<IBackgroundTask>("ScheduledPublishing", logger, _applicationShutdownRegistry);
// we will start the whole process when a successful request is made
_requestAccessor.RouteAttempt += RegisterBackgroundTasksOnce;
}
public void Terminate()
{
// the AppDomain / maindom / whatever takes care of stopping background task runners
}
private void RegisterBackgroundTasksOnce(object sender, RoutableAttemptEventArgs e)
{
switch (e.Outcome)
{
case EnsureRoutableOutcome.IsRoutable:
case EnsureRoutableOutcome.NotDocumentRequest:
_requestAccessor.RouteAttempt -= RegisterBackgroundTasksOnce;
RegisterBackgroundTasks();
break;
}
}
private void RegisterBackgroundTasks()
{
LazyInitializer.EnsureInitialized(ref _tasks, ref _started, ref _locker, () =>
{
_logger.LogDebug("Initializing the scheduler");
var tasks = new List<IBackgroundTask>();
tasks.Add(RegisterScheduledPublishing());
return tasks.ToArray();
});
}
private IBackgroundTask RegisterScheduledPublishing()
{
// scheduled publishing/unpublishing
// install on all, will only run on non-replica servers
var task = new ScheduledPublishing(_publishingRunner, DefaultDelayMilliseconds, OneMinuteMilliseconds, _runtime, _mainDom, _serverRegistrar, _contentService, _umbracoContextFactory, _loggerFactory.CreateLogger<ScheduledPublishing>(), _serverMessenger, _backofficeSecurityFactory);
_publishingRunner.TryAdd(task);
return task;
}
}
}

View File

@@ -1,17 +0,0 @@
using System;
using Umbraco.Core;
using Umbraco.Core.Composing;
namespace Umbraco.Web.Scheduling
{
/// <summary>
/// Used to do the scheduling for tasks, publishing, etc...
/// </summary>
/// <remarks>
/// All tasks are run in a background task runner which is web aware and will wind down
/// the task correctly instead of killing it completely when the app domain shuts down.
/// </remarks>
[RuntimeLevel(MinLevel = RuntimeLevel.Run)]
internal sealed class SchedulerComposer : ComponentComposer<SchedulerComponent>, ICoreComposer
{ }
}