Skip to content

Commit

Permalink
Changes to GetReducedEventList (#11444)
Browse files Browse the repository at this point in the history
* Instead of only using first event, we combine events of same type into a single event with multiple arguments

* Added generic method to DRY up grouping logic.

* Renamed method to better reflect new functionality.

Co-authored-by: Andy Butland <[email protected]>
  • Loading branch information
bergmania and AndyButland authored Oct 22, 2021
1 parent 213d8c0 commit 8acf7e1
Show file tree
Hide file tree
Showing 2 changed files with 129 additions and 39 deletions.
76 changes: 67 additions & 9 deletions src/Umbraco.Tests/Cache/DistributedCacheBinderTests.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using Moq;
Expand All @@ -10,6 +11,7 @@
using Umbraco.Core.Models.Membership;
using Umbraco.Core.Services;
using Umbraco.Core.Services.Changes;
using Umbraco.Tests.TestHelpers.Entities;
using Umbraco.Tests.Testing;
using Umbraco.Tests.Testing.Objects.Accessors;
using Umbraco.Web;
Expand Down Expand Up @@ -168,17 +170,73 @@ public void CanHandleEvent()
}

[Test]
public void OnlyHandlesOnContentTypeEvent()
public void GroupsContentTypeEvents()
{
var definitions = new IEventDefinition[]
var num = 30;
var contentTypes = Enumerable.Repeat(MockedContentTypes.CreateBasicContentType(), num);
var mediaTypes = Enumerable.Repeat(MockedContentTypes.CreateImageMediaType(), num);
var memberTypes = Enumerable.Repeat(MockedContentTypes.CreateSimpleMemberType(), num);
var definitionsContent = contentTypes.SelectMany(x => new IEventDefinition[]
{
new EventDefinition<IContentTypeService, ContentTypeChange<IContentType>.EventArgs>(null, Current.Services.ContentTypeService, new ContentTypeChange<IContentType>.EventArgs(Enumerable.Empty<ContentTypeChange<IContentType>>()), "Changed"),
new EventDefinition<IContentTypeService, SaveEventArgs<IContentType>>(null, Current.Services.ContentTypeService, new SaveEventArgs<IContentType>(Enumerable.Empty<IContentType>()), "Saved"),
new EventDefinition<IContentTypeService, ContentTypeChange<IContentType>.EventArgs>(null, Current.Services.ContentTypeService, new ContentTypeChange<IContentType>.EventArgs(Enumerable.Empty<ContentTypeChange<IContentType>>()), "Changed"),
new EventDefinition<IContentTypeService, SaveEventArgs<IContentType>>(null, Current.Services.ContentTypeService, new SaveEventArgs<IContentType>(Enumerable.Empty<IContentType>()), "Saved"),
};
var result = DistributedCacheBinder.GetReducedEventList(definitions);
Assert.AreEqual(1, result.Count());
new EventDefinition<IContentTypeService, ContentTypeChange<IContentType>.EventArgs>(null, Current.Services.ContentTypeService, new ContentTypeChange<IContentType>.EventArgs(new ContentTypeChange<IContentType>(x, ContentTypeChangeTypes.Create)), "Changed"),
new EventDefinition<IContentTypeService, SaveEventArgs<IContentType>>(null, Current.Services.ContentTypeService, new SaveEventArgs<IContentType>(x), "Saved"),
});

