Refactored message related methods to allow provision of an HttpContext, and used this in DistributedCacheBinder to ensure messages created are flushed from the same context.

This commit is contained in:
Andy Butland
2021-12-14 09:23:55 +01:00
parent 1c36af8c7e
commit d9a4c50a73
2 changed files with 53 additions and 9 deletions

View File

@@ -68,9 +68,11 @@ namespace Umbraco.Web
BatchMessage(refresher, messageType, idsA, arrayType, json);
}
public void FlushBatch()
public void FlushBatch() => FlushBatch(null);
internal void FlushBatch(HttpContextBase httpContext)
{
var batch = GetBatch(false);
var batch = httpContext != null ? GetBatch(false, httpContext) : GetBatch(false);
if (batch == null) return;
var instructions = batch.SelectMany(x => x.Instructions).ToArray();
@@ -83,9 +85,9 @@ namespace Umbraco.Web
{
WriteInstructions(scope, instructionsBatch);
}
scope.Complete();
}
}
private void WriteInstructions(IScope scope, IEnumerable<RefreshInstruction> instructions)
@@ -111,10 +113,15 @@ namespace Umbraco.Web
// the case if the asp.net synchronization context has kicked in
?? (HttpContext.Current == null ? null : new HttpContextWrapper(HttpContext.Current));
// if no context was found, return null - we cannot not batch
// if no context was found, return null - we cannot batch
if (httpContext == null) return null;
var key = typeof (BatchedDatabaseServerMessenger).Name;
return GetBatch(create, httpContext);
}
protected ICollection<RefreshInstructionEnvelope> GetBatch(bool create, HttpContextBase httpContext)
{
var key = typeof(BatchedDatabaseServerMessenger).Name;
// no thread-safety here because it'll run in only 1 thread (request) at a time
var batch = (ICollection<RefreshInstructionEnvelope>)httpContext.Items[key];
@@ -128,9 +135,17 @@ namespace Umbraco.Web
MessageType messageType,
IEnumerable<object> ids = null,
Type idType = null,
string json = null) => BatchMessage(refresher, messageType, null, ids, idType, json);
protected void BatchMessage(
ICacheRefresher refresher,
MessageType messageType,
HttpContextBase httpContext,
IEnumerable<object> ids = null,
Type idType = null,
string json = null)
{
var batch = GetBatch(true);
var batch = httpContext != null ? GetBatch(true, httpContext) : GetBatch(true);
var instructions = RefreshInstruction.GetInstructions(refresher, messageType, ids, idType, json);
// batch if we can, else write to DB immediately

View File

@@ -9,6 +9,7 @@ using Umbraco.Core.Logging;
using Umbraco.Core.Models;
using Umbraco.Core.Services;
using Umbraco.Core.Services.Changes;
using Umbraco.Core.Sync;
namespace Umbraco.Web.Cache
{
@@ -21,10 +22,12 @@ namespace Umbraco.Web.Cache
private readonly DistributedCache _distributedCache;
private readonly IUmbracoContextFactory _umbracoContextFactory;
private readonly ILogger _logger;
private readonly BatchedDatabaseServerMessenger _serverMessenger;
/// <summary>
/// Initializes a new instance of the <see cref="DistributedCacheBinder"/> class.
/// </summary>
[Obsolete("Please use the constructor accepting an instance of IServerMessenger. This constructor will be removed in a future version.")]
public DistributedCacheBinder(DistributedCache distributedCache, IUmbracoContextFactory umbracoContextFactory, ILogger logger)
{
_distributedCache = distributedCache;
@@ -32,6 +35,15 @@ namespace Umbraco.Web.Cache
_umbracoContextFactory = umbracoContextFactory;
}
/// <summary>
/// Initializes a new instance of the <see cref="DistributedCacheBinder"/> class.
/// </summary>
public DistributedCacheBinder(DistributedCache distributedCache, IUmbracoContextFactory umbracoContextFactory, ILogger logger, IServerMessenger serverMessenger)
: this(distributedCache, umbracoContextFactory, logger)
{
_serverMessenger = serverMessenger as BatchedDatabaseServerMessenger;
}
// internal for tests
internal static MethodInfo FindHandler(IEventDefinition eventDefinition)
{
@@ -42,7 +54,6 @@ namespace Umbraco.Web.Cache
private static readonly Lazy<MethodInfo[]> CandidateHandlers = new Lazy<MethodInfo[]>(() =>
{
return typeof(DistributedCacheBinder)
.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
.Select(x =>
@@ -66,9 +77,9 @@ namespace Umbraco.Web.Cache
{
// Ensure we run with an UmbracoContext, because this may run in a background task,
// yet developers may be using the 'current' UmbracoContext in the event handlers.
using (_umbracoContextFactory.EnsureUmbracoContext())
using (var umbracoContextReference = _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.
// When it comes to content types, a change to any single one will trigger a reload of the content and media caches.
// 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)
@@ -84,6 +95,24 @@ namespace Umbraco.Web.Cache
handler.Invoke(this, new[] { e.Sender, e.Args });
}
// Handled events may be triggering messages to be sent for load balanced servers to refresh their caches.
// When the state changes that initiate the events are handled outside of an Umbraco request and rather in a
// background task, we'll have ensured an Umbraco context, but using a newly created HttpContext.
//
// An example of this is when using an Umbraco Deploy content transfer operation
// (see: https://github.com/umbraco/Umbraco.Deploy.Issues/issues/90).
//
// This will be used in the event handlers, and when the methods on BatchedDatabaseServerMessenger are called,
// they'll be using this "ensured" HttpContext, populating a batch of message stored in HttpContext.Items.
// When the FlushBatch method is called on the end of an Umbraco request (via the event handler wired up in
// DatabaseServerRegistrarAndMessengerComponent), this will use the HttpContext associated with the request,
// which will be a different one, and so won't have the batch stored in it's HttpContext.Items.
//
// As such by making an explicit call here, and providing the ensured HttpContext that will have had it's
// Items dictionary populated with the batch of messages, we'll make sure the batch is flushed, and the
// database instructions written.
_serverMessenger?.FlushBatch(umbracoContextReference.UmbracoContext.HttpContext);
}
}