Components Initialize and Terminate

This commit is contained in:
Stephan
2019-01-07 09:30:47 +01:00
parent 4fa43abe23
commit 84f6239c98
23 changed files with 340 additions and 119 deletions

View File

@@ -23,7 +23,10 @@ namespace Umbraco.Core.Components
_auditService = auditService;
_userService = userService;
_entityService = entityService;
}
public void Initialize()
{
UserService.SavedUserGroup += OnSavedUserGroupWithUsers;
UserService.SavedUser += OnSavedUser;
@@ -37,6 +40,9 @@ namespace Umbraco.Core.Components
MemberService.Exported += OnMemberExported;
}
public void Terminate()
{ }
private IUser CurrentPerformingUser
{
get

View File

@@ -20,6 +20,21 @@ namespace Umbraco.Core.Components
_logger = logger;
}
public void Initialize()
{
using (_logger.DebugDuration<ComponentCollection>($"Initializing. (log components when >{LogThresholdMilliseconds}ms)", "Initialized."))
{
foreach (var component in this.Reverse()) // terminate components in reverse order
{
var componentType = component.GetType();
using (_logger.DebugDuration<ComponentCollection>($"Initializing {componentType.FullName}.", $"Initialized {componentType.FullName}.", thresholdMilliseconds: LogThresholdMilliseconds))
{
component.Initialize();
}
}
}
}
public void Terminate()
{
using (_logger.DebugDuration<ComponentCollection>($"Terminating. (log components when >{LogThresholdMilliseconds}ms)", "Terminated."))
@@ -29,6 +44,7 @@ namespace Umbraco.Core.Components
var componentType = component.GetType();
using (_logger.DebugDuration<ComponentCollection>($"Terminating {componentType.FullName}.", $"Terminated {componentType.FullName}.", thresholdMilliseconds: LogThresholdMilliseconds))
{
component.Terminate();
component.DisposeIfDisposable();
}
}

View File

@@ -5,11 +5,21 @@
/// </summary>
/// <remarks>
/// <para>Components are created by DI and therefore must have a public constructor.</para>
/// <para>All components which are also disposable, will be disposed in reverse
/// order, when Umbraco terminates.</para>
/// <para>All components are terminated in reverse order when Umbraco terminates, and
/// disposable components are disposed.</para>
/// <para>The Dispose method may be invoked more than once, and components
/// should ensure they support this.</para>
/// </remarks>
public interface IComponent
{ }
{
/// <summary>
/// Initializes the component.
/// </summary>
void Initialize();
/// <summary>
/// Terminates the component.
/// </summary>
void Terminate();
}
}

View File

