Removes Booting event, backports lazy changes for PublishedSnapshotService from v9.

This commit is contained in:
Shannon
2021-05-24 12:32:57 -07:00
parent 43ee6f288e
commit be3bfe1b63
3 changed files with 119 additions and 110 deletions

View File

@@ -12,10 +12,5 @@ namespace Umbraco.Core.Sync
/// </summary>
/// <returns></returns>
SyncBootState GetSyncBootState();
/// <summary>
/// Raised when the boot state is known
/// </summary>
event EventHandler<SyncBootState> Booting; // TODO: This should be removed in netcore
}
}

View File

@@ -36,6 +36,8 @@ namespace Umbraco.Web.PublishedCache.NuCache
internal class PublishedSnapshotService : PublishedSnapshotServiceBase
{
private readonly PublishedSnapshotServiceOptions _options;
private readonly IMainDom _mainDom;
private readonly ServiceContext _serviceContext;
private readonly IPublishedContentTypeFactory _publishedContentTypeFactory;
private readonly IScopeProvider _scopeProvider;
@@ -50,12 +52,13 @@ namespace Umbraco.Web.PublishedCache.NuCache
private readonly IDefaultCultureAccessor _defaultCultureAccessor;
private readonly UrlSegmentProviderCollection _urlSegmentProviders;
// volatile because we read it with no lock
private volatile bool _isReady;
private bool _isReady;
private bool _isReadSet;
private object _isReadyLock;
private readonly ContentStore _contentStore;
private readonly ContentStore _mediaStore;
private readonly SnapDictionary<int, Domain> _domainStore;
private ContentStore _contentStore;
private ContentStore _mediaStore;
private SnapDictionary<int, Domain> _domainStore;
private readonly object _storesLock = new object();
private readonly object _elementsLock = new object();
@@ -88,9 +91,12 @@ namespace Umbraco.Web.PublishedCache.NuCache
ISyncBootStateAccessor syncBootStateAccessor)
: base(publishedSnapshotAccessor, variationContextAccessor)
{
//if (Interlocked.Increment(ref _singletonCheck) > 1)
// throw new Exception("Singleton must be instantiated only once!");
_options = options;
_mainDom = mainDom;
_serviceContext = serviceContext;
_publishedContentTypeFactory = publishedContentTypeFactory;
_dataSource = dataSource;
@@ -123,44 +129,6 @@ namespace Umbraco.Web.PublishedCache.NuCache
if (runtime.Level != RuntimeLevel.Run)
return;
// lock this entire call, we only want a single thread to be accessing the stores at once and within
// the call below to mainDom.Register, a callback may occur on a threadpool thread to MainDomRelease
// at the same time as we are trying to write to the stores. MainDomRelease also locks on _storesLock so
// it will not be able to close the stores until we are done populating (if the store is empty)
lock (_storesLock)
{
if (options.IgnoreLocalDb == false)
{
var registered = mainDom.Register(MainDomRegister, MainDomRelease);
// stores are created with a db so they can write to it, but they do not read from it,
// stores need to be populated, happens in OnResolutionFrozen which uses _localDbExists to
// figure out whether it can read the databases or it should populate them from sql
_logger.Info<PublishedSnapshotService,bool>("Creating the content store, localContentDbExists? {LocalContentDbExists}", _localContentDbExists);
_contentStore = new ContentStore(publishedSnapshotAccessor, variationContextAccessor, logger, _localContentDb);
_logger.Info<PublishedSnapshotService,bool>("Creating the media store, localMediaDbExists? {LocalMediaDbExists}", _localMediaDbExists);
_mediaStore = new ContentStore(publishedSnapshotAccessor, variationContextAccessor, logger, _localMediaDb);
}
else
{
_logger.Info<PublishedSnapshotService>("Creating the content store (local db ignored)");
_contentStore = new ContentStore(publishedSnapshotAccessor, variationContextAccessor, logger);
_logger.Info<PublishedSnapshotService>("Creating the media store (local db ignored)");
_mediaStore = new ContentStore(publishedSnapshotAccessor, variationContextAccessor, logger);
}
_domainStore = new SnapDictionary<int, Domain>();
_syncBootStateAccessor.Booting += (sender, args) =>
{
LoadCachesOnStartup(args);
};
}
Guid GetUid(ContentStore store, int id) => store.LiveSnapshot.Get(id)?.Uid ?? default;
int GetId(ContentStore store, Guid uid) => store.LiveSnapshot.Get(uid)?.Id ?? default;
if (idkMap != null)
{
idkMap.SetMapper(UmbracoObjectTypes.Document, id => GetUid(_contentStore, id), uid => GetId(_contentStore, uid));
@@ -168,6 +136,18 @@ namespace Umbraco.Web.PublishedCache.NuCache
}
}
private int GetId(ContentStore store, Guid uid)
{
EnsureCaches();
return store.LiveSnapshot.Get(uid)?.Id ?? default;
}
private Guid GetUid(ContentStore store, int id)
{
EnsureCaches();
return store.LiveSnapshot.Get(id)?.Uid ?? default;
}
/// <summary>
/// Install phase of <see cref="IMainDom"/>
/// </summary>
@@ -219,51 +199,82 @@ namespace Umbraco.Web.PublishedCache.NuCache
}
/// <summary>
/// Populates the stores
/// Lazily populates the stores only when they are first requested
/// </summary>
/// <remarks>This is called inside of a lock for _storesLock</remarks>
private void LoadCachesOnStartup(SyncBootState bootState)
{
// TODO: This is super ugly that this does this as part of the ctor.
// In netcore this will be different, the initialization will happen
// outside of the ctor.
var okContent = false;
var okMedia = false;
try
internal void EnsureCaches() => LazyInitializer.EnsureInitialized(
ref _isReady,
ref _isReadSet,
ref _isReadyLock,
() =>
{
if (bootState != SyncBootState.ColdBoot && _localContentDbExists)
// lock this entire call, we only want a single thread to be accessing the stores at once and within
// the call below to mainDom.Register, a callback may occur on a threadpool thread to MainDomRelease
// at the same time as we are trying to write to the stores. MainDomRelease also locks on _storesLock so
// it will not be able to close the stores until we are done populating (if the store is empty)
lock (_storesLock)
{
okContent = LockAndLoadContent(scope => LoadContentFromLocalDbLocked(true));
if (!okContent)
_logger.Warn<PublishedSnapshotService>("Loading content from local db raised warnings, will reload from database.");
if (!_options.IgnoreLocalDb)
{
var registered = _mainDom.Register(MainDomRegister, MainDomRelease);
// stores are created with a db so they can write to it, but they do not read from it,
// stores need to be populated, happens in OnResolutionFrozen which uses _localDbExists to
// figure out whether it can read the databases or it should populate them from sql
_logger.Info<PublishedSnapshotService, bool>("Creating the content store, localContentDbExists? {LocalContentDbExists}", _localContentDbExists);
_contentStore = new ContentStore(PublishedSnapshotAccessor, VariationContextAccessor, _logger, _localContentDb);
_logger.Info<PublishedSnapshotService, bool>("Creating the media store, localMediaDbExists? {LocalMediaDbExists}", _localMediaDbExists);
_mediaStore = new ContentStore(PublishedSnapshotAccessor, VariationContextAccessor, _logger, _localMediaDb);
}
else
{
_logger.Info<PublishedSnapshotService>("Creating the content store (local db ignored)");
_contentStore = new ContentStore(PublishedSnapshotAccessor, VariationContextAccessor, _logger);
_logger.Info<PublishedSnapshotService>("Creating the media store (local db ignored)");
_mediaStore = new ContentStore(PublishedSnapshotAccessor, VariationContextAccessor, _logger);
}
_domainStore = new SnapDictionary<int, Domain>();
SyncBootState bootState = _syncBootStateAccessor.GetSyncBootState();
var okContent = false;
var okMedia = false;
try
{
if (bootState != SyncBootState.ColdBoot && _localContentDbExists)
{
okContent = LockAndLoadContent(scope => LoadContentFromLocalDbLocked(true));
if (!okContent)
_logger.Warn<PublishedSnapshotService>("Loading content from local db raised warnings, will reload from database.");
}
if (bootState != SyncBootState.ColdBoot && _localMediaDbExists)
{
okMedia = LockAndLoadMedia(scope => LoadMediaFromLocalDbLocked(true));
if (!okMedia)
_logger.Warn<PublishedSnapshotService>("Loading media from local db raised warnings, will reload from database.");
}
if (!okContent)
LockAndLoadContent(scope => LoadContentFromDatabaseLocked(scope, true));
if (!okMedia)
LockAndLoadMedia(scope => LoadMediaFromDatabaseLocked(scope, true));
LockAndLoadDomains();
}
catch (Exception ex)
{
_logger.Fatal<PublishedSnapshotService>(ex, "Panic, exception while loading cache data.");
throw;
}
// finally, cache is ready!
return true;
}
if (bootState != SyncBootState.ColdBoot && _localMediaDbExists)
{
okMedia = LockAndLoadMedia(scope => LoadMediaFromLocalDbLocked(true));
if (!okMedia)
_logger.Warn<PublishedSnapshotService>("Loading media from local db raised warnings, will reload from database.");
}
if (!okContent)
LockAndLoadContent(scope => LoadContentFromDatabaseLocked(scope, true));
if (!okMedia)
LockAndLoadMedia(scope => LoadMediaFromDatabaseLocked(scope, true));
LockAndLoadDomains();
}
catch (Exception ex)
{
_logger.Fatal<PublishedSnapshotService>(ex, "Panic, exception while loading cache data.");
throw;
}
// finally, cache is ready!
_isReady = true;
}
});
private void InitializeRepositoryEvents()
{
@@ -1146,9 +1157,13 @@ namespace Umbraco.Web.PublishedCache.NuCache
public override IPublishedSnapshot CreatePublishedSnapshot(string previewToken)
{
EnsureCaches();
// no cache, no joy
if (_isReady == false)
if (Volatile.Read(ref _isReady) == false)
{
throw new InvalidOperationException("The published snapshot service has not properly initialized.");
}
var preview = previewToken.IsNullOrWhiteSpace() == false;
return new PublishedSnapshot(this, preview);
@@ -1159,6 +1174,8 @@ namespace Umbraco.Web.PublishedCache.NuCache
// even though the underlying elements may not change (store snapshots)
public PublishedSnapshot.PublishedSnapshotElements GetElements(bool previewDefault)
{
EnsureCaches();
// note: using ObjectCacheAppCache for elements and snapshot caches
// is not recommended because it creates an inner MemoryCache which is a heavy
// thing - better use a dictionary-based cache which "just" creates a concurrent
@@ -1805,6 +1822,8 @@ AND cmsContentNu.nodeId IS NULL
public void Collect()
{
EnsureCaches();
var contentCollect = _contentStore.CollectAsync();
var mediaCollect = _mediaStore.CollectAsync();
System.Threading.Tasks.Task.WaitAll(contentCollect, mediaCollect);
@@ -1814,8 +1833,17 @@ AND cmsContentNu.nodeId IS NULL
#region Internals/Testing
internal ContentStore GetContentStore() => _contentStore;
internal ContentStore GetMediaStore() => _mediaStore;
internal ContentStore GetContentStore()
{
EnsureCaches();
return _contentStore;
}
internal ContentStore GetMediaStore()
{
EnsureCaches();
return _mediaStore;
}
#endregion
}

View File

@@ -29,26 +29,10 @@ namespace Umbraco.Web.Search
_indexRebuilder = indexRebuilder;
_mainDom = mainDom;
_syncBootStateAccessor = syncBootStateAccessor;
// must add the handler in the ctor because it will be too late in Initialize
// TODO: All of this boot synchronization for cold boot logic needs should be fixed in netcore
_syncBootStateAccessor.Booting += _syncBootStateAccessor_Booting;
}
/// <summary>
/// Once the boot state is known we can see if we require rebuilds
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void _syncBootStateAccessor_Booting(object sender, SyncBootState e)
{
UmbracoModule.RouteAttempt += UmbracoModule_RouteAttempt;
}
private void UmbracoModule_RouteAttempt(object sender, RoutableAttemptEventArgs e)
{
UmbracoModule.RouteAttempt -= UmbracoModule_RouteAttempt;
if (!_initialized)
{
lock (_locker)
@@ -58,6 +42,8 @@ namespace Umbraco.Web.Search
{
_initialized = true;
UmbracoModule.RouteAttempt -= UmbracoModule_RouteAttempt;
if (!_mainDom.IsMainDom) return;
var bootState = _syncBootStateAccessor.GetSyncBootState();
@@ -74,12 +60,12 @@ namespace Umbraco.Web.Search
}
public void Initialize()
{
{
UmbracoModule.RouteAttempt += UmbracoModule_RouteAttempt;
}
public void Terminate()
{
_syncBootStateAccessor.Booting -= _syncBootStateAccessor_Booting;
}
}
}