Merge pull request #5752 from OrchardCMS/feature/distributedlocking

Refactored distributed locks to support existing installations.
This commit is contained in:
Sipke Schoorstra
2015-09-10 21:55:16 +01:00
5 changed files with 102 additions and 51 deletions

View File

@@ -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();
}
} }
} }

View File

@@ -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" />

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}
}
}

View File

@@ -44,14 +44,14 @@ 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;
@@ -62,12 +62,12 @@ namespace Orchard.Tasks.Locking.Services {
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,14 +106,14 @@ 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);
@@ -121,19 +121,19 @@ namespace Orchard.Tasks.Locking.Services {
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();
@@ -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,7 +177,7 @@ 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);
} }
} }
@@ -198,13 +198,13 @@ 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.