Fixed some med trust issues with UmbracoExamine. Created new framework for distributed cache providers with unit tests. Still uses the

old ICacheRefresher but now we can plugin 2 new providers - one for resolving a list of servers and the other to notify the servers of changes.
By default we have the configuration based providers which uses the umbracoSettings.
This commit is contained in:
Shannon Deminick
2013-02-12 03:46:27 +06:00
parent f741861014
commit 4e6bdd1fc7
24 changed files with 1120 additions and 467 deletions

View File

@@ -1,155 +1,155 @@
using Umbraco.Core.IO;
//using Umbraco.Core.IO;
namespace Umbraco.Web.Cache
{
//namespace Umbraco.Web.Cache
//{
/// <summary>
/// The client Soap service for making distrubuted cache calls between servers
/// </summary>
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Web.Services.WebServiceBindingAttribute(Name = "CacheRefresherSoap", Namespace = "http://umbraco.org/webservices/")]
internal class CacheRefresherClient : System.Web.Services.Protocols.SoapHttpClientProtocol
{
// /// <summary>
// /// The client Soap service for making distrubuted cache calls between servers
// /// </summary>
// [System.Diagnostics.DebuggerStepThroughAttribute()]
// [System.ComponentModel.DesignerCategoryAttribute("code")]
// [System.Web.Services.WebServiceBindingAttribute(Name = "CacheRefresherSoap", Namespace = "http://umbraco.org/webservices/")]
// internal class CacheRefresherClient : System.Web.Services.Protocols.SoapHttpClientProtocol
// {
/// <remarks/>
public CacheRefresherClient()
{
// only set the url if the httpcontext is present, else it's set by the cache dispatcher methods (when using distributed calls)
if (System.Web.HttpContext.Current != null)
this.Url = "http://" + System.Web.HttpContext.Current.Request.ServerVariables["SERVER_NAME"] + IOHelper.ResolveUrl(SystemDirectories.WebServices) + "/cacheRefresher.asmx";
// /// <remarks/>
// public CacheRefresherClient()
// {
// // only set the url if the httpcontext is present, else it's set by the cache dispatcher methods (when using distributed calls)
// if (System.Web.HttpContext.Current != null)
// this.Url = "http://" + System.Web.HttpContext.Current.Request.ServerVariables["SERVER_NAME"] + IOHelper.ResolveUrl(SystemDirectories.WebServices) + "/cacheRefresher.asmx";
}
// }
/// <remarks/>
[System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://umbraco.org/webservices/RefreshAll", RequestNamespace = "http://umbraco.org/webservices/", ResponseNamespace = "http://umbraco.org/webservices/", Use = System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle = System.Web.Services.Protocols.SoapParameterStyle.Wrapped)]
public void RefreshAll(System.Guid uniqueIdentifier, string Login, string Password)
{
this.Invoke("RefreshAll", new object[] {
uniqueIdentifier,
Login,
Password});
}
// /// <remarks/>
// [System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://umbraco.org/webservices/RefreshAll", RequestNamespace = "http://umbraco.org/webservices/", ResponseNamespace = "http://umbraco.org/webservices/", Use = System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle = System.Web.Services.Protocols.SoapParameterStyle.Wrapped)]
// public void RefreshAll(System.Guid uniqueIdentifier, string Login, string Password)
// {
// this.Invoke("RefreshAll", new object[] {
// uniqueIdentifier,
// Login,
// Password});
// }
/// <remarks/>
public System.IAsyncResult BeginRefreshAll(System.Guid uniqueIdentifier, string Login, string Password, System.AsyncCallback callback, object asyncState)
{
return this.BeginInvoke("RefreshAll", new object[] {
uniqueIdentifier,
Login,
Password}, callback, asyncState);
}
// /// <remarks/>
// public System.IAsyncResult BeginRefreshAll(System.Guid uniqueIdentifier, string Login, string Password, System.AsyncCallback callback, object asyncState)
// {
// return this.BeginInvoke("RefreshAll", new object[] {
// uniqueIdentifier,
// Login,
// Password}, callback, asyncState);
// }
/// <remarks/>
public void EndRefreshAll(System.IAsyncResult asyncResult)
{
this.EndInvoke(asyncResult);
}
// /// <remarks/>
// public void EndRefreshAll(System.IAsyncResult asyncResult)
// {
// this.EndInvoke(asyncResult);
// }
/// <remarks/>
[System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://umbraco.org/webservices/RefreshByGuid", RequestNamespace = "http://umbraco.org/webservices/", ResponseNamespace = "http://umbraco.org/webservices/", Use = System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle = System.Web.Services.Protocols.SoapParameterStyle.Wrapped)]
public void RefreshByGuid(System.Guid uniqueIdentifier, System.Guid Id, string Login, string Password)
{
this.Invoke("RefreshByGuid", new object[] {
uniqueIdentifier,
Id,
Login,
Password});
}
// /// <remarks/>
// [System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://umbraco.org/webservices/RefreshByGuid", RequestNamespace = "http://umbraco.org/webservices/", ResponseNamespace = "http://umbraco.org/webservices/", Use = System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle = System.Web.Services.Protocols.SoapParameterStyle.Wrapped)]
// public void RefreshByGuid(System.Guid uniqueIdentifier, System.Guid Id, string Login, string Password)
// {
// this.Invoke("RefreshByGuid", new object[] {
// uniqueIdentifier,
// Id,
// Login,
// Password});
// }
/// <remarks/>
public System.IAsyncResult BeginRefreshByGuid(System.Guid uniqueIdentifier, System.Guid Id, string Login, string Password, System.AsyncCallback callback, object asyncState)
{
return this.BeginInvoke("RefreshByGuid", new object[] {
uniqueIdentifier,
Id,
Login,
Password}, callback, asyncState);
}
// /// <remarks/>
// public System.IAsyncResult BeginRefreshByGuid(System.Guid uniqueIdentifier, System.Guid Id, string Login, string Password, System.AsyncCallback callback, object asyncState)
// {
// return this.BeginInvoke("RefreshByGuid", new object[] {
// uniqueIdentifier,
// Id,
// Login,
// Password}, callback, asyncState);
// }
/// <remarks/>
public void EndRefreshByGuid(System.IAsyncResult asyncResult)
{
this.EndInvoke(asyncResult);
}
// /// <remarks/>
// public void EndRefreshByGuid(System.IAsyncResult asyncResult)
// {
// this.EndInvoke(asyncResult);
// }
/// <remarks/>
[System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://umbraco.org/webservices/RefreshById", RequestNamespace = "http://umbraco.org/webservices/", ResponseNamespace = "http://umbraco.org/webservices/", Use = System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle = System.Web.Services.Protocols.SoapParameterStyle.Wrapped)]
public void RefreshById(System.Guid uniqueIdentifier, int Id, string Login, string Password)
{
this.Invoke("RefreshById", new object[] {
uniqueIdentifier,
Id,
Login,
Password});
}
// /// <remarks/>
// [System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://umbraco.org/webservices/RefreshById", RequestNamespace = "http://umbraco.org/webservices/", ResponseNamespace = "http://umbraco.org/webservices/", Use = System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle = System.Web.Services.Protocols.SoapParameterStyle.Wrapped)]
// public void RefreshById(System.Guid uniqueIdentifier, int Id, string Login, string Password)
// {
// this.Invoke("RefreshById", new object[] {
// uniqueIdentifier,
// Id,
// Login,
// Password});
// }
/// <remarks/>
public System.IAsyncResult BeginRefreshById(System.Guid uniqueIdentifier, int Id, string Login, string Password, System.AsyncCallback callback, object asyncState)
{
return this.BeginInvoke("RefreshById", new object[] {
uniqueIdentifier,
Id,
Login,
Password}, callback, asyncState);
}
// /// <remarks/>
// public System.IAsyncResult BeginRefreshById(System.Guid uniqueIdentifier, int Id, string Login, string Password, System.AsyncCallback callback, object asyncState)
// {
// return this.BeginInvoke("RefreshById", new object[] {
// uniqueIdentifier,
// Id,
// Login,
// Password}, callback, asyncState);
// }
/// <remarks/>
public void EndRefreshById(System.IAsyncResult asyncResult)
{
this.EndInvoke(asyncResult);
}
// /// <remarks/>
// public void EndRefreshById(System.IAsyncResult asyncResult)
// {
// this.EndInvoke(asyncResult);
// }
/// <remarks/>
[System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://umbraco.org/webservices/RemoveById", RequestNamespace = "http://umbraco.org/webservices/", ResponseNamespace = "http://umbraco.org/webservices/", Use = System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle = System.Web.Services.Protocols.SoapParameterStyle.Wrapped)]
public void RemoveById(System.Guid uniqueIdentifier, int Id, string Login, string Password)
{
this.Invoke("RemoveById", new object[] {
uniqueIdentifier,
Id,
Login,
Password});
}
// /// <remarks/>
// [System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://umbraco.org/webservices/RemoveById", RequestNamespace = "http://umbraco.org/webservices/", ResponseNamespace = "http://umbraco.org/webservices/", Use = System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle = System.Web.Services.Protocols.SoapParameterStyle.Wrapped)]
// public void RemoveById(System.Guid uniqueIdentifier, int Id, string Login, string Password)
// {
// this.Invoke("RemoveById", new object[] {
// uniqueIdentifier,
// Id,
// Login,
// Password});
// }
/// <remarks/>
public System.IAsyncResult BeginRemoveById(System.Guid uniqueIdentifier, int Id, string Login, string Password, System.AsyncCallback callback, object asyncState)
{
return this.BeginInvoke("RemoveById", new object[] {
uniqueIdentifier,
Id,
Login,
Password}, callback, asyncState);
}
// /// <remarks/>
// public System.IAsyncResult BeginRemoveById(System.Guid uniqueIdentifier, int Id, string Login, string Password, System.AsyncCallback callback, object asyncState)
// {
// return this.BeginInvoke("RemoveById", new object[] {
// uniqueIdentifier,
// Id,
// Login,
// Password}, callback, asyncState);
// }
/// <remarks/>
public void EndRemoveById(System.IAsyncResult asyncResult)
{
this.EndInvoke(asyncResult);
}
// /// <remarks/>
// public void EndRemoveById(System.IAsyncResult asyncResult)
// {
// this.EndInvoke(asyncResult);
// }
/// <remarks/>
[System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://umbraco.org/webservices/GetRefreshers", RequestNamespace = "http://umbraco.org/webservices/", ResponseNamespace = "http://umbraco.org/webservices/", Use = System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle = System.Web.Services.Protocols.SoapParameterStyle.Wrapped)]
public System.Xml.XmlNode GetRefreshers(string Login, string Password)
{
object[] results = this.Invoke("GetRefreshers", new object[] {
Login,
Password});
return ((System.Xml.XmlNode)(results[0]));
}
// /// <remarks/>
// [System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://umbraco.org/webservices/GetRefreshers", RequestNamespace = "http://umbraco.org/webservices/", ResponseNamespace = "http://umbraco.org/webservices/", Use = System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle = System.Web.Services.Protocols.SoapParameterStyle.Wrapped)]
// public System.Xml.XmlNode GetRefreshers(string Login, string Password)
// {
// object[] results = this.Invoke("GetRefreshers", new object[] {
// Login,
// Password});
// return ((System.Xml.XmlNode)(results[0]));
// }
/// <remarks/>
public System.IAsyncResult BeginGetRefreshers(string Login, string Password, System.AsyncCallback callback, object asyncState)
{
return this.BeginInvoke("GetRefreshers", new object[] {
Login,
Password}, callback, asyncState);
}
// /// <remarks/>
// public System.IAsyncResult BeginGetRefreshers(string Login, string Password, System.AsyncCallback callback, object asyncState)
// {
// return this.BeginInvoke("GetRefreshers", new object[] {
// Login,
// Password}, callback, asyncState);
// }
/// <remarks/>
public System.Xml.XmlNode EndGetRefreshers(System.IAsyncResult asyncResult)
{
object[] results = this.EndInvoke(asyncResult);
return ((System.Xml.XmlNode)(results[0]));
}
}
}
// /// <remarks/>
// public System.Xml.XmlNode EndGetRefreshers(System.IAsyncResult asyncResult)
// {
// object[] results = this.EndInvoke(asyncResult);
// return ((System.Xml.XmlNode)(results[0]));
// }
// }
//}

View File

@@ -16,17 +16,13 @@ using umbraco.interfaces;
namespace Umbraco.Web.Cache
{
/// <summary>
/// DistrubutedCacheDispatcher is used to handle Umbraco's load balancing.
/// DistributedCache is used to invalidate cache throughout the application which also takes in to account load balancing environments automatically
/// </summary>
/// <remarks>
/// Distributing calls to all registered load balanced servers, ensuring that content are synced and cached on all servers.
/// Dispatcher is exendable, so 3rd party services can easily be integrated into the workflow, using the interfaces.ICacheRefresher interface.
///
/// Dispatcher can refresh/remove content, templates and macros.
/// Load balanced servers are registered in umbracoSettings.config.
///
/// UPDATE 2010 02 - Alex Norcliffe - Refactored Dispatcher to support parallel dispatch threads, and preventing failure of whole dispatch
/// if one node fails. Still needs more work to get it to Enterprise level though but this is for 4.1
/// </remarks>
public class DistributedCache
{
@@ -41,19 +37,13 @@ namespace Umbraco.Web.Cache
#endregion
private readonly string _login;
private readonly string _password;
private readonly string _webServicesUrl;
private static readonly DistributedCache InstanceObject = new DistributedCache();
/// <summary>
/// Constructor
/// </summary>
private DistributedCache()
{
_login = User.GetUser(UmbracoSettings.DistributedCallUser).LoginName;
_password = User.GetUser(UmbracoSettings.DistributedCallUser).GetPassword();
_webServicesUrl = IOHelper.ResolveUrl(SystemDirectories.WebServices);
{
}
/// <summary>
@@ -76,7 +66,10 @@ namespace Umbraco.Web.Cache
/// <param name="id">The id of the node.</param>
public void Refresh(Guid factoryGuid, int id)
{
InvokeDispatchMethod(DispatchType.RefreshByNumericId, factoryGuid, id, Guid.Empty);
ServerMessengerResolver.Current.Messenger.PerformRefresh(
ServerRegistrarResolver.Current.Registrar.Registrations,
GetRefresherById(factoryGuid),
id);
}
/// <summary>
@@ -87,7 +80,10 @@ namespace Umbraco.Web.Cache
/// <param name="id">The guid of the node.</param>
public void Refresh(Guid factoryGuid, Guid id)
{
InvokeDispatchMethod(DispatchType.RefreshByGuid, factoryGuid, 0, id);
ServerMessengerResolver.Current.Messenger.PerformRefresh(
ServerRegistrarResolver.Current.Registrar.Registrations,
GetRefresherById(factoryGuid),
id);
}
/// <summary>
@@ -97,7 +93,9 @@ namespace Umbraco.Web.Cache
/// <param name="factoryGuid">The unique identifier.</param>
public void RefreshAll(Guid factoryGuid)
{
InvokeDispatchMethod(DispatchType.RefreshAll, factoryGuid, 0, Guid.Empty);
ServerMessengerResolver.Current.Messenger.PerformRefreshAll(
ServerRegistrarResolver.Current.Registrar.Registrations,
GetRefresherById(factoryGuid));
}
/// <summary>
@@ -108,192 +106,10 @@ namespace Umbraco.Web.Cache
/// <param name="id">The id.</param>
public void Remove(Guid factoryGuid, int id)
{
InvokeDispatchMethod(DispatchType.RemoveById, factoryGuid, id, Guid.Empty);
}
/// <summary>
/// Used to invoke the method on an ICacheRefresher instance if we are not currently using distributed calls.
/// </summary>
/// <param name="refresher"></param>
/// <param name="dispatchType"></param>
/// <param name="numericId"></param>
/// <param name="guidId"></param>
private void InvokeMethodOnRefresherInstance(ICacheRefresher refresher, DispatchType dispatchType, int numericId, Guid guidId)
{
//if we are not, then just invoke the call on the cache refresher
switch (dispatchType)
{
case DispatchType.RefreshAll:
refresher.RefreshAll();
break;
case DispatchType.RefreshByNumericId:
refresher.Refresh(numericId);
break;
case DispatchType.RefreshByGuid:
refresher.Refresh(guidId);
break;
case DispatchType.RemoveById:
refresher.Remove(numericId);
break;
}
}
/// <summary>
/// Invokes the relevant dispatch method.
/// </summary>
/// <param name="dispatchType">Type of the dispatch.</param>
/// <param name="factoryGuid">The factory GUID.</param>
/// <param name="numericId">The numeric id.</param>
/// <param name="guidId">The GUID id.</param>
private void InvokeDispatchMethod(DispatchType dispatchType, Guid factoryGuid, int numericId, Guid guidId)
{
//get the refresher, it must be found or else we cannot continue
var refresher = GetRefresherById(factoryGuid);
if (refresher == null)
{
var ex = new InvalidOperationException(
"Could not find an " + typeof(ICacheRefresher).Name + " with the Id " + guidId);
LogHelper.Error<DistributedCache>("Could not continue with DistributedCache call", ex);
return;
}
//Now, check if we are using Distrubuted calls
if (!UmbracoSettings.UseDistributedCalls)
{
//if we are not, then just invoke the call on the cache refresher
InvokeMethodOnRefresherInstance(refresher, dispatchType, numericId, guidId);
return;
}
//We are using distributed calls, so lets make them...
try
{
using (var cacheRefresher = new CacheRefresherClient())
{
var asyncResultsList = new List<IAsyncResult>();
LogStartDispatch();
//var nodes =
// Go through each configured node submitting a request asynchronously
foreach (XmlNode n in GetDistributedNodes())
{
SetWebServiceUrlFromNode(cacheRefresher, n);
// Add the returned WaitHandle to the list for later checking
switch (dispatchType)
{
case DispatchType.RefreshAll:
asyncResultsList.Add(cacheRefresher.BeginRefreshAll(factoryGuid, _login, _password, null,
null));
break;
case DispatchType.RefreshByGuid:
asyncResultsList.Add(cacheRefresher.BeginRefreshByGuid(factoryGuid, guidId, _login,
_password, null, null));
break;
case DispatchType.RefreshByNumericId:
asyncResultsList.Add(cacheRefresher.BeginRefreshById(factoryGuid, numericId, _login,
_password, null, null));
break;
case DispatchType.RemoveById:
asyncResultsList.Add(cacheRefresher.BeginRemoveById(factoryGuid, numericId, _login,
_password, null, null));
break;
}
}
List<WaitHandle> waitHandlesList;
IAsyncResult[] asyncResults = GetAsyncResults(asyncResultsList, out waitHandlesList);
int errorCount = 0;
// Once for each WaitHandle that we have, wait for a response and log it
// We're previously submitted all these requests effectively in parallel and will now retrieve responses on a FIFO basis
for (int waitCalls = 0; waitCalls < asyncResults.Length; waitCalls++)
{
int handleIndex = WaitHandle.WaitAny(waitHandlesList.ToArray(), TimeSpan.FromSeconds(15));
try
{
// Find out if the call succeeded
switch (dispatchType)
{
case DispatchType.RefreshAll:
cacheRefresher.EndRefreshAll(asyncResults[waitCalls]);
break;
case DispatchType.RefreshByGuid:
cacheRefresher.EndRefreshByGuid(asyncResults[waitCalls]);
break;
case DispatchType.RefreshByNumericId:
cacheRefresher.EndRefreshById(asyncResults[waitCalls]);
break;
case DispatchType.RemoveById:
cacheRefresher.EndRemoveById(asyncResults[waitCalls]);
break;
}
}
catch (WebException ex)
{
LogDispatchNodeError(ex);
errorCount++;
}
catch (Exception ex)
{
LogDispatchNodeError(ex);
errorCount++;
}
}
LogDispatchBatchResult(errorCount);
}
}
catch (Exception ee)
{
LogDispatchBatchError(ee);
}
}
private void LogDispatchBatchError(Exception ee)
{
LogHelper.Error<DistributedCache>("Error refreshing distributed list", ee);
}
private void LogDispatchBatchResult(int errorCount)
{
LogHelper.Debug<DistributedCache>(string.Format("Distributed server push completed with {0} nodes reporting an error", errorCount == 0 ? "no" : errorCount.ToString(CultureInfo.InvariantCulture)));
}
private void LogDispatchNodeError(Exception ex)
{
LogHelper.Error<DistributedCache>("Error refreshing a node in the distributed list", ex);
}
private void LogDispatchNodeError(WebException ex)
{
string url = (ex.Response != null) ? ex.Response.ResponseUri.ToString() : "invalid url (responseUri null)";
LogHelper.Error<DistributedCache>("Error refreshing a node in the distributed list, URI attempted: " + url, ex);
}
/// <summary>
/// Sets the web service URL for a CacheRefresher from an XmlNode.
/// </summary>
/// <param name="cr">The CacheRefresher.</param>
/// <param name="n">The XmlNode.</param>
private void SetWebServiceUrlFromNode(WebClientProtocol cr, XmlNode n)
{
//string protocol = GlobalSettings.UseSSL ? "https" : "http";
//if (n.Attributes.GetNamedItem("forceProtocol") != null && !String.IsNullOrEmpty(n.Attributes.GetNamedItem("forceProtocol").Value))
// protocol = n.Attributes.GetNamedItem("forceProtocol").Value;
//string domain = XmlHelper.GetNodeValue(n);
//if (n.Attributes.GetNamedItem("forcePortnumber") != null && !String.IsNullOrEmpty(n.Attributes.GetNamedItem("forcePortnumber").Value))
// domain += string.Format(":{0}", n.Attributes.GetNamedItem("forcePortnumber").Value);
//cr.Url = string.Format("{0}://{1}{2}/cacheRefresher.asmx", protocol, domain, _webServicesUrl);
//ServerRegistrarResolver.Current.Registrar.Registrations
ServerMessengerResolver.Current.Messenger.PerformRemove(
ServerRegistrarResolver.Current.Registrar.Registrations,
GetRefresherById(factoryGuid),
id);
}
private static ICacheRefresher GetRefresherById(Guid uniqueIdentifier)
@@ -301,42 +117,5 @@ namespace Umbraco.Web.Cache
return CacheRefreshersResolver.Current.GetById(uniqueIdentifier);
}
private void LogStartDispatch()
{
LogHelper.Info<DistributedCache>("Submitting calls to distributed servers");
}
/// <summary>
/// Gets the node list of DistributionServers from config.
/// </summary>
/// <returns></returns>
private XmlNodeList GetDistributedNodes()
{
return UmbracoSettings.DistributionServers.SelectNodes("./server");
}
private IAsyncResult[] GetAsyncResults(List<IAsyncResult> asyncResultsList,
out List<WaitHandle> waitHandlesList)
{
IAsyncResult[] asyncResults = asyncResultsList.ToArray();
waitHandlesList = new List<WaitHandle>();
foreach (IAsyncResult asyncResult in asyncResults)
{
waitHandlesList.Add(asyncResult.AsyncWaitHandle);
}
return asyncResults;
}
#region Nested type: DispatchType
private enum DispatchType
{
RefreshAll,
RefreshByNumericId,
RefreshByGuid,
RemoveById
}
#endregion
}
}