Refactoring locking strategy

Adding support for tenants
Removed ThreadId field
Removed Count field
Optimized db access by using Monitor for the local machine/tenant checks
Removed Machine/Thread scopes
This commit is contained in:
Sebastien Ros
2015-09-02 18:37:38 -07:00
parent d0ad5de25c
commit 9ea87837c3
21 changed files with 300 additions and 379 deletions

View File

@@ -24,7 +24,7 @@ namespace Orchard.Tests {
protected string _databaseFilePath;
protected ISessionFactory _sessionFactory;
protected StubClock _clock;
protected ShellSettings _shellSettings;
[TestFixtureSetUp]
public void InitFixture() {
@@ -48,7 +48,7 @@ namespace Orchard.Tests {
builder.RegisterInstance(new StubLocator(_session)).As<ISessionLocator>();
builder.RegisterInstance(_clock).As<IClock>();
builder.RegisterGeneric(typeof(Repository<>)).As(typeof(IRepository<>)).InstancePerLifetimeScope();
builder.RegisterInstance(new ShellSettings { Name = ShellSettings.DefaultName, DataProvider = "SqlCe" });
builder.RegisterInstance(_shellSettings = new ShellSettings { Name = ShellSettings.DefaultName, DataProvider = "SqlCe" });
builder.RegisterType<TestTransactionManager>().As<ITransactionManager>().InstancePerLifetimeScope();
builder.Register(context => _sessionFactory.OpenSession()).As<ISession>().InstancePerLifetimeScope();

View File

@@ -272,7 +272,6 @@
<Compile Include="Localization\DateTimePartsTests.cs" />
<Compile Include="Localization\DefaultDateLocalizationServicesTests.cs" />
<Compile Include="Localization\DefaultDateFormatterTests.cs" />
<Compile Include="Stubs\StubThreadProvider.cs" />
<Compile Include="Stubs\StubMachineNameProvider.cs" />
<Compile Include="Stubs\StubCultureSelector.cs" />
<Compile Include="Localization\TestHelpers.cs" />

View File

@@ -1,15 +0,0 @@
using Orchard.Environment;
namespace Orchard.Tests.Stubs {
public class StubThreadProvider : IThreadProvider {
public StubThreadProvider() {
ManagedThreadId = 1;
}
public int ManagedThreadId { get; set; }
public int GetCurrentThreadId() {
return ManagedThreadId;
}
}
}

View File

@@ -1,10 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Autofac;
using NUnit.Framework;
using Orchard.Data;
using Orchard.Environment;
using Orchard.Environment.Configuration;
using Orchard.Services;
using Orchard.Tasks.Locking.Records;
using Orchard.Tasks.Locking.Services;
@@ -16,10 +18,8 @@ namespace Orchard.Tests.Tasks {
private const string LockName = "Orchard Test Lock";
private DistributedLockService _distributedLockService;
private StubMachineNameProvider _machineNameProvider;
private StubThreadProvider _threadProvider;
private IRepository<DistributedLockRecord> _distributedLockRepository;
private ITransactionManager _transactionManager;
protected override IEnumerable<Type> DatabaseTypes {
get { yield return typeof(DistributedLockRecord); }
@@ -28,7 +28,6 @@ namespace Orchard.Tests.Tasks {
public override void Register(ContainerBuilder builder) {
builder.RegisterType<StubClock>().As<IClock>();
builder.RegisterType<StubMachineNameProvider>().As<IMachineNameProvider>().SingleInstance();
builder.RegisterType<StubThreadProvider>().As<IThreadProvider>().SingleInstance();
builder.RegisterType<DistributedLockService>().AsSelf();
}
@@ -36,74 +35,64 @@ namespace Orchard.Tests.Tasks {
base.Init();
_distributedLockService = _container.Resolve<DistributedLockService>();
_machineNameProvider = (StubMachineNameProvider)_container.Resolve<IMachineNameProvider>();
_threadProvider = (StubThreadProvider)_container.Resolve<IThreadProvider>();
_distributedLockRepository = _container.Resolve<IRepository<DistributedLockRecord>>();
_transactionManager = _container.Resolve<ITransactionManager>();
}
[Test]
public void TryAcquiringLockSucceeds() {
DistributedLock @lock;
var lockAcquired = _distributedLockService.TryAcquireLockForMachine(LockName, TimeSpan.FromSeconds(60), null, out @lock);
Assert.That(lockAcquired, Is.True);
}
[Test]
public void TryAcquiringLockTwiceOnSameMachineSucceeds() {
DistributedLock @lock;
var attempt1 = _distributedLockService.TryAcquireLockForMachine(LockName, TimeSpan.FromSeconds(60), null, out @lock);
var attempt2 = _distributedLockService.TryAcquireLockForMachine(LockName, TimeSpan.FromSeconds(60), null, out @lock);
IDistributedLock @lock;
var attempt1 = _distributedLockService.TryAcquireLock(LockName, TimeSpan.FromSeconds(60), out @lock);
var attempt2 = _distributedLockService.TryAcquireLock(LockName, TimeSpan.FromSeconds(60), out @lock);
Assert.That(attempt1, Is.True);
Assert.That(attempt2, Is.True);
}
[Test]
public void TryAcquiringLockTwiceOnSameMachineIncreasesLockCountTwice() {
DistributedLock @lock;
_distributedLockService.TryAcquireLockForMachine(LockName, TimeSpan.FromSeconds(60), null, out @lock);
_distributedLockService.TryAcquireLockForMachine(LockName, TimeSpan.FromSeconds(60), null, out @lock);
var lockId = Int32.Parse(@lock.Id);
var lockRecord = _distributedLockRepository.Get(lockId);
Assert.That(lockRecord.Count, Is.EqualTo(2));
[Test]
public void AcquiringTheLockOnTheSameMachineReturnsTheSameLock() {
IDistributedLock lock1, lock2;
_distributedLockService.TryAcquireLock(LockName, TimeSpan.FromSeconds(60), out lock1);
_distributedLockService.TryAcquireLock(LockName, TimeSpan.FromSeconds(60), out lock2);
Assert.AreEqual(lock1, lock2);
}
[Test]
public void ReleasingLockDecreasesLockCount() {
DistributedLock @lock;
_distributedLockService.TryAcquireLockForMachine(LockName, TimeSpan.FromSeconds(60), null, out @lock);
_distributedLockService.TryAcquireLockForMachine(LockName, TimeSpan.FromSeconds(60), null, out @lock);
public void ReleasingSingleLockDeletesRecord() {
IDistributedLock lock1;
_distributedLockService.TryAcquireLock(LockName, TimeSpan.FromSeconds(60), out lock1);
var lockId = Int32.Parse(@lock.Id);
var lockRecord = _distributedLockRepository.Get(lockId);
_distributedLockService.ReleaseLock(@lock);
_session.Refresh(lockRecord);
Assert.That(lockRecord.Count, Is.EqualTo(1));
}
[Test]
public void ReleasingLockAndCountReachesZeroDeletesLock()
{
DistributedLock @lock;
_distributedLockService.TryAcquireLockForMachine(LockName, TimeSpan.FromSeconds(60), null, out @lock);
var lockId = Int32.Parse(@lock.Id);
_distributedLockService.ReleaseLock(@lock);
var lockRecord = _distributedLockRepository.Get(lockId);
lock1.Dispose();
var lockRecord = _distributedLockRepository.Table.FirstOrDefault();
Assert.That(lockRecord, Is.Null);
}
[Test]
public void ReleasingFirstLockDoesntDeleteRecord() {
IDistributedLock lock1, lock2;
_distributedLockService.TryAcquireLock(LockName, TimeSpan.FromSeconds(60), out lock1);
_distributedLockService.TryAcquireLock(LockName, TimeSpan.FromSeconds(60), out lock2);
lock1.Dispose();
var lockRecord = _distributedLockRepository.Table.FirstOrDefault();
Assert.That(lockRecord, Is.Not.Null);
lock2.Dispose();
lockRecord = _distributedLockRepository.Table.FirstOrDefault();
Assert.That(lockRecord, Is.Null);
}
[Test]
public void TryAcquiringLockTwiceFails() {
DistributedLock @lock;
_distributedLockService.DisableMonitorLock = true;
IDistributedLock @lock;
_machineNameProvider.MachineName = "Orchard Test Machine 1";
var attempt1 = _distributedLockService.TryAcquireLockForMachine(LockName, TimeSpan.FromSeconds(60), null, out @lock);
var attempt1 = _distributedLockService.TryAcquireLock(LockName, TimeSpan.FromSeconds(60), out @lock);
_machineNameProvider.MachineName = "Orchard Test Machine 2";
var attempt2 = _distributedLockService.TryAcquireLockForMachine(LockName, TimeSpan.FromSeconds(60), null, out @lock);
var attempt2 = _distributedLockService.TryAcquireLock(LockName, TimeSpan.FromSeconds(60), out @lock);
Assert.That(attempt1, Is.True);
Assert.That(attempt2, Is.False);
@@ -111,66 +100,90 @@ namespace Orchard.Tests.Tasks {
[Test]
public void TryAcquiringNonExpiredActiveLockFails() {
DistributedLock @lock;
CreateNonExpiredActiveLock("Other Machine", threadId: null);
var success = _distributedLockService.TryAcquireLockForMachine(LockName, TimeSpan.FromHours(1), null, out @lock);
IDistributedLock @lock;
CreateNonExpiredActiveLock("Other Machine");
var success = _distributedLockService.TryAcquireLock(LockName, TimeSpan.FromHours(1), out @lock);
Assert.That(success, Is.False);
}
[Test]
public void TryAcquiringNonExpiredButInactiveLockSucceeds() {
DistributedLock @lock;
CreateNonExpiredButInactiveLock("Other Machine", threadId: null);
var success = _distributedLockService.TryAcquireLockForMachine(LockName, TimeSpan.FromHours(1), null, out @lock);
public void TryAcquiringNonExpiredButInactiveLockFromOtherMachineFails() {
IDistributedLock @lock;
CreateNonExpiredActiveLock("Other Machine");
var success = _distributedLockService.TryAcquireLock(LockName, TimeSpan.FromHours(1), out @lock);
Assert.That(success, Is.False);
}
[Test]
public void TryAcquiringNonExpiredButInactiveLockFromSameMachineSucceeds() {
IDistributedLock @lock;
CreateNonExpiredActiveLock("Orchard Machine");
var success = _distributedLockService.TryAcquireLock(LockName, TimeSpan.FromHours(1), out @lock);
Assert.That(success, Is.True);
}
[Test]
public void TryAcquiringExpiredButActiveLockSucceeds() {
DistributedLock @lock;
CreateExpiredButActiveLock("Other Machine", threadId: null);
var success = _distributedLockService.TryAcquireLockForMachine(LockName, TimeSpan.FromHours(1), null, out @lock);
IDistributedLock @lock;
CreateExpiredButActiveLock("Other Machine");
var success = _distributedLockService.TryAcquireLock(LockName, TimeSpan.FromHours(1), out @lock);
Assert.That(success, Is.True);
}
[Test]
public void TryAcquiringNonExpiredAndActiveLockFromCurrentOwnerSucceeds() {
DistributedLock @lock;
CreateNonExpiredActiveLock(GetMachineName(), threadId: null);
var success = _distributedLockService.TryAcquireLockForMachine(LockName, TimeSpan.FromHours(1), null, out @lock);
IDistributedLock @lock;
CreateNonExpiredActiveLock(GetMachineName());
var success = _distributedLockService.TryAcquireLock(LockName, TimeSpan.FromHours(1), out @lock);
Assert.That(success, Is.True);
}
[Test]
public void AcquiringNonExpiredAndActiveLockFromDifferentOwnerThrowsTimeoutException() {
CreateNonExpiredActiveLock("Other Machine", threadId: null);
Assert.Throws<TimeoutException>(() => _distributedLockService.AcquireLockForMachine(LockName, TimeSpan.FromHours(1), TimeSpan.Zero));
CreateNonExpiredActiveLock("Other Machine");
Assert.Throws<TimeoutException>(() => _distributedLockService.AcquireLock(LockName, TimeSpan.FromHours(1), TimeSpan.Zero));
}
[Test]
public void MultipleAcquisitionsFromDifferentMachinesShouldFail() {
DistributedLock @lock;
IDistributedLock @lock;
_distributedLockService.DisableMonitorLock = true;
_machineNameProvider.MachineName = "Orchard Test Machine 1";
var attempt1 = _distributedLockService.TryAcquireLockForMachine(LockName, TimeSpan.FromSeconds(60), null, out @lock);
var attempt1 = _distributedLockService.TryAcquireLock(LockName, TimeSpan.FromSeconds(60), out @lock);
_machineNameProvider.MachineName = "Orchard Test Machine 2";
var attempt2 = _distributedLockService.TryAcquireLockForMachine(LockName, TimeSpan.FromSeconds(60), null, out @lock);
var attempt2 = _distributedLockService.TryAcquireLock(LockName, TimeSpan.FromSeconds(60), out @lock);
Assert.That(attempt1, Is.True);
Assert.That(attempt2, Is.False);
}
[Test]
public void MultipleAcquisitionsFromDifferentMachinesOnDifferentTenantShouldSucceed() {
IDistributedLock @lock;
_distributedLockService.DisableMonitorLock = true;
_machineNameProvider.MachineName = "Orchard Test Machine 1";
var attempt1 = _distributedLockService.TryAcquireLock(LockName, TimeSpan.FromSeconds(60), out @lock);
_machineNameProvider.MachineName = "Orchard Test Machine 2";
_shellSettings.Name = "Foo";
var attempt2 = _distributedLockService.TryAcquireLock(LockName, TimeSpan.FromSeconds(60), out @lock);
Assert.That(attempt1, Is.True);
Assert.That(attempt2, Is.True);
}
[Test]
public void MultithreadedAcquisitionsShouldNotCauseTransactionErrors() {
var tasks = new List<Task>();
for (var i = 0; i < 10; i++) {
var task = Task.Factory.StartNew(() => {
DistributedLock @lock;
Assert.DoesNotThrow(() => _distributedLockService.TryAcquireLockForMachine(LockName, TimeSpan.FromHours(1), null, out @lock));
IDistributedLock @lock;
Assert.DoesNotThrow(() => _distributedLockService.TryAcquireLock(LockName, TimeSpan.FromHours(1), out @lock));
});
tasks.Add(task);
@@ -179,61 +192,57 @@ namespace Orchard.Tests.Tasks {
Task.WaitAll(tasks.ToArray());
}
[Test]
public void MixedScopeAcquisitionsShouldThrow() {
DistributedLock @lock;
Assert.DoesNotThrow(() => _distributedLockService.TryAcquireLockForMachine(LockName, TimeSpan.FromSeconds(60), null, out @lock));
Assert.Throws<InvalidOperationException>(() => _distributedLockService.TryAcquireLockForThread(LockName, TimeSpan.FromSeconds(60), null, out @lock));
}
[Test]
public void TryAcquireActiveLockWithNullTimeoutReturnsFalseImmediately() {
CreateNonExpiredActiveLock("Other Machine", null);
// Disable monitor locking to simulate concurrent requests
_distributedLockService.DisableMonitorLock = true;
CreateNonExpiredActiveLock("Other Machine");
DistributedLock @lock;
var acquired = _distributedLockService.TryAcquireLockForThread(LockName, TimeSpan.FromMinutes(1), null, out @lock);
IDistributedLock @lock;
var acquired = _distributedLockService.TryAcquireLock(LockName, TimeSpan.FromMinutes(1), out @lock);
Assert.That(acquired, Is.False);
}
[Test]
public void ActiveLockWithUndefinedValidUntilNeverExpires() {
CreateNonExpiredActiveLockThatNeverExpires("Other Machine", null);
// Disable monitor locking to simulate concurrent requests
_distributedLockService.DisableMonitorLock = true;
CreateNonExpiredActiveLockThatNeverExpires("Other Machine");
_clock.Advance(DateTime.MaxValue - _clock.UtcNow); // Fast forward to the End of Time.
DistributedLock @lock;
var acquired = _distributedLockService.TryAcquireLockForThread(LockName, TimeSpan.FromMinutes(1), null, out @lock);
IDistributedLock @lock;
var acquired = _distributedLockService.TryAcquireLock(LockName, TimeSpan.FromMinutes(1), out @lock);
Assert.That(acquired, Is.False);
}
[Test]
public void ActiveLockWithUndefinedValidUntilNeverExpiresUntilReleased() {
DistributedLock @lock;
IDistributedLock @lock;
// Create a never expiring lock.
_machineNameProvider.MachineName = "Orchard Test Machine 1";
var attempt1 = _distributedLockService.TryAcquireLockForThread(LockName, maxValidFor: null, timeout: null, @lock: out @lock);
var attempt1 = _distributedLockService.TryAcquireLock(LockName, maxValidFor: null, timeout: null, l: out @lock);
// Release the lock.
_distributedLockService.ReleaseLock(@lock);
_distributedLockService.ReleaseDistributedLock((DistributedLock)@lock);
// Acquire the lock from another machine.
_machineNameProvider.MachineName = "Orchard Test Machine 2";
var attempt2 = _distributedLockService.TryAcquireLockForThread(LockName, maxValidFor: null, timeout: null, @lock: out @lock);
var attempt2 = _distributedLockService.TryAcquireLock(LockName, maxValidFor: null, timeout: null, l: out @lock);
// Validate the results.
Assert.That(attempt1, Is.True);
Assert.That(attempt2, Is.True);
}
private DistributedLockRecord CreateLockRecord(int count, DateTime createdUtc, DateTime? validUntilUtc, string machineName, int? threadId) {
private DistributedLockRecord CreateLockRecord(DateTime createdUtc, DateTime? validUntilUtc, string machineName) {
var record = new DistributedLockRecord {
Name = LockName,
Count = count,
Name = ShellSettings.DefaultName + ":" + LockName,
CreatedUtc = createdUtc,
ValidUntilUtc = validUntilUtc,
MachineName = machineName,
ThreadId = threadId
};
_distributedLockRepository.Create(record);
@@ -241,32 +250,23 @@ namespace Orchard.Tests.Tasks {
return record;
}
private DistributedLockRecord CreateNonExpiredActiveLock(string machineName, int? threadId) {
private DistributedLockRecord CreateNonExpiredActiveLock(string machineName) {
var now = _clock.UtcNow;
return CreateLockRecord(1, now, now + TimeSpan.FromHours(1), machineName, threadId);
return CreateLockRecord(now, now + TimeSpan.FromHours(1), machineName);
}
private DistributedLockRecord CreateNonExpiredButInactiveLock(string machineName, int? threadId) {
private DistributedLockRecord CreateExpiredButActiveLock(string machineName) {
var now = _clock.UtcNow;
return CreateLockRecord(0, now, now + TimeSpan.FromHours(1), machineName, threadId);
return CreateLockRecord(now, now - TimeSpan.FromHours(1), machineName);
}
private DistributedLockRecord CreateExpiredButActiveLock(string machineName, int? threadId) {
private DistributedLockRecord CreateNonExpiredActiveLockThatNeverExpires(string machineName) {
var now = _clock.UtcNow;
return CreateLockRecord(1, now, now - TimeSpan.FromHours(1), machineName, threadId);
}
private DistributedLockRecord CreateNonExpiredActiveLockThatNeverExpires(string machineName, int? threadId) {
var now = _clock.UtcNow;
return CreateLockRecord(1, now, null, machineName, threadId);
return CreateLockRecord(now, null, machineName);
}
private string GetMachineName() {
return _machineNameProvider.GetMachineName();
}
private int GetThreadId() {
return _threadProvider.GetCurrentThreadId();
}
}
}

View File

@@ -7,24 +7,23 @@ namespace Orchard.Tests.Tasks {
[TestFixture]
public class LockTests : ContainerTestBase {
private const string LockName = "Orchard Test Lock";
private const string LockId = "1";
private Mock<IDistributedLockService> _distributedLockServiceMock;
private Mock<DistributedLockService> _distributedLockServiceMock;
private DistributedLock _lock;
protected override void Register(ContainerBuilder builder) {
_distributedLockServiceMock = new Mock<IDistributedLockService>();
_distributedLockServiceMock = new Mock<DistributedLockService>();
builder.RegisterInstance(_distributedLockServiceMock.Object);
}
protected override void Resolve(ILifetimeScope container) {
_lock = DistributedLock.ForMachine(_distributedLockServiceMock.Object, LockName, "Orchard Test Machine", LockId);
_lock = new DistributedLock(_distributedLockServiceMock.Object, LockName);
}
[Test]
public void DisposeInvokesDistributedLockServiceDisposeLock() {
_lock.Dispose();
_distributedLockServiceMock.Verify(service => service.ReleaseLock(_lock), Times.Exactly(1));
_distributedLockServiceMock.Verify(service => service.ReleaseDistributedLock(_lock), Times.Exactly(1));
}
[Test]
@@ -33,7 +32,7 @@ namespace Orchard.Tests.Tasks {
_lock.Dispose();
_lock.Dispose();
_distributedLockServiceMock.Verify(service => service.ReleaseLock(_lock), Times.Exactly(1));
_distributedLockServiceMock.Verify(service => service.ReleaseDistributedLock(_lock), Times.Exactly(1));
}
}
}

View File

@@ -41,8 +41,8 @@ namespace Orchard.AuditTrail.Services {
Logger.Debug("Beginning sweep.");
// Only allow this task to run on one farm node at a time.
DistributedLock @lock;
if (_distributedLockService.TryAcquireLockForMachine(GetType().FullName, TimeSpan.FromHours(1), out @lock)) {
IDistributedLock @lock;
if (_distributedLockService.TryAcquireLock(GetType().FullName, TimeSpan.FromHours(1), out @lock)) {
using (@lock) {
// We don't need to check the audit trail for events to remove every minute. Let's stick with twice a day.

View File

@@ -51,8 +51,8 @@ namespace Orchard.Azure.MediaServices.Services.Jobs {
}
// Only allow this task to run on one farm node at a time.
DistributedLock @lock;
if (_distributedLockService.TryAcquireLockForMachine(GetType().FullName, TimeSpan.FromHours(1), out @lock)) {
IDistributedLock @lock;
if (_distributedLockService.TryAcquireLock(GetType().FullName, TimeSpan.FromHours(1), out @lock)) {
using (@lock) {
var jobs = _jobManager.GetActiveJobs().ToDictionary(job => job.WamsJobId);

View File

@@ -32,8 +32,8 @@ namespace Orchard.JobsQueue.Services {
// prevent two threads on the same machine to process the message queue
if (_rwl.TryEnterWriteLock(0)) {
try {
DistributedLock @lock;
if(_distributedLockService.TryAcquireLockForMachine(GetType().FullName, TimeSpan.FromMinutes(5), out @lock)){
IDistributedLock @lock;
if(_distributedLockService.TryAcquireLock(GetType().FullName, TimeSpan.FromMinutes(5), out @lock)){
using (@lock) {
IEnumerable<QueuedJobRecord> messages;

View File

@@ -6,7 +6,7 @@ namespace Orchard.TaskLease.Services {
/// Describes a service to save and acquire task leases. A task lease can't be acquired by two different machines,
/// for a specific amount of time. Optionnally a State can be saved along with the lease.
/// </summary>
[Obsolete("Use Orchard.Tasks.Locking.IDistributedLockService and the AcquireLockForMachine/TryAcquireLockForMachine methods instead.")]
[Obsolete("Use Orchard.Tasks.Locking.IDistributedLockService instead.")]
public interface ITaskLeaseService : IDependency {
/// <summary>

View File

@@ -9,7 +9,7 @@ namespace Orchard.TaskLease.Services {
/// <summary>
/// Provides a database driven implementation of <see cref="ITaskLeaseService" />
/// </summary>
[Obsolete("Use Orchard.Tasks.Locking.DistributedLockService and the AcquireLockForMachine/TryAcquireLockForMachine methods instead.")]
[Obsolete("Use Orchard.Tasks.Locking.DistributedLockService instead.")]
public class TaskLeaseService : ITaskLeaseService {
private readonly IRepository<TaskLeaseRecord> _repository;

View File

@@ -29,8 +29,8 @@ namespace Orchard.Data.Migration {
public ILogger Logger { get; set; }
public void Activated() {
DistributedLock @lock;
if(_distributedLockService.TryAcquireLockForThread(GetType().FullName, TimeSpan.FromMinutes(30), TimeSpan.FromMilliseconds(250), out @lock)) {
IDistributedLock @lock;
if(_distributedLockService.TryAcquireLock(GetType().FullName, TimeSpan.FromMinutes(30), TimeSpan.FromMilliseconds(250), out @lock)) {
using (@lock) {
// Let's make sure that the basic set of features is enabled. If there are any that are not enabled, then let's enable them first.
var theseFeaturesShouldAlwaysBeActive = new[] {

View File

@@ -1,13 +0,0 @@
namespace Orchard.Environment {
/// <summary>
/// Describes a service which returns the managed thread ID of the current thread.
/// </summary>
public interface IThreadProvider {
/// <summary>
/// Returns the managed thread ID of the current thread.
/// </summary>
int GetCurrentThreadId();
}
}

View File

@@ -67,7 +67,6 @@ namespace Orchard.Environment {
builder.RegisterType<ViewsBackgroundCompilation>().As<IViewsBackgroundCompilation>().SingleInstance();
builder.RegisterType<DefaultExceptionPolicy>().As<IExceptionPolicy>().SingleInstance();
builder.RegisterType<DefaultCriticalErrorProvider>().As<ICriticalErrorProvider>().SingleInstance();
builder.RegisterType<ThreadProvider>().As<IThreadProvider>().SingleInstance();
//builder.RegisterType<RazorTemplateCache>().As<IRazorTemplateProvider>().SingleInstance();
RegisterVolatileProvider<WebSiteFolder, IWebSiteFolder>(builder);

View File

@@ -1,9 +0,0 @@
using System.Threading;
namespace Orchard.Environment {
public class ThreadProvider : IThreadProvider {
public int GetCurrentThreadId() {
return Thread.CurrentThread.ManagedThreadId;
}
}
}

View File

@@ -152,8 +152,6 @@
<Compile Include="Data\Migration\Schema\AddUniqueConstraintCommand.cs" />
<Compile Include="Data\Migration\Schema\DropUniqueConstraintCommand.cs" />
<Compile Include="Environment\Extensions\Models\LifecycleStatus.cs" />
<Compile Include="Environment\IThreadProvider.cs" />
<Compile Include="Environment\ThreadProvider.cs" />
<Compile Include="Environment\ShellBuilders\ICompositionStrategy.cs" />
<Compile Include="Mvc\Updater.cs" />
<Compile Include="Recipes\Models\ConfigurationContext.cs" />
@@ -402,6 +400,7 @@
<Compile Include="Settings\CurrentSiteWorkContext.cs" />
<Compile Include="Settings\ResourceDebugMode.cs" />
<Compile Include="Tasks\Locking\Migrations\FrameworkMigrations.cs" />
<Compile Include="Tasks\Locking\Services\IDistributedLock.cs" />
<Compile Include="Tasks\Locking\Services\DistributedLock.cs" />
<Compile Include="Tasks\Locking\Services\IDistributedLockService.cs" />
<Compile Include="Tasks\Locking\Services\DistributedLockService.cs" />

View File

@@ -7,15 +7,13 @@ namespace Orchard.Tasks.Locking.Migrations {
public int Create() {
SchemaBuilder.CreateTable("DistributedLockRecord", table => table
.Column<int>("Id", column => column.PrimaryKey().Identity())
.Column<string>("Name", column => column.NotNull().WithLength(256))
.Column<string>("Name", column => column.NotNull().WithLength(512).Unique())
.Column<string>("MachineName", column => column.WithLength(256))
.Column<int>("ThreadId", column => column.Nullable())
.Column<int>("Count")
.Column<DateTime>("CreatedUtc")
.Column<DateTime>("ValidUntilUtc", column => column.Nullable()));
SchemaBuilder.AlterTable("DistributedLockRecord", table => {
table.CreateIndex("IDX_DistributedLockRecord_Name_ValidUntilUtc_Count", "Name", "ValidUntilUtc", "Count");
table.CreateIndex("IDX_DistributedLockRecord_Name", "Name");
});
return 1;

View File

@@ -5,8 +5,6 @@ namespace Orchard.Tasks.Locking.Records {
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual string MachineName { get; set; }
public virtual int? ThreadId { get; set; }
public virtual int Count { get; set; }
public virtual DateTime CreatedUtc { get; set; }
public virtual DateTime? ValidUntilUtc { get; set; }
}

View File

@@ -3,44 +3,34 @@ using System.Threading;
namespace Orchard.Tasks.Locking.Services {
/// <summary>
/// Represents a distributed lock. />
/// </summary>
public class DistributedLock : IDisposable {
public static DistributedLock ForMachine(IDistributedLockService service, string name, string machineName, string lockId) {
return new DistributedLock {
_service = service,
Name = name,
MachineName = machineName,
Id = lockId
};
public class DistributedLock : IDistributedLock {
private DistributedLockService _service;
private string _name;
private int _count;
public string Name {
get {
return _name;
}
}
public static DistributedLock ForThread(IDistributedLockService service, string name, string machineName, int threadId, string lockId) {
return new DistributedLock {
_service = service,
Name = name,
MachineName = machineName,
ThreadId = threadId,
Id = lockId
};
public DistributedLock(DistributedLockService service, string name) {
_service = service;
_name = name;
_count = 1;
}
private IDistributedLockService _service;
private int _isDisposed;
private DistributedLock() {
public void IncreaseReferenceCount() {
_count++;
}
public string Id { get; private set; }
public string Name { get; private set; }
public string MachineName { get; private set; }
public int? ThreadId { get; private set; }
// This will be called at least and at the latest by the IoC container when the request ends.
public void Dispose() {
if(Interlocked.CompareExchange(ref _isDisposed, 1, 0) == 0)
_service.ReleaseLock(this);
_count--;
if (_count == 0) {
Monitor.Exit(String.Intern(_name));
_service.ReleaseDistributedLock(this);
}
}
}
}

View File

@@ -1,10 +1,13 @@
using System;
using System.Collections.Concurrent;
using System.Data;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Autofac;
using Orchard.Data;
using Orchard.Environment;
using Orchard.Environment.Configuration;
using Orchard.Exceptions;
using Orchard.Logging;
using Orchard.Services;
@@ -13,85 +16,49 @@ using Orchard.Tasks.Locking.Records;
namespace Orchard.Tasks.Locking.Services {
public class DistributedLockService : Component, IDistributedLockService {
private readonly IMachineNameProvider _machineNameProvider;
private readonly ILifetimeScope _lifetimeScope;
private readonly IClock _clock;
private readonly IThreadProvider _threadProvider;
private readonly IMachineNameProvider _machineNameProvider;
private readonly ShellSettings _shellSettings;
private readonly ILifetimeScope _lifetimeScope;
private readonly ConcurrentDictionary<string, DistributedLock> _locks = new ConcurrentDictionary<string, DistributedLock>();
public DistributedLockService(IMachineNameProvider machineNameProvider, IThreadProvider threadProvider, ILifetimeScope lifetimeScope, IClock clock) {
_machineNameProvider = machineNameProvider;
_lifetimeScope = lifetimeScope;
public bool DisableMonitorLock { get; set; }
public DistributedLockService(
IMachineNameProvider machineNameProvider,
ILifetimeScope lifetimeScope,
IClock clock,
ShellSettings shellSettings) {
_clock = clock;
_threadProvider = threadProvider;
_lifetimeScope = lifetimeScope;
_shellSettings = shellSettings;
_machineNameProvider = machineNameProvider;
}
public bool TryAcquireLockForMachine(string name, TimeSpan? maxValidFor, TimeSpan? timeout, out DistributedLock @lock) {
return TryAcquireLock(name, maxValidFor, timeout, GetMachineName(), null, out @lock);
}
public DistributedLock AcquireLockForMachine(string name, TimeSpan? maxValidFor, TimeSpan? timeout) {
return AcquireLock(name, maxValidFor, timeout, GetMachineName(), null);
}
public bool TryAcquireLockForThread(string name, TimeSpan? maxValidFor, TimeSpan? timeout, out DistributedLock @lock) {
return TryAcquireLock(name, maxValidFor, timeout, GetMachineName(), GetThreadId(), out @lock);
}
public DistributedLock AcquireLockForThread(string name, TimeSpan? maxValidFor, TimeSpan? timeout) {
return AcquireLock(name, maxValidFor, timeout, GetMachineName(), GetThreadId());
}
public void ReleaseLock(DistributedLock @lock) {
var childLifetimeScope = CreateChildLifetimeScope(@lock.Name);
public bool TryAcquireLock(string name, TimeSpan? maxValidFor, TimeSpan? timeout, out IDistributedLock l) {
try {
var repository = childLifetimeScope.Resolve<IRepository<DistributedLockRecord>>();
var transactionManager = childLifetimeScope.Resolve<ITransactionManager>();
transactionManager.RequireNew(IsolationLevel.ReadCommitted);
var lockId = Int32.Parse(@lock.Id);
var record = repository.Get(lockId);
if (record == null)
throw new OrchardException(T("No lock record could be found for the specified lock to be released."));
if (record.Count <= 0)
throw new OrchardException(T("The specified lock has already been released."));
record.Count--;
if(record.Count == 0)
repository.Delete(record);
l = AcquireLock(name, maxValidFor, timeout);
return l != null;
}
catch (Exception ex) {
if (ex.IsFatal()) throw;
Logger.Error(ex, "An non-fatal error occurred while trying to dispose a distributed lock with name '{0}' and ID {1}.", @lock.Name, @lock.Id);
}
finally {
childLifetimeScope.Dispose();
catch {
l = null;
return false;
}
}
private bool TryAcquireLock(string name, TimeSpan? maxValidFor, TimeSpan? timeout, string machineName, int? threadId, out DistributedLock @lock) {
@lock = AcquireLock(name, maxValidFor, machineName, threadId, timeout ?? TimeSpan.Zero);
return @lock != null;
}
private DistributedLock AcquireLock(string name, TimeSpan? maxValidFor, TimeSpan? timeout, string machineName, int? threadId) {
var @lock = AcquireLock(name, maxValidFor, machineName, threadId, timeout);
if (@lock != null)
return @lock;
throw new TimeoutException(String.Format("Failed to acquire a lock named '{0}' within the specified timeout ('{1}').", name, timeout));
}
private DistributedLock AcquireLock(string name, TimeSpan? maxValidFor, string machineName, int? threadId, TimeSpan? timeout = null) {
public IDistributedLock AcquireLock(string name, TimeSpan? maxValidFor, TimeSpan? timeout) {
DistributedLock l = null;
try {
DistributedLock @lock = null;
var acquired = Poll(() => (@lock = AcquireLockInternal(name, maxValidFor, machineName, threadId)) != null, timeout);
var acquired = Poll(() => (l = AcquireLockInternal(name, maxValidFor)) != null, timeout);
if (acquired) {
Logger.Debug("Successfully acquired a lock named '{0}'.", name);
return @lock;
}
else {
Logger.Debug("Failed to acquire a lock named '{0}'.", name);
}
}
catch (Exception ex) {
@@ -99,42 +66,79 @@ namespace Orchard.Tasks.Locking.Services {
throw;
}
Logger.Debug(timeout == null
? "Failed to acquire a lock named '{0}'."
: "Failed to acquire a lock named '{0}' within the specified timeout ('{1}')."
, name, timeout);
if (l == null && timeout != null) {
throw new TimeoutException(String.Format("Failed to acquire a lock named '{0}' within the specified timeout ('{1}').", name, timeout));
}
return null;
return l;
}
private DistributedLock AcquireLockInternal(string name, TimeSpan? maxValidFor, string machineName, int? threadId) {
var childLifetimeScope = CreateChildLifetimeScope(name);
public void ReleaseDistributedLock(DistributedLock l) {
try {
var transactionManager = childLifetimeScope.Resolve<ITransactionManager>();
transactionManager.RequireNew(IsolationLevel.ReadCommitted);
var record = GetDistributedLockRecordByName(l.Name);
// This way we can create a nested transaction scope instead of having the unwanted effect
// of manipulating the transaction of the caller.
var repository = childLifetimeScope.Resolve<IRepository<DistributedLockRecord>>();
if (record == null) {
throw new OrchardException(T("No lock record could be found for the specified lock to be released."));
}
TryCommitNewTransaction(repository => repository.Delete(record));
}
catch (Exception ex) {
if (ex.IsFatal()) throw;
Logger.Error(ex, "An non-fatal error occurred while trying to dispose a distributed lock with name '{0}'.", l.Name);
}
}
private DistributedLockRecord GetDistributedLockRecordByName(string name) {
DistributedLockRecord result = null;
TryCommitNewTransaction(repository => {
result = repository.Table.FirstOrDefault(x =>
x.Name == name
);
});
return result;
}
private DistributedLockRecord GetValidDistributedLockRecordByName(string name) {
DistributedLockRecord result = null;
TryCommitNewTransaction(repository => {
result = repository.Table.FirstOrDefault(x =>
x.Name == name &&
(x.ValidUntilUtc == null || x.ValidUntilUtc >= _clock.UtcNow)
);
});
return result;
}
private DistributedLock AcquireLockInternal(string name, TimeSpan? maxValidFor) {
try {
name = GetTenantLockName(name);
if (!DisableMonitorLock && !Monitor.TryEnter(String.Intern(name))) {
return null;
}
DistributedLock dLock = null;
// Returns the existing lock in case of reentrancy.
if(!DisableMonitorLock && _locks.TryGetValue(name, out dLock)) {
dLock.IncreaseReferenceCount();
return dLock;
}
// Find an existing, active lock, if any.
var record = repository.Table.FirstOrDefault(x => x.Name == name && (x.ValidUntilUtc == null || x.ValidUntilUtc >= _clock.UtcNow) && x.Count > 0);
var record = GetValidDistributedLockRecordByName(name);
// The current owner name (based on machine name and current thread ID).
// The current owner name (based on machine name).
var canAcquireLock = false;
// Check if there's already an active lock.
if (record != null) {
// Check if the machine name assigned to the lock is the one trying to acquire it.
if (record.MachineName == machineName) {
if (record.ThreadId != threadId)
throw new InvalidOperationException(
threadId == null
? "An attempt to acquire a lock for a machine was detected while the requested lock is already assigned to a specific thread."
: "An attempt to acquire a lock for a thread was detected while the requested lock is already assigned to a machine.");
record.Count++;
if (record.MachineName == _machineNameProvider.GetMachineName()) {
canAcquireLock = true;
}
}
@@ -142,30 +146,38 @@ namespace Orchard.Tasks.Locking.Services {
// No one has an active lock yet, so good to go.
record = new DistributedLockRecord {
Name = name,
MachineName = machineName,
ThreadId = threadId,
Count = 1,
MachineName = _machineNameProvider.GetMachineName(),
CreatedUtc = _clock.UtcNow,
ValidUntilUtc = maxValidFor != null ? _clock.UtcNow + maxValidFor : null
};
repository.Create(record);
canAcquireLock = true;
canAcquireLock = TryCommitNewTransaction( repository => {
repository.Create(record);
});
}
if (!canAcquireLock)
if (!canAcquireLock) {
return null;
}
return threadId != null
? DistributedLock.ForThread(this, name, machineName, threadId.Value, record.Id.ToString())
: DistributedLock.ForMachine(this, name, machineName, record.Id.ToString());
dLock = new DistributedLock(this, name);
if (!DisableMonitorLock) {
_locks.TryAdd(name, dLock);
}
return dLock;
}
catch (Exception ex) {
Monitor.Exit(String.Intern(name));
Logger.Error(ex, "An error occurred while trying to acquire a lock.");
throw;
}
finally {
childLifetimeScope.Dispose();
}
}
}
private string GetTenantLockName(string name) {
return _shellSettings.Name + ":" + name;
}
/// <summary>
@@ -188,16 +200,25 @@ namespace Orchard.Tasks.Locking.Services {
return acquired;
}
private string GetMachineName() {
return _machineNameProvider.GetMachineName();
}
private bool TryCommitNewTransaction(Action<IRepository<DistributedLockRecord>> action) {
if (action == null) {
throw new ArgumentNullException();
}
private int GetThreadId() {
return _threadProvider.GetCurrentThreadId();
}
try {
using (var childLifetimeScope = _lifetimeScope.BeginLifetimeScope()) {
var repository = childLifetimeScope.Resolve<IRepository<DistributedLockRecord>>();
var transactionManager = childLifetimeScope.Resolve<ITransactionManager>();
transactionManager.RequireNew(IsolationLevel.ReadCommitted);
action(repository);
}
private ILifetimeScope CreateChildLifetimeScope(string name) {
return _lifetimeScope.BeginLifetimeScope("Orchard.Tasks.Locking." + name);
return true;
}
catch {
return false;
}
}
}
}

View File

@@ -0,0 +1,11 @@
using System;
namespace Orchard.Tasks.Locking.Services {
/// <summary>
/// Represents a distributed lock. />
/// </summary>
public interface IDistributedLock : IDisposable {
string Name { get; }
}
}

View File

@@ -4,51 +4,26 @@ namespace Orchard.Tasks.Locking.Services {
/// <summary>
/// Provides distributed locking functionality.
/// </summary>
public interface IDistributedLockService : ISingletonDependency {
public interface IDistributedLockService : IDependency {
/// <summary>
/// Tries to acquire a lock on the specified name for the current machine.
/// Tries to acquire a distributed lock on a named resource for the current tenant.
/// </summary>
/// <param name="name">The name to use for the lock.</param>
/// <param name="maxValidFor">The maximum amount of time the lock is allowed. This is a safety net in case the caller fails to release the lock.</param>
/// <param name="timeout">The amount of time to wait for the lock to be acquired before timing out. A null value will cause the method to return immedieately if no lock could be acquired.</param>
/// <param name="maxValidFor">The maximum amount of time the lock is allowed. This is a safety net in case the caller fails to release the lock. If <c>null</c> is specified, the lock never expires until it's released by the owner.</param>
/// <param name="timeout">The amount of time to wait for the lock to be acquired before timing out. A null value will cause the method to block indefinitely until a lock can be acquired.</param>
/// <param name="lock">The acquired lock.</param>
/// <returns>Returns true if a lock was successfully acquired, false otherwise.</returns>
bool TryAcquireLockForMachine(string name, TimeSpan? maxValidFor, TimeSpan? timeout, out DistributedLock @lock);
bool TryAcquireLock(string name, TimeSpan? maxValidFor, TimeSpan? timeout, out IDistributedLock @lock);
/// <summary>
/// Acquires a lock with the specified parameters for the current machine.
/// Acquires a distributed lock on a named resource for the current tenant.
/// </summary>
/// <param name="name">The name to use for the lock.</param>
/// <param name="maxValidFor">The maximum amount of time the lock is allowed. This is a safety net in case the caller fails to release the lock. If null is specified, the lock never expires until it's released by the owner.</param>
/// <param name="maxValidFor">The maximum amount of time the lock is allowed. This is a safety net in case the caller fails to release the lock. If <c>null</c> is specified, the lock never expires until it's released by the owner.</param>
/// <param name="timeout">The amount of time to wait for the lock to be acquired before timing out. A null value will cause the method to block indefinitely until a lock can be acquired.</param>
/// <returns>Returns a lock.</returns>
/// <exception cref="TimeoutException">Throws a TimeoutException if no lock could be acquired in time.</exception>
DistributedLock AcquireLockForMachine(string name, TimeSpan? maxValidFor, TimeSpan? timeout);
/// <summary>
/// Tries to acquire a lock on the specified name for the current thread.
/// </summary>
/// <param name="name">The name to use for the lock.</param>
/// <param name="maxValidFor">The maximum amount of time the lock is allowed. This is a safety net in case the caller fails to release the lock. If null is specified, the lock never expires until it's released by the owner.</param>
/// <param name="timeout">The amount of time to wait for the lock to be acquired before timing out. A null value will cause the method to return immedieately if no lock could be acquired.</param>
/// <param name="lock">The acquired lock.</param>
/// <returns>Returns true if a lock was successfully acquired, false otherwise.</returns>
bool TryAcquireLockForThread(string name, TimeSpan? maxValidFor, TimeSpan? timeout, out DistributedLock @lock);
/// <summary>
/// Acquires a lock with the specified parameters for the current thread.
/// </summary>
/// <param name="name">The name to use for the lock.</param>
/// <param name="maxValidFor">The maximum amount of time the lock is allowed. This is a safety net in case the caller fails to release the lock. If null is specified, the lock never expires until it's released by the owner.</param>
/// <param name="timeout">The amount of time to wait for the lock to be acquired before timing out. A null value will cause the method to block indefinitely until a lock can be acquired.</param>
/// <returns>Returns a lock.</returns>
/// <exception cref="TimeoutException">Throws a TimeoutException if no lock could be acquired in time.</exception>
DistributedLock AcquireLockForThread(string name, TimeSpan? maxValidFor, TimeSpan? timeout);
/// <summary>
/// Disposes the specified lock.
/// </summary>
void ReleaseLock(DistributedLock @lock);
IDistributedLock AcquireLock(string name, TimeSpan? maxValidFor, TimeSpan? timeout);
}
public static class DistributedLockServiceExtensions {
@@ -59,8 +34,8 @@ namespace Orchard.Tasks.Locking.Services {
/// <param name="maxValidFor">The maximum amount of time the lock is allowed. This is a safety net in case the caller fails to release the lock. If null is specified, the lock never expires until it's released by the owner.</param>
/// <param name="lock">The acquired lock.</param>
/// <returns>Returns true if a lock was successfully acquired, false otherwise.</returns>
public static bool TryAcquireLockForMachine(this IDistributedLockService service, string name, TimeSpan? maxValidFor, out DistributedLock @lock) {
return service.TryAcquireLockForMachine(name, maxValidFor, null, out @lock);
public static bool TryAcquireLock(this IDistributedLockService service, string name, TimeSpan? maxValidFor, out IDistributedLock @lock) {
return service.TryAcquireLock(name, maxValidFor, TimeSpan.Zero, out @lock);
}
/// <summary>
@@ -69,8 +44,8 @@ namespace Orchard.Tasks.Locking.Services {
/// <param name="name">The name to use for the lock.</param>
/// <param name="lock">The acquired lock.</param>
/// <returns>Returns true if a lock was successfully acquired, false otherwise.</returns>
public static bool TryAcquireLockForMachine(this IDistributedLockService service, string name, out DistributedLock @lock) {
return service.TryAcquireLockForMachine(name, null, null, out @lock);
public static bool TryAcquireLock(this IDistributedLockService service, string name, out IDistributedLock @lock) {
return service.TryAcquireLock(name, null, TimeSpan.Zero, out @lock);
}
/// <summary>
@@ -80,8 +55,8 @@ namespace Orchard.Tasks.Locking.Services {
/// <param name="maxValidFor">The maximum amount of time the lock is allowed. This is a safety net in case the caller fails to release the lock. If null is specified, the lock never expires until it's released by the owner.</param>
/// <returns>Returns a lock.</returns>
/// <exception cref="TimeoutException">Throws a TimeoutException if no lock could be acquired in time.</exception>
public static DistributedLock AcquireLockForMachine(this IDistributedLockService service, string name, TimeSpan? maxValidFor) {
return service.AcquireLockForMachine(name, maxValidFor, null);
public static IDistributedLock AcquireLock(this IDistributedLockService service, string name, TimeSpan? maxValidFor) {
return service.AcquireLock(name, maxValidFor, null);
}
/// <summary>
@@ -90,39 +65,8 @@ namespace Orchard.Tasks.Locking.Services {
/// <param name="name">The name to use for the lock.</param>
/// <returns>Returns a lock.</returns>
/// <exception cref="TimeoutException">Throws a TimeoutException if no lock could be acquired in time.</exception>
public static DistributedLock AcquireLockForMachine(this IDistributedLockService service, string name) {
return service.AcquireLockForMachine(name, null, null);
}
/// <summary>
/// Tries to acquire a lock on the specified name for the current thread.
/// </summary>
/// <param name="name">The name to use for the lock.</param>
/// <param name="maxValidFor">The maximum amount of time the lock is allowed. This is a safety net in case the caller fails to release the lock. If null is specified, the lock never expires until it's released by the owner.</param>
/// <param name="lock">The acquired lock.</param>
/// <returns>Returns true if a lock was successfully acquired, false otherwise.</returns>
public static bool TryAcquireLockForThread(this IDistributedLockService service, string name, TimeSpan? maxValidFor, out DistributedLock @lock) {
return service.TryAcquireLockForThread(name, maxValidFor, null, out @lock);
}
/// <summary>
/// Tries to acquire a lock on the specified name for the current thread.
/// </summary>
/// <param name="name">The name to use for the lock.</param>
/// <param name="lock">The acquired lock.</param>
/// <returns>Returns true if a lock was successfully acquired, false otherwise.</returns>
public static bool TryAcquireLockForThread(this IDistributedLockService service, string name, out DistributedLock @lock) {
return service.TryAcquireLockForThread(name, null, null, out @lock);
}
/// <summary>
/// Acquires a lock with the specified parameters for the current thread.
/// </summary>
/// <param name="name">The name to use for the lock.</param>
/// <returns>Returns a lock.</returns>
/// <exception cref="TimeoutException">Throws a TimeoutException if no lock could be acquired in time.</exception>
public static DistributedLock AcquireLockForThread(this IDistributedLockService service, string name) {
return service.AcquireLockForThread(name, null, null);
public static IDistributedLock AcquireLock(this IDistributedLockService service, string name) {
return service.AcquireLock(name, null, null);
}
}
}