mirror of
https://github.com/OrchardCMS/Orchard.git
synced 2026-02-09 09:16:41 +08:00
Merge pull request #5752 from OrchardCMS/feature/distributedlocking
Refactored distributed locks to support existing installations.
This commit is contained in:
@@ -1,6 +1,9 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using Orchard.Data.Migration.Interpreters;
|
||||||
|
using Orchard.Data.Migration.Schema;
|
||||||
using Orchard.Environment;
|
using Orchard.Environment;
|
||||||
|
using Orchard.Environment.Configuration;
|
||||||
using Orchard.Environment.Features;
|
using Orchard.Environment.Features;
|
||||||
using Orchard.Logging;
|
using Orchard.Logging;
|
||||||
using Orchard.Tasks.Locking.Services;
|
using Orchard.Tasks.Locking.Services;
|
||||||
@@ -14,15 +17,24 @@ namespace Orchard.Data.Migration {
|
|||||||
private readonly IDataMigrationManager _dataMigrationManager;
|
private readonly IDataMigrationManager _dataMigrationManager;
|
||||||
private readonly IFeatureManager _featureManager;
|
private readonly IFeatureManager _featureManager;
|
||||||
private readonly IDistributedLockService _distributedLockService;
|
private readonly IDistributedLockService _distributedLockService;
|
||||||
|
private readonly IDataMigrationInterpreter _dataMigrationInterpreter;
|
||||||
|
private readonly ShellSettings _shellSettings;
|
||||||
|
private readonly ITransactionManager _transactionManager;
|
||||||
|
|
||||||
public AutomaticDataMigrations(
|
public AutomaticDataMigrations(
|
||||||
IDataMigrationManager dataMigrationManager,
|
IDataMigrationManager dataMigrationManager,
|
||||||
|
IDataMigrationInterpreter dataMigrationInterpreter,
|
||||||
IFeatureManager featureManager,
|
IFeatureManager featureManager,
|
||||||
IDistributedLockService distributedLockService) {
|
IDistributedLockService distributedLockService,
|
||||||
|
ITransactionManager transactionManager,
|
||||||
|
ShellSettings shellSettings) {
|
||||||
|
|
||||||
_dataMigrationManager = dataMigrationManager;
|
_dataMigrationManager = dataMigrationManager;
|
||||||
_featureManager = featureManager;
|
_featureManager = featureManager;
|
||||||
_distributedLockService = distributedLockService;
|
_distributedLockService = distributedLockService;
|
||||||
|
_shellSettings = shellSettings;
|
||||||
|
_transactionManager = transactionManager;
|
||||||
|
_dataMigrationInterpreter = dataMigrationInterpreter;
|
||||||
|
|
||||||
Logger = NullLogger.Instance;
|
Logger = NullLogger.Instance;
|
||||||
}
|
}
|
||||||
@@ -30,6 +42,8 @@ namespace Orchard.Data.Migration {
|
|||||||
public ILogger Logger { get; set; }
|
public ILogger Logger { get; set; }
|
||||||
|
|
||||||
public void Activated() {
|
public void Activated() {
|
||||||
|
EnsureDistributedLockSchemaExists();
|
||||||
|
|
||||||
IDistributedLock @lock;
|
IDistributedLock @lock;
|
||||||
if (_distributedLockService.TryAcquireLock(GetType().FullName, TimeSpan.FromMinutes(30), TimeSpan.FromMilliseconds(250), out @lock)) {
|
if (_distributedLockService.TryAcquireLock(GetType().FullName, TimeSpan.FromMinutes(30), TimeSpan.FromMilliseconds(250), out @lock)) {
|
||||||
using (@lock) {
|
using (@lock) {
|
||||||
@@ -62,5 +76,16 @@ namespace Orchard.Data.Migration {
|
|||||||
public void Terminating() {
|
public void Terminating() {
|
||||||
// No-op.
|
// No-op.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This ensures that the framework migrations have run for the distributed locking feature, as existing Orchard installations will not have the required tables when upgrading.
|
||||||
|
/// </summary>
|
||||||
|
private void EnsureDistributedLockSchemaExists() {
|
||||||
|
// Ensure the distributed lock record schema exists.
|
||||||
|
var schemaBuilder = new SchemaBuilder(_dataMigrationInterpreter);
|
||||||
|
var distributedLockSchemaBuilder = new DistributedLockSchemaBuilder(_shellSettings, schemaBuilder);
|
||||||
|
if (distributedLockSchemaBuilder.EnsureSchema())
|
||||||
|
_transactionManager.RequireNew();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -401,7 +401,7 @@
|
|||||||
<Compile Include="Services\IJsonConverter.cs" />
|
<Compile Include="Services\IJsonConverter.cs" />
|
||||||
<Compile Include="Settings\CurrentSiteWorkContext.cs" />
|
<Compile Include="Settings\CurrentSiteWorkContext.cs" />
|
||||||
<Compile Include="Settings\ResourceDebugMode.cs" />
|
<Compile Include="Settings\ResourceDebugMode.cs" />
|
||||||
<Compile Include="Tasks\Locking\Migrations\FrameworkMigrations.cs" />
|
<Compile Include="Tasks\Locking\Services\DistributedLockSchemaBuilder.cs" />
|
||||||
<Compile Include="Tasks\Locking\Services\IDistributedLock.cs" />
|
<Compile Include="Tasks\Locking\Services\IDistributedLock.cs" />
|
||||||
<Compile Include="Tasks\Locking\Services\DistributedLock.cs" />
|
<Compile Include="Tasks\Locking\Services\DistributedLock.cs" />
|
||||||
<Compile Include="Tasks\Locking\Services\IDistributedLockService.cs" />
|
<Compile Include="Tasks\Locking\Services\IDistributedLockService.cs" />
|
||||||
|
|||||||
@@ -1,22 +0,0 @@
|
|||||||
using System;
|
|
||||||
using Orchard.Data.Migration;
|
|
||||||
|
|
||||||
namespace Orchard.Tasks.Locking.Migrations {
|
|
||||||
public class FrameworkMigrations : DataMigrationImpl {
|
|
||||||
|
|
||||||
public int Create() {
|
|
||||||
SchemaBuilder.CreateTable("DistributedLockRecord", table => table
|
|
||||||
.Column<int>("Id", column => column.PrimaryKey().Identity())
|
|
||||||
.Column<string>("Name", column => column.NotNull().WithLength(512).Unique())
|
|
||||||
.Column<string>("MachineName", column => column.WithLength(256))
|
|
||||||
.Column<DateTime>("CreatedUtc")
|
|
||||||
.Column<DateTime>("ValidUntilUtc", column => column.Nullable()));
|
|
||||||
|
|
||||||
SchemaBuilder.AlterTable("DistributedLockRecord", table => {
|
|
||||||
table.CreateIndex("IDX_DistributedLockRecord_Name", "Name");
|
|
||||||
});
|
|
||||||
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
using System;
|
||||||
|
using Orchard.Data.Migration.Schema;
|
||||||
|
using Orchard.Environment.Configuration;
|
||||||
|
|
||||||
|
namespace Orchard.Tasks.Locking.Services {
|
||||||
|
public class DistributedLockSchemaBuilder {
|
||||||
|
private readonly ShellSettings _shellSettings;
|
||||||
|
private readonly SchemaBuilder _schemaBuilder;
|
||||||
|
private const string TableName = "Orchard_Framework_DistributedLockRecord";
|
||||||
|
|
||||||
|
public DistributedLockSchemaBuilder(ShellSettings shellSettings, SchemaBuilder schemaBuilder) {
|
||||||
|
_shellSettings = shellSettings;
|
||||||
|
_schemaBuilder = schemaBuilder;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool EnsureSchema() {
|
||||||
|
if (SchemaExists())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
CreateSchema();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CreateSchema() {
|
||||||
|
_schemaBuilder.CreateTable(TableName, table => table
|
||||||
|
.Column<int>("Id", column => column.PrimaryKey().Identity())
|
||||||
|
.Column<string>("Name", column => column.NotNull().WithLength(512).Unique())
|
||||||
|
.Column<string>("MachineName", column => column.WithLength(256))
|
||||||
|
.Column<DateTime>("CreatedUtc")
|
||||||
|
.Column<DateTime>("ValidUntilUtc", column => column.Nullable()));
|
||||||
|
|
||||||
|
_schemaBuilder.AlterTable(TableName, table => {
|
||||||
|
table.CreateIndex("IDX_DistributedLockRecord_Name", "Name");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool SchemaExists() {
|
||||||
|
try {
|
||||||
|
var tablePrefix = String.IsNullOrEmpty(_shellSettings.DataTablePrefix) ? "" : _shellSettings.DataTablePrefix + "_";
|
||||||
|
_schemaBuilder.ExecuteSql(String.Format("select * from {0}{1}", tablePrefix, TableName));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -44,30 +44,30 @@ namespace Orchard.Tasks.Locking.Services {
|
|||||||
if (dLock != null) {
|
if (dLock != null) {
|
||||||
Logger.Debug("Successfully acquired lock '{0}'.", name);
|
Logger.Debug("Successfully acquired lock '{0}'.", name);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
Logger.Warning("Failed to acquire lock '{0}' within the specified timeout ({1}).", name, timeout);
|
Logger.Warning("Failed to acquire lock '{0}' within the specified timeout ({1}).", name, timeout);
|
||||||
}
|
}
|
||||||
catch (Exception ex) {
|
catch (Exception ex) {
|
||||||
Logger.Error(ex, "Error while trying to acquire lock '{0}'.", name);
|
Logger.Error(ex, "Error while trying to acquire lock '{0}'.", name);
|
||||||
// TODO: Is it correct to not throw here? Should we instead ONLY swallow TimeoutException?
|
// TODO: Is it correct to not throw here? Should we instead ONLY swallow TimeoutException?
|
||||||
}
|
}
|
||||||
|
|
||||||
dLock = null;
|
dLock = null;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IDistributedLock AcquireLock(string name, TimeSpan? maxValidFor, TimeSpan? timeout) {
|
public IDistributedLock AcquireLock(string name, TimeSpan? maxValidFor, TimeSpan? timeout) {
|
||||||
try {
|
try {
|
||||||
DistributedLock result = AcquireLockInternal(name, maxValidFor, timeout, throwOnTimeout: true);
|
DistributedLock result = AcquireLockInternal(name, maxValidFor, timeout, throwOnTimeout: true);
|
||||||
Logger.Debug("Successfully acquired lock '{0}'.", name);
|
Logger.Debug("Successfully acquired lock '{0}'.", name);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
catch (Exception ex) {
|
catch (Exception ex) {
|
||||||
Logger.Error(ex, "Error while trying to acquire lock '{0}'.", name);
|
Logger.Error(ex, "Error while trying to acquire lock '{0}'.", name);
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private DistributedLock AcquireLockInternal(string name, TimeSpan? maxValidFor, TimeSpan? timeout, bool throwOnTimeout) {
|
private DistributedLock AcquireLockInternal(string name, TimeSpan? maxValidFor, TimeSpan? timeout, bool throwOnTimeout) {
|
||||||
var internalName = GetInternalLockName(name);
|
var internalName = GetInternalLockName(name);
|
||||||
@@ -81,7 +81,7 @@ namespace Orchard.Tasks.Locking.Services {
|
|||||||
throw new TimeoutException(String.Format("Failed to acquire lock '{0}' within the specified timeout ({1}).", internalName, timeout));
|
throw new TimeoutException(String.Format("Failed to acquire lock '{0}' within the specified timeout ({1}).", internalName, timeout));
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
Logger.Debug("Successfully entered local monitor for lock '{0}'.", internalName);
|
Logger.Debug("Successfully entered local monitor for lock '{0}'.", internalName);
|
||||||
|
|
||||||
@@ -106,34 +106,34 @@ namespace Orchard.Tasks.Locking.Services {
|
|||||||
dLock = new DistributedLock(name, internalName, releaseLockAction: () => {
|
dLock = new DistributedLock(name, internalName, releaseLockAction: () => {
|
||||||
Monitor.Exit(monitorObj);
|
Monitor.Exit(monitorObj);
|
||||||
DeleteDistributedLockRecord(internalName);
|
DeleteDistributedLockRecord(internalName);
|
||||||
});
|
});
|
||||||
|
|
||||||
_locks.Add(monitorObj, dLock);
|
_locks.Add(monitorObj, dLock);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!success) {
|
if (!success) {
|
||||||
Logger.Debug("Record for lock '{0}' could not be created for current machine within the specified timeout ({1}).", internalName, timeout);
|
Logger.Debug("Record for lock '{0}' could not be created for current machine within the specified timeout ({1}).", internalName, timeout);
|
||||||
|
|
||||||
if (throwOnTimeout)
|
if (throwOnTimeout)
|
||||||
throw new TimeoutException(String.Format("Failed to acquire lock '{0}' within the specified timeout ({1}).", internalName, timeout));
|
throw new TimeoutException(String.Format("Failed to acquire lock '{0}' within the specified timeout ({1}).", internalName, timeout));
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return dLock;
|
return dLock;
|
||||||
}
|
}
|
||||||
catch (Exception ex) {
|
catch (Exception ex) {
|
||||||
Monitor.Exit(monitorObj);
|
Monitor.Exit(monitorObj);
|
||||||
|
|
||||||
Logger.Error(ex, "An error occurred while trying to acquire lock '{0}'.", internalName);
|
Logger.Error(ex, "An error occurred while trying to acquire lock '{0}'.", internalName);
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool EnsureDistributedLockRecord(string internalName, TimeSpan? maxValidFor) {
|
private bool EnsureDistributedLockRecord(string internalName, TimeSpan? maxValidFor) {
|
||||||
var localMachineName = _machineNameProvider.GetMachineName();
|
var localMachineName = _machineNameProvider.GetMachineName();
|
||||||
@@ -145,7 +145,7 @@ namespace Orchard.Tasks.Locking.Services {
|
|||||||
if (record == null) {
|
if (record == null) {
|
||||||
// No record existed, so we're good to create a new one.
|
// No record existed, so we're good to create a new one.
|
||||||
Logger.Debug("No valid record was found for lock '{0}'; creating a new record.", internalName);
|
Logger.Debug("No valid record was found for lock '{0}'; creating a new record.", internalName);
|
||||||
|
|
||||||
repository.Create(new DistributedLockRecord {
|
repository.Create(new DistributedLockRecord {
|
||||||
Name = internalName,
|
Name = internalName,
|
||||||
MachineName = localMachineName,
|
MachineName = localMachineName,
|
||||||
@@ -163,7 +163,7 @@ namespace Orchard.Tasks.Locking.Services {
|
|||||||
});
|
});
|
||||||
|
|
||||||
return hasLockRecord;
|
return hasLockRecord;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DeleteDistributedLockRecord(string internalName) {
|
private void DeleteDistributedLockRecord(string internalName) {
|
||||||
try {
|
try {
|
||||||
@@ -177,9 +177,9 @@ namespace Orchard.Tasks.Locking.Services {
|
|||||||
}
|
}
|
||||||
catch (Exception ex) {
|
catch (Exception ex) {
|
||||||
if (ex.IsFatal())
|
if (ex.IsFatal())
|
||||||
throw;
|
throw;
|
||||||
Logger.Warning(ex, "An error occurred while deleting record for lock '{0}'.", internalName);
|
Logger.Warning(ex, "An error occurred while deleting record for lock '{0}'.", internalName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool RepeatUntilTimeout(TimeSpan? timeout, TimeSpan repeatInterval, Func<bool> action) {
|
private bool RepeatUntilTimeout(TimeSpan? timeout, TimeSpan repeatInterval, Func<bool> action) {
|
||||||
@@ -198,14 +198,14 @@ namespace Orchard.Tasks.Locking.Services {
|
|||||||
if (action == null)
|
if (action == null)
|
||||||
throw new ArgumentNullException();
|
throw new ArgumentNullException();
|
||||||
|
|
||||||
using (var childLifetimeScope = _lifetimeScope.BeginLifetimeScope()) {
|
using (var childLifetimeScope = _lifetimeScope.BeginLifetimeScope()) {
|
||||||
var repository = childLifetimeScope.Resolve<IRepository<DistributedLockRecord>>();
|
var repository = childLifetimeScope.Resolve<IRepository<DistributedLockRecord>>();
|
||||||
var transactionManager = childLifetimeScope.Resolve<ITransactionManager>();
|
var transactionManager = childLifetimeScope.Resolve<ITransactionManager>();
|
||||||
transactionManager.RequireNew(IsolationLevel.ReadCommitted);
|
transactionManager.RequireNew(IsolationLevel.ReadCommitted);
|
||||||
action(repository);
|
action(repository);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private string GetInternalLockName(string name) {
|
private string GetInternalLockName(string name) {
|
||||||
// Prefix the requested lock name by a constant and the tenant name.
|
// Prefix the requested lock name by a constant and the tenant name.
|
||||||
return String.Format("DistributedLock:{0}:{1}", _shellSettings.Name, name);
|
return String.Format("DistributedLock:{0}:{1}", _shellSettings.Name, name);
|
||||||
|
|||||||
Reference in New Issue
Block a user