Merge remote-tracking branch 'origin/netcore/netcore' into netcore/members-userstore

This commit is contained in:
Bjarke Berg
2021-01-26 08:25:31 +01:00
14 changed files with 113 additions and 128 deletions

View File

@@ -1,14 +1,11 @@
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Common;
using System.Data.SqlClient;
using System.Linq;
using Microsoft.Extensions.Logging;
using NPoco;
using Umbraco.Core.Logging;
using Umbraco.Core.Persistence.DatabaseModelDefinitions;
using Umbraco.Core.Scoping;
namespace Umbraco.Core.Persistence.SqlSyntax
{
@@ -260,7 +257,7 @@ where tbl.[name]=@0 and col.[name]=@1;", tableName, columnName)
public override void WriteLock(IDatabase db, params int[] lockIds)
{
WriteLock(db, TimeSpan.FromMilliseconds(1800), lockIds);
WriteLock(db, TimeSpan.FromSeconds(5), lockIds);
}
public void WriteLock(IDatabase db, TimeSpan timeout, params int[] lockIds)

View File

@@ -12,12 +12,11 @@ using Umbraco.Core.DependencyInjection;
using Umbraco.Core.Events;
using Umbraco.Core.Models.PublishedContent;
using Umbraco.ModelsBuilder.Embedded.Building;
using Umbraco.Web.Common.DependencyInjection;
using Umbraco.Web.WebAssets;
/*
* OVERVIEW:
*
*
* The CSharpCompiler is responsible for the actual compilation of razor at runtime.
* It creates a CSharpCompilation instance to do the compilation. This is where DLL references
* are applied. However, the way this works is not flexible for dynamic assemblies since the references
@@ -27,7 +26,7 @@ using Umbraco.Web.WebAssets;
* RazorReferenceManager. Unfortunately this is also internal and cannot be replaced, though it can be extended
* using MvcRazorRuntimeCompilationOptions, except this is the place where references are only loaded once which
* is done with a LazyInitializer. See https://github.com/dotnet/aspnetcore/blob/master/src/Mvc/Mvc.Razor.RuntimeCompilation/src/RazorReferenceManager.cs#L35.
*
*
* The way that RazorReferenceManager works is by resolving references from the ApplicationPartsManager - either by
* an application part that is specifically an ICompilationReferencesProvider or an AssemblyPart. So to fulfill this
* requirement, we add the MB assembly to the assembly parts manager within the PureLiveModelFactory when the assembly
@@ -35,25 +34,25 @@ using Umbraco.Web.WebAssets;
* have already been resolved with the LazyInitializer in the RazorReferenceManager. There is a known public API
* where you can add reference paths to the runtime razor compiler via it's IOptions: MvcRazorRuntimeCompilationOptions
* however this falls short too because those references are just loaded via the RazorReferenceManager and lazy initialized.
*
*
* The services that can be replaced are: IViewCompilerProvider (default is the internal RuntimeViewCompilerProvider) and
* IViewCompiler (default is the internal RuntimeViewCompiler). There is one specific public extension point that I was
* hoping would solve all of the problems which was IMetadataReferenceFeature (implemented by LazyMetadataReferenceFeature
* which uses RazorReferencesManager) which is a razor feature that you can add
* to the RazorProjectEngine. It is used to resolve roslyn references and by default is backed by RazorReferencesManager.
* Unfortunately, this service is not used by the CSharpCompiler, it seems to only be used by some tag helper compilations.
*
*
* There are caches at several levels, all of which are not publicly accessible APIs (apart from RazorViewEngine.ViewLookupCache
* which is possible to clear by casting and then calling cache.Compact(100); but that doesn't get us far enough).
*
*
* For this to work, several caches must be cleared:
* - RazorViewEngine.ViewLookupCache
* - RazorReferencesManager._compilationReferences
* - RazorPageActivator._activationInfo (though this one may be optional)
* - RuntimeViewCompiler._cache
*
*
* What are our options?
*
*
* a) We can copy a ton of code into our application: CSharpCompiler, RuntimeViewCompilerProvider, RuntimeViewCompiler and
* RazorReferenceManager (probably more depending on the extent of Internal references).
* b) We can use reflection to try to access all of the above resources and try to forcefully clear caches and reset initialization flags.
@@ -62,7 +61,7 @@ using Umbraco.Web.WebAssets;
* services from scratch which means there is no caches.
*
* ... Option C works, we will use that but need to verify how this affects memory since ideally the old services will be GC'd.
*
*
* Option C, how its done:
* - Before we add our custom razor services to the container, we make a copy of the services collection which is the snapshot of registered services
* with razor defaults before ours are added.
@@ -93,6 +92,7 @@ namespace Umbraco.ModelsBuilder.Embedded.DependencyInjection
builder.AddNotificationHandler<UmbracoApplicationStarting, ModelsBuilderNotificationHandler>();
builder.AddNotificationHandler<ServerVariablesParsing, ModelsBuilderNotificationHandler>();
builder.AddNotificationHandler<UmbracoApplicationStarting, LiveModelsProvider>();
builder.AddNotificationHandler<UmbracoRequestEnd, LiveModelsProvider>();
builder.AddNotificationHandler<UmbracoApplicationStarting, OutOfDateModelsStatus>();
builder.Services.AddUnique<ModelsGenerator>();
builder.Services.AddUnique<LiveModelsProvider>();

View File

@@ -1,6 +1,5 @@
using System;
using System.Threading;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Umbraco.Configuration;
@@ -10,19 +9,17 @@ using Umbraco.Core.Events;
using Umbraco.Extensions;
using Umbraco.ModelsBuilder.Embedded.Building;
using Umbraco.Web.Cache;
using Umbraco.Web.Common.Lifetime;
namespace Umbraco.ModelsBuilder.Embedded
{
// supports LiveAppData - but not PureLive
public sealed class LiveModelsProvider : INotificationHandler<UmbracoApplicationStarting>
public sealed class LiveModelsProvider : INotificationHandler<UmbracoApplicationStarting>, INotificationHandler<UmbracoRequestEnd>
{
private static int s_req;
private readonly ILogger<LiveModelsProvider> _logger;
private readonly ModelsBuilderSettings _config;
private readonly ModelsGenerator _modelGenerator;
private readonly ModelsGenerationError _mbErrors;
private readonly IUmbracoRequestLifetime _umbracoRequestLifetime;
private readonly IMainDom _mainDom;
/// <summary>
@@ -33,14 +30,12 @@ namespace Umbraco.ModelsBuilder.Embedded
IOptions<ModelsBuilderSettings> config,
ModelsGenerator modelGenerator,
ModelsGenerationError mbErrors,
IUmbracoRequestLifetime umbracoRequestLifetime,
IMainDom mainDom)
{
_logger = logger;
_config = config.Value ?? throw new ArgumentNullException(nameof(config));
_modelGenerator = modelGenerator;
_mbErrors = mbErrors;
_umbracoRequestLifetime = umbracoRequestLifetime;
_mainDom = mainDom;
}
@@ -68,8 +63,6 @@ namespace Umbraco.ModelsBuilder.Embedded
// and we also don't generate models.
_mainDom.Register(() =>
{
_umbracoRequestLifetime.RequestEnd += (sender, context) => AppEndRequest(context);
// anything changes, and we want to re-generate models.
ContentTypeCacheRefresher.CacheUpdated += RequestModelsGeneration;
DataTypeCacheRefresher.CacheUpdated += RequestModelsGeneration;
@@ -125,19 +118,12 @@ namespace Umbraco.ModelsBuilder.Embedded
}
}
private void AppEndRequest(HttpContext context)
public void Handle(UmbracoRequestEnd notification)
{
if (context.Request.IsClientSideRequest())
if (IsEnabled && _mainDom.IsMainDom && !notification.HttpContext.Request.IsClientSideRequest())
{
return;
GenerateModelsIfRequested();
}
if (!IsEnabled)
{
return;
}
GenerateModelsIfRequested();
}
}
}

View File

@@ -202,8 +202,11 @@ namespace Umbraco.Tests.Persistence
thread1.Join();
thread2.Join();
Assert.IsNotNull(e1);
AssertIsSqlLockException(e1);
//Assert.IsNotNull(e1);
if (e1 != null)
{
AssertIsSqlLockException(e1);
}
// the assertion below depends on timing conditions - on a fast enough environment,
// thread1 dies (deadlock) and frees thread2, which succeeds - however on a slow
@@ -240,7 +243,7 @@ namespace Umbraco.Tests.Persistence
if (id1 == 1)
otherEv.WaitOne();
else
Thread.Sleep(200); // cannot wait due to deadlock... just give it a bit of time
Thread.Sleep(5200); // wait for deadlock...
Console.WriteLine($"[{id1}] WAIT {id2}");
scope.WriteLock(id2);

View File

@@ -4,13 +4,13 @@ using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Extensions;
using Microsoft.Extensions.Options;
using Umbraco.Core.Configuration.Models;
using Umbraco.Core.Events;
using Umbraco.Extensions;
using Umbraco.Web.Common.Lifetime;
using Umbraco.Web.Routing;
namespace Umbraco.Web.Common.AspNetCore
{
public class AspNetCoreRequestAccessor : IRequestAccessor
public class AspNetCoreRequestAccessor : IRequestAccessor, INotificationHandler<UmbracoRequestBegin>, INotificationHandler<UmbracoRequestEnd>
{
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly IUmbracoContextAccessor _umbracoContextAccessor;
@@ -19,7 +19,6 @@ namespace Umbraco.Web.Common.AspNetCore
private Uri _currentApplicationUrl;
public AspNetCoreRequestAccessor(IHttpContextAccessor httpContextAccessor,
IUmbracoRequestLifetime umbracoRequestLifetime,
IUmbracoContextAccessor umbracoContextAccessor,
IOptions<WebRoutingSettings> webRoutingSettings)
{
@@ -27,20 +26,8 @@ namespace Umbraco.Web.Common.AspNetCore
_umbracoContextAccessor = umbracoContextAccessor;
_webRoutingSettings = webRoutingSettings.Value;
umbracoRequestLifetime.RequestStart += RequestStart;
umbracoRequestLifetime.RequestEnd += RequestEnd;
}
private void RequestEnd(object sender, HttpContext e)
{
EndRequest?.Invoke(sender, new UmbracoRequestEventArgs(_umbracoContextAccessor.UmbracoContext));
}
private void RequestStart(object sender, HttpContext e)
{
var reason = EnsureRoutableOutcome.IsRoutable; //TODO get the correct value here like in UmbracoInjectedModule
RouteAttempt?.Invoke(sender, new RoutableAttemptEventArgs(reason, _umbracoContextAccessor.UmbracoContext));
}
public string GetRequestValue(string name) => GetFormValue(name) ?? GetQueryStringValue(name);
@@ -92,5 +79,18 @@ namespace Umbraco.Web.Common.AspNetCore
return _currentApplicationUrl;
}
public void Handle(UmbracoRequestBegin notification)
{
var reason = EnsureRoutableOutcome.IsRoutable; //TODO get the correct value here like in UmbracoInjectedModule
RouteAttempt?.Invoke(this, new RoutableAttemptEventArgs(reason, _umbracoContextAccessor.UmbracoContext));
}
public void Handle(UmbracoRequestEnd notification)
{
EndRequest?.Invoke(this, new UmbracoRequestEventArgs(_umbracoContextAccessor.UmbracoContext));
}
}
}

View File

@@ -39,7 +39,6 @@ using Umbraco.Web.Common.ApplicationModels;
using Umbraco.Web.Common.AspNetCore;
using Umbraco.Web.Common.Controllers;
using Umbraco.Web.Common.Install;
using Umbraco.Web.Common.Lifetime;
using Umbraco.Web.Common.Localization;
using Umbraco.Web.Common.Macros;
using Umbraco.Web.Common.Middleware;
@@ -233,9 +232,8 @@ namespace Umbraco.Web.Common.DependencyInjection
// AspNetCore specific services
builder.Services.AddUnique<IHttpContextAccessor, HttpContextAccessor>();
builder.Services.AddUnique<IRequestAccessor, AspNetCoreRequestAccessor>();
// The umbraco request lifetime
builder.Services.AddMultipleUnique<IUmbracoRequestLifetime, IUmbracoRequestLifetimeManager, UmbracoRequestLifetime>();
builder.AddNotificationHandler<UmbracoRequestBegin, AspNetCoreRequestAccessor>();
builder.AddNotificationHandler<UmbracoRequestEnd, AspNetCoreRequestAccessor>();
// Password hasher
builder.Services.AddUnique<IPasswordHasher, AspNetCorePasswordHasher>();

View File

@@ -0,0 +1,20 @@
// Copyright (c) Umbraco.
// See LICENSE for more details.
using Microsoft.AspNetCore.Http;
namespace Umbraco.Core.Events
{
/// <summary>
/// Notification raised on each request begin.
/// </summary>
public class UmbracoRequestBegin : INotification
{
public UmbracoRequestBegin(HttpContext httpContext)
{
HttpContext = httpContext;
}
public HttpContext HttpContext { get; }
};
}

View File

@@ -0,0 +1,20 @@
// Copyright (c) Umbraco.
// See LICENSE for more details.
using Microsoft.AspNetCore.Http;
namespace Umbraco.Core.Events
{
/// <summary>
/// Notification raised on each request end.
/// </summary>
public class UmbracoRequestEnd : INotification
{
public UmbracoRequestEnd(HttpContext httpContext)
{
HttpContext = httpContext;
}
public HttpContext HttpContext { get; }
}
}

View File

@@ -1,12 +0,0 @@
using System;
using Microsoft.AspNetCore.Http;
namespace Umbraco.Web.Common.Lifetime
{
// TODO: Should be killed and replaced with IEventAggregator
public interface IUmbracoRequestLifetime
{
event EventHandler<HttpContext> RequestStart;
event EventHandler<HttpContext> RequestEnd;
}
}

View File

@@ -1,10 +0,0 @@
using Microsoft.AspNetCore.Http;
namespace Umbraco.Web.Common.Lifetime
{
public interface IUmbracoRequestLifetimeManager
{
void InitRequest(HttpContext context);
void EndRequest(HttpContext context);
}
}

View File

@@ -1,21 +0,0 @@
using System;
using Microsoft.AspNetCore.Http;
namespace Umbraco.Web.Common.Lifetime
{
public class UmbracoRequestLifetime : IUmbracoRequestLifetime, IUmbracoRequestLifetimeManager
{
public event EventHandler<HttpContext> RequestStart;
public event EventHandler<HttpContext> RequestEnd;
public void InitRequest(HttpContext context)
{
RequestStart?.Invoke(this, context);
}
public void EndRequest(HttpContext context)
{
RequestEnd?.Invoke(this, context);
}
}
}

View File

@@ -7,9 +7,10 @@ using Microsoft.AspNetCore.Http.Extensions;
using Microsoft.Extensions.Logging;
using Umbraco.Core;
using Umbraco.Core.Cache;
using Umbraco.Core.Events;
using Umbraco.Core.Logging;
using Umbraco.Extensions;
using Umbraco.Web.Common.Lifetime;
using Umbraco.Web.Common.Profiler;
using Umbraco.Web.PublishedCache.NuCache;
namespace Umbraco.Web.Common.Middleware
@@ -29,11 +30,13 @@ namespace Umbraco.Web.Common.Middleware
public class UmbracoRequestMiddleware : IMiddleware
{
private readonly ILogger<UmbracoRequestMiddleware> _logger;
private readonly IUmbracoRequestLifetimeManager _umbracoRequestLifetimeManager;
private readonly IUmbracoContextFactory _umbracoContextFactory;
private readonly IRequestCache _requestCache;
private readonly IBackOfficeSecurityFactory _backofficeSecurityFactory;
private readonly PublishedSnapshotServiceEventHandler _publishedSnapshotServiceEventHandler;
private readonly IEventAggregator _eventAggregator;
private readonly WebProfiler _profiler;
private static bool s_cacheInitialized = false;
private static bool s_cacheInitializedFlag = false;
private static object s_cacheInitializedLock = new object();
@@ -43,18 +46,20 @@ namespace Umbraco.Web.Common.Middleware
/// </summary>
public UmbracoRequestMiddleware(
ILogger<UmbracoRequestMiddleware> logger,
IUmbracoRequestLifetimeManager umbracoRequestLifetimeManager,
IUmbracoContextFactory umbracoContextFactory,
IRequestCache requestCache,
IBackOfficeSecurityFactory backofficeSecurityFactory,
PublishedSnapshotServiceEventHandler publishedSnapshotServiceEventHandler)
PublishedSnapshotServiceEventHandler publishedSnapshotServiceEventHandler,
IEventAggregator eventAggregator,
IProfiler profiler)
{
_logger = logger;
_umbracoRequestLifetimeManager = umbracoRequestLifetimeManager;
_umbracoContextFactory = umbracoContextFactory;
_requestCache = requestCache;
_backofficeSecurityFactory = backofficeSecurityFactory;
_publishedSnapshotServiceEventHandler = publishedSnapshotServiceEventHandler;
_eventAggregator = eventAggregator;
_profiler = profiler as WebProfiler; // Ignore if not a WebProfiler
}
/// <inheritdoc/>
@@ -67,6 +72,10 @@ namespace Umbraco.Web.Common.Middleware
return;
}
// Profiling start needs to be one of the first things that happens.
// Also MiniProfiler.Current becomes null if it is handled by the event aggregator due to async/await
_profiler?.UmbracoApplicationBeginRequest(context);
EnsureContentCacheInitialized();
_backofficeSecurityFactory.EnsureBackOfficeSecurity(); // Needs to be before UmbracoContext, TODO: Why?
@@ -86,7 +95,7 @@ namespace Umbraco.Web.Common.Middleware
try
{
_umbracoRequestLifetimeManager.InitRequest(context);
await _eventAggregator.PublishAsync(new UmbracoRequestBegin(context));
}
catch (Exception ex)
{
@@ -98,10 +107,11 @@ namespace Umbraco.Web.Common.Middleware
try
{
await next(context);
}
finally
{
_umbracoRequestLifetimeManager.EndRequest(context);
await _eventAggregator.PublishAsync(new UmbracoRequestEnd(context));
}
}
}
@@ -122,6 +132,10 @@ namespace Umbraco.Web.Common.Middleware
umbracoContextReference.Dispose();
}
}
// Profiling end needs to be last of the first things that happens.
// Also MiniProfiler.Current becomes null if it is handled by the event aggregator due to async/await
_profiler?.UmbracoApplicationEndRequest(context);
}
/// <summary>

View File

@@ -4,7 +4,6 @@
using Microsoft.Extensions.Logging;
using Umbraco.Core.Events;
using Umbraco.Core.Logging;
using Umbraco.Web.Common.Lifetime;
namespace Umbraco.Web.Common.Profiler
{
@@ -15,14 +14,12 @@ namespace Umbraco.Web.Common.Profiler
{
private readonly bool _profile;
private readonly WebProfiler _profiler;
private readonly IUmbracoRequestLifetime _umbracoRequestLifetime;
/// <summary>
/// Initializes a new instance of the <see cref="InitializeWebProfiling"/> class.
/// </summary>
public InitializeWebProfiling(IProfiler profiler, IUmbracoRequestLifetime umbracoRequestLifetime, ILogger<InitializeWebProfiling> logger)
public InitializeWebProfiling(IProfiler profiler, ILogger<InitializeWebProfiling> logger)
{
_umbracoRequestLifetime = umbracoRequestLifetime;
_profile = true;
// although registered in UmbracoBuilderExtensions.AddUmbraco, ensure that we have not
@@ -48,13 +45,10 @@ namespace Umbraco.Web.Common.Profiler
{
if (_profile)
{
_umbracoRequestLifetime.RequestStart += (sender, context) => _profiler.UmbracoApplicationBeginRequest(context);
_umbracoRequestLifetime.RequestEnd += (sender, context) => _profiler.UmbracoApplicationEndRequest(context);
// Stop the profiling of the booting process
_profiler.StopBoot();
}
}
}
}

View File

@@ -2,9 +2,7 @@
using System.Linq;
using System.Threading;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Extensions;
using StackExchange.Profiling;
using Umbraco.Core;
using Umbraco.Core.Logging;
using Umbraco.Extensions;
@@ -13,9 +11,15 @@ namespace Umbraco.Web.Common.Profiler
public class WebProfiler : IProfiler
{
public static readonly AsyncLocal<MiniProfiler> MiniProfilerContext = new AsyncLocal<MiniProfiler>(x =>
{
_ = x;
});
private MiniProfiler _startupProfiler;
private int _first;
public IDisposable Step(string name)
{
return MiniProfiler.Current?.Step(name);
@@ -24,22 +28,14 @@ namespace Umbraco.Web.Common.Profiler
public void Start()
{
MiniProfiler.StartNew();
MiniProfilerContext.Value = MiniProfiler.Current;
}
public void StartBoot()
{
_startupProfiler = MiniProfiler.StartNew("Startup Profiler");
}
public void StartBoot() => _startupProfiler = MiniProfiler.StartNew("Startup Profiler");
public void StopBoot()
{
_startupProfiler.Stop();
}
public void StopBoot() => _startupProfiler.Stop();
public void Stop(bool discardResults = false)
{
MiniProfiler.Current?.Stop(discardResults);
}
public void Stop(bool discardResults = false) => MiniProfilerContext.Value?.Stop(discardResults);
public void UmbracoApplicationBeginRequest(HttpContext context)
@@ -60,9 +56,9 @@ namespace Umbraco.Web.Common.Profiler
{
var startupDuration = _startupProfiler.Root.DurationMilliseconds.GetValueOrDefault();
MiniProfiler.Current.DurationMilliseconds += startupDuration;
MiniProfiler.Current.GetTimingHierarchy().First().DurationMilliseconds += startupDuration;
MiniProfiler.Current.Root.AddChild(_startupProfiler.Root);
MiniProfilerContext.Value.DurationMilliseconds += startupDuration;
MiniProfilerContext.Value.GetTimingHierarchy().First().DurationMilliseconds += startupDuration;
MiniProfilerContext.Value.Root.AddChild(_startupProfiler.Root);
_startupProfiler = null;
}