Introduce a new IMainDomLock and both default and sql implementations
This commit is contained in:
@@ -151,6 +151,8 @@ namespace Umbraco.Core.Migrations.Install
|
||||
_database.Insert(Constants.DatabaseSchema.Tables.Lock, "id", false, new LockDto { Id = Constants.Locks.Domains, Name = "Domains" });
|
||||
_database.Insert(Constants.DatabaseSchema.Tables.Lock, "id", false, new LockDto { Id = Constants.Locks.KeyValues, Name = "KeyValues" });
|
||||
_database.Insert(Constants.DatabaseSchema.Tables.Lock, "id", false, new LockDto { Id = Constants.Locks.Languages, Name = "Languages" });
|
||||
|
||||
_database.Insert(Constants.DatabaseSchema.Tables.Lock, "id", false, new LockDto { Id = Constants.Locks.MainDom, Name = "MainDom" });
|
||||
}
|
||||
|
||||
private void CreateContentTypeData()
|
||||
|
||||
@@ -185,6 +185,7 @@ namespace Umbraco.Core.Migrations.Upgrade
|
||||
|
||||
// to 8.6.0
|
||||
To<AddPropertyTypeValidationMessageColumns>("{3D67D2C8-5E65-47D0-A9E1-DC2EE0779D6B}");
|
||||
To<AddMainDomLock>("{2AB29964-02A1-474D-BD6B-72148D2A53A2}");
|
||||
|
||||
//FINAL
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
using Umbraco.Core.Persistence.Dtos;
|
||||
|
||||
namespace Umbraco.Core.Migrations.Upgrade.V_8_6_0
|
||||
{
|
||||
public class AddMainDomLock : MigrationBase
|
||||
{
|
||||
public AddMainDomLock(IMigrationContext context)
|
||||
: base(context)
|
||||
{ }
|
||||
|
||||
public override void Migrate()
|
||||
{
|
||||
Database.Insert(Constants.DatabaseSchema.Tables.Lock, "id", false, new LockDto { Id = Constants.Locks.MainDom, Name = "MainDom" });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ using Umbraco.Core.Persistence.Dtos;
|
||||
|
||||
namespace Umbraco.Core.Migrations.Upgrade.V_8_6_0
|
||||
{
|
||||
|
||||
public class AddPropertyTypeValidationMessageColumns : MigrationBase
|
||||
{
|
||||
public AddPropertyTypeValidationMessageColumns(IMigrationContext context)
|
||||
|
||||
@@ -8,6 +8,11 @@ namespace Umbraco.Core
|
||||
/// </summary>
|
||||
public static class Locks
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="IMainDom"/> lock
|
||||
/// </summary>
|
||||
public const int MainDom = -1000;
|
||||
|
||||
/// <summary>
|
||||
/// All servers.
|
||||
/// </summary>
|
||||
|
||||
@@ -250,6 +250,11 @@ where tbl.[name]=@0 and col.[name]=@1;", tableName, columnName)
|
||||
}
|
||||
|
||||
public override void WriteLock(IDatabase db, params int[] lockIds)
|
||||
{
|
||||
WriteLock(db, 1800, lockIds);
|
||||
}
|
||||
|
||||
public void WriteLock(IDatabase db, int millisecondsTimeout, params int[] lockIds)
|
||||
{
|
||||
// soon as we get Database, a transaction is started
|
||||
|
||||
@@ -260,7 +265,7 @@ where tbl.[name]=@0 and col.[name]=@1;", tableName, columnName)
|
||||
// *not* using a unique 'WHERE IN' query here because the *order* of lockIds is important to avoid deadlocks
|
||||
foreach (var lockId in lockIds)
|
||||
{
|
||||
db.Execute(@"SET LOCK_TIMEOUT 1800;");
|
||||
db.Execute($"SET LOCK_TIMEOUT {millisecondsTimeout};");
|
||||
var i = db.Execute(@"UPDATE umbracoLock WITH (REPEATABLEREAD) SET value = (CASE WHEN (value=1) THEN -1 ELSE 1 END) WHERE id=@id", new { id = lockId });
|
||||
if (i == 0) // ensure we are actually locking!
|
||||
throw new ArgumentException($"LockObject with id={lockId} does not exist.");
|
||||
|
||||
@@ -147,7 +147,7 @@ namespace Umbraco.Core.Runtime
|
||||
// TODO: remove this in netcore, this is purely backwards compat hacks with the empty ctor
|
||||
if (MainDom == null)
|
||||
{
|
||||
MainDom = new MainDom(Logger);
|
||||
MainDom = new MainDom(Logger, new MainDomSemaphoreLock());
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
|
||||
// TODO: Can't change namespace due to breaking changes, change in netcore
|
||||
namespace Umbraco.Core
|
||||
{
|
||||
/// <summary>
|
||||
30
src/Umbraco.Core/Runtime/IMainDomLock.cs
Normal file
30
src/Umbraco.Core/Runtime/IMainDomLock.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Umbraco.Core.Runtime
|
||||
{
|
||||
/// <summary>
|
||||
/// An application-wide distributed lock
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Disposing releases the lock
|
||||
/// </remarks>
|
||||
public interface IMainDomLock : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// Acquires an application-wide distributed lock
|
||||
/// </summary>
|
||||
/// <param name="millisecondsTimeout"></param>
|
||||
/// <returns>
|
||||
/// A disposable object which will be disposed in order to release the lock
|
||||
/// </returns>
|
||||
/// <exception cref="TimeoutException">Throws a <see cref="TimeoutException"/> if the elapsed millsecondsTimeout value is exceeded</exception>
|
||||
Task<bool> AcquireLockAsync(int millisecondsTimeout);
|
||||
|
||||
/// <summary>
|
||||
/// Wait on a background thread to receive a signal from another AppDomain
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
Task ListenAsync();
|
||||
}
|
||||
}
|
||||
@@ -4,10 +4,13 @@ using System.Linq;
|
||||
using System.Security.Cryptography;
|
||||
using System.Threading;
|
||||
using System.Web.Hosting;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Persistence;
|
||||
|
||||
namespace Umbraco.Core
|
||||
namespace Umbraco.Core.Runtime
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Provides the full implementation of <see cref="IMainDom"/>.
|
||||
/// </summary>
|
||||
@@ -20,18 +23,11 @@ namespace Umbraco.Core
|
||||
#region Vars
|
||||
|
||||
private readonly ILogger _logger;
|
||||
private readonly IMainDomLock _mainDomLock;
|
||||
|
||||
// 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
|
||||
@@ -47,32 +43,12 @@ namespace Umbraco.Core
|
||||
#region Ctor
|
||||
|
||||
// initializes a new instance of MainDom
|
||||
public MainDom(ILogger logger)
|
||||
public MainDom(ILogger logger, IMainDomLock systemLock)
|
||||
{
|
||||
HostingEnvironment.RegisterObject(this);
|
||||
|
||||
_logger = logger;
|
||||
|
||||
// HostingEnvironment.ApplicationID is null in unit tests, making ReplaceNonAlphanumericChars fail
|
||||
var appId = HostingEnvironment.ApplicationID?.ReplaceNonAlphanumericChars(string.Empty) ?? 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);
|
||||
_mainDomLock = systemLock;
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -130,7 +106,6 @@ namespace Umbraco.Core
|
||||
{
|
||||
_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
|
||||
@@ -140,14 +115,13 @@ namespace Umbraco.Core
|
||||
_logger.Error<MainDom>(e, "Error while running callback");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
_logger.Debug<MainDom>("Stopped ({SignalSource})", source);
|
||||
}
|
||||
finally
|
||||
{
|
||||
// in any case...
|
||||
_isMainDom = false;
|
||||
_systemLocker?.Dispose();
|
||||
_mainDomLock.Dispose();
|
||||
_logger.Info<MainDom>("Released ({SignalSource})", source);
|
||||
}
|
||||
|
||||
@@ -167,35 +141,11 @@ namespace Umbraco.Core
|
||||
|
||||
_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
|
||||
// Get the lock
|
||||
_mainDomLock.AcquireLockAsync(LockTimeoutMilliseconds).Wait();
|
||||
|
||||
// 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()
|
||||
// Listen for the signal from another AppDomain coming online to release the lock
|
||||
_mainDomLock.ListenAsync()
|
||||
.ContinueWith(_ => OnSignal("signal"));
|
||||
|
||||
_logger.Info<MainDom>("Acquired.");
|
||||
@@ -230,8 +180,7 @@ namespace Umbraco.Core
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
_signal?.Close();
|
||||
_signal?.Dispose();
|
||||
_mainDomLock.Dispose();
|
||||
}
|
||||
|
||||
disposedValue = true;
|
||||
@@ -244,5 +193,25 @@ namespace Umbraco.Core
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public static string GetMainDomId()
|
||||
{
|
||||
// HostingEnvironment.ApplicationID is null in unit tests, making ReplaceNonAlphanumericChars fail
|
||||
var appId = HostingEnvironment.ApplicationID?.ReplaceNonAlphanumericChars(string.Empty) ?? 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>();
|
||||
|
||||
return hash;
|
||||
}
|
||||
}
|
||||
}
|
||||
92
src/Umbraco.Core/Runtime/MainDomSemaphoreLock.cs
Normal file
92
src/Umbraco.Core/Runtime/MainDomSemaphoreLock.cs
Normal file
@@ -0,0 +1,92 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Umbraco.Core.Runtime
|
||||
{
|
||||
/// <summary>
|
||||
/// Uses a system-wide Semaphore and EventWaitHandle to synchronize the current AppDomain
|
||||
/// </summary>
|
||||
internal class MainDomSemaphoreLock : IMainDomLock
|
||||
{
|
||||
private readonly SystemLock _systemLock;
|
||||
|
||||
// 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 IDisposable _lockRelease;
|
||||
|
||||
public MainDomSemaphoreLock()
|
||||
{
|
||||
var lockName = "UMBRACO-" + MainDom.GetMainDomId() + "-MAINDOM-LCK";
|
||||
_systemLock = new SystemLock(lockName);
|
||||
|
||||
var eventName = "UMBRACO-" + MainDom.GetMainDomId() + "-MAINDOM-EVT";
|
||||
_signal = new EventWaitHandle(false, EventResetMode.AutoReset, eventName);
|
||||
}
|
||||
|
||||
//WaitOneAsync (ext method) will wait for a signal without blocking the main thread, the waiting is done on a background thread
|
||||
public Task ListenAsync() => _signal.WaitOneAsync();
|
||||
|
||||
public Task<bool> AcquireLockAsync(int millisecondsTimeout)
|
||||
{
|
||||
// signal other instances that we want the lock, then wait on 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
|
||||
{
|
||||
_lockRelease = _systemLock.Lock(millisecondsTimeout);
|
||||
return Task.FromResult(true);
|
||||
}
|
||||
catch (TimeoutException)
|
||||
{
|
||||
return Task.FromResult(false);
|
||||
}
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
#region IDisposable Support
|
||||
private bool disposedValue = false; // To detect redundant calls
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!disposedValue)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
_lockRelease?.Dispose();
|
||||
_signal.Close();
|
||||
_signal.Dispose();
|
||||
}
|
||||
|
||||
disposedValue = true;
|
||||
}
|
||||
}
|
||||
|
||||
// This code added to correctly implement the disposable pattern.
|
||||
public void Dispose()
|
||||
{
|
||||
// Do not change this code. Put cleanup code in Dispose(bool disposing) above.
|
||||
Dispose(true);
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
178
src/Umbraco.Core/Runtime/SqlMainDomLock.cs
Normal file
178
src/Umbraco.Core/Runtime/SqlMainDomLock.cs
Normal file
@@ -0,0 +1,178 @@
|
||||
using System;
|
||||
using System.Data;
|
||||
using System.Data.SqlClient;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Persistence;
|
||||
using Umbraco.Core.Persistence.Dtos;
|
||||
using Umbraco.Core.Persistence.Mappers;
|
||||
using Umbraco.Core.Persistence.SqlSyntax;
|
||||
|
||||
namespace Umbraco.Core.Runtime
|
||||
{
|
||||
internal class SqlMainDomLock : IMainDomLock
|
||||
{
|
||||
private string _appDomainId;
|
||||
private const string MainDomKey = "Umbraco.Core.Runtime.SqlMainDom";
|
||||
private readonly ILogger _logger;
|
||||
private IUmbracoDatabase _db;
|
||||
private CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();
|
||||
private SqlServerSyntaxProvider _sqlServerSyntax = new SqlServerSyntaxProvider();
|
||||
|
||||
public SqlMainDomLock(ILogger logger)
|
||||
{
|
||||
_appDomainId = AppDomain.CurrentDomain.Id.ToString();
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public Task<bool> AcquireLockAsync(int millisecondsTimeout)
|
||||
{
|
||||
var factory = new UmbracoDatabaseFactory(
|
||||
Constants.System.UmbracoConnectionName,
|
||||
_logger,
|
||||
new Lazy<IMapperCollection>(() => new Persistence.Mappers.MapperCollection(Enumerable.Empty<BaseMapper>())));
|
||||
|
||||
_db = factory.CreateDatabase();
|
||||
|
||||
try
|
||||
{
|
||||
_db.BeginTransaction(IsolationLevel.ReadCommitted);
|
||||
|
||||
try
|
||||
{
|
||||
// wait to get a write lock
|
||||
_sqlServerSyntax.WriteLock(_db, millisecondsTimeout, Constants.Locks.MainDom);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (IsLockTimeoutException(ex))
|
||||
{
|
||||
return Task.FromResult(false);
|
||||
}
|
||||
|
||||
// unexpected
|
||||
throw;
|
||||
}
|
||||
|
||||
InsertLockRecord();
|
||||
|
||||
return Task.FromResult(true);
|
||||
}
|
||||
catch(Exception)
|
||||
{
|
||||
_db.AbortTransaction();
|
||||
|
||||
// unexpected
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_db.CompleteTransaction();
|
||||
}
|
||||
}
|
||||
|
||||
public Task ListenAsync()
|
||||
{
|
||||
// Create a long running task (dedicated thread)
|
||||
// to poll to check if we are still the MainDom registered in the DB
|
||||
return Task.Factory.StartNew(() =>
|
||||
{
|
||||
while(true)
|
||||
{
|
||||
if (_cancellationTokenSource.IsCancellationRequested)
|
||||
break;
|
||||
|
||||
// poll every 1 second
|
||||
Thread.Sleep(1000);
|
||||
|
||||
try
|
||||
{
|
||||
_db.BeginTransaction(IsolationLevel.ReadCommitted);
|
||||
|
||||
// get a read lock
|
||||
_sqlServerSyntax.ReadLock(_db, Constants.Locks.MainDom);
|
||||
|
||||
if (!IsStillMainDom())
|
||||
{
|
||||
// we are no longer main dom, exit
|
||||
return;
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
_db.AbortTransaction();
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_db.CompleteTransaction();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}, _cancellationTokenSource.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default);
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Inserts or updates the key/value row to check if the current appdomain is registerd as the maindom
|
||||
/// </summary>
|
||||
private void InsertLockRecord()
|
||||
{
|
||||
_db.InsertOrUpdate(new KeyValueDto
|
||||
{
|
||||
Key = MainDomKey,
|
||||
Value = _appDomainId,
|
||||
Updated = DateTime.Now
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the DB row value is our current appdomain value
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private bool IsStillMainDom()
|
||||
{
|
||||
return _db.ExecuteScalar<int>("SELECT COUNT(*) FROM umbracoKeyValue WHERE [key] = @key AND [value] = @val",
|
||||
new { key = MainDomKey, val = _appDomainId }) == 1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the exception is an SQL timeout
|
||||
/// </summary>
|
||||
/// <param name="exception"></param>
|
||||
/// <returns></returns>
|
||||
private bool IsLockTimeoutException(Exception exception) => exception is SqlException sqlException && sqlException.Number == 1222;
|
||||
|
||||
#region IDisposable Support
|
||||
private bool disposedValue = false; // To detect redundant calls
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!disposedValue)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
_db.Dispose();
|
||||
_cancellationTokenSource.Cancel();
|
||||
_cancellationTokenSource.Dispose();
|
||||
}
|
||||
|
||||
disposedValue = true;
|
||||
}
|
||||
}
|
||||
|
||||
// This code added to correctly implement the disposable pattern.
|
||||
public void Dispose()
|
||||
{
|
||||
// Do not change this code. Put cleanup code in Dispose(bool disposing) above.
|
||||
Dispose(true);
|
||||
}
|
||||
#endregion
|
||||
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,7 @@ namespace Umbraco.Core.Scoping
|
||||
/// Provides scopes.
|
||||
/// </summary>
|
||||
public interface IScopeProvider
|
||||
{
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates an ambient scope.
|
||||
/// </summary>
|
||||
|
||||
@@ -128,6 +128,10 @@
|
||||
</Compile>
|
||||
-->
|
||||
<Compile Include="AssemblyExtensions.cs" />
|
||||
<Compile Include="Migrations\Upgrade\V_8_6_0\AddMainDomLock.cs" />
|
||||
<Compile Include="Runtime\IMainDomLock.cs" />
|
||||
<Compile Include="Runtime\MainDomSemaphoreLock.cs" />
|
||||
<Compile Include="Runtime\SqlMainDomLock.cs" />
|
||||
<Compile Include="SystemLock.cs" />
|
||||
<Compile Include="Attempt.cs" />
|
||||
<Compile Include="AttemptOfTResult.cs" />
|
||||
@@ -398,7 +402,7 @@
|
||||
<Compile Include="Events\ExportedMemberEventArgs.cs" />
|
||||
<Compile Include="Events\RolesEventArgs.cs" />
|
||||
<Compile Include="Events\UserGroupWithUsers.cs" />
|
||||
<Compile Include="IMainDom.cs" />
|
||||
<Compile Include="Runtime\IMainDom.cs" />
|
||||
<Compile Include="IO\IFileSystems.cs" />
|
||||
<Compile Include="IO\IMediaFileSystem.cs" />
|
||||
<Compile Include="GuidUtils.cs" />
|
||||
@@ -729,7 +733,7 @@
|
||||
<Compile Include="Logging\ProfilingLogger.cs" />
|
||||
<Compile Include="Logging\VoidProfiler.cs" />
|
||||
<Compile Include="Macros\MacroErrorBehaviour.cs" />
|
||||
<Compile Include="MainDom.cs" />
|
||||
<Compile Include="Runtime\MainDom.cs" />
|
||||
<Compile Include="Manifest\ManifestParser.cs" />
|
||||
<Compile Include="Manifest\ValueValidatorConverter.cs" />
|
||||
<Compile Include="Manifest\ManifestWatcher.cs" />
|
||||
|
||||
@@ -9,6 +9,7 @@ using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Models.Membership;
|
||||
using Umbraco.Core.Models.PublishedContent;
|
||||
using Umbraco.Core.Persistence.Repositories;
|
||||
using Umbraco.Core.Runtime;
|
||||
using Umbraco.Core.Scoping;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Web;
|
||||
|
||||
@@ -14,6 +14,7 @@ using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Persistence;
|
||||
using Umbraco.Core.Persistence.Repositories;
|
||||
using Umbraco.Core.Persistence.Repositories.Implement;
|
||||
using Umbraco.Core.Runtime;
|
||||
using Umbraco.Core.Scoping;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Core.Services.Changes;
|
||||
|
||||
Reference in New Issue
Block a user