Merge branch 'dev-v7' into temp-U4-9107
This commit is contained in:
@@ -25,7 +25,7 @@ namespace Umbraco.Core
|
||||
private readonly AsyncLock _asyncLock;
|
||||
private IDisposable _asyncLocker;
|
||||
|
||||
// event wait handle used to notify current main domain that it should
|
||||
// event wait handle used to notify current main domain that it should
|
||||
// release the lock because a new domain wants to be the main domain
|
||||
private readonly EventWaitHandle _signal;
|
||||
|
||||
@@ -97,7 +97,7 @@ namespace Umbraco.Core
|
||||
|
||||
try
|
||||
{
|
||||
_logger.Debug<MainDom>("Stopping...");
|
||||
_logger.Info<MainDom>("Stopping...");
|
||||
foreach (var callback in _callbacks.Values)
|
||||
{
|
||||
try
|
||||
@@ -109,7 +109,7 @@ namespace Umbraco.Core
|
||||
_logger.Error<MainDom>("Error while running callback, remaining callbacks will not run.", e);
|
||||
throw;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
_logger.Debug<MainDom>("Stopped.");
|
||||
}
|
||||
@@ -118,7 +118,7 @@ namespace Umbraco.Core
|
||||
// in any case...
|
||||
_isMainDom = false;
|
||||
_asyncLocker.Dispose();
|
||||
_logger.Debug<MainDom>("Released MainDom.");
|
||||
_logger.Info<MainDom>("Released MainDom.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -131,11 +131,11 @@ namespace Umbraco.Core
|
||||
// the handler is not installed so that would be the hosting environment
|
||||
if (_signaled)
|
||||
{
|
||||
_logger.Debug<MainDom>("Cannot acquire MainDom (signaled).");
|
||||
_logger.Info<MainDom>("Cannot acquire MainDom (signaled).");
|
||||
return false;
|
||||
}
|
||||
|
||||
_logger.Debug<MainDom>("Acquiring MainDom...");
|
||||
_logger.Info<MainDom>("Acquiring MainDom...");
|
||||
|
||||
// signal other instances that we want the lock, then wait one the lock,
|
||||
// which may timeout, and this is accepted - see comments below
|
||||
@@ -162,7 +162,7 @@ namespace Umbraco.Core
|
||||
|
||||
HostingEnvironment.RegisterObject(this);
|
||||
|
||||
_logger.Debug<MainDom>("Acquired MainDom.");
|
||||
_logger.Info<MainDom>("Acquired MainDom.");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -183,30 +183,25 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
if (contentTypeIds == null)
|
||||
{
|
||||
var subQuery = new Sql()
|
||||
.Select("DISTINCT cmsContentXml.nodeId")
|
||||
.From<ContentXmlDto>()
|
||||
.InnerJoin<DocumentDto>()
|
||||
.On<ContentXmlDto, DocumentDto>(left => left.NodeId, right => right.NodeId);
|
||||
.Select("id")
|
||||
.From<NodeDto>(SqlSyntax)
|
||||
.Where<NodeDto>(x => x.NodeObjectType == NodeObjectTypeId);
|
||||
|
||||
var deleteSql = SqlSyntax.GetDeleteSubquery("cmsContentXml", "nodeId", subQuery);
|
||||
Database.Execute(deleteSql);
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var id in contentTypeIds)
|
||||
{
|
||||
var id1 = id;
|
||||
var subQuery = new Sql()
|
||||
.Select("cmsDocument.nodeId")
|
||||
.From<DocumentDto>()
|
||||
.InnerJoin<ContentDto>()
|
||||
.On<DocumentDto, ContentDto>(left => left.NodeId, right => right.NodeId)
|
||||
.Where<DocumentDto>(dto => dto.Published)
|
||||
.Where<ContentDto>(dto => dto.ContentTypeId == id1);
|
||||
var subQuery = new Sql()
|
||||
.Select("umbracoNode.id as nodeId")
|
||||
.From<ContentDto>(SqlSyntax)
|
||||
.InnerJoin<NodeDto>(SqlSyntax)
|
||||
.On<ContentDto, NodeDto>(SqlSyntax, left => left.NodeId, right => right.NodeId)
|
||||
.WhereIn<ContentDto>(dto => dto.ContentTypeId, contentTypeIds, SqlSyntax)
|
||||
.Where<NodeDto>(x => x.NodeObjectType == NodeObjectTypeId);
|
||||
|
||||
var deleteSql = SqlSyntax.GetDeleteSubquery("cmsContentXml", "nodeId", subQuery);
|
||||
Database.Execute(deleteSql);
|
||||
}
|
||||
var deleteSql = SqlSyntax.GetDeleteSubquery("cmsContentXml", "nodeId", subQuery);
|
||||
Database.Execute(deleteSql);
|
||||
}
|
||||
|
||||
//now insert the data, again if something fails here, the whole transaction is reversed
|
||||
|
||||
@@ -172,36 +172,26 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
//Remove all the data first, if anything fails after this it's no problem the transaction will be reverted
|
||||
if (contentTypeIds == null)
|
||||
{
|
||||
var mediaObjectType = Guid.Parse(Constants.ObjectTypes.Media);
|
||||
var subQuery = new Sql()
|
||||
.Select("DISTINCT cmsContentXml.nodeId")
|
||||
.From<ContentXmlDto>()
|
||||
.InnerJoin<NodeDto>()
|
||||
.On<ContentXmlDto, NodeDto>(left => left.NodeId, right => right.NodeId)
|
||||
.Where<NodeDto>(dto => dto.NodeObjectType == mediaObjectType);
|
||||
.Select("id")
|
||||
.From<NodeDto>(SqlSyntax)
|
||||
.Where<NodeDto>(x => x.NodeObjectType == NodeObjectTypeId);
|
||||
|
||||
var deleteSql = SqlSyntax.GetDeleteSubquery("cmsContentXml", "nodeId", subQuery);
|
||||
Database.Execute(deleteSql);
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var id in contentTypeIds)
|
||||
{
|
||||
var id1 = id;
|
||||
var mediaObjectType = Guid.Parse(Constants.ObjectTypes.Media);
|
||||
var subQuery = new Sql()
|
||||
.Select("DISTINCT cmsContentXml.nodeId")
|
||||
.From<ContentXmlDto>()
|
||||
.InnerJoin<NodeDto>()
|
||||
.On<ContentXmlDto, NodeDto>(left => left.NodeId, right => right.NodeId)
|
||||
.InnerJoin<ContentDto>()
|
||||
.On<ContentDto, NodeDto>(left => left.NodeId, right => right.NodeId)
|
||||
.Where<NodeDto>(dto => dto.NodeObjectType == mediaObjectType)
|
||||
.Where<ContentDto>(dto => dto.ContentTypeId == id1);
|
||||
var subQuery = new Sql()
|
||||
.Select("umbracoNode.id as nodeId")
|
||||
.From<ContentDto>(SqlSyntax)
|
||||
.InnerJoin<NodeDto>(SqlSyntax)
|
||||
.On<ContentDto, NodeDto>(SqlSyntax, left => left.NodeId, right => right.NodeId)
|
||||
.WhereIn<ContentDto>(dto => dto.ContentTypeId, contentTypeIds, SqlSyntax)
|
||||
.Where<NodeDto>(x => x.NodeObjectType == NodeObjectTypeId);
|
||||
|
||||
var deleteSql = SqlSyntax.GetDeleteSubquery("cmsContentXml", "nodeId", subQuery);
|
||||
Database.Execute(deleteSql);
|
||||
}
|
||||
var deleteSql = SqlSyntax.GetDeleteSubquery("cmsContentXml", "nodeId", subQuery);
|
||||
Database.Execute(deleteSql);
|
||||
}
|
||||
|
||||
//now insert the data, again if something fails here, the whole transaction is reversed
|
||||
|
||||
@@ -389,36 +389,26 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
//Remove all the data first, if anything fails after this it's no problem the transaction will be reverted
|
||||
if (contentTypeIds == null)
|
||||
{
|
||||
var memberObjectType = Guid.Parse(Constants.ObjectTypes.Member);
|
||||
var subQuery = new Sql()
|
||||
.Select("DISTINCT cmsContentXml.nodeId")
|
||||
.From<ContentXmlDto>()
|
||||
.InnerJoin<NodeDto>()
|
||||
.On<ContentXmlDto, NodeDto>(left => left.NodeId, right => right.NodeId)
|
||||
.Where<NodeDto>(dto => dto.NodeObjectType == memberObjectType);
|
||||
.Select("id")
|
||||
.From<NodeDto>(SqlSyntax)
|
||||
.Where<NodeDto>(x => x.NodeObjectType == NodeObjectTypeId);
|
||||
|
||||
var deleteSql = SqlSyntax.GetDeleteSubquery("cmsContentXml", "nodeId", subQuery);
|
||||
Database.Execute(deleteSql);
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var id in contentTypeIds)
|
||||
{
|
||||
var id1 = id;
|
||||
var memberObjectType = Guid.Parse(Constants.ObjectTypes.Member);
|
||||
var subQuery = new Sql()
|
||||
.Select("DISTINCT cmsContentXml.nodeId")
|
||||
.From<ContentXmlDto>()
|
||||
.InnerJoin<NodeDto>()
|
||||
.On<ContentXmlDto, NodeDto>(left => left.NodeId, right => right.NodeId)
|
||||
.InnerJoin<ContentDto>()
|
||||
.On<ContentDto, NodeDto>(left => left.NodeId, right => right.NodeId)
|
||||
.Where<NodeDto>(dto => dto.NodeObjectType == memberObjectType)
|
||||
.Where<ContentDto>(dto => dto.ContentTypeId == id1);
|
||||
var subQuery = new Sql()
|
||||
.Select("umbracoNode.id as nodeId")
|
||||
.From<ContentDto>(SqlSyntax)
|
||||
.InnerJoin<NodeDto>(SqlSyntax)
|
||||
.On<ContentDto, NodeDto>(SqlSyntax, left => left.NodeId, right => right.NodeId)
|
||||
.WhereIn<ContentDto>(dto => dto.ContentTypeId, contentTypeIds, SqlSyntax)
|
||||
.Where<NodeDto>(x => x.NodeObjectType == NodeObjectTypeId);
|
||||
|
||||
var deleteSql = SqlSyntax.GetDeleteSubquery("cmsContentXml", "nodeId", subQuery);
|
||||
Database.Execute(deleteSql);
|
||||
}
|
||||
var deleteSql = SqlSyntax.GetDeleteSubquery("cmsContentXml", "nodeId", subQuery);
|
||||
Database.Execute(deleteSql);
|
||||
}
|
||||
|
||||
//now insert the data, again if something fails here, the whole transaction is reversed
|
||||
|
||||
@@ -29,7 +29,6 @@ namespace Umbraco.Core.Sync
|
||||
public class DatabaseServerMessenger : ServerMessengerBase
|
||||
{
|
||||
private readonly ApplicationContext _appContext;
|
||||
private readonly DatabaseServerMessengerOptions _options;
|
||||
private readonly ManualResetEvent _syncIdle;
|
||||
private readonly object _locko = new object();
|
||||
private readonly ILogger _logger;
|
||||
@@ -41,6 +40,7 @@ namespace Umbraco.Core.Sync
|
||||
private bool _released;
|
||||
private readonly ProfilingLogger _profilingLogger;
|
||||
|
||||
protected DatabaseServerMessengerOptions Options { get; private set; }
|
||||
protected ApplicationContext ApplicationContext { get { return _appContext; } }
|
||||
|
||||
public DatabaseServerMessenger(ApplicationContext appContext, bool distributedEnabled, DatabaseServerMessengerOptions options)
|
||||
@@ -50,7 +50,7 @@ namespace Umbraco.Core.Sync
|
||||
if (options == null) throw new ArgumentNullException("options");
|
||||
|
||||
_appContext = appContext;
|
||||
_options = options;
|
||||
Options = options;
|
||||
_lastPruned = _lastSync = DateTime.UtcNow;
|
||||
_syncIdle = new ManualResetEvent(true);
|
||||
_profilingLogger = appContext.ProfilingLogger;
|
||||
@@ -115,7 +115,17 @@ namespace Umbraco.Core.Sync
|
||||
{
|
||||
_released = true; // no more syncs
|
||||
}
|
||||
_syncIdle.WaitOne(); // wait for pending sync
|
||||
|
||||
// wait a max of 5 seconds and then return, so that we don't block
|
||||
// the entire MainDom callbacks chain and prevent the AppDomain from
|
||||
// properly releasing MainDom - a timeout here means that one refresher
|
||||
// is taking too much time processing, however when it's done we will
|
||||
// not update lastId and stop everything
|
||||
var idle =_syncIdle.WaitOne(5000);
|
||||
if (idle == false)
|
||||
{
|
||||
_logger.Warn<DatabaseServerMessenger>("The wait lock timed out, application is shutting down. The current instruction batch will be re-processed.");
|
||||
}
|
||||
},
|
||||
weight);
|
||||
|
||||
@@ -154,14 +164,16 @@ namespace Umbraco.Core.Sync
|
||||
else
|
||||
{
|
||||
//check for how many instructions there are to process
|
||||
//TODO: In 7.6 we need to store the count of instructions per row since this is not affective because there can be far more than one (if not thousands)
|
||||
// of instructions in a single row.
|
||||
var count = _appContext.DatabaseContext.Database.ExecuteScalar<int>("SELECT COUNT(*) FROM umbracoCacheInstruction WHERE id > @lastId", new {lastId = _lastId});
|
||||
if (count > _options.MaxProcessingInstructionCount)
|
||||
if (count > Options.MaxProcessingInstructionCount)
|
||||
{
|
||||
//too many instructions, proceed to cold boot
|
||||
_logger.Warn<DatabaseServerMessenger>("The instruction count ({0}) exceeds the specified MaxProcessingInstructionCount ({1})."
|
||||
+ " The server will skip existing instructions, rebuild its caches and indexes entirely, adjust its last synced Id"
|
||||
+ " to the latest found in the database and maintain cache updates based on that Id.",
|
||||
() => count, () => _options.MaxProcessingInstructionCount);
|
||||
() => count, () => Options.MaxProcessingInstructionCount);
|
||||
|
||||
coldboot = true;
|
||||
}
|
||||
@@ -179,8 +191,8 @@ namespace Umbraco.Core.Sync
|
||||
SaveLastSynced(maxId);
|
||||
|
||||
// execute initializing callbacks
|
||||
if (_options.InitializingCallbacks != null)
|
||||
foreach (var callback in _options.InitializingCallbacks)
|
||||
if (Options.InitializingCallbacks != null)
|
||||
foreach (var callback in Options.InitializingCallbacks)
|
||||
callback();
|
||||
}
|
||||
|
||||
@@ -198,12 +210,14 @@ namespace Umbraco.Core.Sync
|
||||
if (_syncing)
|
||||
return;
|
||||
|
||||
//Don't continue if we are released
|
||||
if (_released)
|
||||
return;
|
||||
|
||||
if ((DateTime.UtcNow - _lastSync).TotalSeconds <= _options.ThrottleSeconds)
|
||||
if ((DateTime.UtcNow - _lastSync).TotalSeconds <= Options.ThrottleSeconds)
|
||||
return;
|
||||
|
||||
//Set our flag and the lock to be in it's original state (i.e. it can be awaited)
|
||||
_syncing = true;
|
||||
_syncIdle.Reset();
|
||||
_lastSync = DateTime.UtcNow;
|
||||
@@ -215,7 +229,8 @@ namespace Umbraco.Core.Sync
|
||||
{
|
||||
ProcessDatabaseInstructions();
|
||||
|
||||
if ((DateTime.UtcNow - _lastPruned).TotalSeconds <= _options.PruneThrottleSeconds)
|
||||
//Check for pruning throttling
|
||||
if ((_released || (DateTime.UtcNow - _lastPruned).TotalSeconds <= Options.PruneThrottleSeconds))
|
||||
return;
|
||||
|
||||
_lastPruned = _lastSync;
|
||||
@@ -231,7 +246,12 @@ namespace Umbraco.Core.Sync
|
||||
}
|
||||
finally
|
||||
{
|
||||
_syncing = false;
|
||||
lock (_locko)
|
||||
{
|
||||
//We must reset our flag and signal any waiting locks
|
||||
_syncing = false;
|
||||
}
|
||||
|
||||
_syncIdle.Set();
|
||||
}
|
||||
}
|
||||
@@ -255,13 +275,17 @@ namespace Umbraco.Core.Sync
|
||||
//
|
||||
// FIXME not true if we're running on a background thread, assuming we can?
|
||||
|
||||
|
||||
var sql = new Sql().Select("*")
|
||||
.From<CacheInstructionDto>(_appContext.DatabaseContext.SqlSyntax)
|
||||
.Where<CacheInstructionDto>(dto => dto.Id > _lastId)
|
||||
.OrderBy<CacheInstructionDto>(dto => dto.Id, _appContext.DatabaseContext.SqlSyntax);
|
||||
|
||||
var dtos = _appContext.DatabaseContext.Database.Fetch<CacheInstructionDto>(sql);
|
||||
if (dtos.Count <= 0) return;
|
||||
//only retrieve the top 100 (just in case there's tons)
|
||||
// even though MaxProcessingInstructionCount is by default 1000 we still don't want to process that many
|
||||
// rows in one request thread since each row can contain a ton of instructions (until 7.5.5 in which case
|
||||
// a row can only contain MaxProcessingInstructionCount)
|
||||
var topSql = _appContext.DatabaseContext.SqlSyntax.SelectTop(sql, 100);
|
||||
|
||||
// only process instructions coming from a remote server, and ignore instructions coming from
|
||||
// the local server as they've already been processed. We should NOT assume that the sequence of
|
||||
@@ -269,8 +293,22 @@ namespace Umbraco.Core.Sync
|
||||
var localIdentity = LocalIdentity;
|
||||
|
||||
var lastId = 0;
|
||||
foreach (var dto in dtos)
|
||||
|
||||
//tracks which ones have already been processed to avoid duplicates
|
||||
var processed = new HashSet<RefreshInstruction>();
|
||||
|
||||
//It would have been nice to do this in a Query instead of Fetch using a data reader to save
|
||||
// some memory however we cannot do thta because inside of this loop the cache refreshers are also
|
||||
// performing some lookups which cannot be done with an active reader open
|
||||
foreach (var dto in _appContext.DatabaseContext.Database.Fetch<CacheInstructionDto>(topSql))
|
||||
{
|
||||
//If this flag gets set it means we're shutting down! In this case, we need to exit asap and cannot
|
||||
// continue processing anything otherwise we'll hold up the app domain shutdown
|
||||
if (_released)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (dto.OriginIdentity == localIdentity)
|
||||
{
|
||||
// just skip that local one but update lastId nevertheless
|
||||
@@ -291,27 +329,69 @@ namespace Umbraco.Core.Sync
|
||||
continue;
|
||||
}
|
||||
|
||||
// execute remote instructions & update lastId
|
||||
try
|
||||
{
|
||||
NotifyRefreshers(jsonA);
|
||||
lastId = dto.Id;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error<DatabaseServerMessenger>(
|
||||
string.Format("DISTRIBUTED CACHE IS NOT UPDATED. Failed to execute instructions ({0}: \"{1}\"). Instruction is being skipped/ignored", dto.Id, dto.Instructions), ex);
|
||||
var instructionBatch = GetAllInstructions(jsonA);
|
||||
|
||||
//we cannot throw here because this invalid instruction will just keep getting processed over and over and errors
|
||||
// will be thrown over and over. The only thing we can do is ignore and move on.
|
||||
lastId = dto.Id;
|
||||
//process as per-normal
|
||||
var success = ProcessDatabaseInstructions(instructionBatch, dto, processed, ref lastId);
|
||||
|
||||
//if they couldn't be all processed (i.e. we're shutting down) then exit
|
||||
if (success == false)
|
||||
{
|
||||
_logger.Info<DatabaseServerMessenger>("The current batch of instructions was not processed, app is shutting down");
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (lastId > 0)
|
||||
SaveLastSynced(lastId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Processes the instruction batch and checks for errors
|
||||
/// </summary>
|
||||
/// <param name="instructionBatch"></param>
|
||||
/// <param name="dto"></param>
|
||||
/// <param name="processed">
|
||||
/// Tracks which instructions have already been processed to avoid duplicates
|
||||
/// </param>
|
||||
/// <param name="lastId"></param>
|
||||
/// <returns>
|
||||
/// returns true if all instructions in the batch were processed, otherwise false if they could not be due to the app being shut down
|
||||
/// </returns>
|
||||
private bool ProcessDatabaseInstructions(IReadOnlyCollection<RefreshInstruction> instructionBatch, CacheInstructionDto dto, HashSet<RefreshInstruction> processed, ref int lastId)
|
||||
{
|
||||
// execute remote instructions & update lastId
|
||||
try
|
||||
{
|
||||
var result = NotifyRefreshers(instructionBatch, processed);
|
||||
if (result)
|
||||
{
|
||||
//if all instructions we're processed, set the last id
|
||||
lastId = dto.Id;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
//catch (ThreadAbortException ex)
|
||||
//{
|
||||
// //This will occur if the instructions processing is taking too long since this is occuring on a request thread.
|
||||
// // Or possibly if IIS terminates the appdomain. In any case, we should deal with this differently perhaps...
|
||||
//}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error<DatabaseServerMessenger>(
|
||||
string.Format("DISTRIBUTED CACHE IS NOT UPDATED. Failed to execute instructions (id: {0}, instruction count: {1}). Instruction is being skipped/ignored", dto.Id, instructionBatch.Count), ex);
|
||||
|
||||
//we cannot throw here because this invalid instruction will just keep getting processed over and over and errors
|
||||
// will be thrown over and over. The only thing we can do is ignore and move on.
|
||||
lastId = dto.Id;
|
||||
return false;
|
||||
}
|
||||
|
||||
////if this is returned it will not be saved
|
||||
//return -1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove old instructions from the database
|
||||
/// </summary>
|
||||
@@ -322,7 +402,7 @@ namespace Umbraco.Core.Sync
|
||||
/// </remarks>
|
||||
private void PruneOldInstructions()
|
||||
{
|
||||
var pruneDate = DateTime.UtcNow.AddDays(-_options.DaysToRetainInstructions);
|
||||
var pruneDate = DateTime.UtcNow.AddDays(-Options.DaysToRetainInstructions);
|
||||
|
||||
// using 2 queries is faster than convoluted joins
|
||||
|
||||
@@ -459,8 +539,14 @@ namespace Umbraco.Core.Sync
|
||||
return jsonRefresher;
|
||||
}
|
||||
|
||||
private static void NotifyRefreshers(IEnumerable<JToken> jsonArray)
|
||||
/// <summary>
|
||||
/// Parses out the individual instructions to be processed
|
||||
/// </summary>
|
||||
/// <param name="jsonArray"></param>
|
||||
/// <returns></returns>
|
||||
private static List<RefreshInstruction> GetAllInstructions(IEnumerable<JToken> jsonArray)
|
||||
{
|
||||
var result = new List<RefreshInstruction>();
|
||||
foreach (var jsonItem in jsonArray)
|
||||
{
|
||||
// could be a JObject in which case we can convert to a RefreshInstruction,
|
||||
@@ -469,35 +555,64 @@ namespace Umbraco.Core.Sync
|
||||
if (jsonObj != null)
|
||||
{
|
||||
var instruction = jsonObj.ToObject<RefreshInstruction>();
|
||||
switch (instruction.RefreshType)
|
||||
{
|
||||
case RefreshMethodType.RefreshAll:
|
||||
RefreshAll(instruction.RefresherId);
|
||||
break;
|
||||
case RefreshMethodType.RefreshByGuid:
|
||||
RefreshByGuid(instruction.RefresherId, instruction.GuidId);
|
||||
break;
|
||||
case RefreshMethodType.RefreshById:
|
||||
RefreshById(instruction.RefresherId, instruction.IntId);
|
||||
break;
|
||||
case RefreshMethodType.RefreshByIds:
|
||||
RefreshByIds(instruction.RefresherId, instruction.JsonIds);
|
||||
break;
|
||||
case RefreshMethodType.RefreshByJson:
|
||||
RefreshByJson(instruction.RefresherId, instruction.JsonPayload);
|
||||
break;
|
||||
case RefreshMethodType.RemoveById:
|
||||
RemoveById(instruction.RefresherId, instruction.IntId);
|
||||
break;
|
||||
}
|
||||
|
||||
result.Add(instruction);
|
||||
}
|
||||
else
|
||||
{
|
||||
var jsonInnerArray = (JArray) jsonItem;
|
||||
NotifyRefreshers(jsonInnerArray); // recurse
|
||||
var jsonInnerArray = (JArray)jsonItem;
|
||||
result.AddRange(GetAllInstructions(jsonInnerArray)); // recurse
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// executes the instructions against the cache refresher instances
|
||||
/// </summary>
|
||||
/// <param name="instructions"></param>
|
||||
/// <param name="processed"></param>
|
||||
/// <returns>
|
||||
/// Returns true if all instructions were processed, otherwise false if the processing was interupted (i.e. app shutdown)
|
||||
/// </returns>
|
||||
private bool NotifyRefreshers(IEnumerable<RefreshInstruction> instructions, HashSet<RefreshInstruction> processed)
|
||||
{
|
||||
foreach (var instruction in instructions)
|
||||
{
|
||||
//Check if the app is shutting down, we need to exit if this happens.
|
||||
if (_released)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
//this has already been processed
|
||||
if (processed.Contains(instruction))
|
||||
continue;
|
||||
|
||||
switch (instruction.RefreshType)
|
||||
{
|
||||
case RefreshMethodType.RefreshAll:
|
||||
RefreshAll(instruction.RefresherId);
|
||||
break;
|
||||
case RefreshMethodType.RefreshByGuid:
|
||||
RefreshByGuid(instruction.RefresherId, instruction.GuidId);
|
||||
break;
|
||||
case RefreshMethodType.RefreshById:
|
||||
RefreshById(instruction.RefresherId, instruction.IntId);
|
||||
break;
|
||||
case RefreshMethodType.RefreshByIds:
|
||||
RefreshByIds(instruction.RefresherId, instruction.JsonIds);
|
||||
break;
|
||||
case RefreshMethodType.RefreshByJson:
|
||||
RefreshByJson(instruction.RefresherId, instruction.JsonPayload);
|
||||
break;
|
||||
case RefreshMethodType.RemoveById:
|
||||
RemoveById(instruction.RefresherId, instruction.IntId);
|
||||
break;
|
||||
}
|
||||
|
||||
processed.Add(instruction);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void RefreshAll(Guid uniqueIdentifier)
|
||||
|
||||
@@ -539,7 +539,7 @@ namespace Umbraco.Tests.Persistence.Repositories
|
||||
using (var repository = CreateRepository(unitOfWork))
|
||||
{
|
||||
var contentType = repository.Get(NodeDto.NodeIdSeed + 1);
|
||||
var child1 = MockedContentTypes.CreateSimpleContentType("aabc", "aabc", contentType, randomizeAliases: true);
|
||||
var child1 = MockedContentTypes.CreateSimpleContentType("abc", "abc", contentType, randomizeAliases: true);
|
||||
repository.AddOrUpdate(child1);
|
||||
var child3 = MockedContentTypes.CreateSimpleContentType("zyx", "zyx", contentType, randomizeAliases: true);
|
||||
repository.AddOrUpdate(child3);
|
||||
@@ -553,7 +553,7 @@ namespace Umbraco.Tests.Persistence.Repositories
|
||||
// Assert
|
||||
Assert.That(contentTypes.Count(), Is.EqualTo(3));
|
||||
Assert.AreEqual("a123", contentTypes.ElementAt(0).Name);
|
||||
Assert.AreEqual("aabc", contentTypes.ElementAt(1).Name);
|
||||
Assert.AreEqual("abc", contentTypes.ElementAt(1).Name);
|
||||
Assert.AreEqual("zyx", contentTypes.ElementAt(2).Name);
|
||||
}
|
||||
|
||||
|
||||
@@ -84,11 +84,16 @@ namespace Umbraco.Web
|
||||
|
||||
var instructions = batch.SelectMany(x => x.Instructions).ToArray();
|
||||
batch.Clear();
|
||||
if (instructions.Length == 0) return;
|
||||
WriteInstructions(instructions);
|
||||
|
||||
//Write the instructions but only create JSON blobs with a max instruction count equal to MaxProcessingInstructionCount
|
||||
foreach (var instructionsBatch in instructions.InGroupsOf(Options.MaxProcessingInstructionCount))
|
||||
{
|
||||
WriteInstructions(instructionsBatch);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void WriteInstructions(RefreshInstruction[] instructions)
|
||||
private void WriteInstructions(IEnumerable<RefreshInstruction> instructions)
|
||||
{
|
||||
var dto = new CacheInstructionDto
|
||||
{
|
||||
@@ -136,9 +141,18 @@ namespace Umbraco.Web
|
||||
|
||||
// batch if we can, else write to DB immediately
|
||||
if (batch == null)
|
||||
WriteInstructions(instructions.ToArray());
|
||||
{
|
||||
//only write the json blob with a maximum count of the MaxProcessingInstructionCount
|
||||
foreach (var maxBatch in instructions.InGroupsOf(Options.MaxProcessingInstructionCount))
|
||||
{
|
||||
WriteInstructions(maxBatch);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
batch.Add(new RefreshInstructionEnvelope(servers, refresher, instructions));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,8 @@ using Examine.LuceneEngine.Providers;
|
||||
using Examine.Providers;
|
||||
using Lucene.Net.Index;
|
||||
using Lucene.Net.Search;
|
||||
using Lucene.Net.Store;
|
||||
using Umbraco.Core.Logging;
|
||||
|
||||
namespace Umbraco.Web.Search
|
||||
{
|
||||
@@ -21,9 +23,17 @@ namespace Umbraco.Web.Search
|
||||
/// <returns></returns>
|
||||
public static int GetIndexDocumentCount(this LuceneIndexer indexer)
|
||||
{
|
||||
using (var reader = indexer.GetIndexWriter().GetReader())
|
||||
try
|
||||
{
|
||||
return reader.NumDocs();
|
||||
using (var reader = indexer.GetIndexWriter().GetReader())
|
||||
{
|
||||
return reader.NumDocs();
|
||||
}
|
||||
}
|
||||
catch (AlreadyClosedException)
|
||||
{
|
||||
LogHelper.Warn(typeof(ExamineExtensions), "Cannot get GetIndexDocumentCount, the writer is already closed");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,9 +44,19 @@ namespace Umbraco.Web.Search
|
||||
/// <returns></returns>
|
||||
public static int GetIndexFieldCount(this LuceneIndexer indexer)
|
||||
{
|
||||
using (var reader = indexer.GetIndexWriter().GetReader())
|
||||
//TODO: check for closing! and AlreadyClosedException
|
||||
|
||||
try
|
||||
{
|
||||
return reader.GetFieldNames(IndexReader.FieldOption.ALL).Count;
|
||||
using (var reader = indexer.GetIndexWriter().GetReader())
|
||||
{
|
||||
return reader.GetFieldNames(IndexReader.FieldOption.ALL).Count;
|
||||
}
|
||||
}
|
||||
catch (AlreadyClosedException)
|
||||
{
|
||||
LogHelper.Warn(typeof(ExamineExtensions), "Cannot get GetIndexFieldCount, the writer is already closed");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,9 +67,17 @@ namespace Umbraco.Web.Search
|
||||
/// <returns></returns>
|
||||
public static bool IsIndexOptimized(this LuceneIndexer indexer)
|
||||
{
|
||||
using (var reader = indexer.GetIndexWriter().GetReader())
|
||||
try
|
||||
{
|
||||
return reader.IsOptimized();
|
||||
using (var reader = indexer.GetIndexWriter().GetReader())
|
||||
{
|
||||
return reader.IsOptimized();
|
||||
}
|
||||
}
|
||||
catch (AlreadyClosedException)
|
||||
{
|
||||
LogHelper.Warn(typeof(ExamineExtensions), "Cannot get IsIndexOptimized, the writer is already closed");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,9 +102,17 @@ namespace Umbraco.Web.Search
|
||||
/// <returns></returns>
|
||||
public static int GetDeletedDocumentsCount(this LuceneIndexer indexer)
|
||||
{
|
||||
using (var reader = indexer.GetIndexWriter().GetReader())
|
||||
try
|
||||
{
|
||||
return reader.NumDeletedDocs();
|
||||
using (var reader = indexer.GetIndexWriter().GetReader())
|
||||
{
|
||||
return reader.NumDeletedDocs();
|
||||
}
|
||||
}
|
||||
catch (AlreadyClosedException)
|
||||
{
|
||||
LogHelper.Warn(typeof(ExamineExtensions), "Cannot get GetDeletedDocumentsCount, the writer is already closed");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user