@@ -1,20 +1,28 @@
using System;
using System.IO;
using System.IO;
using Umbraco.Core.IO;
using Umbraco.Core.Logging;
using Umbraco.Core.Manifest;
namespace Umbraco.Core.Components
{
public sealed class ManifestWatcherComponent : IComponent, IDisposable
public sealed class ManifestWatcherComponent : IComponent
{
private readonly IRuntimeState _runtimeState;
private readonly ILogger _logger;
// if configured and in debug mode, a ManifestWatcher watches App_Plugins folders for
// package.manifest chances and restarts the application on any change
private ManifestWatcher _mw;
public ManifestWatcherComponent(IRuntimeState runtimeState, ILogger logger)
{
if (runtimeState.Debug == false) return;
_runtimeState = runtimeState;
_logger = logger;
}
public void Initialize()
{
if (_runtimeState.Debug == false) return;
//if (ApplicationContext.Current.IsConfigured == false || GlobalSettings.DebugMode == false)
// return;
@@ -22,11 +30,11 @@ namespace Umbraco.Core.Components
var appPlugins = IOHelper.MapPath("~/App_Plugins/");
if (Directory.Exists(appPlugins) == false) return;
_mw = new ManifestWatcher(logger);
_mw = new ManifestWatcher(_logger);
_mw.Start(Directory.GetDirectories(appPlugins));
}
public void Dispose()
public void Terminate()
{
if (_mw == null) return;

View File

@@ -8,11 +8,14 @@ namespace Umbraco.Core.Components
//TODO: This should just exist in the content service/repo!
public sealed class RelateOnCopyComponent : IComponent
{
public RelateOnCopyComponent()
public void Initialize()
{
ContentService.Copied += ContentServiceCopied;
}
public void Terminate()
{ }
private static void ContentServiceCopied(IContentService sender, Events.CopyEventArgs<IContent> e)
{
if (e.RelateToOriginal == false) return;

View File

@@ -9,7 +9,7 @@ namespace Umbraco.Core.Components
{
public sealed class RelateOnTrashComponent : IComponent
{
public RelateOnTrashComponent()
public void Initialize()
{
ContentService.Moved += ContentService_Moved;
ContentService.Trashed += ContentService_Trashed;
@@ -17,6 +17,9 @@ namespace Umbraco.Core.Components
MediaService.Trashed += MediaService_Trashed;
}
public void Terminate()
{ }
private static void ContentService_Moved(IContentService sender, MoveEventArgs<IContent> e)
{
foreach (var item in e.MoveInfoCollection.Where(x => x.OriginalPath.Contains(Constants.System.RecycleBinContent.ToInvariantString())))

View File

@@ -146,8 +146,9 @@ namespace Umbraco.Core.Runtime
// create the factory
_factory = Current.Factory = composition.CreateFactory();
// create the components
// create & initialize the components
_components = _factory.GetInstance<ComponentCollection>();
_components.Initialize();
}
catch (Exception e)
{

View File

@@ -7,12 +7,19 @@ namespace Umbraco.Core.Runtime
{
public class CoreRuntimeComponent : IComponent
{
private readonly IEnumerable<Profile> _mapperProfiles;
public CoreRuntimeComponent(IEnumerable<Profile> mapperProfiles)
{
_mapperProfiles = mapperProfiles;
}
public void Initialize()
{
// mapper profiles have been registered & are created by the container
Mapper.Initialize(configuration =>
{
foreach (var profile in mapperProfiles)
foreach (var profile in _mapperProfiles)
configuration.AddProfile(profile);
});
@@ -24,5 +31,8 @@ namespace Umbraco.Core.Runtime
IOHelper.EnsurePathExists(SystemDirectories.MvcViews + "/Partials");
IOHelper.EnsurePathExists(SystemDirectories.MvcViews + "/MacroPartials");
}
public void Terminate()
{ }
}
}

View File

@@ -18,7 +18,8 @@ namespace Umbraco.Tests.Components
public class ComponentTests
{
private static readonly List<Type> Composed = new List<Type>();
private static readonly List<string> Initialized = new List<string>();
private static readonly List<Type> Initialized = new List<Type>();
private static readonly List<Type> Terminated = new List<Type>();
private static IFactory MockFactory(Action<Mock<IFactory>> setup = null)
{
@@ -64,13 +65,36 @@ namespace Umbraco.Tests.Components
var composition = new Composition(register, MockTypeLoader(), Mock.Of<IProfilingLogger>(), MockRuntimeState(RuntimeLevel.Unknown));
var types = TypeArray<Composer1, Composer2, Composer3, Composer4>();
var components = new Core.Components.Composers(composition, types, Mock.Of<IProfilingLogger>());
var composers = new Composers(composition, types, Mock.Of<IProfilingLogger>());
Composed.Clear();
// 2 is Core and requires 4
// 3 is User - goes away with RuntimeLevel.Unknown
// => reorder components accordingly
components.Compose();
composers.Compose();
AssertTypeArray(TypeArray<Composer1, Composer4, Composer2>(), Composed);
var factory = MockFactory(m =>
{
m.Setup(x => x.TryGetInstance(It.Is<Type>(t => t == typeof(ISomeResource)))).Returns(() => new SomeResource());
m.Setup(x => x.GetInstance(It.IsAny<Type>())).Returns<Type>((type) =>
{
if (type == typeof(Composer1)) return new Composer1();
if (type == typeof(Composer5)) return new Composer5();
if (type == typeof(Component5)) return new Component5(new SomeResource());
if (type == typeof(IProfilingLogger)) return new ProfilingLogger(Mock.Of<ILogger>(), Mock.Of<IProfiler>());
throw new NotSupportedException(type.FullName);
});
});
var builder = composition.WithCollectionBuilder<ComponentCollectionBuilder>();
builder.RegisterWith(register);
var components = builder.CreateCollection(factory);
Assert.IsEmpty(components);
components.Initialize();
Assert.IsEmpty(Initialized);
components.Terminate();
Assert.IsEmpty(Terminated);
}
[Test]
@@ -164,6 +188,10 @@ namespace Umbraco.Tests.Components
[Test]
public void Initialize()
{
Composed.Clear();
Initialized.Clear();
Terminated.Clear();
var register = MockRegister();
var factory = MockFactory(m =>
{
@@ -181,17 +209,22 @@ namespace Umbraco.Tests.Components
var types = new[] { typeof(Composer1), typeof(Composer5) };
var composers = new Composers(composition, types, Mock.Of<IProfilingLogger>());
Composed.Clear();
Initialized.Clear();
Assert.IsEmpty(Composed);
composers.Compose();
AssertTypeArray(TypeArray<Composer1, Composer5>(), Composed);
var builder = composition.WithCollectionBuilder<ComponentCollectionBuilder>();
builder.RegisterWith(register);
var components = builder.CreateCollection(factory);
Assert.AreEqual(2, Composed.Count);
Assert.AreEqual(typeof(Composer1), Composed[0]);
Assert.AreEqual(typeof(Composer5), Composed[1]);
Assert.AreEqual(1, Initialized.Count);
Assert.AreEqual("Umbraco.Tests.Components.ComponentTests+SomeResource", Initialized[0]);
Assert.IsEmpty(Initialized);
components.Initialize();
AssertTypeArray(TypeArray<Component5>(), Initialized);
Assert.IsEmpty(Terminated);
components.Terminate();
AssertTypeArray(TypeArray<Component5>(), Terminated);
}
[Test]
@@ -301,9 +334,6 @@ namespace Umbraco.Tests.Components
}
}
public class TestComponentBase : IComponent
{ }
public class Composer1 : TestComposerBase
{ }
@@ -326,11 +356,26 @@ namespace Umbraco.Tests.Components
}
}
public class Component5 : IComponent
public class TestComponentBase : IComponent
{
public virtual void Initialize()
{
Initialized.Add(GetType());
}
public virtual void Terminate()
{
Terminated.Add(GetType());
}
}
public class Component5 : TestComponentBase
{
private readonly ISomeResource _resource;
public Component5(ISomeResource resource)
{
Initialized.Add(resource.GetType().FullName);
_resource = resource;
}
}

View File

@@ -191,8 +191,7 @@ namespace Umbraco.Tests.Runtimes
{
// test flags
public static bool Ctored;
public static bool Initialized1;
public static bool Initialized2;
public static bool Initialized;
public static bool Terminated;
public static IProfilingLogger ProfilingLogger;
@@ -200,7 +199,7 @@ namespace Umbraco.Tests.Runtimes
public static void Reset()
{
Ctored = Initialized1 = Initialized2 = Terminated = false;
Ctored = Initialized = Terminated = false;
ProfilingLogger = null;
}
@@ -210,10 +209,19 @@ namespace Umbraco.Tests.Runtimes
ProfilingLogger = proflog;
}
public void Initialize()
{
Initialized = true;
}
public void Terminate()
{
Terminated = true;
}
public void Dispose()
{
Disposed = true;
Terminated = true;
}
}
}

View File

@@ -4,9 +4,19 @@ namespace Umbraco.Web.Cache
{
public class DistributedCacheBinderComponent : IComponent
{
private readonly IDistributedCacheBinder _binder;
public DistributedCacheBinderComponent(IDistributedCacheBinder distributedCacheBinder)
{
distributedCacheBinder.BindEvents();
_binder = distributedCacheBinder;
}
public void Initialize()
{
_binder.BindEvents();
}
public void Terminate()
{ }
}
}

View File

@@ -12,17 +12,14 @@ namespace Umbraco.Web.Components
private readonly IAuditService _auditService;
private readonly IUserService _userService;
private IUser GetPerformingUser(int userId)
{
var found = userId >= 0 ? _userService.GetUserById(userId) : null;
return found ?? new User {Id = 0, Name = "SYSTEM", Email = ""};
}
public BackOfficeUserAuditEventsComponent(IAuditService auditService, IUserService userService)
{
_auditService = auditService;
_userService = userService;
}
public void Initialize()
{
//BackOfficeUserManager.AccountLocked += ;
//BackOfficeUserManager.AccountUnlocked += ;
BackOfficeUserManager.ForgotPasswordRequested += OnForgotPasswordRequest;
@@ -34,7 +31,15 @@ namespace Umbraco.Web.Components
BackOfficeUserManager.PasswordChanged += OnPasswordChanged;
BackOfficeUserManager.PasswordReset += OnPasswordReset;
//BackOfficeUserManager.ResetAccessFailedCount += ;
}
public void Terminate()
{ }
private IUser GetPerformingUser(int userId)
{
var found = userId >= 0 ? _userService.GetUserById(userId) : null;
return found ?? new User { Id = 0, Name = "SYSTEM", Email = "" };
}
private static string FormatEmail(IMembershipUser user)
@@ -42,7 +47,6 @@ namespace Umbraco.Web.Components
return user == null ? string.Empty : user.Email.IsNullOrWhiteSpace() ? "" : $"<{user.Email}>";
}
private void OnLoginSuccess(object sender, EventArgs args)
{
if (args is IdentityAuditEventArgs identityArgs)

View File

@@ -121,7 +121,10 @@ namespace Umbraco.Web.Components
new BackgroundTaskRunnerOptions { AutoStart = true }, logger);
_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
UmbracoModule.RouteAttempt += RegisterBackgroundTasksOnce;
@@ -129,6 +132,9 @@ namespace Umbraco.Web.Components
_messenger.Startup();
}
public void Terminate()
{ }
/// <summary>
/// Handle when a request is made
/// </summary>

View File

@@ -18,27 +18,39 @@ namespace Umbraco.Web.Components
{
public sealed class NotificationsComponent : IComponent
{
public NotificationsComponent(INotificationService notificationService, Notifier notifier, ActionCollection actions)
private readonly Notifier _notifier;
private readonly ActionCollection _actions;
public NotificationsComponent(Notifier notifier, ActionCollection actions)
{
_notifier = notifier;
_actions = actions;
}
public void Initialize()
{
//Send notifications for the send to publish action
ContentService.SentToPublish += (sender, args) => notifier.Notify(actions.GetAction<ActionToPublish>(), args.Entity);
ContentService.SentToPublish += (sender, args) => _notifier.Notify(_actions.GetAction<ActionToPublish>(), args.Entity);
//Send notifications for the published action
ContentService.Published += (sender, args) => notifier.Notify(actions.GetAction<ActionPublish>(), args.PublishedEntities.ToArray());
ContentService.Published += (sender, args) => _notifier.Notify(_actions.GetAction<ActionPublish>(), args.PublishedEntities.ToArray());
//Send notifications for the saved action
ContentService.Sorted += (sender, args) => ContentServiceSorted(notifier, sender, args, actions);
ContentService.Sorted += (sender, args) => ContentServiceSorted(_notifier, sender, args, _actions);
//Send notifications for the update and created actions
ContentService.Saved += (sender, args) => ContentServiceSaved(notifier, sender, args, actions);
ContentService.Saved += (sender, args) => ContentServiceSaved(_notifier, sender, args, _actions);
//Send notifications for the delete action
ContentService.Deleted += (sender, args) => notifier.Notify(actions.GetAction<ActionDelete>(), args.DeletedEntities.ToArray());
ContentService.Deleted += (sender, args) => _notifier.Notify(_actions.GetAction<ActionDelete>(), args.DeletedEntities.ToArray());
//Send notifications for the unpublish action
ContentService.Unpublished += (sender, args) => notifier.Notify(actions.GetAction<ActionUnpublish>(), args.PublishedEntities.ToArray());
ContentService.Unpublished += (sender, args) => _notifier.Notify(_actions.GetAction<ActionUnpublish>(), args.PublishedEntities.ToArray());
}
public void Terminate()
{ }
private void ContentServiceSorted(Notifier notifier, IContentService sender, Core.Events.SaveEventArgs<IContent> args, ActionCollection actions)
{
var parentId = args.SavedEntities.Select(x => x.ParentId).Distinct().ToList();

View File

@@ -8,11 +8,14 @@ namespace Umbraco.Web.Components
{
public sealed class PublicAccessComponent : IComponent
{
public PublicAccessComponent()
public void Initialize()
{
MemberGroupService.Saved += MemberGroupService_Saved;
}
public void Terminate()
{ }
static void MemberGroupService_Saved(IMemberGroupService sender, Core.Events.SaveEventArgs<Core.Models.IMemberGroup> e)
{
foreach (var grp in e.SavedEntities)

View File

@@ -1,6 +1,5 @@
using System;
using System.Web;
using Umbraco.Core;
using Umbraco.Core.Components;
using Umbraco.Core.Logging;
@@ -9,30 +8,38 @@ namespace Umbraco.Web.Logging
internal sealed class WebProfilerComponent : IComponent
{
private readonly WebProfiler _profiler;
private readonly bool _profile;
public WebProfilerComponent(IProfiler profiler, ILogger logger, IRuntimeState runtime)
public WebProfilerComponent(IProfiler profiler, ILogger logger)
{
_profile = true;
// although registered in WebRuntime.Compose, ensure that we have not
// been replaced by another component, and we are still "the" profiler
_profiler = profiler as WebProfiler;
if (_profiler == null)
{
// if VoidProfiler was registered, let it be known
var vp = profiler as VoidProfiler;
if (vp != null)
logger.Info<WebProfilerComponent>("Profiler is VoidProfiler, not profiling (must run debug mode to profile).");
return;
}
if (_profiler != null) return;
// if VoidProfiler was registered, let it be known
if (profiler is VoidProfiler)
logger.Info<WebProfilerComponent>("Profiler is VoidProfiler, not profiling (must run debug mode to profile).");
_profile = false;
}
public void Initialize()
{
if (!_profile) return;
// bind to ApplicationInit - ie execute the application initialization for *each* application
// it would be a mistake to try and bind to the current application events
UmbracoApplicationBase.ApplicationInit += InitializeApplication;
}
public void Terminate()
{ }
private void InitializeApplication(object sender, EventArgs args)
{
var app = sender as HttpApplication;
if (app == null) return;
if (!(sender is HttpApplication app)) return;
// for *each* application (this will run more than once)
app.BeginRequest += (s, a) => _profiler.UmbracoApplicationBeginRequest(s, a);

View File

@@ -1,5 +1,4 @@
using System.Linq;
using Umbraco.Core;
using Umbraco.Core.Components;
using Umbraco.Core.Models;
using Umbraco.Core.PropertyEditors;
@@ -9,18 +8,26 @@ namespace Umbraco.Web.PropertyEditors
{
internal sealed class PropertyEditorsComponent : IComponent
{
private readonly PropertyEditorCollection _propertyEditors;
public PropertyEditorsComponent(PropertyEditorCollection propertyEditors)
{
var fileUpload = propertyEditors.OfType<FileUploadPropertyEditor>().FirstOrDefault();
_propertyEditors = propertyEditors;
}
public void Initialize()
{
var fileUpload = _propertyEditors.OfType<FileUploadPropertyEditor>().FirstOrDefault();
if (fileUpload != null) Initialize(fileUpload);
var imageCropper = propertyEditors.OfType<ImageCropperPropertyEditor>().FirstOrDefault();
var imageCropper = _propertyEditors.OfType<ImageCropperPropertyEditor>().FirstOrDefault();
if (imageCropper != null) Initialize(imageCropper);
// grid/examine moved to ExamineComponent
}
// as long as these methods are private+static they won't be executed by the boot loader
public void Terminate()
{ }
private static void Initialize(FileUploadPropertyEditor fileUpload)
{

View File

@@ -8,5 +8,11 @@ namespace Umbraco.Web.PublishedCache.NuCache
{
// nothing - this just ensures that the service is created at boot time
}
public void Initialize()
{ }
public void Terminate()
{ }
}
}

View File

@@ -69,8 +69,7 @@ namespace Umbraco.Web.Redirects
}
}
public RedirectTrackingComponent()
//protected void Initialize(ContentFinderCollectionBuilder contentFinderCollectionBuilder)
public void Initialize()
{
// don't let the event handlers kick in if Redirect Tracking is turned off in the config
if (UmbracoConfig.GetConfig<IUmbracoSettingsSection>("umbracoConfiguration/settings").WebRouting.DisableRedirectUrlTracking) return;
@@ -103,6 +102,9 @@ namespace Umbraco.Web.Redirects
// rolled back items have to be published, so publishing will take care of that
}
public void Terminate()
{ }
private static void ContentCacheRefresher_CacheUpdated(ContentCacheRefresher sender,
CacheRefresherEventArgs args)
{

View File

@@ -34,8 +34,17 @@ namespace Umbraco.Web.Runtime
{
public sealed class WebRuntimeComponent : IComponent
{
private readonly IUmbracoContextAccessor _umbracoContextAccessor;
private readonly SurfaceControllerTypeCollection _surfaceControllerTypes;
private readonly UmbracoApiControllerTypeCollection _apiControllerTypes;
private readonly IPublishedSnapshotService _publishedSnapshotService;
private readonly IUserService _userService;
private readonly IUmbracoSettingsSection _umbracoSettings;
private readonly IGlobalSettings _globalSettings;
private readonly IVariationContextAccessor _variationContextAccessor;
private readonly UrlProviderCollection _urlProviders;
public WebRuntimeComponent(
IRuntimeState runtime,
IUmbracoContextAccessor umbracoContextAccessor,
SurfaceControllerTypeCollection surfaceControllerTypes,
UmbracoApiControllerTypeCollection apiControllerTypes,
@@ -43,15 +52,27 @@ namespace Umbraco.Web.Runtime
IUserService userService,
IUmbracoSettingsSection umbracoSettings,
IGlobalSettings globalSettings,
IEntityService entityService,
IVariationContextAccessor variationContextAccessor,
UrlProviderCollection urlProviders)
{
_umbracoContextAccessor = umbracoContextAccessor;
_surfaceControllerTypes = surfaceControllerTypes;
_apiControllerTypes = apiControllerTypes;
_publishedSnapshotService = publishedSnapshotService;
_userService = userService;
_umbracoSettings = umbracoSettings;
_globalSettings = globalSettings;
_variationContextAccessor = variationContextAccessor;
_urlProviders = urlProviders;
}
public void Initialize()
{
// setup mvc and webapi services
SetupMvcAndWebApi();
// client dependency
ConfigureClientDependency(globalSettings);
ConfigureClientDependency(_globalSettings);
// Disable the X-AspNetMvc-Version HTTP Header
MvcHandler.DisableMvcResponseHeader = true;
@@ -65,7 +86,7 @@ namespace Umbraco.Web.Runtime
ConfigureGlobalFilters();
// set routes
CreateRoutes(umbracoContextAccessor, globalSettings, surfaceControllerTypes, apiControllerTypes);
CreateRoutes(_umbracoContextAccessor, _globalSettings, _surfaceControllerTypes, _apiControllerTypes);
// get an http context
// at that moment, HttpContext.Current != null but its .Request property is null
@@ -75,19 +96,22 @@ namespace Umbraco.Web.Runtime
// (also sets the accessor)
// this is a *temp* UmbracoContext
UmbracoContext.EnsureContext(
umbracoContextAccessor,
_umbracoContextAccessor,
new HttpContextWrapper(HttpContext.Current),
publishedSnapshotService,
new WebSecurity(httpContext, userService, globalSettings),
umbracoSettings,
urlProviders,
globalSettings,
variationContextAccessor);
_publishedSnapshotService,
new WebSecurity(httpContext, _userService, _globalSettings),
_umbracoSettings,
_urlProviders,
_globalSettings,
_variationContextAccessor);
// ensure WebAPI is initialized, after everything
GlobalConfiguration.Configuration.EnsureInitialized();
}
public void Terminate()
{ }
private static void ConfigureGlobalFilters()
{
GlobalFilters.Filters.Add(new EnsurePartialViewMacroViewContextFilterAttribute());

View File

@@ -24,11 +24,11 @@ namespace Umbraco.Web.Scheduling
private readonly HealthCheckCollection _healthChecks;
private readonly HealthCheckNotificationMethodCollection _notifications;
private readonly BackgroundTaskRunner<IBackgroundTask> _keepAliveRunner;
private readonly BackgroundTaskRunner<IBackgroundTask> _publishingRunner;
private readonly BackgroundTaskRunner<IBackgroundTask> _tasksRunner;
private readonly BackgroundTaskRunner<IBackgroundTask> _scrubberRunner;
private readonly BackgroundTaskRunner<IBackgroundTask> _healthCheckRunner;
private BackgroundTaskRunner<IBackgroundTask> _keepAliveRunner;
private BackgroundTaskRunner<IBackgroundTask> _publishingRunner;
private BackgroundTaskRunner<IBackgroundTask> _tasksRunner;
private BackgroundTaskRunner<IBackgroundTask> _scrubberRunner;
private BackgroundTaskRunner<IBackgroundTask> _healthCheckRunner;
private bool _started;
private object _locker = new object();
@@ -47,18 +47,26 @@ namespace Umbraco.Web.Scheduling
_healthChecks = healthChecks;
_notifications = notifications;
}
public void Initialize()
{
// backgrounds runners are web aware, if the app domain dies, these tasks will wind down correctly
_keepAliveRunner = new BackgroundTaskRunner<IBackgroundTask>("KeepAlive", logger);
_publishingRunner = new BackgroundTaskRunner<IBackgroundTask>("ScheduledPublishing", logger);
_tasksRunner = new BackgroundTaskRunner<IBackgroundTask>("ScheduledTasks", logger);
_scrubberRunner = new BackgroundTaskRunner<IBackgroundTask>("LogScrubber", logger);
_healthCheckRunner = new BackgroundTaskRunner<IBackgroundTask>("HealthCheckNotifier", logger);
_keepAliveRunner = new BackgroundTaskRunner<IBackgroundTask>("KeepAlive", _logger);
_publishingRunner = new BackgroundTaskRunner<IBackgroundTask>("ScheduledPublishing", _logger);
_tasksRunner = new BackgroundTaskRunner<IBackgroundTask>("ScheduledTasks", _logger);
_scrubberRunner = new BackgroundTaskRunner<IBackgroundTask>("LogScrubber", _logger);
_healthCheckRunner = new BackgroundTaskRunner<IBackgroundTask>("HealthCheckNotifier", _logger);
// we will start the whole process when a successful request is made
UmbracoModule.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)

View File

@@ -26,7 +26,7 @@ namespace Umbraco.Web.Search
{
private readonly IExamineManager _examineManager;
private readonly IContentValueSetBuilder _contentValueSetBuilder;
public IPublishedContentValueSetBuilder _publishedContentValueSetBuilder;
private readonly IPublishedContentValueSetBuilder _publishedContentValueSetBuilder;
private readonly IValueSetBuilder<IMedia> _mediaValueSetBuilder;
private readonly IValueSetBuilder<IMember> _memberValueSetBuilder;
private static bool _disableExamineIndexing = false;
@@ -36,6 +36,10 @@ namespace Umbraco.Web.Search
private readonly ServiceContext _services;
private static BackgroundTaskRunner<IBackgroundTask> _rebuildOnStartupRunner;
private static readonly object RebuildLocker = new object();
private readonly IMainDom _mainDom;
private readonly IProfilingLogger _logger;
private readonly IUmbracoIndexesCreator _indexCreator;
private readonly IndexRebuilder _indexRebuilder;
// the default enlist priority is 100
// enlist with a lower priority to ensure that anything "default" runs after us
@@ -59,6 +63,14 @@ namespace Umbraco.Web.Search
_mediaValueSetBuilder = mediaValueSetBuilder;
_memberValueSetBuilder = memberValueSetBuilder;
_mainDom = mainDom;
_logger = profilingLogger;
_indexCreator = indexCreator;
_indexRebuilder = indexRebuilder;
}
public void Initialize()
{
//we want to tell examine to use a different fs lock instead of the default NativeFSFileLock which could cause problems if the appdomain
//terminates and in some rare cases would only allow unlocking of the file if IIS is forcefully terminated. Instead we'll rely on the simplefslock
//which simply checks the existence of the lock file
@@ -69,9 +81,9 @@ namespace Umbraco.Web.Search
};
//let's deal with shutting down Examine with MainDom
var examineShutdownRegistered = mainDom.Register(() =>
var examineShutdownRegistered = _mainDom.Register(() =>
{
using (profilingLogger.TraceDuration<ExamineComponent>("Examine shutting down"))
using (_logger.TraceDuration<ExamineComponent>("Examine shutting down"))
{
_examineManager.Dispose();
}
@@ -79,23 +91,23 @@ namespace Umbraco.Web.Search
if (!examineShutdownRegistered)
{
profilingLogger.Debug<ExamineComponent>("Examine shutdown not registered, this appdomain is not the MainDom, Examine will be disabled");
_logger.Debug<ExamineComponent>("Examine shutdown not registered, this appdomain is not the MainDom, Examine will be disabled");
//if we could not register the shutdown examine ourselves, it means we are not maindom! in this case all of examine should be disabled!
Suspendable.ExamineEvents.SuspendIndexers(profilingLogger);
Suspendable.ExamineEvents.SuspendIndexers(_logger);
_disableExamineIndexing = true;
return; //exit, do not continue
}
//create the indexes and register them with the manager
foreach(var index in indexCreator.Create())
foreach(var index in _indexCreator.Create())
_examineManager.AddIndex(index);
profilingLogger.Debug<ExamineComponent>("Examine shutdown registered with MainDom");
_logger.Debug<ExamineComponent>("Examine shutdown registered with MainDom");
var registeredIndexers = examineManager.Indexes.OfType<IUmbracoIndex>().Count(x => x.EnableDefaultEventHandler);
var registeredIndexers = _examineManager.Indexes.OfType<IUmbracoIndex>().Count(x => x.EnableDefaultEventHandler);
profilingLogger.Info<ExamineComponent>("Adding examine event handlers for {RegisteredIndexers} index providers.", registeredIndexers);
_logger.Info<ExamineComponent>("Adding examine event handlers for {RegisteredIndexers} index providers.", registeredIndexers);
// don't bind event handlers if we're not suppose to listen
if (registeredIndexers == 0)
@@ -108,12 +120,15 @@ namespace Umbraco.Web.Search
MediaCacheRefresher.CacheUpdated += MediaCacheRefresherUpdated;
MemberCacheRefresher.CacheUpdated += MemberCacheRefresherUpdated;
EnsureUnlocked(profilingLogger, examineManager);
EnsureUnlocked(_logger, _examineManager);
//TODO: Instead of waiting 5000 ms, we could add an event handler on to fulfilling the first request, then start?
RebuildIndexes(indexRebuilder, profilingLogger, true, 5000);
RebuildIndexes(_indexRebuilder, _logger, true, 5000);
}
public void Terminate()
{ }
/// <summary>
/// Called to rebuild empty indexes on startup
/// </summary>
@@ -146,8 +161,6 @@ namespace Umbraco.Web.Search
}
}
/// <summary>
/// Must be called to each index is unlocked before any indexing occurs
/// </summary>

View File

@@ -1,5 +1,6 @@
using System;
using Microsoft.AspNet.SignalR;
using Umbraco.Core.Cache;
using Umbraco.Core.Components;
using Umbraco.Core.Sync;
using Umbraco.Web.Cache;
@@ -8,31 +9,39 @@ namespace Umbraco.Web.SignalR
{
public class PreviewHubComponent : IComponent
{
private readonly Lazy<IHubContext<IPreviewHub>> _hubContext;
// using a lazy arg here means that we won't create the hub until necessary
// and therefore we won't have too bad an impact on boot time
public PreviewHubComponent(Lazy<IHubContext<IPreviewHub>> hubContext)
{
// ContentService.Saved is too soon - the content cache is not ready yet
// try using the content cache refresher event, because when it triggers
// the cache has already been notified of the changes
//ContentService.Saved += (sender, args) =>
//{
// var entity = args.SavedEntities.FirstOrDefault();
// if (entity != null)
// _previewHub.Clients.All.refreshed(entity.Id);
//};
_hubContext = hubContext;
}
ContentCacheRefresher.CacheUpdated += (sender, args) =>
public void Initialize()
{
// ContentService.Saved is too soon - the content cache is not ready yet,
// so use the content cache refresher event, because when it triggers
// the cache has already been notified of the changes
ContentCacheRefresher.CacheUpdated += HandleCacheUpdated;
}
public void Terminate()
{
ContentCacheRefresher.CacheUpdated -= HandleCacheUpdated;
}
private void HandleCacheUpdated(ContentCacheRefresher sender, CacheRefresherEventArgs args)
{
if (args.MessageType != MessageType.RefreshByPayload) return;
var payloads = (ContentCacheRefresher.JsonPayload[])args.MessageObject;
var hubContextInstance = _hubContext.Value;
foreach (var payload in payloads)
{
if (args.MessageType != MessageType.RefreshByPayload) return;
var payloads = (ContentCacheRefresher.JsonPayload[])args.MessageObject;
var hubContextInstance = hubContext.Value;
foreach (var payload in payloads)
{
var id = payload.Id; // keep it simple for now, ignore ChangeTypes
hubContextInstance.Clients.All.refreshed(id);
}
};
var id = payload.Id; // keep it simple for now, ignore ChangeTypes
hubContextInstance.Clients.All.refreshed(id);
}
}
}
}