From 99ff86aefb334b486494d1504af9d8123a02e16d Mon Sep 17 00:00:00 2001 From: Stephan Date: Wed, 8 Apr 2015 14:21:58 +0200 Subject: [PATCH] DistributedCache - don't serialize payload on local node --- .../Cache/IPayloadCacheRefresher.cs | 16 +++++++ .../Cache/JsonCacheRefresherBase.cs | 15 ++++--- .../Cache/PayloadCacheRefresherBase.cs | 27 ++++++++++++ .../Sync/DatabaseServerMessenger.cs | 37 +++++++++++----- src/Umbraco.Core/Sync/IServerMessenger.cs | 25 ++++------- src/Umbraco.Core/Sync/MessageType.cs | 3 +- src/Umbraco.Core/Sync/ServerMessengerBase.cs | 42 +++++++++++++++++++ src/Umbraco.Core/Umbraco.Core.csproj | 2 + .../DistributedCache/DistributedCacheTests.cs | 4 ++ src/Umbraco.Web/Cache/DistributedCache.cs | 10 +++++ 10 files changed, 144 insertions(+), 37 deletions(-) create mode 100644 src/Umbraco.Core/Cache/IPayloadCacheRefresher.cs create mode 100644 src/Umbraco.Core/Cache/PayloadCacheRefresherBase.cs diff --git a/src/Umbraco.Core/Cache/IPayloadCacheRefresher.cs b/src/Umbraco.Core/Cache/IPayloadCacheRefresher.cs new file mode 100644 index 0000000000..416cb223d7 --- /dev/null +++ b/src/Umbraco.Core/Cache/IPayloadCacheRefresher.cs @@ -0,0 +1,16 @@ +using umbraco.interfaces; + +namespace Umbraco.Core.Cache +{ + /// + /// A cache refresher that supports refreshing cache based on a custom payload + /// + interface IPayloadCacheRefresher : IJsonCacheRefresher + { + /// + /// Refreshes, clears, etc... any cache based on the information provided in the payload + /// + /// + void Refresh(object payload); + } +} diff --git a/src/Umbraco.Core/Cache/JsonCacheRefresherBase.cs b/src/Umbraco.Core/Cache/JsonCacheRefresherBase.cs index 708e2e1605..48dd008a3d 100644 --- a/src/Umbraco.Core/Cache/JsonCacheRefresherBase.cs +++ b/src/Umbraco.Core/Cache/JsonCacheRefresherBase.cs @@ -4,17 +4,16 @@ using umbraco.interfaces; namespace Umbraco.Core.Cache { /// - /// A base class for json cache refreshers that ensures the correct events are raised when - /// cache refreshing occurs. + /// Provides a base class for "json" cache refreshers. /// - /// The real cache refresher type, this is used for raising strongly typed events - public abstract class JsonCacheRefresherBase : CacheRefresherBase, IJsonCacheRefresher - where TInstanceType : ICacheRefresher + /// The actual cache refresher type. + /// Ensures that the correct events are raised when cache refreshing occurs. + public abstract class JsonCacheRefresherBase : CacheRefresherBase, IJsonCacheRefresher + where TInstance : ICacheRefresher { - - public virtual void Refresh(string jsonPayload) + public virtual void Refresh(string json) { - OnCacheUpdated(Instance, new CacheRefresherEventArgs(jsonPayload, MessageType.RefreshByJson)); + OnCacheUpdated(Instance, new CacheRefresherEventArgs(json, MessageType.RefreshByJson)); } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Cache/PayloadCacheRefresherBase.cs b/src/Umbraco.Core/Cache/PayloadCacheRefresherBase.cs new file mode 100644 index 0000000000..b3ea2ff7b1 --- /dev/null +++ b/src/Umbraco.Core/Cache/PayloadCacheRefresherBase.cs @@ -0,0 +1,27 @@ +using Umbraco.Core.Sync; +using umbraco.interfaces; + +namespace Umbraco.Core.Cache +{ + /// + /// Provides a base class for "payload" cache refreshers. + /// + /// The actual cache refresher type. + /// Ensures that the correct events are raised when cache refreshing occurs. + public abstract class PayloadCacheRefresherBase : JsonCacheRefresherBase, IPayloadCacheRefresher + where TInstance : ICacheRefresher + { + protected abstract object Deserialize(string json); + + public override void Refresh(string json) + { + var payload = Deserialize(json); + Refresh(payload); + } + + public virtual void Refresh(object payload) + { + OnCacheUpdated(Instance, new CacheRefresherEventArgs(payload, MessageType.RefreshByPayload)); + } + } +} diff --git a/src/Umbraco.Core/Sync/DatabaseServerMessenger.cs b/src/Umbraco.Core/Sync/DatabaseServerMessenger.cs index 956903d96d..e6ce565280 100644 --- a/src/Umbraco.Core/Sync/DatabaseServerMessenger.cs +++ b/src/Umbraco.Core/Sync/DatabaseServerMessenger.cs @@ -187,25 +187,42 @@ namespace Umbraco.Core.Sync // the local server as they've already been processed. We should NOT assume that the sequence of // instructions in the database makes any sense whatsoever, because it's all async. var localIdentity = GetLocalIdentity(); - var remoteDtos = dtos.Where(x => x.OriginIdentity != localIdentity); var lastId = 0; - foreach (var dto in remoteDtos) + foreach (var dto in dtos) { + if (dto.OriginIdentity == localIdentity) + { + // just skip that local one but update lastId nevertheless + lastId = dto.Id; + continue; + } + + // deserialize remote instructions & skip if it fails + JArray jsonA; try { - var jsonArray = JsonConvert.DeserializeObject(dto.Instructions); - NotifyRefreshers(jsonArray); - lastId = dto.Id; + jsonA = JsonConvert.DeserializeObject(dto.Instructions); } catch (JsonException ex) { - // FIXME - // if we cannot deserialize then it's OK to skip the instructions - // but what if NotifyRefreshers throws?! - - LogHelper.Error("Could not deserialize a distributed cache instruction (\"" + dto.Instructions + "\").", ex); + LogHelper.Error(string.Format("Failed to deserialize instructions ({0}: \"{1}\").", dto.Id, dto.Instructions), ex); + lastId = dto.Id; // skip + continue; } + + // execute remote instructions & update lastId + try + { + NotifyRefreshers(jsonA); + lastId = dto.Id; + } + catch (Exception ex) + { + LogHelper.Error(string.Format("Failed to execute instructions ({0}: \"{1}\").", dto.Id, dto.Instructions), ex); + LogHelper.Warn("BEWARE - DISTRIBUTED CACHE IS NOT UPDATED."); + throw; + } } if (lastId > 0) diff --git a/src/Umbraco.Core/Sync/IServerMessenger.cs b/src/Umbraco.Core/Sync/IServerMessenger.cs index 100638c202..1208ea3caf 100644 --- a/src/Umbraco.Core/Sync/IServerMessenger.cs +++ b/src/Umbraco.Core/Sync/IServerMessenger.cs @@ -10,15 +10,13 @@ namespace Umbraco.Core.Sync /// Also ensures that the notification is processed on the local environment. public interface IServerMessenger { - // TODO - // everything we do "by JSON" means that data is serialized then deserialized on the local server - // we should stop using this, and instead use Notify() with an actual object that can be passed - // around locally, and serialized for remote messaging - but that would break backward compat ;-( - // - // and then ServerMessengerBase must be able to handle Notify(), and all messengers too - // and then ICacheRefresher (or INotifiableCacheRefresher?) must be able to handle it too - // - // >> v8 + /// + /// Notifies the distributed cache, for a specified . + /// + /// The servers that compose the load balanced environment. + /// The ICacheRefresher. + /// The notification content. + void PerformRefresh(IEnumerable servers, ICacheRefresher refresher, object payload); /// /// Notifies the distributed cache, for a specified . @@ -28,15 +26,6 @@ namespace Umbraco.Core.Sync /// The notification content. void PerformRefresh(IEnumerable servers, ICacheRefresher refresher, string jsonPayload); - ///// - ///// Notifies the distributed cache, for a specified . - ///// - ///// The servers that compose the load balanced environment. - ///// The ICacheRefresher. - ///// The notification content. - ///// A custom Json serializer. - //void Notify(IEnumerable servers, ICacheRefresher refresher, object payload, Func serializer = null); - /// /// Notifies the distributed cache of specifieds item invalidation, for a specified . /// diff --git a/src/Umbraco.Core/Sync/MessageType.cs b/src/Umbraco.Core/Sync/MessageType.cs index 80e9bcae76..9cdf94bafa 100644 --- a/src/Umbraco.Core/Sync/MessageType.cs +++ b/src/Umbraco.Core/Sync/MessageType.cs @@ -10,6 +10,7 @@ RefreshByJson, RemoveById, RefreshByInstance, - RemoveByInstance + RemoveByInstance, + RefreshByPayload } } \ No newline at end of file diff --git a/src/Umbraco.Core/Sync/ServerMessengerBase.cs b/src/Umbraco.Core/Sync/ServerMessengerBase.cs index 5da0837c2d..573be67169 100644 --- a/src/Umbraco.Core/Sync/ServerMessengerBase.cs +++ b/src/Umbraco.Core/Sync/ServerMessengerBase.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using Newtonsoft.Json; using Umbraco.Core.Cache; using Umbraco.Core.Logging; using umbraco.interfaces; @@ -55,6 +56,15 @@ namespace Umbraco.Core.Sync #region IServerMessenger + public void PerformRefresh(IEnumerable servers, ICacheRefresher refresher, object payload) + { + if (servers == null) throw new ArgumentNullException("servers"); + if (refresher == null) throw new ArgumentNullException("refresher"); + if (payload == null) throw new ArgumentNullException("payload"); + + Deliver(servers, refresher, payload); + } + public void PerformRefresh(IEnumerable servers, ICacheRefresher refresher, string jsonPayload) { if (servers == null) throw new ArgumentNullException("servers"); @@ -144,6 +154,19 @@ namespace Umbraco.Core.Sync #region Deliver + protected void DeliverLocal(ICacheRefresher refresher, object payload) + { + if (refresher == null) throw new ArgumentNullException("refresher"); + + LogHelper.Debug("Invoking refresher {0} on local server for message type RefreshByPayload", + refresher.GetType); + + var payloadRefresher = refresher as IPayloadCacheRefresher; + if (payloadRefresher == null) + throw new InvalidOperationException("The cache refresher " + refresher.GetType() + " is not of type " + typeof(IPayloadCacheRefresher)); + payloadRefresher.Refresh(payload); + } + /// /// Executes the non strongly typed on the local/current server /// @@ -269,6 +292,25 @@ namespace Umbraco.Core.Sync //protected abstract void DeliverRemote(IEnumerable servers, ICacheRefresher refresher, object payload); + protected virtual void Deliver(IEnumerable servers, ICacheRefresher refresher, object payload) + { + if (servers == null) throw new ArgumentNullException("servers"); + if (refresher == null) throw new ArgumentNullException("refresher"); + + var serversA = servers.ToArray(); + + // deliver local + DeliverLocal(refresher, payload); + + // distribute? + if (RequiresDistributed(serversA, refresher, MessageType.RefreshByJson) == false) + return; + + // deliver remote + var json = JsonConvert.SerializeObject(payload); + DeliverRemote(serversA, refresher, MessageType.RefreshByJson, null, json); + } + protected virtual void Deliver(IEnumerable servers, ICacheRefresher refresher, MessageType messageType, IEnumerable ids = null, string json = null) { if (servers == null) throw new ArgumentNullException("servers"); diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 4b1ec43f96..7729357568 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -154,12 +154,14 @@ + + diff --git a/src/Umbraco.Tests/Cache/DistributedCache/DistributedCacheTests.cs b/src/Umbraco.Tests/Cache/DistributedCache/DistributedCacheTests.cs index 06d3e6b27d..8873a72de4 100644 --- a/src/Umbraco.Tests/Cache/DistributedCache/DistributedCacheTests.cs +++ b/src/Umbraco.Tests/Cache/DistributedCache/DistributedCacheTests.cs @@ -138,6 +138,10 @@ namespace Umbraco.Tests.Cache.DistributedCache public List PayloadsRefreshed = new List(); public int CountOfFullRefreshes = 0; + public void PerformRefresh(IEnumerable servers, ICacheRefresher refresher, object payload) + { + // doing nothing + } public void PerformRefresh(IEnumerable servers, ICacheRefresher refresher, string jsonPayload) { diff --git a/src/Umbraco.Web/Cache/DistributedCache.cs b/src/Umbraco.Web/Cache/DistributedCache.cs index eb46f91ad1..9788618bb5 100644 --- a/src/Umbraco.Web/Cache/DistributedCache.cs +++ b/src/Umbraco.Web/Cache/DistributedCache.cs @@ -145,6 +145,16 @@ namespace Umbraco.Web.Cache id); } + public void RefreshByPayload(Guid factoryGuid, object payload) + { + if (factoryGuid == Guid.Empty || payload == null) return; + + ServerMessengerResolver.Current.Messenger.PerformRefresh( + ServerRegistrarResolver.Current.Registrar.Registrations, + GetRefresherById(factoryGuid), + payload); + } + /// /// Notifies the distributed cache, for a specified . ///