var definitionsMedia = mediaTypes.SelectMany(x => new IEventDefinition[]
{
new EventDefinition<IMediaTypeService, ContentTypeChange<IMediaType>.EventArgs>(null, Current.Services.MediaTypeService, new ContentTypeChange<IMediaType>.EventArgs(new ContentTypeChange<IMediaType>(x, ContentTypeChangeTypes.Create)), "Changed"),
new EventDefinition<IMediaTypeService, SaveEventArgs<IMediaType>>(null, Current.Services.MediaTypeService, new SaveEventArgs<IMediaType>(x), "Saved"),
});
var definitionsMember = memberTypes.SelectMany(x => new IEventDefinition[]
{
new EventDefinition<IMemberTypeService, ContentTypeChange<IMemberType>.EventArgs>(null, Current.Services.MemberTypeService, new ContentTypeChange<IMemberType>.EventArgs(new ContentTypeChange<IMemberType>(x, ContentTypeChangeTypes.Create)), "Changed"),
new EventDefinition<IMemberTypeService, SaveEventArgs<IMemberType>>(null, Current.Services.MemberTypeService, new SaveEventArgs<IMemberType>(x), "Saved"),
});

var definitions = new List<IEventDefinition>();
definitions.AddRange(definitionsContent);
definitions.AddRange(definitionsMedia);
definitions.AddRange(definitionsMember);

var result = DistributedCacheBinder.GetGroupedEventList(definitions);

Assert.Multiple(() =>
{
Assert.AreEqual(num * 6, definitions.Count(), "Precondition is we have many definitions");
Assert.AreEqual(6, result.Count(), "Unexpected number of reduced definitions");
foreach (var eventDefinition in result)
{
if (eventDefinition.Args is SaveEventArgs<IContentType> saveContentEventArgs)
{
Assert.AreEqual(num, saveContentEventArgs.SavedEntities.Count());
}

if (eventDefinition.Args is ContentTypeChange<IContentType>.EventArgs changeContentEventArgs)
{
Assert.AreEqual(num, changeContentEventArgs.Changes.Count());
}

if (eventDefinition.Args is SaveEventArgs<IMediaType> saveMediaEventArgs)
{
Assert.AreEqual(num, saveMediaEventArgs.SavedEntities.Count());
}

if (eventDefinition.Args is ContentTypeChange<IMediaType>.EventArgs changeMediaEventArgs)
{
Assert.AreEqual(num, changeMediaEventArgs.Changes.Count());
}

if (eventDefinition.Args is SaveEventArgs<IMemberType> saveMemberEventArgs)
{
Assert.AreEqual(num, saveMemberEventArgs.SavedEntities.Count());
}

if (eventDefinition.Args is ContentTypeChange<IMemberType>.EventArgs changeMemberEventArgs)
{
Assert.AreEqual(num, changeMemberEventArgs.Changes.Count());
}
}
});
}
}
}
92 changes: 62 additions & 30 deletions src/Umbraco.Web/Cache/DistributedCacheBinder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
using Umbraco.Core;
using Umbraco.Core.Events;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
using Umbraco.Core.Services;
using Umbraco.Core.Services.Changes;

