diff --git a/src/Umbraco.Core/MainDom.cs b/src/Umbraco.Core/MainDom.cs index 6f4a539194..fb8ad06999 100644 --- a/src/Umbraco.Core/MainDom.cs +++ b/src/Umbraco.Core/MainDom.cs @@ -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("Stopping..."); + _logger.Info("Stopping..."); foreach (var callback in _callbacks.Values) { try @@ -109,7 +109,7 @@ namespace Umbraco.Core _logger.Error("Error while running callback, remaining callbacks will not run.", e); throw; } - + } _logger.Debug("Stopped."); } @@ -118,7 +118,7 @@ namespace Umbraco.Core // in any case... _isMainDom = false; _asyncLocker.Dispose(); - _logger.Debug("Released MainDom."); + _logger.Info("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("Cannot acquire MainDom (signaled)."); + _logger.Info("Cannot acquire MainDom (signaled)."); return false; } - _logger.Debug("Acquiring MainDom..."); + _logger.Info("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("Acquired MainDom."); + _logger.Info("Acquired MainDom."); return true; } } diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs index 9827382b00..84caeb2a3e 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs @@ -183,30 +183,25 @@ namespace Umbraco.Core.Persistence.Repositories if (contentTypeIds == null) { var subQuery = new Sql() - .Select("DISTINCT cmsContentXml.nodeId") - .From() - .InnerJoin() - .On(left => left.NodeId, right => right.NodeId); + .Select("id") + .From(SqlSyntax) + .Where(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() - .InnerJoin() - .On(left => left.NodeId, right => right.NodeId) - .Where(dto => dto.Published) - .Where(dto => dto.ContentTypeId == id1); + var subQuery = new Sql() + .Select("umbracoNode.id as nodeId") + .From(SqlSyntax) + .InnerJoin(SqlSyntax) + .On(SqlSyntax, left => left.NodeId, right => right.NodeId) + .WhereIn(dto => dto.ContentTypeId, contentTypeIds, SqlSyntax) + .Where(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 diff --git a/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs index 8c9bec71d4..598c9e912d 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs @@ -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() - .InnerJoin() - .On(left => left.NodeId, right => right.NodeId) - .Where(dto => dto.NodeObjectType == mediaObjectType); + .Select("id") + .From(SqlSyntax) + .Where(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() - .InnerJoin() - .On(left => left.NodeId, right => right.NodeId) - .InnerJoin() - .On(left => left.NodeId, right => right.NodeId) - .Where(dto => dto.NodeObjectType == mediaObjectType) - .Where(dto => dto.ContentTypeId == id1); + var subQuery = new Sql() + .Select("umbracoNode.id as nodeId") + .From(SqlSyntax) + .InnerJoin(SqlSyntax) + .On(SqlSyntax, left => left.NodeId, right => right.NodeId) + .WhereIn(dto => dto.ContentTypeId, contentTypeIds, SqlSyntax) + .Where(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 diff --git a/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs index 0f0e797f17..216fc223db 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs @@ -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() - .InnerJoin() - .On(left => left.NodeId, right => right.NodeId) - .Where(dto => dto.NodeObjectType == memberObjectType); + .Select("id") + .From(SqlSyntax) + .Where(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() - .InnerJoin() - .On(left => left.NodeId, right => right.NodeId) - .InnerJoin() - .On(left => left.NodeId, right => right.NodeId) - .Where(dto => dto.NodeObjectType == memberObjectType) - .Where(dto => dto.ContentTypeId == id1); + var subQuery = new Sql() + .Select("umbracoNode.id as nodeId") + .From(SqlSyntax) + .InnerJoin(SqlSyntax) + .On(SqlSyntax, left => left.NodeId, right => right.NodeId) + .WhereIn(dto => dto.ContentTypeId, contentTypeIds, SqlSyntax) + .Where(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 diff --git a/src/Umbraco.Core/Sync/DatabaseServerMessenger.cs b/src/Umbraco.Core/Sync/DatabaseServerMessenger.cs index 4e46c0ab5c..12cb465b50 100644 --- a/src/Umbraco.Core/Sync/DatabaseServerMessenger.cs +++ b/src/Umbraco.Core/Sync/DatabaseServerMessenger.cs @@ -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("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("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("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(_appContext.DatabaseContext.SqlSyntax) .Where(dto => dto.Id > _lastId) .OrderBy(dto => dto.Id, _appContext.DatabaseContext.SqlSyntax); - var dtos = _appContext.DatabaseContext.Database.Fetch(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(); + + //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(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( - 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("The current batch of instructions was not processed, app is shutting down"); + break; } + } if (lastId > 0) SaveLastSynced(lastId); } + /// + /// Processes the instruction batch and checks for errors + /// + /// + /// + /// + /// Tracks which instructions have already been processed to avoid duplicates + /// + /// + /// + /// returns true if all instructions in the batch were processed, otherwise false if they could not be due to the app being shut down + /// + private bool ProcessDatabaseInstructions(IReadOnlyCollection instructionBatch, CacheInstructionDto dto, HashSet 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( + 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; + } + /// /// Remove old instructions from the database /// @@ -322,7 +402,7 @@ namespace Umbraco.Core.Sync /// 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 jsonArray) + /// + /// Parses out the individual instructions to be processed + /// + /// + /// + private static List GetAllInstructions(IEnumerable jsonArray) { + var result = new List(); 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(); - 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; + } + + /// + /// executes the instructions against the cache refresher instances + /// + /// + /// + /// + /// Returns true if all instructions were processed, otherwise false if the processing was interupted (i.e. app shutdown) + /// + private bool NotifyRefreshers(IEnumerable instructions, HashSet 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) diff --git a/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs index 7d8d7659c8..84cdef73e1 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs @@ -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); } diff --git a/src/Umbraco.Web/BatchedDatabaseServerMessenger.cs b/src/Umbraco.Web/BatchedDatabaseServerMessenger.cs index 730efd380b..f06a7a332f 100644 --- a/src/Umbraco.Web/BatchedDatabaseServerMessenger.cs +++ b/src/Umbraco.Web/BatchedDatabaseServerMessenger.cs @@ -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 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)); + } + } } } \ No newline at end of file diff --git a/src/Umbraco.Web/Search/LuceneIndexerExtensions.cs b/src/Umbraco.Web/Search/LuceneIndexerExtensions.cs index 3cc7908dfd..34ad71d2f9 100644 --- a/src/Umbraco.Web/Search/LuceneIndexerExtensions.cs +++ b/src/Umbraco.Web/Search/LuceneIndexerExtensions.cs @@ -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 /// 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 /// 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 /// 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 /// 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; } } }