AB3986 - Wrapped System.Web.Hosting.IRegisteredObject in the AspNetHostingEnvironment. Updated Maindom to use our own new interface and the wrapper and moved to infrastructure. Removed the Umbraco.Core project now that it was empty.

This commit is contained in:
Bjarke Berg
2020-01-08 09:01:58 +01:00
parent cdfd3df98a
commit c7f60d8312
21 changed files with 137 additions and 271 deletions

View File

@@ -1,249 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using System.Threading;
using System.Web.Hosting;
using Umbraco.Core.Hosting;
using Umbraco.Core.Logging;
namespace Umbraco.Core
{
/// <summary>
/// Provides the full implementation of <see cref="IMainDom"/>.
/// </summary>
/// <remarks>
/// <para>When an AppDomain starts, it tries to acquire the main domain status.</para>
/// <para>When an AppDomain stops (eg the application is restarting) it should release the main domain status.</para>
/// </remarks>
internal class MainDom : IMainDom, IRegisteredObject, IDisposable
{
#region Vars
private readonly ILogger _logger;
// our own lock for local consistency
private object _locko = new object();
// async lock representing the main domain lock
private readonly SystemLock _systemLock;
private IDisposable _systemLocker;
// event wait handle used to notify current main domain that it should
// release the lock because a new domain wants to be the main domain
private readonly EventWaitHandle _signal;
private bool _isInitialized;
// indicates whether...
private bool _isMainDom; // we are the main domain
private volatile bool _signaled; // we have been signaled
// actions to run before releasing the main domain
private readonly List<KeyValuePair<int, Action>> _callbacks = new List<KeyValuePair<int, Action>>();
private const int LockTimeoutMilliseconds = 90000; // (1.5 * 60 * 1000) == 1 min 30 seconds
#endregion
#region Ctor
// initializes a new instance of MainDom
public MainDom(ILogger logger, IHostingEnvironment hostingEnvironment)
{
HostingEnvironment.RegisterObject(this);
_logger = logger;
// HostingEnvironment.ApplicationID is null in unit tests, making ReplaceNonAlphanumericChars fail
var appId = hostingEnvironment.ApplicationId?.ReplaceNonAlphanumericChars(string.Empty);
// combining with the physical path because if running on eg IIS Express,
// two sites could have the same appId even though they are different.
//
// now what could still collide is... two sites, running in two different processes
// and having the same appId, and running on the same app physical path
//
// we *cannot* use the process ID here because when an AppPool restarts it is
// a new process for the same application path
var appPath = hostingEnvironment.ApplicationPhysicalPath?.ToLowerInvariant() ?? string.Empty;
var hash = (appId + ":::" + appPath).GenerateHash<SHA1>();
var lockName = "UMBRACO-" + hash + "-MAINDOM-LCK";
_systemLock = new SystemLock(lockName);
var eventName = "UMBRACO-" + hash + "-MAINDOM-EVT";
_signal = new EventWaitHandle(false, EventResetMode.AutoReset, eventName);
}
#endregion
/// <summary>
/// Registers a resource that requires the current AppDomain to be the main domain to function.
/// </summary>
/// <param name="release">An action to execute before the AppDomain releases the main domain status.</param>
/// <param name="weight">An optional weight (lower goes first).</param>
/// <returns>A value indicating whether it was possible to register.</returns>
public bool Register(Action release, int weight = 100)
=> Register(null, release, weight);
/// <summary>
/// Registers a resource that requires the current AppDomain to be the main domain to function.
/// </summary>
/// <param name="install">An action to execute when registering.</param>
/// <param name="release">An action to execute before the AppDomain releases the main domain status.</param>
/// <param name="weight">An optional weight (lower goes first).</param>
/// <returns>A value indicating whether it was possible to register.</returns>
/// <remarks>If registering is successful, then the <paramref name="install"/> action
/// is guaranteed to execute before the AppDomain releases the main domain status.</remarks>
public bool Register(Action install, Action release, int weight = 100)
{
lock (_locko)
{
if (_signaled) return false;
if (_isMainDom == false)
{
_logger.Warn<MainDom>("Register called when MainDom has not been acquired");
return false;
}
install?.Invoke();
if (release != null)
_callbacks.Add(new KeyValuePair<int, Action>(weight, release));
return true;
}
}
// handles the signal requesting that the main domain is released
private void OnSignal(string source)
{
// once signaled, we stop waiting, but then there is the hosting environment
// so we have to make sure that we only enter that method once
lock (_locko)
{
_logger.Debug<MainDom>("Signaled ({Signaled}) ({SignalSource})", _signaled ? "again" : "first", source);
if (_signaled) return;
if (_isMainDom == false) return; // probably not needed
_signaled = true;
try
{
_logger.Info<MainDom>("Stopping ({SignalSource})", source);
foreach (var callback in _callbacks.OrderBy(x => x.Key).Select(x => x.Value))
{
try
{
callback(); // no timeout on callbacks
}
catch (Exception e)
{
_logger.Error<MainDom>(e, "Error while running callback");
continue;
}
}
_logger.Debug<MainDom>("Stopped ({SignalSource})", source);
}
finally
{
// in any case...
_isMainDom = false;
_systemLocker?.Dispose();
_logger.Info<MainDom>("Released ({SignalSource})", source);
}
}
}
// acquires the main domain
private bool Acquire()
{
// if signaled, too late to acquire, give up
// the handler is not installed so that would be the hosting environment
if (_signaled)
{
_logger.Info<MainDom>("Cannot acquire (signaled).");
return false;
}
_logger.Info<MainDom>("Acquiring.");
// signal other instances that we want the lock, then wait one the lock,
// which may timeout, and this is accepted - see comments below
// signal, then wait for the lock, then make sure the event is
// reset (maybe there was noone listening..)
_signal.Set();
// if more than 1 instance reach that point, one will get the lock
// and the other one will timeout, which is accepted
//This can throw a TimeoutException - in which case should this be in a try/finally to ensure the signal is always reset.
try
{
_systemLocker = _systemLock.Lock(LockTimeoutMilliseconds);
}
finally
{
// we need to reset the event, because otherwise we would end up
// signaling ourselves and committing suicide immediately.
// only 1 instance can reach that point, but other instances may
// have started and be trying to get the lock - they will timeout,
// which is accepted
_signal.Reset();
}
//WaitOneAsync (ext method) will wait for a signal without blocking the main thread, the waiting is done on a background thread
_signal.WaitOneAsync()
.ContinueWith(_ => OnSignal("signal"));
_logger.Info<MainDom>("Acquired.");
return true;
}
/// <summary>
/// Gets a value indicating whether the current domain is the main domain.
/// </summary>
public bool IsMainDom => LazyInitializer.EnsureInitialized(ref _isMainDom, ref _isInitialized, ref _locko, () => Acquire());
// IRegisteredObject
void IRegisteredObject.Stop(bool immediate)
{
OnSignal("environment"); // will run once
// The web app is stopping, need to wind down
Dispose(true);
HostingEnvironment.UnregisterObject(this);
}
#region IDisposable Support
// This code added to correctly implement the disposable pattern.
private bool disposedValue = false; // To detect redundant calls
protected virtual void Dispose(bool disposing)
{
if (!disposedValue)
{
if (disposing)
{
_signal?.Close();
_signal?.Dispose();
}
disposedValue = true;
}
}
public void Dispose()
{
Dispose(true);
}
#endregion
}
}

