mirror of
https://github.com/OrchardCMS/Orchard.git
synced 2025-04-05 21:01:35 +08:00
Merge pull request #5628 from OrchardCMS/feature/distributed-locking
Feature/distributed locking
This commit is contained in:
commit
748a354632
@ -281,6 +281,10 @@
|
||||
<Project>{5D0F00F0-26C9-4785-AD61-B85710C60EB0}</Project>
|
||||
<Name>Orchard.Tags</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\Orchard.Web\Modules\Orchard.TaskLease\Orchard.TaskLease.csproj">
|
||||
<Project>{3f72a4e9-7b72-4260-b010-c16ec54f9baf}</Project>
|
||||
<Name>Orchard.TaskLease</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\Orchard.Web\Modules\Orchard.Themes\Orchard.Themes.csproj">
|
||||
<Project>{CDE24A24-01D3-403C-84B9-37722E18DFB7}</Project>
|
||||
<Name>Orchard.Themes</Name>
|
||||
|
@ -14,6 +14,12 @@ namespace Orchard.Tests {
|
||||
Resolve(_container);
|
||||
}
|
||||
|
||||
[TearDown]
|
||||
public void Cleanup() {
|
||||
if (_container != null)
|
||||
_container.Dispose();
|
||||
}
|
||||
|
||||
#if false
|
||||
// technically more accurate, and doesn't work
|
||||
[SetUp]
|
||||
|
@ -47,9 +47,10 @@ namespace Orchard.Tests {
|
||||
builder.RegisterType<InfosetHandler>().As<IContentHandler>();
|
||||
builder.RegisterInstance(new StubLocator(_session)).As<ISessionLocator>();
|
||||
builder.RegisterInstance(_clock).As<IClock>();
|
||||
builder.RegisterGeneric(typeof(Repository<>)).As(typeof(IRepository<>));
|
||||
builder.RegisterGeneric(typeof(Repository<>)).As(typeof(IRepository<>)).InstancePerLifetimeScope();
|
||||
builder.RegisterInstance(new ShellSettings { Name = ShellSettings.DefaultName, DataProvider = "SqlCe" });
|
||||
builder.RegisterInstance(new TestTransactionManager(_session)).As<ITransactionManager>();
|
||||
builder.RegisterType<TestTransactionManager>().As<ITransactionManager>().InstancePerLifetimeScope();
|
||||
builder.Register(context => _sessionFactory.OpenSession()).As<ISession>().InstancePerLifetimeScope();
|
||||
|
||||
Register(builder);
|
||||
_container = builder.Build();
|
||||
|
@ -272,6 +272,8 @@
|
||||
<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" />
|
||||
<Compile Include="Logging\OrchardFileAppenderTests.cs" />
|
||||
@ -293,6 +295,8 @@
|
||||
<Compile Include="Stubs\StubVirtualPathMonitor.cs" />
|
||||
<Compile Include="Stubs\StubCacheManager.cs" />
|
||||
<Compile Include="Stubs\StubWebSiteFolder.cs" />
|
||||
<Compile Include="Tasks\LockTests.cs" />
|
||||
<Compile Include="Tasks\DistributedLockServiceTests.cs" />
|
||||
<Compile Include="Time\TimeZoneSelectorTests.cs" />
|
||||
<Compile Include="UI\Resources\ResourceManagerTests.cs" />
|
||||
<Compile Include="UI\ShapeTests.cs" />
|
||||
|
13
src/Orchard.Tests/Stubs/StubMachineNameProvider.cs
Normal file
13
src/Orchard.Tests/Stubs/StubMachineNameProvider.cs
Normal file
@ -0,0 +1,13 @@
|
||||
using Orchard.Environment;
|
||||
|
||||
namespace Orchard.Tests.Stubs {
|
||||
public class StubMachineNameProvider : IMachineNameProvider {
|
||||
public StubMachineNameProvider() {
|
||||
MachineName = "Orchard Machine";
|
||||
}
|
||||
public string MachineName { get; set; }
|
||||
public string GetMachineName() {
|
||||
return MachineName;
|
||||
}
|
||||
}
|
||||
}
|
15
src/Orchard.Tests/Stubs/StubThreadProvider.cs
Normal file
15
src/Orchard.Tests/Stubs/StubThreadProvider.cs
Normal file
@ -0,0 +1,15 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
272
src/Orchard.Tests/Tasks/DistributedLockServiceTests.cs
Normal file
272
src/Orchard.Tests/Tasks/DistributedLockServiceTests.cs
Normal file
@ -0,0 +1,272 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Autofac;
|
||||
using NUnit.Framework;
|
||||
using Orchard.Data;
|
||||
using Orchard.Environment;
|
||||
using Orchard.Services;
|
||||
using Orchard.Tasks.Locking.Records;
|
||||
using Orchard.Tasks.Locking.Services;
|
||||
using Orchard.Tests.Stubs;
|
||||
|
||||
namespace Orchard.Tests.Tasks {
|
||||
[TestFixture]
|
||||
public class DistributedLockServiceTests : DatabaseEnabledTestsBase {
|
||||
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); }
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
public override void Init() {
|
||||
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);
|
||||
|
||||
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 ReleasingLockDecreasesLockCount() {
|
||||
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);
|
||||
|
||||
_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);
|
||||
|
||||
Assert.That(lockRecord, Is.Null);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TryAcquiringLockTwiceFails() {
|
||||
DistributedLock @lock;
|
||||
_machineNameProvider.MachineName = "Orchard Test Machine 1";
|
||||
var attempt1 = _distributedLockService.TryAcquireLockForMachine(LockName, TimeSpan.FromSeconds(60), null, out @lock);
|
||||
_machineNameProvider.MachineName = "Orchard Test Machine 2";
|
||||
var attempt2 = _distributedLockService.TryAcquireLockForMachine(LockName, TimeSpan.FromSeconds(60), null, out @lock);
|
||||
|
||||
Assert.That(attempt1, Is.True);
|
||||
Assert.That(attempt2, Is.False);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TryAcquiringNonExpiredActiveLockFails() {
|
||||
DistributedLock @lock;
|
||||
CreateNonExpiredActiveLock("Other Machine", threadId: null);
|
||||
var success = _distributedLockService.TryAcquireLockForMachine(LockName, TimeSpan.FromHours(1), null, 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);
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void MultipleAcquisitionsFromDifferentMachinesShouldFail() {
|
||||
DistributedLock @lock;
|
||||
_machineNameProvider.MachineName = "Orchard Test Machine 1";
|
||||
var attempt1 = _distributedLockService.TryAcquireLockForMachine(LockName, TimeSpan.FromSeconds(60), null, out @lock);
|
||||
_machineNameProvider.MachineName = "Orchard Test Machine 2";
|
||||
var attempt2 = _distributedLockService.TryAcquireLockForMachine(LockName, TimeSpan.FromSeconds(60), null, out @lock);
|
||||
|
||||
Assert.That(attempt1, Is.True);
|
||||
Assert.That(attempt2, Is.False);
|
||||
}
|
||||
|
||||
[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));
|
||||
});
|
||||
|
||||
tasks.Add(task);
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
DistributedLock @lock;
|
||||
var acquired = _distributedLockService.TryAcquireLockForThread(LockName, TimeSpan.FromMinutes(1), null, out @lock);
|
||||
|
||||
Assert.That(acquired, Is.False);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ActiveLockWithUndefinedValidUntilNeverExpires() {
|
||||
CreateNonExpiredActiveLockThatNeverExpires("Other Machine", null);
|
||||
_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);
|
||||
|
||||
Assert.That(acquired, Is.False);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ActiveLockWithUndefinedValidUntilNeverExpiresUntilReleased() {
|
||||
DistributedLock @lock;
|
||||
|
||||
// Create a never expiring lock.
|
||||
_machineNameProvider.MachineName = "Orchard Test Machine 1";
|
||||
var attempt1 = _distributedLockService.TryAcquireLockForThread(LockName, maxValidFor: null, timeout: null, @lock: out @lock);
|
||||
|
||||
// Release the lock.
|
||||
_distributedLockService.ReleaseLock(@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);
|
||||
|
||||
// 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) {
|
||||
var record = new DistributedLockRecord {
|
||||
Name = LockName,
|
||||
Count = count,
|
||||
CreatedUtc = createdUtc,
|
||||
ValidUntilUtc = validUntilUtc,
|
||||
MachineName = machineName,
|
||||
ThreadId = threadId
|
||||
};
|
||||
|
||||
_distributedLockRepository.Create(record);
|
||||
_transactionManager.RequireNew();
|
||||
return record;
|
||||
}
|
||||
|
||||
private DistributedLockRecord CreateNonExpiredActiveLock(string machineName, int? threadId) {
|
||||
var now = _clock.UtcNow;
|
||||
return CreateLockRecord(1, now, now + TimeSpan.FromHours(1), machineName, threadId);
|
||||
}
|
||||
|
||||
private DistributedLockRecord CreateNonExpiredButInactiveLock(string machineName, int? threadId) {
|
||||
var now = _clock.UtcNow;
|
||||
return CreateLockRecord(0, now, now + TimeSpan.FromHours(1), machineName, threadId);
|
||||
}
|
||||
|
||||
private DistributedLockRecord CreateExpiredButActiveLock(string machineName, int? threadId) {
|
||||
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);
|
||||
}
|
||||
|
||||
private string GetMachineName() {
|
||||
return _machineNameProvider.GetMachineName();
|
||||
}
|
||||
|
||||
private int GetThreadId() {
|
||||
return _threadProvider.GetCurrentThreadId();
|
||||
}
|
||||
}
|
||||
}
|
39
src/Orchard.Tests/Tasks/LockTests.cs
Normal file
39
src/Orchard.Tests/Tasks/LockTests.cs
Normal file
@ -0,0 +1,39 @@
|
||||
using Autofac;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using Orchard.Tasks.Locking.Services;
|
||||
|
||||
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 DistributedLock _lock;
|
||||
|
||||
protected override void Register(ContainerBuilder builder) {
|
||||
_distributedLockServiceMock = new Mock<IDistributedLockService>();
|
||||
builder.RegisterInstance(_distributedLockServiceMock.Object);
|
||||
}
|
||||
|
||||
protected override void Resolve(ILifetimeScope container) {
|
||||
_lock = DistributedLock.ForMachine(_distributedLockServiceMock.Object, LockName, "Orchard Test Machine", LockId);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void DisposeInvokesDistributedLockServiceDisposeLock() {
|
||||
_lock.Dispose();
|
||||
|
||||
_distributedLockServiceMock.Verify(service => service.ReleaseLock(_lock), Times.Exactly(1));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void DisposingMultipleTimesInvokesDistributedLockServiceDisposeLockOnce() {
|
||||
_lock.Dispose();
|
||||
_lock.Dispose();
|
||||
_lock.Dispose();
|
||||
|
||||
_distributedLockServiceMock.Verify(service => service.ReleaseLock(_lock), Times.Exactly(1));
|
||||
}
|
||||
}
|
||||
}
|
@ -7,8 +7,8 @@ using Orchard.Environment.Extensions;
|
||||
using Orchard.Logging;
|
||||
using Orchard.Services;
|
||||
using Orchard.Settings;
|
||||
using Orchard.TaskLease.Services;
|
||||
using Orchard.Tasks;
|
||||
using Orchard.Tasks.Locking.Services;
|
||||
|
||||
namespace Orchard.AuditTrail.Services {
|
||||
[OrchardFeature("Orchard.AuditTrail.Trimming")]
|
||||
@ -16,19 +16,19 @@ namespace Orchard.AuditTrail.Services {
|
||||
private static readonly object _sweepLock = new object();
|
||||
private readonly ISiteService _siteService;
|
||||
private readonly IClock _clock;
|
||||
private readonly ITaskLeaseService _taskLeaseService;
|
||||
private readonly IAuditTrailManager _auditTrailManager;
|
||||
private readonly IDistributedLockService _distributedLockService;
|
||||
|
||||
public AuditTrailTrimmingBackgroundTask(
|
||||
ISiteService siteService,
|
||||
IClock clock,
|
||||
ITaskLeaseService taskLeaseService,
|
||||
IAuditTrailManager auditTrailManager) {
|
||||
IAuditTrailManager auditTrailManager,
|
||||
IDistributedLockService distributedLockService) {
|
||||
|
||||
_siteService = siteService;
|
||||
_clock = clock;
|
||||
_taskLeaseService = taskLeaseService;
|
||||
_auditTrailManager = auditTrailManager;
|
||||
_distributedLockService = distributedLockService;
|
||||
}
|
||||
|
||||
public AuditTrailTrimmingSettingsPart Settings {
|
||||
@ -41,17 +41,20 @@ namespace Orchard.AuditTrail.Services {
|
||||
Logger.Debug("Beginning sweep.");
|
||||
|
||||
// Only allow this task to run on one farm node at a time.
|
||||
if (_taskLeaseService.Acquire(GetType().FullName, _clock.UtcNow.AddHours(1)) != null) {
|
||||
DistributedLock @lock;
|
||||
if (_distributedLockService.TryAcquireLockForMachine(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.
|
||||
if (!GetIsTimeToTrim())
|
||||
return;
|
||||
// We don't need to check the audit trail for events to remove every minute. Let's stick with twice a day.
|
||||
if (!GetIsTimeToTrim())
|
||||
return;
|
||||
|
||||
Logger.Debug("Starting audit trail trimming.");
|
||||
var deletedRecords = _auditTrailManager.Trim(TimeSpan.FromDays(Settings.RetentionPeriod));
|
||||
Logger.Debug("Audit trail trimming completed. {0} records were deleted.", deletedRecords.Count());
|
||||
Settings.LastRunUtc = _clock.UtcNow;
|
||||
}
|
||||
Logger.Debug("Starting audit trail trimming.");
|
||||
var deletedRecords = _auditTrailManager.Trim(TimeSpan.FromDays(Settings.RetentionPeriod));
|
||||
Logger.Debug("Audit trail trimming completed. {0} records were deleted.", deletedRecords.Count());
|
||||
Settings.LastRunUtc = _clock.UtcNow;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex) {
|
||||
Logger.Error(ex, "Error during sweep.");
|
||||
|
@ -3,19 +3,17 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using Microsoft.WindowsAzure.MediaServices.Client;
|
||||
using Orchard.Azure.MediaServices.Helpers;
|
||||
using Orchard.Azure.MediaServices.Models;
|
||||
using Orchard.Azure.MediaServices.Models.Assets;
|
||||
using Orchard.Azure.MediaServices.Models.Jobs;
|
||||
using Orchard.Azure.MediaServices.Services.Assets;
|
||||
using Orchard.Azure.MediaServices.Services.Wams;
|
||||
using Microsoft.WindowsAzure.MediaServices.Client;
|
||||
using Orchard;
|
||||
using Orchard.ContentManagement;
|
||||
using Orchard.Logging;
|
||||
using Orchard.Services;
|
||||
using Orchard.TaskLease.Services;
|
||||
using Orchard.Tasks;
|
||||
using Orchard.Tasks.Locking.Services;
|
||||
|
||||
namespace Orchard.Azure.MediaServices.Services.Jobs {
|
||||
public class JobProcessor : Component, IBackgroundTask {
|
||||
@ -23,26 +21,23 @@ namespace Orchard.Azure.MediaServices.Services.Jobs {
|
||||
private static readonly object _sweepLock = new object();
|
||||
|
||||
private readonly IWamsClient _wamsClient;
|
||||
private readonly IClock _clock;
|
||||
private readonly ITaskLeaseService _taskLeaseService;
|
||||
private readonly IAssetManager _assetManager;
|
||||
private readonly IJobManager _jobManager;
|
||||
private readonly IOrchardServices _orchardServices;
|
||||
private readonly IDistributedLockService _distributedLockService;
|
||||
|
||||
public JobProcessor(
|
||||
IWamsClient wamsClient,
|
||||
IClock clock,
|
||||
ITaskLeaseService taskLeaseService,
|
||||
IAssetManager assetManager,
|
||||
IJobManager jobManager,
|
||||
IOrchardServices orchardServices) {
|
||||
IOrchardServices orchardServices,
|
||||
IDistributedLockService distributedLockService) {
|
||||
|
||||
_wamsClient = wamsClient;
|
||||
_clock = clock;
|
||||
_taskLeaseService = taskLeaseService;
|
||||
_assetManager = assetManager;
|
||||
_jobManager = jobManager;
|
||||
_orchardServices = orchardServices;
|
||||
_distributedLockService = distributedLockService;
|
||||
}
|
||||
|
||||
public void Sweep() {
|
||||
@ -56,103 +51,106 @@ namespace Orchard.Azure.MediaServices.Services.Jobs {
|
||||
}
|
||||
|
||||
// Only allow this task to run on one farm node at a time.
|
||||
if (_taskLeaseService.Acquire(GetType().FullName, _clock.UtcNow.AddHours(1)) != null) {
|
||||
var jobs = _jobManager.GetActiveJobs().ToDictionary(job => job.WamsJobId);
|
||||
DistributedLock @lock;
|
||||
if (_distributedLockService.TryAcquireLockForMachine(GetType().FullName, TimeSpan.FromHours(1), out @lock)) {
|
||||
using (@lock) {
|
||||
var jobs = _jobManager.GetActiveJobs().ToDictionary(job => job.WamsJobId);
|
||||
|
||||
if (!jobs.Any()) {
|
||||
Logger.Debug("No open jobs were found; going back to sleep.");
|
||||
return;
|
||||
}
|
||||
|
||||
Logger.Information("Beginning processing of {0} open jobs.", jobs.Count());
|
||||
|
||||
var wamsJobs = _wamsClient.GetJobsById(jobs.Keys);
|
||||
|
||||
foreach (var wamsJob in wamsJobs) {
|
||||
Logger.Information("Processing job '{0}'...", wamsJob.Name);
|
||||
|
||||
var job = jobs[wamsJob.Id];
|
||||
var tasks = job.Tasks.ToDictionary(task => task.WamsTaskId);
|
||||
var wamsTasks = wamsJob.Tasks.ToArray();
|
||||
|
||||
foreach (var wamsTask in wamsTasks) {
|
||||
var task = tasks[wamsTask.Id];
|
||||
task.Status = MapWamsJobState(wamsTask.State);
|
||||
task.PercentComplete = (int)wamsTask.Progress;
|
||||
if (!jobs.Any()) {
|
||||
Logger.Debug("No open jobs were found; going back to sleep.");
|
||||
return;
|
||||
}
|
||||
|
||||
var previousStatus = job.Status;
|
||||
var wamsJobErrors = HarvestWamsJobErrors(wamsJob).ToArray();
|
||||
Logger.Information("Beginning processing of {0} open jobs.", jobs.Count());
|
||||
|
||||
job.CreatedUtc = wamsJob.Created;
|
||||
job.StartedUtc = wamsJob.StartTime;
|
||||
job.FinishedUtc = wamsJob.EndTime;
|
||||
job.Status = MapWamsJobState(wamsJob.State);
|
||||
job.ErrorMessage = GetAggregateErrorMessage(wamsJobErrors);
|
||||
var wamsJobs = _wamsClient.GetJobsById(jobs.Keys);
|
||||
|
||||
LogWamsJobErrors(wamsJobErrors);
|
||||
foreach (var wamsJob in wamsJobs) {
|
||||
Logger.Information("Processing job '{0}'...", wamsJob.Name);
|
||||
|
||||
if (job.Status != previousStatus) {
|
||||
if (job.Status == JobStatus.Finished) {
|
||||
Logger.Information("Job '{0}' was finished in WAMS; creating locators.", wamsJob.Name);
|
||||
var job = jobs[wamsJob.Id];
|
||||
var tasks = job.Tasks.ToDictionary(task => task.WamsTaskId);
|
||||
var wamsTasks = wamsJob.Tasks.ToArray();
|
||||
|
||||
var lastTask = job.Tasks.Last();
|
||||
var lastWamsTask = wamsTasks.Where(task => task.Id == lastTask.WamsTaskId).Single();
|
||||
var outputAsset = lastWamsTask.OutputAssets.First();
|
||||
var outputAssetName = !String.IsNullOrWhiteSpace(job.OutputAssetName) ? job.OutputAssetName : lastWamsTask.Name;
|
||||
var outputAssetDescription = job.OutputAssetDescription.TrimSafe();
|
||||
var encoderMetadataXml = _wamsClient.GetEncoderMetadataXml(outputAsset).Result;
|
||||
var cloudVideoPart = job.CloudVideoPart;
|
||||
var wamsLocators = _wamsClient.CreateLocatorsAsync(outputAsset, WamsLocatorCategory.Private).Result;
|
||||
foreach (var wamsTask in wamsTasks) {
|
||||
var task = tasks[wamsTask.Id];
|
||||
task.Status = MapWamsJobState(wamsTask.State);
|
||||
task.PercentComplete = (int)wamsTask.Progress;
|
||||
}
|
||||
|
||||
// HACK: Temporary workaround to disable dynamic packaging for VC1-based assets. In future versions
|
||||
// this will be implemented more robustly by testing all the dynamic URLs to see which ones work
|
||||
// and only store and use the working ones.
|
||||
var forceNonDynamicAsset = lastWamsTask.Configuration.StartsWith("VC1");
|
||||
var previousStatus = job.Status;
|
||||
var wamsJobErrors = HarvestWamsJobErrors(wamsJob).ToArray();
|
||||
|
||||
if (wamsLocators.OnDemandLocator != null && !forceNonDynamicAsset) {
|
||||
_assetManager.CreateAssetFor<DynamicVideoAsset>(cloudVideoPart, asset => {
|
||||
asset.IncludeInPlayer = true;
|
||||
asset.Name = outputAssetName;
|
||||
asset.Description = outputAssetDescription;
|
||||
asset.EncodingPreset = lastTask.HarvestAssetName;
|
||||
asset.WamsPrivateLocatorId = wamsLocators.SasLocator.Id;
|
||||
asset.WamsPrivateLocatorUrl = wamsLocators.SasLocator.Url;
|
||||
asset.WamsPrivateOnDemandLocatorId = wamsLocators.OnDemandLocator.Id;
|
||||
asset.WamsPrivateOnDemandLocatorUrl = wamsLocators.OnDemandLocator.Url;
|
||||
asset.WamsManifestFilename = wamsLocators.OnDemandManifestFilename;
|
||||
asset.WamsAssetId = outputAsset.Id;
|
||||
asset.WamsEncoderMetadataXml = encoderMetadataXml;
|
||||
asset.UploadState.Status = AssetUploadStatus.Uploaded;
|
||||
asset.PublishState.Status = AssetPublishStatus.None;
|
||||
});
|
||||
}
|
||||
else {
|
||||
_assetManager.CreateAssetFor<VideoAsset>(cloudVideoPart, asset => {
|
||||
asset.IncludeInPlayer = true;
|
||||
asset.Name = outputAssetName;
|
||||
asset.Description = outputAssetDescription;
|
||||
asset.EncodingPreset = lastTask.HarvestAssetName;
|
||||
asset.WamsPrivateLocatorId = wamsLocators.SasLocator.Id;
|
||||
asset.WamsPrivateLocatorUrl = wamsLocators.SasLocator.Url;
|
||||
asset.WamsAssetId = outputAsset.Id;
|
||||
asset.WamsEncoderMetadataXml = encoderMetadataXml;
|
||||
asset.UploadState.Status = AssetUploadStatus.Uploaded;
|
||||
asset.PublishState.Status = AssetPublishStatus.None;
|
||||
});
|
||||
}
|
||||
job.CreatedUtc = wamsJob.Created;
|
||||
job.StartedUtc = wamsJob.StartTime;
|
||||
job.FinishedUtc = wamsJob.EndTime;
|
||||
job.Status = MapWamsJobState(wamsJob.State);
|
||||
job.ErrorMessage = GetAggregateErrorMessage(wamsJobErrors);
|
||||
|
||||
try {
|
||||
if (cloudVideoPart.IsPublished())
|
||||
_assetManager.PublishAssetsFor(cloudVideoPart);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
Logger.Warning(ex, "Processing of job '{0}' was completed but an error occurred while publishing the cloud video item with ID {1} after processing.", wamsJob.Name, cloudVideoPart.Id);
|
||||
LogWamsJobErrors(wamsJobErrors);
|
||||
|
||||
if (job.Status != previousStatus) {
|
||||
if (job.Status == JobStatus.Finished) {
|
||||
Logger.Information("Job '{0}' was finished in WAMS; creating locators.", wamsJob.Name);
|
||||
|
||||
var lastTask = job.Tasks.Last();
|
||||
var lastWamsTask = wamsTasks.Single(task => task.Id == lastTask.WamsTaskId);
|
||||
var outputAsset = lastWamsTask.OutputAssets.First();
|
||||
var outputAssetName = !String.IsNullOrWhiteSpace(job.OutputAssetName) ? job.OutputAssetName : lastWamsTask.Name;
|
||||
var outputAssetDescription = job.OutputAssetDescription.TrimSafe();
|
||||
var encoderMetadataXml = _wamsClient.GetEncoderMetadataXml(outputAsset).Result;
|
||||
var cloudVideoPart = job.CloudVideoPart;
|
||||
var wamsLocators = _wamsClient.CreateLocatorsAsync(outputAsset, WamsLocatorCategory.Private).Result;
|
||||
|
||||
// HACK: Temporary workaround to disable dynamic packaging for VC1-based assets. In future versions
|
||||
// this will be implemented more robustly by testing all the dynamic URLs to see which ones work
|
||||
// and only store and use the working ones.
|
||||
var forceNonDynamicAsset = lastWamsTask.Configuration.StartsWith("VC1");
|
||||
|
||||
if (wamsLocators.OnDemandLocator != null && !forceNonDynamicAsset) {
|
||||
_assetManager.CreateAssetFor<DynamicVideoAsset>(cloudVideoPart, asset => {
|
||||
asset.IncludeInPlayer = true;
|
||||
asset.Name = outputAssetName;
|
||||
asset.Description = outputAssetDescription;
|
||||
asset.EncodingPreset = lastTask.HarvestAssetName;
|
||||
asset.WamsPrivateLocatorId = wamsLocators.SasLocator.Id;
|
||||
asset.WamsPrivateLocatorUrl = wamsLocators.SasLocator.Url;
|
||||
asset.WamsPrivateOnDemandLocatorId = wamsLocators.OnDemandLocator.Id;
|
||||
asset.WamsPrivateOnDemandLocatorUrl = wamsLocators.OnDemandLocator.Url;
|
||||
asset.WamsManifestFilename = wamsLocators.OnDemandManifestFilename;
|
||||
asset.WamsAssetId = outputAsset.Id;
|
||||
asset.WamsEncoderMetadataXml = encoderMetadataXml;
|
||||
asset.UploadState.Status = AssetUploadStatus.Uploaded;
|
||||
asset.PublishState.Status = AssetPublishStatus.None;
|
||||
});
|
||||
}
|
||||
else {
|
||||
_assetManager.CreateAssetFor<VideoAsset>(cloudVideoPart, asset => {
|
||||
asset.IncludeInPlayer = true;
|
||||
asset.Name = outputAssetName;
|
||||
asset.Description = outputAssetDescription;
|
||||
asset.EncodingPreset = lastTask.HarvestAssetName;
|
||||
asset.WamsPrivateLocatorId = wamsLocators.SasLocator.Id;
|
||||
asset.WamsPrivateLocatorUrl = wamsLocators.SasLocator.Url;
|
||||
asset.WamsAssetId = outputAsset.Id;
|
||||
asset.WamsEncoderMetadataXml = encoderMetadataXml;
|
||||
asset.UploadState.Status = AssetUploadStatus.Uploaded;
|
||||
asset.PublishState.Status = AssetPublishStatus.None;
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
if (cloudVideoPart.IsPublished())
|
||||
_assetManager.PublishAssetsFor(cloudVideoPart);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
Logger.Warning(ex, "Processing of job '{0}' was completed but an error occurred while publishing the cloud video item with ID {1} after processing.", wamsJob.Name, cloudVideoPart.Id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Logger.Information("Processing of job '{0}' was successfully completed.", wamsJob.Name);
|
||||
Logger.Information("Processing of job '{0}' was successfully completed.", wamsJob.Name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5,28 +5,25 @@ using System.Threading;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Orchard.Environment;
|
||||
using Orchard.Events;
|
||||
using Orchard.Logging;
|
||||
using Orchard.JobsQueue.Models;
|
||||
using Orchard.Services;
|
||||
using Orchard.TaskLease.Services;
|
||||
using Orchard.Logging;
|
||||
using Orchard.Tasks.Locking.Services;
|
||||
|
||||
namespace Orchard.JobsQueue.Services {
|
||||
public class JobsQueueProcessor : IJobsQueueProcessor {
|
||||
private readonly Work<IJobsQueueManager> _jobsQueueManager;
|
||||
private readonly Work<IClock> _clock;
|
||||
private readonly Work<ITaskLeaseService> _taskLeaseService;
|
||||
private readonly Work<IEventBus> _eventBus;
|
||||
private readonly ReaderWriterLockSlim _rwl = new ReaderWriterLockSlim();
|
||||
private readonly IDistributedLockService _distributedLockService;
|
||||
|
||||
public JobsQueueProcessor(
|
||||
Work<IClock> clock,
|
||||
Work<IJobsQueueManager> jobsQueueManager,
|
||||
Work<ITaskLeaseService> taskLeaseService,
|
||||
Work<IEventBus> eventBus) {
|
||||
_clock = clock;
|
||||
Work<IEventBus> eventBus,
|
||||
IDistributedLockService distributedLockService) {
|
||||
|
||||
_jobsQueueManager = jobsQueueManager;
|
||||
_taskLeaseService = taskLeaseService;
|
||||
_eventBus = eventBus;
|
||||
_distributedLockService = distributedLockService;
|
||||
Logger = NullLogger.Instance;
|
||||
}
|
||||
|
||||
@ -35,12 +32,15 @@ namespace Orchard.JobsQueue.Services {
|
||||
// prevent two threads on the same machine to process the message queue
|
||||
if (_rwl.TryEnterWriteLock(0)) {
|
||||
try {
|
||||
if (_taskLeaseService.Value.Acquire("JobsQueueProcessor", _clock.Value.UtcNow.AddMinutes(5)) != null) {
|
||||
IEnumerable<QueuedJobRecord> messages;
|
||||
DistributedLock @lock;
|
||||
if(_distributedLockService.TryAcquireLockForMachine(GetType().FullName, TimeSpan.FromMinutes(5), out @lock)){
|
||||
using (@lock) {
|
||||
IEnumerable<QueuedJobRecord> messages;
|
||||
|
||||
while ((messages = _jobsQueueManager.Value.GetJobs(0, 10).ToArray()).Any()) {
|
||||
foreach (var message in messages) {
|
||||
ProcessMessage(message);
|
||||
while ((messages = _jobsQueueManager.Value.GetJobs(0, 10).ToArray()).Any()) {
|
||||
foreach (var message in messages) {
|
||||
ProcessMessage(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -29,6 +29,7 @@ using Orchard.Mvc.ViewEngines.ThemeAwareness;
|
||||
using Orchard.Recipes.Services;
|
||||
using Orchard.Settings;
|
||||
using Orchard.Tasks;
|
||||
using Orchard.Tasks.Locking;
|
||||
using Orchard.Themes;
|
||||
using Orchard.UI.Notify;
|
||||
using Orchard.UI.PageClass;
|
||||
|
@ -1,6 +1,4 @@
|
||||
using System;
|
||||
using Orchard.ContentManagement.MetaData;
|
||||
using Orchard.Core.Contents.Extensions;
|
||||
using Orchard.Data.Migration;
|
||||
|
||||
namespace Orchard.TaskLease {
|
||||
|
@ -4,5 +4,6 @@ Author: The Orchard Team
|
||||
Website: http://orchardtasklease.codeplex.com
|
||||
Version: 1.9.1
|
||||
OrchardVersion: 1.9
|
||||
Description: Provides services to synchronize tasks in a farm environment
|
||||
LifecycleStatus: Deprecated
|
||||
Description: Provides services to synchronize tasks in a farm environment.
|
||||
Category: Hosting
|
@ -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" />
|
||||
|
@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using Orchard.TaskLease.Models;
|
||||
|
||||
namespace Orchard.TaskLease.Services {
|
||||
|
||||
@ -7,6 +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.")]
|
||||
public interface ITaskLeaseService : IDependency {
|
||||
|
||||
/// <summary>
|
||||
|
@ -9,6 +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.")]
|
||||
public class TaskLeaseService : ITaskLeaseService {
|
||||
|
||||
private readonly IRepository<TaskLeaseRecord> _repository;
|
||||
|
@ -1133,7 +1133,7 @@ Global
|
||||
{642A49D7-8752-4177-80D6-BFBBCFAD3DE0} = {E9C9F120-07BA-4DFB-B9C3-3AFB9D44C9D5}
|
||||
{966EC390-3C7F-4D98-92A6-F0F30D02E9B1} = {902528F6-1444-42A3-8B75-A54B775B539C}
|
||||
{3158C928-888C-4A84-8BC1-4A8257489538} = {E9C9F120-07BA-4DFB-B9C3-3AFB9D44C9D5}
|
||||
{3F72A4E9-7B72-4260-B010-C16EC54F9BAF} = {E9C9F120-07BA-4DFB-B9C3-3AFB9D44C9D5}
|
||||
{3F72A4E9-7B72-4260-B010-C16EC54F9BAF} = {902528F6-1444-42A3-8B75-A54B775B539C}
|
||||
{E07AFA7E-7B36-44C3-A537-AFCCAA93EA7A} = {74E681ED-FECC-4034-B9BD-01B0BB1BDECA}
|
||||
{5531E894-D259-45A3-AA61-26DBE720C1CE} = {E9C9F120-07BA-4DFB-B9C3-3AFB9D44C9D5}
|
||||
{2969635F-D9C3-4D01-890D-437B46659690} = {74E681ED-FECC-4034-B9BD-01B0BB1BDECA}
|
||||
|
@ -3,6 +3,7 @@ using System.Linq;
|
||||
using Orchard.Environment;
|
||||
using Orchard.Environment.Features;
|
||||
using Orchard.Logging;
|
||||
using Orchard.Tasks.Locking.Services;
|
||||
|
||||
namespace Orchard.Data.Migration {
|
||||
/// <summary>
|
||||
@ -11,13 +12,16 @@ namespace Orchard.Data.Migration {
|
||||
public class AutomaticDataMigrations : IOrchardShellEvents {
|
||||
private readonly IDataMigrationManager _dataMigrationManager;
|
||||
private readonly IFeatureManager _featureManager;
|
||||
private readonly IDistributedLockService _distributedLockService;
|
||||
|
||||
public AutomaticDataMigrations(
|
||||
IDataMigrationManager dataMigrationManager,
|
||||
IFeatureManager featureManager
|
||||
) {
|
||||
IFeatureManager featureManager,
|
||||
IDistributedLockService distributedLockService) {
|
||||
|
||||
_dataMigrationManager = dataMigrationManager;
|
||||
_featureManager = featureManager;
|
||||
_distributedLockService = distributedLockService;
|
||||
|
||||
Logger = NullLogger.Instance;
|
||||
}
|
||||
@ -25,30 +29,34 @@ 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)) {
|
||||
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[] {
|
||||
"Common", "Containers", "Contents", "Dashboard", "Feeds", "Navigation", "Scheduling", "Settings", "Shapes", "Title"
|
||||
};
|
||||
|
||||
// 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[] {
|
||||
"Common", "Containers", "Contents", "Dashboard", "Feeds", "Navigation", "Scheduling", "Settings", "Shapes", "Title"
|
||||
};
|
||||
var enabledFeatures = _featureManager.GetEnabledFeatures().Select(f => f.Id).ToList();
|
||||
var featuresToEnable = theseFeaturesShouldAlwaysBeActive.Where(shouldBeActive => !enabledFeatures.Contains(shouldBeActive)).ToList();
|
||||
if (featuresToEnable.Any()) {
|
||||
_featureManager.EnableFeatures(featuresToEnable, true);
|
||||
}
|
||||
|
||||
var enabledFeatures = _featureManager.GetEnabledFeatures().Select(f => f.Id).ToList();
|
||||
var featuresToEnable = theseFeaturesShouldAlwaysBeActive.Where(shouldBeActive => !enabledFeatures.Contains(shouldBeActive)).ToList();
|
||||
if (featuresToEnable.Any()) {
|
||||
_featureManager.EnableFeatures(featuresToEnable, true);
|
||||
}
|
||||
|
||||
foreach (var feature in _dataMigrationManager.GetFeaturesThatNeedUpdate()) {
|
||||
try {
|
||||
_dataMigrationManager.Update(feature);
|
||||
}
|
||||
catch (Exception e) {
|
||||
Logger.Error("Could not run migrations automatically on " + feature, e);
|
||||
foreach (var feature in _dataMigrationManager.GetFeaturesThatNeedUpdate()) {
|
||||
try {
|
||||
_dataMigrationManager.Update(feature);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
Logger.Error(ex, "Could not run migrations automatically on {0}.", feature);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Terminating() {
|
||||
|
||||
// No-op.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
namespace Orchard.Environment {
|
||||
|
||||
/// <summary>
|
||||
/// Describes a service which the name of the machine running the application.
|
||||
/// Describes a service which returns the name of the machine running the application.
|
||||
/// </summary>
|
||||
public interface IMachineNameProvider {
|
||||
|
||||
|
13
src/Orchard/Environment/IThreadProvider.cs
Normal file
13
src/Orchard/Environment/IThreadProvider.cs
Normal file
@ -0,0 +1,13 @@
|
||||
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();
|
||||
}
|
||||
}
|
@ -67,6 +67,7 @@ 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);
|
||||
|
9
src/Orchard/Environment/ThreadProvider.cs
Normal file
9
src/Orchard/Environment/ThreadProvider.cs
Normal file
@ -0,0 +1,9 @@
|
||||
using System.Threading;
|
||||
|
||||
namespace Orchard.Environment {
|
||||
public class ThreadProvider : IThreadProvider {
|
||||
public int GetCurrentThreadId() {
|
||||
return Thread.CurrentThread.ManagedThreadId;
|
||||
}
|
||||
}
|
||||
}
|
@ -152,6 +152,8 @@
|
||||
<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" />
|
||||
@ -399,6 +401,11 @@
|
||||
<Compile Include="Services\IJsonConverter.cs" />
|
||||
<Compile Include="Settings\CurrentSiteWorkContext.cs" />
|
||||
<Compile Include="Settings\ResourceDebugMode.cs" />
|
||||
<Compile Include="Tasks\Locking\Migrations\FrameworkMigrations.cs" />
|
||||
<Compile Include="Tasks\Locking\Services\DistributedLock.cs" />
|
||||
<Compile Include="Tasks\Locking\Services\IDistributedLockService.cs" />
|
||||
<Compile Include="Tasks\Locking\Services\DistributedLockService.cs" />
|
||||
<Compile Include="Tasks\Locking\Records\DistributedLockRecord.cs" />
|
||||
<Compile Include="Themes\CurrentThemeWorkContext.cs" />
|
||||
<Compile Include="Themes\ThemeManager.cs" />
|
||||
<Compile Include="Time\CurrentTimeZoneWorkContext.cs" />
|
||||
|
24
src/Orchard/Tasks/Locking/Migrations/FrameworkMigrations.cs
Normal file
24
src/Orchard/Tasks/Locking/Migrations/FrameworkMigrations.cs
Normal file
@ -0,0 +1,24 @@
|
||||
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(256))
|
||||
.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");
|
||||
});
|
||||
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
13
src/Orchard/Tasks/Locking/Records/DistributedLockRecord.cs
Normal file
13
src/Orchard/Tasks/Locking/Records/DistributedLockRecord.cs
Normal file
@ -0,0 +1,13 @@
|
||||
using System;
|
||||
|
||||
namespace Orchard.Tasks.Locking.Records {
|
||||
public class DistributedLockRecord {
|
||||
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; }
|
||||
}
|
||||
}
|
46
src/Orchard/Tasks/Locking/Services/DistributedLock.cs
Normal file
46
src/Orchard/Tasks/Locking/Services/DistributedLock.cs
Normal file
@ -0,0 +1,46 @@
|
||||
using System;
|
||||
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 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
|
||||
};
|
||||
}
|
||||
|
||||
private IDistributedLockService _service;
|
||||
private int _isDisposed;
|
||||
|
||||
private DistributedLock() {
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
203
src/Orchard/Tasks/Locking/Services/DistributedLockService.cs
Normal file
203
src/Orchard/Tasks/Locking/Services/DistributedLockService.cs
Normal file
@ -0,0 +1,203 @@
|
||||
using System;
|
||||
using System.Data;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Autofac;
|
||||
using Orchard.Data;
|
||||
using Orchard.Environment;
|
||||
using Orchard.Exceptions;
|
||||
using Orchard.Logging;
|
||||
using Orchard.Services;
|
||||
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;
|
||||
|
||||
public DistributedLockService(IMachineNameProvider machineNameProvider, IThreadProvider threadProvider, ILifetimeScope lifetimeScope, IClock clock) {
|
||||
_machineNameProvider = machineNameProvider;
|
||||
_lifetimeScope = lifetimeScope;
|
||||
_clock = clock;
|
||||
_threadProvider = threadProvider;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
}
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
try {
|
||||
DistributedLock @lock = null;
|
||||
var acquired = Poll(() => (@lock = AcquireLockInternal(name, maxValidFor, machineName, threadId)) != null, timeout);
|
||||
|
||||
if (acquired) {
|
||||
Logger.Debug("Successfully acquired a lock named '{0}'.", name);
|
||||
return @lock;
|
||||
}
|
||||
}
|
||||
catch (Exception ex) {
|
||||
Logger.Error(ex, "Error while trying to acquire a lock named '{0}'.", name);
|
||||
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);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private DistributedLock AcquireLockInternal(string name, TimeSpan? maxValidFor, string machineName, int? threadId) {
|
||||
var childLifetimeScope = CreateChildLifetimeScope(name);
|
||||
|
||||
try {
|
||||
var transactionManager = childLifetimeScope.Resolve<ITransactionManager>();
|
||||
transactionManager.RequireNew(IsolationLevel.ReadCommitted);
|
||||
|
||||
// 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>>();
|
||||
|
||||
// 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);
|
||||
|
||||
// The current owner name (based on machine name and current thread ID).
|
||||
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++;
|
||||
canAcquireLock = true;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// No one has an active lock yet, so good to go.
|
||||
record = new DistributedLockRecord {
|
||||
Name = name,
|
||||
MachineName = machineName,
|
||||
ThreadId = threadId,
|
||||
Count = 1,
|
||||
CreatedUtc = _clock.UtcNow,
|
||||
ValidUntilUtc = maxValidFor != null ? _clock.UtcNow + maxValidFor : null
|
||||
};
|
||||
repository.Create(record);
|
||||
canAcquireLock = true;
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
catch (Exception ex) {
|
||||
Logger.Error(ex, "An error occurred while trying to acquire a lock.");
|
||||
throw;
|
||||
}
|
||||
finally {
|
||||
childLifetimeScope.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes the specified function until it returns true, for the specified amount of time, or indefinitely if no timeout was given.
|
||||
/// </summary>
|
||||
/// <param name="operation">The operation to repeatedly execute until it returns true.</param>
|
||||
/// <param name="timeout">The amount of time to retry executing the function. If null is specified, the specified function is executed indefinitely until it returns true.</param>
|
||||
/// <returns>Returns true if the specified function returned true within the specified timeout, false otherwise.</returns>
|
||||
private bool Poll(Func<bool> operation, TimeSpan? timeout) {
|
||||
var waitedTime = TimeSpan.Zero;
|
||||
var waitTime = TimeSpan.FromMilliseconds(timeout.GetValueOrDefault().TotalMilliseconds / 10);
|
||||
bool acquired;
|
||||
|
||||
while (!(acquired = operation()) && (timeout == null || waitedTime < timeout.Value)) {
|
||||
Task.Delay(waitTime).ContinueWith(t => {
|
||||
waitedTime += waitTime;
|
||||
}).Wait();
|
||||
}
|
||||
|
||||
return acquired;
|
||||
}
|
||||
|
||||
private string GetMachineName() {
|
||||
return _machineNameProvider.GetMachineName();
|
||||
}
|
||||
|
||||
private int GetThreadId() {
|
||||
return _threadProvider.GetCurrentThreadId();
|
||||
}
|
||||
|
||||
private ILifetimeScope CreateChildLifetimeScope(string name) {
|
||||
return _lifetimeScope.BeginLifetimeScope("Orchard.Tasks.Locking." + name);
|
||||
}
|
||||
}
|
||||
}
|
128
src/Orchard/Tasks/Locking/Services/IDistributedLockService.cs
Normal file
128
src/Orchard/Tasks/Locking/Services/IDistributedLockService.cs
Normal file
@ -0,0 +1,128 @@
|
||||
using System;
|
||||
|
||||
namespace Orchard.Tasks.Locking.Services {
|
||||
/// <summary>
|
||||
/// Provides distributed locking functionality.
|
||||
/// </summary>
|
||||
public interface IDistributedLockService : ISingletonDependency {
|
||||
/// <summary>
|
||||
/// Tries to acquire a lock on the specified name for the current machine.
|
||||
/// </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="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);
|
||||
|
||||
/// <summary>
|
||||
/// Acquires a lock with the specified parameters for the current machine.
|
||||
/// </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 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);
|
||||
}
|
||||
|
||||
public static class DistributedLockServiceExtensions {
|
||||
/// <summary>
|
||||
/// Tries to acquire a lock on the specified name for the current machine.
|
||||
/// </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 TryAcquireLockForMachine(this IDistributedLockService service, string name, TimeSpan? maxValidFor, out DistributedLock @lock) {
|
||||
return service.TryAcquireLockForMachine(name, maxValidFor, null, out @lock);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to acquire a lock on the specified name for the current machine.
|
||||
/// </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 TryAcquireLockForMachine(this IDistributedLockService service, string name, out DistributedLock @lock) {
|
||||
return service.TryAcquireLockForMachine(name, null, null, out @lock);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Acquires a lock with the specified parameters for the current machine.
|
||||
/// </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>
|
||||
/// <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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Acquires a lock with the specified parameters for the current machine.
|
||||
/// </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 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);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user