Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Changes to GetReducedEventList #11444

Merged
merged 3 commits into from
Oct 22, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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));
}
}
}
}