Initial work on a distributed locking service.

This commit is contained in:
Sipke Schoorstra
2015-08-10 11:56:00 +01:00
parent d2c622dac6
commit f363ac2b5f
11 changed files with 234 additions and 2 deletions

View File

@@ -1,6 +1,4 @@
using System;
using Orchard.ContentManagement.MetaData;
using Orchard.Core.Contents.Extensions;
using Orchard.Data.Migration;
namespace Orchard.TaskLease {
@@ -19,5 +17,14 @@ namespace Orchard.TaskLease {
return 1;
}
public int UpdateFrom1() {
SchemaBuilder.CreateTable("DatabaseLockRecord", table => table
.Column<int>("Id", column => column.PrimaryKey().Identity())
.Column<string>("Name", column => column.NotNull().WithLength(256))
.Column<DateTime>("AcquiredUtc"));
return 2;
}
}
}

View File

@@ -0,0 +1,9 @@
using System;
namespace Orchard.TaskLease.Models {
public class DatabaseLockRecord {
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual DateTime? AcquiredUtc { get; set; }
}
}

View File

@@ -49,6 +49,9 @@
<Prefer32Bit>false</Prefer32Bit>
</PropertyGroup>
<ItemGroup>
<Reference Include="Autofac">
<HintPath>..\..\..\..\lib\autofac\Autofac.dll</HintPath>
</Reference>
<Reference Include="Microsoft.CSharp" />
<Reference Include="System" />
<Reference Include="System.Data" />
@@ -73,6 +76,7 @@
</ItemGroup>
<ItemGroup>
<Content Include="Web.config" />
<Compile Include="Models\DatabaseLockRecord.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Content Include="Module.txt" />
</ItemGroup>
@@ -82,6 +86,7 @@
</Compile>
<Compile Include="Models\TaskLeaseRecord.cs" />
<Compile Include="Services\ITaskLeaseService.cs" />
<Compile Include="Services\DatabaseLock.cs" />
<Compile Include="Services\TaskLeaseService.cs" />
</ItemGroup>
<ItemGroup />

View File

@@ -0,0 +1,100 @@
using System;
using System.Data;
using System.Linq;
using Autofac;
using Orchard.Data;
using Orchard.Environment.Extensions;
using Orchard.Exceptions;
using Orchard.Services;
using Orchard.TaskLease.Models;
using Orchard.Tasks.Locking;
using Orchard.Validation;
namespace Orchard.TaskLease.Services {
/// <summary>
/// Provides a database driven implementation of <see cref="ILock" />
/// </summary>
[OrchardSuppressDependency("Orchard.Tasks.Locking.DefaultLock")]
public class DatabaseLock : ILock {
private readonly ILifetimeScope _lifetimeScope;
private readonly IClock _clock;
private string _name;
private bool _isAcquired;
private int _id;
private bool _isDisposed;
public DatabaseLock(ILifetimeScope lifetimeScope, IClock clock) {
_lifetimeScope = lifetimeScope;
_clock = clock;
}
public bool TryAcquire(string name, TimeSpan maxLifetime) {
Argument.ThrowIfNullOrEmpty(name, "name");
if (name.Length > 256)
throw new ArgumentException("The lock's name can't be longer than 256 characters.");
// This way we can create a nested transaction scope instead of having the unwanted effect
// of manipulating the transaction of the caller.
using (var scope = BeginLifeTimeScope(name)) {
var repository = scope.Resolve<IRepository<DatabaseLockRecord>>();
var record = repository.Table.FirstOrDefault(x => x.Name == name);
if (record != null) {
// There is a nexisting lock, but check if it has expired.
var isExpired = record.AcquiredUtc + maxLifetime < _clock.UtcNow;
if (isExpired) {
repository.Delete(record);
record = null;
}
}
var canAcquire = record == null;
if (canAcquire) {
record = new DatabaseLockRecord {
Name = name,
AcquiredUtc = _clock.UtcNow
};
repository.Create(record);
repository.Flush();
_name = name;
_isAcquired = true;
_id = record.Id;
}
return canAcquire;
}
}
// This will be called at least by the IoC container when the request ends.
public void Dispose() {
if (_isDisposed || !_isAcquired) return;
_isDisposed = true;
using (var scope = BeginLifeTimeScope(_name)) {
try {
var repository = scope.Resolve<IRepository<DatabaseLockRecord>>();
var record = repository.Get(_id);
if (record != null) {
repository.Delete(record);
repository.Flush();
}
}
catch (Exception ex) {
if (ex.IsFatal()) throw;
}
}
}
private ILifetimeScope BeginLifeTimeScope(string name) {
var scope = _lifetimeScope.BeginLifetimeScope("Orchard.Tasks.Locking.Database." + name);
scope.Resolve<ITransactionManager>().RequireNew(IsolationLevel.ReadCommitted);
return scope;
}
}
}

View File

@@ -7,6 +7,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.ILockService instead.")]
public interface ITaskLeaseService : IDependency {
/// <summary>

View File

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