namespace Umbraco.Web.Cache
{
Expand Down Expand Up @@ -66,10 +69,9 @@ public void HandleEvents(IEnumerable<IEventDefinition> events)
using (_umbracoContextFactory.EnsureUmbracoContext())
{
// When it comes to content types types, a change to any single one will trigger a reload of the content and media caches.
// As far as I (AB) can tell, there's no type specific logic here, they all clear caches for all content types, and trigger a reload of all content and media.
// We also have events registered for Changed and Saved, which do the same thing, so really only need one of these.
// Hence if we have more than one document or media types, we can and should only handle one of the events for one, to avoid repeated cache reloads.
foreach (var e in GetReducedEventList(events))
// We can reduce the impact of that by grouping the events to invoke just one per type, providing a collection of the individual arguments.
var groupedEvents = GetGroupedEventList(events);
foreach (var e in groupedEvents)
{
var handler = FindHandler(e);
if (handler == null)
Expand All @@ -86,47 +88,77 @@ public void HandleEvents(IEnumerable<IEventDefinition> events)
}

// Internal for tests
internal static IEnumerable<IEventDefinition> GetReducedEventList(IEnumerable<IEventDefinition> events)
internal static IEnumerable<IEventDefinition> GetGroupedEventList(IEnumerable<IEventDefinition> events)
{
var reducedEvents = new List<IEventDefinition>();
var groupedEvents = new List<IEventDefinition>();

var gotDoumentType = false;
var gotMediaType = false;
var gotMemberType = false;
var grouped = events.GroupBy(x => x.GetType());

foreach (var evt in events)
foreach (var group in grouped)
{
if (evt.Sender.ToString().Contains(nameof(Core.Services.Implement.ContentTypeService)))
if (group.Key == typeof(EventDefinition<IContentTypeService, SaveEventArgs<IContentType>>))
{
if (gotDoumentType == false)
{
reducedEvents.Add(evt);
gotDoumentType = true;
}
GroupSaveEvents<IContentTypeService, IContentType>(groupedEvents, group);
}
else if (evt.Sender.ToString().Contains(nameof(Core.Services.Implement.MediaTypeService)))
else if (group.Key == typeof(EventDefinition<IContentTypeService, ContentTypeChange<IContentType>.EventArgs>))
{
if (gotMediaType == false)
{
reducedEvents.Add(evt);
gotMediaType = true;
}
GroupChangeEvents<IContentTypeService, IContentType>(groupedEvents, group);
}
else if (evt.Sender.ToString().Contains(nameof(Core.Services.Implement.MemberTypeService)))
else if (group.Key == typeof(EventDefinition<IMediaTypeService, SaveEventArgs<IMediaType>>))
{
if (gotMemberType == false)
{
reducedEvents.Add(evt);
gotMemberType = true;
}
GroupSaveEvents<IMediaTypeService, IMediaType>(groupedEvents, group);
}
else if (group.Key == typeof(EventDefinition<IMediaTypeService, ContentTypeChange<IMediaType>.EventArgs>))
{
GroupChangeEvents<IMediaTypeService, IMediaType>(groupedEvents, group);
}
else if (group.Key == typeof(EventDefinition<IMemberTypeService, SaveEventArgs<IMemberType>>))
{
GroupSaveEvents<IMemberTypeService, IMemberType>(groupedEvents, group);
}
else if (group.Key == typeof(EventDefinition<IMemberTypeService, ContentTypeChange<IMemberType>.EventArgs>))
{
GroupChangeEvents<IMemberTypeService, IMemberType>(groupedEvents, group);
}
else
{
reducedEvents.Add(evt);
groupedEvents.AddRange(group);
}
}

return reducedEvents;
return groupedEvents;
}

private static void GroupSaveEvents<TService, TType>(List<IEventDefinition> groupedEvents, IGrouping<Type, IEventDefinition> group)
where TService : IContentTypeBaseService
where TType : IContentTypeBase
{
var groupedGroups = group.GroupBy(x => (x.EventName, x.Sender));

foreach (var groupedGroup in groupedGroups)
{
groupedEvents.Add(new EventDefinition<TService, SaveEventArgs<TType>>(
null,
(TService)groupedGroup.Key.Sender,
new SaveEventArgs<TType>(groupedGroup.SelectMany(x => ((SaveEventArgs<TType>)x.Args).SavedEntities)),
groupedGroup.Key.EventName));
}
}

private static void GroupChangeEvents<TService, TType>(List<IEventDefinition> groupedEvents, IGrouping<Type, IEventDefinition> group)
where TService : IContentTypeBaseService
where TType : class, IContentTypeComposition
{
var groupedGroups = group.GroupBy(x => (x.EventName, x.Sender));

foreach (var groupedGroup in groupedGroups)
{
groupedEvents.Add(new EventDefinition<TService, ContentTypeChange<TType>.EventArgs>(
null,
(TService)groupedGroup.Key.Sender,
new ContentTypeChange<TType>.EventArgs(groupedGroup.SelectMany(x => ((ContentTypeChange<TType>.EventArgs)x.Args).Changes)),
groupedGroup.Key.EventName));
}
}
}
}

0 comments on commit 8acf7e1

Please sign in to comment.