Removes the callbacks from DatabaseServerMessenger, moves logic for cold boot into the components responsible for what needs to happen in cold boot. This is still not ideal but we are stuck with backwards compat. In netcore the initialization will be fixed up (if not already). Ensure examine rebuilds don't occur until the first http request is done instead of on a timer which could be problematic with cold boots.

This commit is contained in:
Shannon
2021-04-26 18:07:23 +10:00
parent dccec91073
commit 053a56a45b
15 changed files with 198 additions and 189 deletions

View File

@@ -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(
@@ -118,6 +119,12 @@ namespace Umbraco.Core.Sync
/// Callers MUST ensure thread-safety.
/// </remarks>
protected void Boot()
{
var bootState = GetSyncBootState();
Booting?.Invoke(this, bootState);
}
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 +146,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 +154,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,36 +180,11 @@ 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;
var coldboot = IsColdBoot(database);
// could occur if shutting down immediately once starting up and before we've initialized
if (_released) return SyncBootState.Unknown;
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;
}
}
private bool IsColdBoot(IUmbracoDatabase database)
{
var coldboot = false;
if (_lastId < 0) // never synced before
{
@@ -206,27 +194,48 @@ namespace Umbraco.Core.Sync
+ " 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
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)
{
//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);
//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;
}
}
return coldboot;
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();
}
}
}
return coldboot ? SyncBootState.ColdBoot : SyncBootState.WarmBoot;
}
/// <summary>
@@ -358,7 +367,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);
@@ -416,11 +425,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.
@@ -536,6 +545,8 @@ namespace Umbraco.Core.Sync
+ "/D" + AppDomain.CurrentDomain.Id // eg 22
+ "] " + Guid.NewGuid().ToString("N").ToUpper(); // make it truly unique
public event EventHandler<SyncBootState> Booting;
private string GetDistCacheFilePath(IGlobalSettings globalSettings)
{
var fileName = HttpRuntime.AppDomainAppId.ReplaceNonAlphanumericChars(string.Empty) + "-lastsynced.txt";
@@ -554,29 +565,7 @@ namespace Umbraco.Core.Sync
#endregion
public SyncBootState GetSyncBootState()
{
try
{
ReadLastSynced(); // get _lastId
using (var scope = ScopeProvider.CreateScope())
{
EnsureInstructions(scope.Database);
bool isColdBoot = IsColdBoot(scope.Database);
if (isColdBoot)
{
return SyncBootState.ColdBoot;
}
return SyncBootState.HasSyncState;
}
}
catch(Exception ex)
{
Logger.Warn<DatabaseServerMessenger>("Error determining Sync Boot State", ex);
return SyncBootState.Unknown;
}
}
public SyncBootState GetSyncBootState() => _getSyncBootState.Value;
#region Notify refreshers

View File

@@ -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>

View File

@@ -1,8 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Umbraco.Core.Sync
{
@@ -16,5 +12,10 @@ namespace Umbraco.Core.Sync
/// </summary>
/// <returns></returns>
SyncBootState GetSyncBootState();
/// <summary>
/// Raised when the boot state is known
/// </summary>
event EventHandler<SyncBootState> Booting;
}
}

View File

@@ -11,6 +11,8 @@ namespace Umbraco.Core.Sync
/// </summary>
public class NonRuntimeLevelBootStateAccessor : ISyncBootStateAccessor
{
public event EventHandler<SyncBootState> Booting;
public SyncBootState GetSyncBootState()
{
return SyncBootState.Unknown;

View File

@@ -1,24 +1,20 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Umbraco.Core.Sync
namespace Umbraco.Core.Sync
{
public enum SyncBootState
{
/// <summary>
/// Unknown state. Treat as HasSyncState
/// Unknown state. Treat as WarmBoot
/// </summary>
Unknown = 0,
/// <summary>
/// Cold boot. No Sync state
/// </summary>
ColdBoot = 1,
/// <summary>
/// Warm boot. Sync state present
/// </summary>
HasSyncState = 2
WarmBoot = 2
}
}