Merge pull request #10182 from umbraco/v8/bugfix/8893-examine-startup
Fix startup issues and timing with Examine and Nucache
This commit is contained in:
@@ -28,7 +28,7 @@ namespace Umbraco.Core.Sync
|
||||
// but only processes instructions coming from remote servers,
|
||||
// thus ensuring that instructions run only once
|
||||
//
|
||||
public class DatabaseServerMessenger : ServerMessengerBase
|
||||
public class DatabaseServerMessenger : ServerMessengerBase, ISyncBootStateAccessor
|
||||
{
|
||||
private readonly IRuntimeState _runtime;
|
||||
private readonly ManualResetEvent _syncIdle;
|
||||
@@ -39,9 +39,9 @@ namespace Umbraco.Core.Sync
|
||||
private int _lastId = -1;
|
||||
private DateTime _lastSync;
|
||||
private DateTime _lastPruned;
|
||||
private bool _initialized;
|
||||
private bool _syncing;
|
||||
private bool _released;
|
||||
private readonly Lazy<SyncBootState> _getSyncBootState;
|
||||
|
||||
public DatabaseServerMessengerOptions Options { get; }
|
||||
|
||||
@@ -59,6 +59,7 @@ namespace Umbraco.Core.Sync
|
||||
_lastPruned = _lastSync = DateTime.UtcNow;
|
||||
_syncIdle = new ManualResetEvent(true);
|
||||
_distCacheFilePath = new Lazy<string>(() => GetDistCacheFilePath(globalSettings));
|
||||
_getSyncBootState = new Lazy<SyncBootState>(BootInternal);
|
||||
}
|
||||
|
||||
protected ILogger Logger { get; }
|
||||
@@ -75,7 +76,7 @@ namespace Umbraco.Core.Sync
|
||||
{
|
||||
// we don't care if there's servers listed or not,
|
||||
// if distributed call is enabled we will make the call
|
||||
return _initialized && DistributedEnabled;
|
||||
return _getSyncBootState.IsValueCreated && DistributedEnabled;
|
||||
}
|
||||
|
||||
protected override void DeliverRemote(
|
||||
@@ -110,14 +111,14 @@ namespace Umbraco.Core.Sync
|
||||
|
||||
#region Sync
|
||||
|
||||
/// <summary>
|
||||
/// Boots the messenger.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Thread safety: this is NOT thread safe. Because it is NOT meant to run multi-threaded.
|
||||
/// Callers MUST ensure thread-safety.
|
||||
/// </remarks>
|
||||
[Obsolete("This is no longer used and will be removed in future versions")]
|
||||
protected void Boot()
|
||||
{
|
||||
// if called, just forces the boot logic
|
||||
_ = GetSyncBootState();
|
||||
}
|
||||
|
||||
private SyncBootState BootInternal()
|
||||
{
|
||||
// weight:10, must release *before* the published snapshot service, because once released
|
||||
// the service will *not* be able to properly handle our notifications anymore
|
||||
@@ -139,7 +140,7 @@ namespace Umbraco.Core.Sync
|
||||
// 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);
|
||||
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.");
|
||||
@@ -147,17 +148,23 @@ namespace Umbraco.Core.Sync
|
||||
},
|
||||
weight);
|
||||
|
||||
SyncBootState bootState = SyncBootState.Unknown;
|
||||
|
||||
if (registered == false)
|
||||
return;
|
||||
{
|
||||
return bootState;
|
||||
}
|
||||
|
||||
ReadLastSynced(); // get _lastId
|
||||
using (var scope = ScopeProvider.CreateScope())
|
||||
{
|
||||
EnsureInstructions(scope.Database); // reset _lastId if instructions are missing
|
||||
Initialize(scope.Database); // boot
|
||||
bootState = Initialize(scope.Database); // boot
|
||||
|
||||
scope.Complete();
|
||||
}
|
||||
|
||||
return bootState;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -167,60 +174,62 @@ namespace Umbraco.Core.Sync
|
||||
/// Thread safety: this is NOT thread safe. Because it is NOT meant to run multi-threaded.
|
||||
/// Callers MUST ensure thread-safety.
|
||||
/// </remarks>
|
||||
private void Initialize(IUmbracoDatabase database)
|
||||
private SyncBootState Initialize(IUmbracoDatabase database)
|
||||
{
|
||||
lock (_locko)
|
||||
{
|
||||
if (_released) return;
|
||||
// could occur if shutting down immediately once starting up and before we've initialized
|
||||
if (_released) return SyncBootState.Unknown;
|
||||
|
||||
var coldboot = false;
|
||||
if (_lastId < 0) // never synced before
|
||||
var coldboot = false;
|
||||
if (_lastId < 0) // never synced before
|
||||
{
|
||||
// we haven't synced - in this case we aren't going to sync the whole thing, we will assume this is a new
|
||||
// server and it will need to rebuild it's own caches, eg Lucene or the xml cache file.
|
||||
Logger.Warn<DatabaseServerMessenger>("No last synced Id found, this generally means this is a new server/install."
|
||||
+ " The server will build its caches and indexes, and then adjust its last synced Id to the latest found in"
|
||||
+ " the database and maintain cache updates based on that Id.");
|
||||
|
||||
coldboot = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
//check for how many instructions there are to process, each row contains a count of the number of instructions contained in each
|
||||
//row so we will sum these numbers to get the actual count.
|
||||
var count = database.ExecuteScalar<int>("SELECT SUM(instructionCount) FROM umbracoCacheInstruction WHERE id > @lastId", new { lastId = _lastId });
|
||||
if (count > Options.MaxProcessingInstructionCount)
|
||||
{
|
||||
// we haven't synced - in this case we aren't going to sync the whole thing, we will assume this is a new
|
||||
// server and it will need to rebuild it's own caches, eg Lucene or the xml cache file.
|
||||
Logger.Warn<DatabaseServerMessenger>("No last synced Id found, this generally means this is a new server/install."
|
||||
+ " The server will build its caches and indexes, and then adjust its last synced Id to the latest found in"
|
||||
+ " the database and maintain cache updates based on that Id.");
|
||||
//too many instructions, proceed to cold boot
|
||||
Logger.Warn<DatabaseServerMessenger, int, int>(
|
||||
"The instruction count ({InstructionCount}) exceeds the specified MaxProcessingInstructionCount ({MaxProcessingInstructionCount})."
|
||||
+ " 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);
|
||||
|
||||
coldboot = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
//check for how many instructions there are to process, each row contains a count of the number of instructions contained in each
|
||||
//row so we will sum these numbers to get the actual count.
|
||||
var count = database.ExecuteScalar<int>("SELECT SUM(instructionCount) FROM umbracoCacheInstruction WHERE id > @lastId", new {lastId = _lastId});
|
||||
if (count > Options.MaxProcessingInstructionCount)
|
||||
{
|
||||
//too many instructions, proceed to cold boot
|
||||
Logger.Warn<DatabaseServerMessenger,int,int>(
|
||||
"The instruction count ({InstructionCount}) exceeds the specified MaxProcessingInstructionCount ({MaxProcessingInstructionCount})."
|
||||
+ " 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);
|
||||
}
|
||||
|
||||
coldboot = true;
|
||||
if (coldboot)
|
||||
{
|
||||
// go get the last id in the db and store it
|
||||
// note: do it BEFORE initializing otherwise some instructions might get lost
|
||||
// when doing it before, some instructions might run twice - not an issue
|
||||
var maxId = database.ExecuteScalar<int>("SELECT MAX(id) FROM umbracoCacheInstruction");
|
||||
|
||||
//if there is a max currently, or if we've never synced
|
||||
if (maxId > 0 || _lastId < 0)
|
||||
SaveLastSynced(maxId);
|
||||
|
||||
// execute initializing callbacks
|
||||
if (Options.InitializingCallbacks != null)
|
||||
{
|
||||
foreach (var callback in Options.InitializingCallbacks)
|
||||
{
|
||||
callback();
|
||||
}
|
||||
}
|
||||
|
||||
if (coldboot)
|
||||
{
|
||||
// go get the last id in the db and store it
|
||||
// note: do it BEFORE initializing otherwise some instructions might get lost
|
||||
// when doing it before, some instructions might run twice - not an issue
|
||||
var maxId = database.ExecuteScalar<int>("SELECT MAX(id) FROM umbracoCacheInstruction");
|
||||
|
||||
//if there is a max currently, or if we've never synced
|
||||
if (maxId > 0 || _lastId < 0)
|
||||
SaveLastSynced(maxId);
|
||||
|
||||
// execute initializing callbacks
|
||||
if (Options.InitializingCallbacks != null)
|
||||
foreach (var callback in Options.InitializingCallbacks)
|
||||
callback();
|
||||
}
|
||||
|
||||
_initialized = true;
|
||||
}
|
||||
|
||||
return coldboot ? SyncBootState.ColdBoot : SyncBootState.WarmBoot;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -352,7 +361,7 @@ namespace Umbraco.Core.Sync
|
||||
}
|
||||
catch (JsonException ex)
|
||||
{
|
||||
Logger.Error<DatabaseServerMessenger,int, string>(ex, "Failed to deserialize instructions ({DtoId}: '{DtoInstructions}').",
|
||||
Logger.Error<DatabaseServerMessenger, int, string>(ex, "Failed to deserialize instructions ({DtoId}: '{DtoInstructions}').",
|
||||
dto.Id,
|
||||
dto.Instructions);
|
||||
|
||||
@@ -410,11 +419,11 @@ namespace Umbraco.Core.Sync
|
||||
//}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Error<DatabaseServerMessenger,int, string> (
|
||||
ex,
|
||||
"DISTRIBUTED CACHE IS NOT UPDATED. Failed to execute instructions ({DtoId}: '{DtoInstructions}'). Instruction is being skipped/ignored",
|
||||
dto.Id,
|
||||
dto.Instructions);
|
||||
Logger.Error<DatabaseServerMessenger, int, string>(
|
||||
ex,
|
||||
"DISTRIBUTED CACHE IS NOT UPDATED. Failed to execute instructions ({DtoId}: '{DtoInstructions}'). Instruction is being skipped/ignored",
|
||||
dto.Id,
|
||||
dto.Instructions);
|
||||
|
||||
//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.
|
||||
@@ -548,6 +557,8 @@ namespace Umbraco.Core.Sync
|
||||
|
||||
#endregion
|
||||
|
||||
public virtual SyncBootState GetSyncBootState() => _getSyncBootState.Value;
|
||||
|
||||
#region Notify refreshers
|
||||
|
||||
private static ICacheRefresher GetRefresher(Guid id)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace Umbraco.Core.Sync
|
||||
{
|
||||
@@ -24,13 +25,8 @@ namespace Umbraco.Core.Sync
|
||||
/// </summary>
|
||||
public int MaxProcessingInstructionCount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// A list of callbacks that will be invoked if the lastsynced.txt file does not exist.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// These callbacks will typically be for eg rebuilding the xml cache file, or examine indexes, based on
|
||||
/// the data in the database to get this particular server node up to date.
|
||||
/// </remarks>
|
||||
[Obsolete("This should not be used. If initialization calls need to be invoked on a cold boot, use the ISyncBootStateAccessor.Booting event.")]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public IEnumerable<Action> InitializingCallbacks { get; set; }
|
||||
|
||||
/// <summary>
|
||||
|
||||
16
src/Umbraco.Core/Sync/ISyncBootStateAccessor.cs
Normal file
16
src/Umbraco.Core/Sync/ISyncBootStateAccessor.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using System;
|
||||
|
||||
namespace Umbraco.Core.Sync
|
||||
{
|
||||
/// <summary>
|
||||
/// Retrieve the state of the sync service
|
||||
/// </summary>
|
||||
public interface ISyncBootStateAccessor
|
||||
{
|
||||
/// <summary>
|
||||
/// Get the boot state
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
SyncBootState GetSyncBootState();
|
||||
}
|
||||
}
|
||||
21
src/Umbraco.Core/Sync/NonRuntimeLevelBootStateAccessor.cs
Normal file
21
src/Umbraco.Core/Sync/NonRuntimeLevelBootStateAccessor.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Umbraco.Core.Sync
|
||||
{
|
||||
/// <summary>
|
||||
/// Boot state implementation for when umbraco is not in the run state
|
||||
/// </summary>
|
||||
public class NonRuntimeLevelBootStateAccessor : ISyncBootStateAccessor
|
||||
{
|
||||
public event EventHandler<SyncBootState> Booting;
|
||||
|
||||
public SyncBootState GetSyncBootState()
|
||||
{
|
||||
return SyncBootState.Unknown;
|
||||
}
|
||||
}
|
||||
}
|
||||
20
src/Umbraco.Core/Sync/SyncBootState.cs
Normal file
20
src/Umbraco.Core/Sync/SyncBootState.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
namespace Umbraco.Core.Sync
|
||||
{
|
||||
public enum SyncBootState
|
||||
{
|
||||
/// <summary>
|
||||
/// Unknown state. Treat as WarmBoot
|
||||
/// </summary>
|
||||
Unknown = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Cold boot. No Sync state
|
||||
/// </summary>
|
||||
ColdBoot = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Warm boot. Sync state present
|
||||
/// </summary>
|
||||
WarmBoot = 2
|
||||
}
|
||||
}
|
||||
@@ -180,6 +180,9 @@
|
||||
<Compile Include="Migrations\Upgrade\V_8_7_0\MissingDictionaryIndex.cs" />
|
||||
<Compile Include="Services\IInstallationService.cs" />
|
||||
<Compile Include="Services\IUpgradeService.cs" />
|
||||
<Compile Include="Sync\ISyncBootStateAccessor.cs" />
|
||||
<Compile Include="Sync\NonRuntimeLevelBootStateAccessor.cs" />
|
||||
<Compile Include="Sync\SyncBootState.cs" />
|
||||
<Compile Include="SystemLock.cs" />
|
||||
<Compile Include="Attempt.cs" />
|
||||
<Compile Include="AttemptOfTResult.cs" />
|
||||
|
||||
Reference in New Issue
Block a user