View File

@@ -1,40 +0,0 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
[assembly: AssemblyTitle("Umbraco.Core")]
[assembly: AssemblyDescription("Umbraco Core")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyProduct("Umbraco CMS")]
[assembly: ComVisible(false)]
[assembly: Guid("130a6b5c-50e7-4df3-a0dd-e9e7eb0b7c5c")]
// Umbraco Cms
[assembly: InternalsVisibleTo("Umbraco.Web")]
[assembly: InternalsVisibleTo("Umbraco.Web.UI")]
[assembly: InternalsVisibleTo("Umbraco.Examine")]
[assembly: InternalsVisibleTo("Umbraco.ModelsBuilder.Embedded")]
[assembly: InternalsVisibleTo("Umbraco.Tests")]
[assembly: InternalsVisibleTo("Umbraco.Tests.Benchmarks")]
// Allow this to be mocked in our unit tests
[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")]
// Umbraco Deploy
[assembly: InternalsVisibleTo("Umbraco.Deploy")]
[assembly: InternalsVisibleTo("Umbraco.Deploy.UI")]
[assembly: InternalsVisibleTo("Umbraco.Deploy.Cloud")]
// Umbraco Forms
[assembly: InternalsVisibleTo("Umbraco.Forms.Core")]
[assembly: InternalsVisibleTo("Umbraco.Forms.Core.Providers")]
[assembly: InternalsVisibleTo("Umbraco.Forms.Web")]
// Umbraco Headless
[assembly: InternalsVisibleTo("Umbraco.Cloud.Headless")]
// code analysis
// IDE1006 is broken, wants _value syntax for consts, etc - and it's even confusing ppl at MS, kill it
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "~_~")]

