V10: merge release branch 20220620 (#12590)

* Add Umbraco specific global usings

* Enable implicit usings

* v10: Wait for updated ConnectionStrings during install (#12536)

* Do not change/reload configuration

* Wait for updated connection string options

* recase assigndomain (#12448)

* Add depth property to ICoreScope (#12540)

* Remove ambient scope stack from httpcontext.items. (#12539)

This change makes it easier to use service calls in parallel whilst
a httpcontext is available.

* v10: Prefer SQLite primitive types to flexible types (#12541)

* Prefer SQLite primitive types to flexible types.

* SQLite - column mappings use TEXT for decimals

Thanks @mattbrailsford for sense check.

* Fix issue where languages files are not found in subdir of package dir (#12543)

* Make FindContent return type nullable (#12545)

* Updated nuget dependencies (07-06-2022) (#12525)

* Updated nuget dependencies

* Move Nerdbank.GitVersioning update to Directory.Build.props

* Updated more dependencies

* Improve FlagOutOfDateModels property behaviour.

(cherry picked from commit 54077725c373495fce0d3fbc5cdb6469aad3b676)

* Fix logic error WRT models builder flag out of date models. (#12548)

(cherry picked from commit 6b0149803a879d1c6902a5f61d1f2e9dc8545aac)

* Fixed issue with expected null value. (#12550)

Fixes https://github.com/umbraco/Umbraco-CMS/issues/12526

* Updated Examine to 3.0.0

* Fixes relation issue, when moving a root item to recycle bin, the "Relate Parent Media Folder On Delete"/"Relate Parent Document On Delete" cannot get the parent node type, because it is a fake root.

* Fix possible null error

* Bump version to 10.0.0 final

* Fix attempting to write lock files to LocalTempPath before it exists (#12563)

* Re fix usage statements

Co-authored-by: Ronald Barendse <ronald@barend.se>
Co-authored-by: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com>
Co-authored-by: Paul Johnson <pmj@umbraco.com>
Co-authored-by: Bjarke Berg <mail@bergmania.dk>
This commit is contained in:
Mole
2022-06-20 09:20:47 +02:00
committed by GitHub
parent e762fa91bc
commit 8ffede0441
43 changed files with 458 additions and 646 deletions

View File

@@ -0,0 +1,39 @@
using System.Collections.Concurrent;
using Umbraco.Cms.Core.Scoping;
namespace Umbraco.Cms.Infrastructure.Scoping;
internal class AmbientScopeContextStack : IAmbientScopeContextStack
{
private static AsyncLocal<ConcurrentStack<IScopeContext>> _stack = new();
public IScopeContext? AmbientContext
{
get
{
if (_stack.Value?.TryPeek(out IScopeContext? ambientContext) ?? false)
{
return ambientContext;
}
return null;
}
}
public IScopeContext Pop()
{
if (_stack.Value?.TryPop(out IScopeContext? ambientContext) ?? false)
{
return ambientContext;
}
throw new InvalidOperationException("No AmbientContext was found.");
}
public void Push(IScopeContext scope)
{
_stack.Value ??= new ConcurrentStack<IScopeContext>();
_stack.Value.Push(scope);
}
}

View File

@@ -0,0 +1,39 @@
using System.Collections.Concurrent;
namespace Umbraco.Cms.Infrastructure.Scoping
{
internal class AmbientScopeStack : IAmbientScopeStack
{
private static AsyncLocal<ConcurrentStack<IScope>> _stack = new ();
public IScope? AmbientScope
{
get
{
if (_stack.Value?.TryPeek(out IScope? ambientScope) ?? false)
{
return ambientScope;
}
return null;
}
}
public IScope Pop()
{
if (_stack.Value?.TryPop(out IScope? ambientScope) ?? false)
{
return ambientScope;
}
throw new InvalidOperationException("No AmbientScope was found.");
}
public void Push(IScope scope)
{
_stack.Value ??= new ConcurrentStack<IScope>();
_stack.Value.Push(scope);
}
}
}

View File

@@ -0,0 +1,10 @@
using Umbraco.Cms.Core.Scoping;
namespace Umbraco.Cms.Infrastructure.Scoping;
internal interface IAmbientScopeContextStack
{
IScopeContext? AmbientContext { get; }
IScopeContext Pop();
void Push(IScopeContext scope);
}

View File

@@ -0,0 +1,7 @@
namespace Umbraco.Cms.Infrastructure.Scoping;
internal interface IAmbientScopeStack : IScopeAccessor
{
IScope Pop();
void Push(IScope scope);
}

View File

@@ -33,7 +33,6 @@ namespace Umbraco.Cms.Infrastructure.Scoping
private readonly bool? _scopeFileSystem;
private readonly ScopeProvider _scopeProvider;
private bool _callContext;
private bool? _completed;
private IUmbracoDatabase? _database;
@@ -86,7 +85,6 @@ namespace Umbraco.Cms.Infrastructure.Scoping
_eventDispatcher = eventDispatcher;
_notificationPublisher = notificationPublisher;
_scopeFileSystem = scopeFileSystems;
_callContext = callContext;
_autoComplete = autoComplete;
Detachable = detachable;
_dictionaryLocker = new object();
@@ -248,24 +246,15 @@ namespace Umbraco.Cms.Infrastructure.Scoping
{
}
[Obsolete("Scopes are never stored on HttpContext.Items anymore, so CallContext is always true.")]
// a value indicating whether to force call-context
public bool CallContext
{
get
get => true;
set
{
if (_callContext)
{
return true;
}
if (ParentScope != null)
{
return ParentScope.CallContext;
}
return false;
// NOOP - always true.
}
set => _callContext = value;
}
public bool ScopedFileSystems
@@ -467,6 +456,19 @@ namespace Umbraco.Cms.Infrastructure.Scoping
}
}
public int Depth
{
get
{
if (ParentScope == null)
{
return 0;
}
return ParentScope.Depth + 1;
}
}
public IScopedNotificationPublisher Notifications
{
get
@@ -540,7 +542,7 @@ namespace Umbraco.Cms.Infrastructure.Scoping
}
}
_scopeProvider.PopAmbientScope(this); // might be null = this is how scopes are removed from context objects
_scopeProvider.PopAmbientScope(); // might be null = this is how scopes are removed from context objects
#if DEBUG_SCOPES
_scopeProvider.Disposed(this);
@@ -885,7 +887,7 @@ namespace Umbraco.Cms.Infrastructure.Scoping
// by Deploy which I don't fully understand since there is limited tests on this in the CMS
if (OrigScope != _scopeProvider.AmbientScope)
{
_scopeProvider.PopAmbientScope(_scopeProvider.AmbientScope);
_scopeProvider.PopAmbientScope();
}
if (OrigContext != _scopeProvider.AmbientContext)

View File

@@ -1,15 +1,17 @@
using System.Collections.Concurrent;
using System.Data;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Umbraco.Cms.Core.Cache;
using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Core.DistributedLocking;
using Umbraco.Cms.Core.Events;
using Umbraco.Cms.Core.IO;
using Umbraco.Cms.Core.Scoping;
using Umbraco.Cms.Infrastructure.Persistence;
using Umbraco.Cms.Web.Common.DependencyInjection;
using Umbraco.Extensions;
using CoreDebugSettings = Umbraco.Cms.Core.Configuration.Models.CoreDebugSettings;
#if DEBUG_SCOPES
using System.Linq;
using System.Text;
@@ -18,26 +20,52 @@ using System.Text;
namespace Umbraco.Cms.Infrastructure.Scoping
{
/// <summary>
/// Implements <see cref="IScopeProvider" />.
/// Implements <see cref="IScopeProvider"/>.
/// </summary>
internal class ScopeProvider :
ICoreScopeProvider,
IScopeProvider,
Core.Scoping.IScopeProvider,
IScopeAccessor
IScopeAccessor // TODO: No need to implement this here but literally hundreds of our tests cast ScopeProvider to ScopeAccessor
{
private readonly ILogger<ScopeProvider> _logger;
private readonly ILoggerFactory _loggerFactory;
private readonly IRequestCache _requestCache;
private readonly IEventAggregator _eventAggregator;
private readonly IAmbientScopeStack _ambientScopeStack;
private readonly IAmbientScopeContextStack _ambientContextStack;
private readonly FileSystems _fileSystems;
private CoreDebugSettings _coreDebugSettings;
private readonly MediaFileManager _mediaFileManager;
private static readonly AsyncLocal<ConcurrentStack<IScope>> _scopeStack = new();
private static readonly AsyncLocal<ConcurrentStack<IScopeContext>> _scopeContextStack = new();
private static readonly string _scopeItemKey = typeof(Scope).FullName!;
private static readonly string _contextItemKey = typeof(ScopeProvider).FullName!;
private readonly IEventAggregator _eventAggregator;
public ScopeProvider(
IAmbientScopeStack ambientScopeStack,
IAmbientScopeContextStack ambientContextStack,
IDistributedLockingMechanismFactory distributedLockingMechanismFactory,
IUmbracoDatabaseFactory databaseFactory,
FileSystems fileSystems,
IOptionsMonitor<CoreDebugSettings> coreDebugSettings,
MediaFileManager mediaFileManager,
ILoggerFactory loggerFactory,
IEventAggregator eventAggregator)
{
DistributedLockingMechanismFactory = distributedLockingMechanismFactory;
DatabaseFactory = databaseFactory;
_ambientScopeStack = ambientScopeStack;
_ambientContextStack = ambientContextStack;
_fileSystems = fileSystems;
_coreDebugSettings = coreDebugSettings.CurrentValue;
_mediaFileManager = mediaFileManager;
_logger = loggerFactory.CreateLogger<ScopeProvider>();
_loggerFactory = loggerFactory;
_eventAggregator = eventAggregator;
// take control of the FileSystems
_fileSystems.IsScoped = () => AmbientScope != null && AmbientScope.ScopedFileSystems;
coreDebugSettings.OnChange(x => _coreDebugSettings = x);
}
[Obsolete("Please use an alternative constructor. This constructor is due for removal in v12.")]
public ScopeProvider(
IDistributedLockingMechanismFactory distributedLockingMechanismFactory,
IUmbracoDatabaseFactory databaseFactory,
@@ -47,21 +75,17 @@ namespace Umbraco.Cms.Infrastructure.Scoping
ILoggerFactory loggerFactory,
IRequestCache requestCache,
IEventAggregator eventAggregator)
: this(
StaticServiceProvider.Instance.GetRequiredService<IAmbientScopeStack>(),
StaticServiceProvider.Instance.GetRequiredService<IAmbientScopeContextStack>(),
distributedLockingMechanismFactory,
databaseFactory,
fileSystems,
coreDebugSettings,
mediaFileManager,
loggerFactory,
eventAggregator)
{
DistributedLockingMechanismFactory = distributedLockingMechanismFactory;
DatabaseFactory = databaseFactory;
_fileSystems = fileSystems;
_coreDebugSettings = coreDebugSettings.CurrentValue;
_mediaFileManager = mediaFileManager;
_logger = loggerFactory.CreateLogger<ScopeProvider>();
_loggerFactory = loggerFactory;
_requestCache = requestCache;
_eventAggregator = eventAggregator;
// take control of the FileSystems
_fileSystems.IsScoped = () => AmbientScope != null && AmbientScope.ScopedFileSystems;
coreDebugSettings.OnChange(x => _coreDebugSettings = x);
}
public IDistributedLockingMechanismFactory DistributedLockingMechanismFactory { get; }
@@ -70,282 +94,32 @@ namespace Umbraco.Cms.Infrastructure.Scoping
public ISqlContext SqlContext => DatabaseFactory.SqlContext;
#region Context
private void MoveHttpContextScopeToCallContext()
{
var source = (ConcurrentStack<IScope>?)_requestCache.Get(_scopeItemKey);
ConcurrentStack<IScope>? stack = _scopeStack.Value;
MoveContexts(_scopeItemKey, source, stack, (_, v) => _scopeStack.Value = v);
}
private void MoveHttpContextScopeContextToCallContext()
{
var source = (ConcurrentStack<IScopeContext>?)_requestCache.Get(_contextItemKey);
ConcurrentStack<IScopeContext>? stack = _scopeContextStack.Value;
MoveContexts(_contextItemKey, source, stack, (_, v) => _scopeContextStack.Value = v);
}
private void MoveCallContextScopeToHttpContext()
{
ConcurrentStack<IScope>? source = _scopeStack.Value;
var stack = (ConcurrentStack<IScope>?)_requestCache.Get(_scopeItemKey);
MoveContexts(_scopeItemKey, source, stack, (k, v) => _requestCache.Set(k, v));
}
private void MoveCallContextScopeContextToHttpContext()
{
ConcurrentStack<IScopeContext>? source = _scopeContextStack.Value;
var stack = (ConcurrentStack<IScopeContext>?)_requestCache.Get(_contextItemKey);
MoveContexts(_contextItemKey, source, stack, (k, v) => _requestCache.Set(k, v));
}
private void MoveContexts<T>(string key, ConcurrentStack<T>? source, ConcurrentStack<T>? stack, Action<string, ConcurrentStack<T>> setter)
where T : class, IInstanceIdentifiable
{
if (source == null)
{
return;
}
if (stack != null)
{
stack.Clear();
}
else
{
// TODO: This isn't going to copy it back up the execution context chain
stack = new ConcurrentStack<T>();
setter(key, stack);
}
var arr = new T[source.Count];
source.CopyTo(arr, 0);
Array.Reverse(arr);
foreach (T a in arr)
{
stack.Push(a);
}
source.Clear();
}
private void SetCallContextScope(IScope? value)
{
ConcurrentStack<IScope>? stack = _scopeStack.Value;
#if DEBUG_SCOPES
// first, null-register the existing value
if (stack != null && stack.TryPeek(out IScope ambientScope))
{
RegisterContext(ambientScope, null);
}
// then register the new value
if (value != null)
{
RegisterContext(value, "call");
}
#endif
if (value == null)
{
if (stack != null)
{
stack.TryPop(out _);
}
}
else
{
#if DEBUG_SCOPES
_logger.LogDebug("AddObject " + value.InstanceId.ToString("N").Substring(0, 8));
#endif
if (stack == null)
{
stack = new ConcurrentStack<IScope>();
}
stack.Push(value);
_scopeStack.Value = stack;
}
}
private void SetCallContextScopeContext(IScopeContext? value)
{
ConcurrentStack<IScopeContext>? stack = _scopeContextStack.Value;
if (value == null)
{
if (stack != null)
{
stack.TryPop(out _);
}
}
else
{
if (stack == null)
{
stack = new ConcurrentStack<IScopeContext>();
}
stack.Push(value);
_scopeContextStack.Value = stack;
}
}
private T? GetHttpContextObject<T>(string key, bool required = true)
where T : class
{
if (!_requestCache.IsAvailable && required)
{
throw new Exception("Request cache is unavailable.");
}
var stack = (ConcurrentStack<T>?)_requestCache.Get(key);
return stack != null && stack.TryPeek(out T? peek) ? peek : null;
}
private bool SetHttpContextObject<T>(string key, T? value, bool required = true)
{
if (!_requestCache.IsAvailable)
{
if (required)
{
throw new Exception("Request cache is unavailable.");
}
return false;
}
#if DEBUG_SCOPES
// manage the 'context' that contains the scope (null, "http" or "call")
// only for scopes of course!
if (key == s_scopeItemKey)
{
// first, null-register the existing value
var ambientScope = (IScope)_requestCache.Get(s_scopeItemKey);
if (ambientScope != null)
{
RegisterContext(ambientScope, null);
}
// then register the new value
if (value is IScope scope)
{
RegisterContext(scope, "http");
}
}
#endif
var stack = (ConcurrentStack<T>?)_requestCache.Get(key);
if (value == null)
{
if (stack != null)
{
stack.TryPop(out _);
}
}
else
{
if (stack == null)
{
stack = new ConcurrentStack<T>();
}
stack.Push(value);
_requestCache.Set(key, stack);
}
return true;
}
#endregion
#region Ambient Context
/// <summary>
/// Get the Ambient (Current) <see cref="IScopeContext" /> for the current execution context.
/// Get the Ambient (Current) <see cref="IScopeContext"/> for the current execution context.
/// </summary>
/// <remarks>
/// The current execution context may be request based (HttpContext) or on a background thread (AsyncLocal)
/// The current execution context may be request based (HttpContext) or on a background thread (AsyncLocal)
/// </remarks>
public IScopeContext? AmbientContext
{
get
{
// try http context, fallback onto call context
IScopeContext? value = GetHttpContextObject<IScopeContext>(_contextItemKey, false);
if (value != null)
{
return value;
}
ConcurrentStack<IScopeContext>? stack = _scopeContextStack.Value;
if (stack == null || !stack.TryPeek(out IScopeContext? peek))
{
return null;
}
return peek;
}
}
public IScopeContext? AmbientContext => _ambientContextStack.AmbientContext;
#endregion
#region Ambient Scope
IScope? IScopeAccessor.AmbientScope => AmbientScope;
/// <summary>
/// Gets or set the Ambient (Current) <see cref="Scope" /> for the current execution context.
/// Gets or set the Ambient (Current) <see cref="Scope"/> for the current execution context.
/// </summary>
/// <remarks>
/// The current execution context may be request based (HttpContext) or on a background thread (AsyncLocal)
/// The current execution context may be request based (HttpContext) or on a background thread (AsyncLocal)
/// </remarks>
public Scope? AmbientScope
{
get
{
// try http context, fallback onto call context
IScope? value = GetHttpContextObject<IScope>(_scopeItemKey, false);
if (value != null)
{
return (Scope)value;
}
public Scope? AmbientScope => (Scope?)_ambientScopeStack.AmbientScope;
ConcurrentStack<IScope>? stack = _scopeStack.Value;
if (stack == null || !stack.TryPeek(out IScope? peek))
{
return null;
}
IScope? IScopeAccessor.AmbientScope => _ambientScopeStack.AmbientScope;
return (Scope)peek;
}
}
public void PopAmbientScope(Scope? scope)
{
// pop the stack from all contexts
SetHttpContextObject<IScope>(_scopeItemKey, null, false);
SetCallContextScope(null);
// We need to move the stack to a different context if the parent scope
// is flagged with a different CallContext flag. This is required
// if creating a child scope with callContext: true (thus forcing CallContext)
// when there is actually a current HttpContext available.
// It's weird but is required for Deploy somehow.
var parentScopeCallContext = scope?.ParentScope?.CallContext ?? false;
if ((scope?.CallContext ?? false) && !parentScopeCallContext)
{
MoveCallContextScopeToHttpContext();
MoveCallContextScopeContextToHttpContext();
}
else if ((!scope?.CallContext ?? false) && parentScopeCallContext)
{
MoveHttpContextScopeToCallContext();
MoveHttpContextScopeContextToCallContext();
}
}
public void PopAmbientScope() => _ambientScopeStack.Pop();
#endregion
@@ -356,21 +130,7 @@ namespace Umbraco.Cms.Infrastructure.Scoping
throw new ArgumentNullException(nameof(scope));
}
if (scope.CallContext || !SetHttpContextObject<IScope>(_scopeItemKey, scope, false))
{
// In this case, always ensure that the HttpContext items
// is transfered to CallContext and then cleared since we
// may be migrating context with the callContext = true flag.
// This is a weird case when forcing callContext when HttpContext
// is available. Required by Deploy.
if (_requestCache.IsAvailable)
{
MoveHttpContextScopeToCallContext();
MoveHttpContextScopeContextToCallContext();
}
SetCallContextScope(scope);
}
_ambientScopeStack.Push(scope);
}
public void PushAmbientScopeContext(IScopeContext? scopeContext)
@@ -379,17 +139,10 @@ namespace Umbraco.Cms.Infrastructure.Scoping
{
throw new ArgumentNullException(nameof(scopeContext));
}
SetHttpContextObject(_contextItemKey, scopeContext, false);
SetCallContextScopeContext(scopeContext);
_ambientContextStack.Push(scopeContext);
}
public void PopAmbientScopeContext()
{
// pop stack from all contexts
SetHttpContextObject<IScopeContext>(_contextItemKey, null, false);
SetCallContextScopeContext(null);
}
public void PopAmbientScopeContext() => _ambientContextStack.Pop();
/// <inheritdoc />
public IScope CreateDetachedScope(
@@ -398,21 +151,7 @@ namespace Umbraco.Cms.Infrastructure.Scoping
IEventDispatcher? eventDispatcher = null,
IScopedNotificationPublisher? scopedNotificationPublisher = null,
bool? scopeFileSystems = null)
=>
new Scope(
this,
_coreDebugSettings,
_mediaFileManager,
_eventAggregator,
_loggerFactory.CreateLogger<Scope>(),
_fileSystems,
true,
null,
isolationLevel,
repositoryCacheMode,
eventDispatcher,
scopedNotificationPublisher,
scopeFileSystems);
=> new Scope(this, _coreDebugSettings, _mediaFileManager, _eventAggregator, _loggerFactory.CreateLogger<Scope>(), _fileSystems, true, null, isolationLevel, repositoryCacheMode, eventDispatcher, scopedNotificationPublisher, scopeFileSystems);
/// <inheritdoc />
public void AttachScope(IScope other, bool callContext = false)
@@ -457,20 +196,18 @@ namespace Umbraco.Cms.Infrastructure.Scoping
throw new InvalidOperationException("Ambient scope is not detachable.");
}
PopAmbientScope(ambientScope);
PopAmbientScope();
PopAmbientScopeContext();
Scope? originalScope = AmbientScope;
if (originalScope != ambientScope.OrigScope)
{
throw new InvalidOperationException(
$"The detatched scope ({ambientScope.GetDebugInfo()}) does not match the original ({originalScope?.GetDebugInfo()})");
throw new InvalidOperationException($"The detatched scope ({ambientScope.GetDebugInfo()}) does not match the original ({originalScope?.GetDebugInfo()})");
}
IScopeContext? originalScopeContext = AmbientContext;
if (originalScopeContext != ambientScope.OrigContext)
{
throw new InvalidOperationException("The detatched scope context does not match the original");
throw new InvalidOperationException($"The detatched scope context does not match the original");
}
ambientScope.OrigScope = null;
@@ -494,22 +231,7 @@ namespace Umbraco.Cms.Infrastructure.Scoping
{
IScopeContext? ambientContext = AmbientContext;
ScopeContext? newContext = ambientContext == null ? new ScopeContext() : null;
var scope = new Scope(
this,
_coreDebugSettings,
_mediaFileManager,
_eventAggregator,
_loggerFactory.CreateLogger<Scope>(),
_fileSystems,
false,
newContext,
isolationLevel,
repositoryCacheMode,
eventDispatcher,
notificationPublisher,
scopeFileSystems,
callContext,
autoComplete);
var scope = new Scope(this, _coreDebugSettings, _mediaFileManager, _eventAggregator, _loggerFactory.CreateLogger<Scope>(), _fileSystems, false, newContext, isolationLevel, repositoryCacheMode, eventDispatcher, notificationPublisher, scopeFileSystems, callContext, autoComplete);
// assign only if scope creation did not throw!
PushAmbientScope(scope);
@@ -517,34 +239,17 @@ namespace Umbraco.Cms.Infrastructure.Scoping
{
PushAmbientScopeContext(newContext);
}
return scope;
}
var nested = new Scope(
this,
_coreDebugSettings,
_mediaFileManager,
_eventAggregator,
_loggerFactory.CreateLogger<Scope>(),
_fileSystems,
ambientScope,
isolationLevel,
repositoryCacheMode,
eventDispatcher,
notificationPublisher,
scopeFileSystems,
callContext,
autoComplete);
var nested = new Scope(this, _coreDebugSettings, _mediaFileManager, _eventAggregator, _loggerFactory.CreateLogger<Scope>(), _fileSystems, ambientScope, isolationLevel, repositoryCacheMode, eventDispatcher, notificationPublisher, scopeFileSystems, callContext, autoComplete);
PushAmbientScope(nested);
return nested;
}
/// <inheritdoc />
public IScopeContext? Context => AmbientContext;
// for testing
internal ConcurrentStack<IScope>? GetCallContextScopeValue() => _scopeStack.Value;
#if DEBUG_SCOPES
// this code needs TLC
//