Merge remote-tracking branch 'origin/v10/dev' into v10/feature/nullable-reference-types-in-Umbraco.Web.Backoffice

# Conflicts:
#	src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Collections.cs
#	src/Umbraco.Core/Extensions/PublishedContentExtensions.cs
#	src/Umbraco.Core/Telemetry/Models/TelemetryReportData.cs
#	src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/MultiUrlPickerValueConverter.cs
#	src/Umbraco.PublishedCache.NuCache/ContentStore.cs
#	src/Umbraco.Web.BackOffice/Trees/MemberTypeTreeController.cs
#	src/Umbraco.Web.Common/ModelsBuilder/InMemoryModelFactory.cs
#	src/Umbraco.Web.Common/Security/MemberManager.cs
#	src/Umbraco.Web.Website/Routing/ControllerActionSearcher.cs
#	src/Umbraco.Web.Website/Routing/IControllerActionSearcher.cs
#	src/Umbraco.Web.Website/Routing/UmbracoRouteValueTransformer.cs
This commit is contained in:
Nikolaj Geisle
2022-04-21 10:26:51 +02:00
172 changed files with 4578 additions and 1710 deletions

View File

@@ -344,8 +344,8 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache
var node = link.Value;
if (node == null) continue;
var contentTypeId = node.ContentType?.Id;
if (contentTypeId is null || index.TryGetValue(contentTypeId.Value, out var contentType) == false) continue;
SetValueLocked(_contentNodes, node.Id, new ContentNode(node, _publishedModelFactory, contentType));
if (contentTypeId is null || index.TryGetValue(contentTypeId, out var contentType) == false) continue;
SetValueLocked(_contentNodes, node.Id, new ContentNode(node, _publishedModelFactory, contentType));
}
}
@@ -450,10 +450,18 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache
refreshedIdsA.Contains(x.ContentTypeId) &&
BuildKit(x, out _)))
{
// replacing the node: must preserve the parents
// replacing the node: must preserve the relations
var node = GetHead(_contentNodes, kit.Node.Id)?.Value;
if (node != null)
{
// Preserve children
kit.Node.FirstChildContentId = node.FirstChildContentId;
kit.Node.LastChildContentId = node.LastChildContentId;
// Also preserve siblings
kit.Node.NextSiblingContentId = node.NextSiblingContentId;
kit.Node.PreviousSiblingContentId = node.PreviousSiblingContentId;
}
SetValueLocked(_contentNodes, kit.Node.Id, kit.Node);
@@ -514,7 +522,7 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache
_contentNodes.TryGetValue(id, out var link);
if (link?.Value == null)
continue;
var node = new ContentNode(link.Value, _publishedModelFactory, contentType);
var node = new ContentNode(link.Value, _publishedModelFactory, contentType);
SetValueLocked(_contentNodes, id, node);
if (_localDb != null) RegisterChange(id, node.ToKit());
}
@@ -631,12 +639,12 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache
// moving?
var moving = existing != null && existing.ParentContentId != kit.Node.ParentContentId;
// manage children
if (existing != null)
{
kit.Node.FirstChildContentId = existing.FirstChildContentId;
kit.Node.LastChildContentId = existing.LastChildContentId;
}
// manage children
if (existing != null)
{
kit.Node.FirstChildContentId = existing.FirstChildContentId;
kit.Node.LastChildContentId = existing.LastChildContentId;
}
// set
SetValueLocked(_contentNodes, kit.Node.Id, kit.Node);
@@ -698,7 +706,36 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache
/// <exception cref="InvalidOperationException">
/// Thrown if this method is not called within a write lock
/// </exception>
[Obsolete("Use the overload that takes a 'kitGroupSize' parameter instead")]
public bool SetAllFastSortedLocked(IEnumerable<ContentNodeKit> kits, bool fromDb)
{
return SetAllFastSortedLocked(kits, 1, fromDb);
}
/// <summary>
/// Builds all kits on startup using a fast forward only cursor
/// </summary>
/// <param name="kits">
/// All kits sorted by Level + Parent Id + Sort order
/// </param>
/// <param name="kitGroupSize"></param>
/// <param name="fromDb">True if the data is coming from the database (not the local cache db)</param>
/// <returns></returns>
/// <remarks>
/// <para>
/// This requires that the collection is sorted by Level + ParentId + Sort Order.
/// This should be used only on a site startup as the first generations.
/// This CANNOT be used after startup since it bypasses all checks for Generations.
/// </para>
/// <para>
/// This methods MUST be called from within a write lock, normally wrapped within GetScopedWriteLock
/// otherwise an exception will occur.
/// </para>
/// </remarks>
/// <exception cref="InvalidOperationException">
/// Thrown if this method is not called within a write lock
/// </exception>
public bool SetAllFastSortedLocked(IEnumerable<ContentNodeKit> kits, int kitGroupSize, bool fromDb)
{
EnsureLocked();
@@ -716,54 +753,67 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache
ContentNode? previousNode = null;
ContentNode? parent = null;
foreach (var kit in kits)
// By using InGroupsOf() here we are forcing the database query result extraction to retrieve items in batches,
// reducing the possibility of a database timeout (ThreadAbortException) on large datasets.
// This in turn reduces the possibility that the NuCache file will remain locked, because an exception
// here results in the calling method to not release the lock.
// However the larger the batck size, the more content loaded into memory. So by default, this is set to 1 and can be increased by setting
// the configuration setting Umbraco:CMS:NuCache:KitPageSize to a higher value.
// If we are not loading from the database, then we can ignore this restriction.
foreach (var kitGroup in kits.InGroupsOf(!fromDb || kitGroupSize < 1 ? 1 : kitGroupSize))
{
if (!BuildKit(kit, out var parentLink))
foreach (var kit in kitGroup)
{
ok = false;
continue; // skip that one
if (!BuildKit(kit, out var parentLink))
{
ok = false;
continue; // skip that one
}
var thisNode = kit.Node;
if (parent == null)
{
// first parent
parent = parentLink.Value;
parent!.FirstChildContentId = thisNode.Id; // this node is the first node
}
else if (parent.Id != parentLink.Value!.Id)
{
// new parent
parent = parentLink.Value;
parent.FirstChildContentId = thisNode.Id; // this node is the first node
previousNode = null; // there is no previous sibling
}
if (_logger.IsEnabled(LogLevel.Debug))
{
_logger.LogDebug("Set {thisNodeId} with parent {thisNodeParentContentId}", thisNode.Id, thisNode.ParentContentId);
}
SetValueLocked(_contentNodes, thisNode.Id, thisNode);
// if we are initializing from the database source ensure the local db is updated
if (fromDb && _localDb != null) RegisterChange(thisNode.Id, kit);
// this node is always the last child
parent.LastChildContentId = thisNode.Id;
// wire previous node as previous sibling
if (previousNode != null)
{
previousNode.NextSiblingContentId = thisNode.Id;
thisNode.PreviousSiblingContentId = previousNode.Id;
}
// this node becomes the previous node
previousNode = thisNode;
_contentKeyToIdMap[kit.Node.Uid] = kit.Node.Id;
}
var thisNode = kit.Node;
if (parent == null)
{
// first parent
parent = parentLink.Value;
parent!.FirstChildContentId = thisNode.Id; // this node is the first node
}
else if (parent.Id != parentLink.Value!.Id)
{
// new parent
parent = parentLink.Value;
parent.FirstChildContentId = thisNode.Id; // this node is the first node
previousNode = null; // there is no previous sibling
}
if (_logger.IsEnabled(LogLevel.Debug))
{
_logger.LogDebug("Set {thisNodeId} with parent {thisNodeParentContentId}", thisNode.Id, thisNode.ParentContentId);
}
SetValueLocked(_contentNodes, thisNode.Id, thisNode);
// if we are initializing from the database source ensure the local db is updated
if (fromDb && _localDb != null) RegisterChange(thisNode.Id, kit);
// this node is always the last child
parent.LastChildContentId = thisNode.Id;
// wire previous node as previous sibling
if (previousNode != null)
{
previousNode.NextSiblingContentId = thisNode.Id;
thisNode.PreviousSiblingContentId = previousNode.Id;
}
// this node becomes the previous node
previousNode = thisNode;
_contentKeyToIdMap[kit.Node.Uid] = kit.Node.Id;
}
return ok;
@@ -781,7 +831,27 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache
/// <exception cref="InvalidOperationException">
/// Thrown if this method is not called within a write lock
/// </exception>
[Obsolete("Use the overload that takes the 'kitGroupSize' and 'fromDb' parameters instead")]
public bool SetAllLocked(IEnumerable<ContentNodeKit> kits)
{
return SetAllLocked(kits, 1, false);
}
/// <summary>
/// Set all data for a collection of <see cref="ContentNodeKit"/>
/// </summary>
/// <param name="kits"></param>
/// <param name="kitGroupSize"></param>
/// <param name="fromDb">True if the data is coming from the database (not the local cache db)</param>
/// <returns></returns>
/// <remarks>
/// This methods MUST be called from within a write lock, normally wrapped within GetScopedWriteLock
/// otherwise an exception will occur.
/// </remarks>
/// <exception cref="InvalidOperationException">
/// Thrown if this method is not called within a write lock
/// </exception>
public bool SetAllLocked(IEnumerable<ContentNodeKit> kits, int kitGroupSize, bool fromDb)
{
EnsureLocked();
@@ -794,25 +864,37 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache
//ClearLocked(_contentTypesById);
//ClearLocked(_contentTypesByAlias);
foreach (var kit in kits)
// By using InGroupsOf() here we are forcing the database query result extraction to retrieve items in batches,
// reducing the possibility of a database timeout (ThreadAbortException) on large datasets.
// This in turn reduces the possibility that the NuCache file will remain locked, because an exception
// here results in the calling method to not release the lock.
// However the larger the batck size, the more content loaded into memory. So by default, this is set to 1 and can be increased by setting
// the configuration setting Umbraco:CMS:NuCache:KitPageSize to a higher value.
// If we are not loading from the database, then we can ignore this restriction.
foreach (var kitGroup in kits.InGroupsOf(!fromDb || kitGroupSize < 1 ? 1 : kitGroupSize))
{
if (!BuildKit(kit, out var parent))
foreach (var kit in kitGroup)
{
ok = false;
continue; // skip that one
if (!BuildKit(kit, out var parent))
{
ok = false;
continue; // skip that one
}
if (_logger.IsEnabled(LogLevel.Debug))
{
_logger.LogDebug("Set {kitNodeId} with parent {kitNodeParentContentId}", kit.Node.Id, kit.Node.ParentContentId);
}
SetValueLocked(_contentNodes, kit.Node.Id, kit.Node);
if (_localDb != null) RegisterChange(kit.Node.Id, kit);
AddTreeNodeLocked(kit.Node, parent);
_contentKeyToIdMap[kit.Node.Uid] = kit.Node.Id;
}
if (_logger.IsEnabled(LogLevel.Debug))
{
_logger.LogDebug("Set {kitNodeId} with parent {kitNodeParentContentId}", kit.Node.Id, kit.Node.ParentContentId);
}
SetValueLocked(_contentNodes, kit.Node.Id, kit.Node);
if (_localDb != null) RegisterChange(kit.Node.Id, kit);
AddTreeNodeLocked(kit.Node, parent);
_contentKeyToIdMap[kit.Node.Uid] = kit.Node.Id;
}
return ok;

View File

@@ -337,8 +337,19 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache
_localContentDb?.Clear();
// IMPORTANT GetAllContentSources sorts kits by level + parentId + sortOrder
var kits = _publishedContentService.GetAllContentSources();
return onStartup ? _contentStore.SetAllFastSortedLocked(kits, true) : _contentStore.SetAllLocked(kits);
try
{
var kits = _publishedContentService.GetAllContentSources();
return onStartup ? _contentStore.SetAllFastSortedLocked(kits, _config.KitBatchSize, true) : _contentStore.SetAllLocked(kits, _config.KitBatchSize, true);
}
catch (ThreadAbortException tae)
{
// Caught a ThreadAbortException, most likely from a database timeout.
// If we don't catch it here, the whole local cache can remain locked causing widespread panic (see above comment).
_logger.LogWarning(tae, tae.Message);
}
return false;
}
}
@@ -385,8 +396,20 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache
_logger.LogDebug("Loading media from database...");
// IMPORTANT GetAllMediaSources sorts kits by level + parentId + sortOrder
var kits = _publishedContentService.GetAllMediaSources();
return onStartup ? _mediaStore.SetAllFastSortedLocked(kits, true) : _mediaStore.SetAllLocked(kits);
try
{
var kits = _publishedContentService.GetAllMediaSources();
return onStartup ? _mediaStore.SetAllFastSortedLocked(kits, _config.KitBatchSize, true) : _mediaStore.SetAllLocked(kits, _config.KitBatchSize, true);
}
catch (ThreadAbortException tae)
{
// Caught a ThreadAbortException, most likely from a database timeout.
// If we don't catch it here, the whole local cache can remain locked causing widespread panic (see above comment).
_logger.LogWarning(tae, tae.Message);
}
return false;
}
}
@@ -434,7 +457,7 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache
return false;
}
return onStartup ? store.SetAllFastSortedLocked(kits, false) : store.SetAllLocked(kits);
return onStartup ? store.SetAllFastSortedLocked(kits, _config.KitBatchSize, false) : store.SetAllLocked(kits, _config.KitBatchSize, false);
}
private void LockAndLoadDomains()