View File

@@ -1,145 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
<EnableDefaultCompileItems>false</EnableDefaultCompileItems>
<ProjectGuid>{31785BC3-256C-4613-B2F5-A1B0BDDED8C1}</ProjectGuid>
<OutputType>Library</OutputType>
<AssemblyName>Umbraco.Core</AssemblyName>
<RootNamespace>Umbraco.Core</RootNamespace>
<SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\</SolutionDir>
<TargetFrameworkProfile />
<AdditionalFileItemNames>$(AdditionalFileItemNames);Content</AdditionalFileItemNames>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>portable</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>TRACE;DEBUG</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<Prefer32Bit>false</Prefer32Bit>
<LangVersion>latest</LangVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>portable</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<DocumentationFile>bin\Release\Umbraco.Core.xml</DocumentationFile>
<Prefer32Bit>false</Prefer32Bit>
<LangVersion>latest</LangVersion>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Configuration" />
<Reference Include="System.Data.Entity" />
<Reference Include="System.IO.Compression" />
<Reference Include="System.IO.Compression.FileSystem" />
<Reference Include="System.Runtime.Caching" />
<Reference Include="System.Runtime.Serialization" />
<Reference Include="System.Transactions" />
<Reference Include="System.Web" />
<Reference Include="System.Web.ApplicationServices" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<!-- note: NuGet deals with transitive references now -->
<PackageReference Include="Microsoft.SourceLink.GitHub">
<Version>1.0.0-beta2-19324-01</Version>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="SecurityCodeScan">
<Version>3.3.0</Version>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Serilog.Sinks.Async">
<Version>1.4.0</Version>
</PackageReference>
<PackageReference Include="Serilog.Sinks.Map">
<Version>1.0.0</Version>
</PackageReference>
<PackageReference Include="System.ComponentModel.Annotations">
<Version>4.6.0</Version>
</PackageReference>
<PackageReference Include="Umbraco.Code">
<Version>1.0.5</Version>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="LightInject" Version="5.4.0" />
<PackageReference Include="LightInject.Annotation" Version="1.1.0" />
<PackageReference Include="LightInject.Web" Version="2.0.0" />
<PackageReference Include="Microsoft.AspNet.Identity.Core">
<Version>2.2.2</Version>
</PackageReference>
<PackageReference Include="Microsoft.AspNet.WebApi.Client">
<Version>5.2.7</Version>
</PackageReference>
<PackageReference Include="Microsoft.Owin">
<Version>4.0.1</Version>
</PackageReference>
<PackageReference Include="MiniProfiler" Version="4.0.138" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.1" />
<PackageReference Include="NPoco" Version="4.0.2" />
<PackageReference Include="Serilog">
<Version>2.9.0</Version>
</PackageReference>
<PackageReference Include="Serilog.Enrichers.Process">
<Version>2.0.1</Version>
</PackageReference>
<PackageReference Include="Serilog.Enrichers.Thread">
<Version>3.1.0</Version>
</PackageReference>
<PackageReference Include="Serilog.Filters.Expressions">
<Version>2.0.0</Version>
</PackageReference>
<PackageReference Include="Serilog.Formatting.Compact">
<Version>1.1.0</Version>
</PackageReference>
<PackageReference Include="Serilog.Formatting.Compact.Reader">
<Version>1.0.3</Version>
</PackageReference>
<PackageReference Include="Serilog.Settings.AppSettings">
<Version>2.2.2</Version>
</PackageReference>
<PackageReference Include="Serilog.Sinks.File">
<Version>4.1.0</Version>
</PackageReference>
</ItemGroup>
<ItemGroup>
<!--
this does not fully work - breaks intellisense
Exclude="Constants-*.cs"
<Compile Include="Constants-*.cs">
<DependentUpon>Constants.cs</DependentUpon>
</Compile>
-->
<Compile Include="MainDom.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="..\SolutionInfo.cs">
<Link>Properties\SolutionInfo.cs</Link>
</Compile>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Umbraco.Abstractions\Umbraco.Abstractions.csproj">
<Project>{29aa69d9-b597-4395-8d42-43b1263c240a}</Project>
<Name>Umbraco.Abstractions</Name>
</ProjectReference>
<ProjectReference Include="..\Umbraco.Infrastructure\Umbraco.Infrastructure.csproj">
<Project>{3ae7bf57-966b-45a5-910a-954d7c554441}</Project>
<Name>Umbraco.Infrastructure</Name>
</ProjectReference>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>