Cleaned up naming, formatting and comments in distributed locks.

This commit is contained in:
Daniel Stolt
2015-09-06 15:51:26 +02:00
parent 1cfd5c497c
commit 324709400f
5 changed files with 110 additions and 107 deletions

View File

@@ -223,14 +223,14 @@ namespace Orchard.Tests.Tasks {
// Create a never expiring lock. // Create a never expiring lock.
_machineNameProvider.MachineName = "Orchard Test Machine 1"; _machineNameProvider.MachineName = "Orchard Test Machine 1";
var attempt1 = _distributedLockService.TryAcquireLock(LockName, maxValidFor: null, timeout: null, l: out @lock); var attempt1 = _distributedLockService.TryAcquireLock(LockName, maxValidFor: null, timeout: null, dLock: out @lock);
// Release the lock. // Release the lock.
_distributedLockService.ReleaseDistributedLock((DistributedLock)@lock); _distributedLockService.ReleaseDistributedLock((DistributedLock)@lock);
// Acquire the lock from another machine. // Acquire the lock from another machine.
_machineNameProvider.MachineName = "Orchard Test Machine 2"; _machineNameProvider.MachineName = "Orchard Test Machine 2";
var attempt2 = _distributedLockService.TryAcquireLock(LockName, maxValidFor: null, timeout: null, l: out @lock); var attempt2 = _distributedLockService.TryAcquireLock(LockName, maxValidFor: null, timeout: null, dLock: out @lock);
// Validate the results. // Validate the results.
Assert.That(attempt1, Is.True); Assert.That(attempt1, Is.True);

View File

@@ -9,19 +9,19 @@ namespace Orchard.Tasks.Locking.Services {
private string _name; private string _name;
private int _count; private int _count;
public string Name {
get {
return _name;
}
}
public DistributedLock(DistributedLockService service, string name) { public DistributedLock(DistributedLockService service, string name) {
_service = service; _service = service;
_name = name; _name = name;
_count = 1; _count = 1;
} }
public void IncreaseReferenceCount() { public string Name {
get {
return _name;
}
}
public void Increment() {
_count++; _count++;
} }

View File

@@ -16,13 +16,12 @@ using Orchard.Tasks.Locking.Records;
namespace Orchard.Tasks.Locking.Services { namespace Orchard.Tasks.Locking.Services {
public class DistributedLockService : Component, IDistributedLockService { public class DistributedLockService : Component, IDistributedLockService {
private readonly IClock _clock;
private readonly IMachineNameProvider _machineNameProvider;
private readonly ShellSettings _shellSettings;
private readonly ILifetimeScope _lifetimeScope;
private readonly ConcurrentDictionary<string, DistributedLock> _locks = new ConcurrentDictionary<string, DistributedLock>();
public bool DisableMonitorLock { get; set; } private readonly IMachineNameProvider _machineNameProvider;
private readonly ILifetimeScope _lifetimeScope;
private readonly IClock _clock;
private readonly ShellSettings _shellSettings;
private readonly ConcurrentDictionary<string, DistributedLock> _locks;
public DistributedLockService( public DistributedLockService(
IMachineNameProvider machineNameProvider, IMachineNameProvider machineNameProvider,
@@ -31,62 +30,58 @@ namespace Orchard.Tasks.Locking.Services {
ShellSettings shellSettings) { ShellSettings shellSettings) {
_clock = clock; _clock = clock;
_lifetimeScope = lifetimeScope; _lifetimeScope = lifetimeScope;
_shellSettings = shellSettings; _shellSettings = shellSettings;
_machineNameProvider = machineNameProvider; _machineNameProvider = machineNameProvider;
} _locks = new ConcurrentDictionary<string, DistributedLock>();
}
public bool TryAcquireLock(string name, TimeSpan? maxValidFor, TimeSpan? timeout, out IDistributedLock l) { public bool DisableMonitorLock { get; set; }
public bool TryAcquireLock(string name, TimeSpan? maxValidFor, TimeSpan? timeout, out IDistributedLock dLock) {
try { try {
l = AcquireLock(name, maxValidFor, timeout); dLock = AcquireLock(name, maxValidFor, timeout);
return l != null; return dLock != null;
} }
catch { catch {
l = null; dLock = null;
return false; return false;
} }
} }
public IDistributedLock AcquireLock(string name, TimeSpan? maxValidFor, TimeSpan? timeout) { public IDistributedLock AcquireLock(string name, TimeSpan? maxValidFor, TimeSpan? timeout) {
DistributedLock l = null; DistributedLock dLock = null;
try { try {
var acquired = Poll(() => (dLock = AcquireLockInternal(name, maxValidFor)) != null, timeout);
var acquired = Poll(() => (l = AcquireLockInternal(name, maxValidFor)) != null, timeout); if (acquired)
Logger.Debug("Successfully acquired lock '{0}'.", name);
if (acquired) { else
Logger.Debug("Successfully acquired a lock named '{0}'.", name); Logger.Debug("Failed to acquire lock '{0}' within the specified timeout.", name);
}
else {
Logger.Debug("Failed to acquire a lock named '{0}'.", name);
}
} }
catch (Exception ex) { catch (Exception ex) {
Logger.Error(ex, "Error while trying to acquire a lock named '{0}'.", name); Logger.Error(ex, "Error while trying to acquire lock '{0}'.", name);
throw; throw;
} }
if (l == null && timeout != null) { if (dLock == null && timeout != null)
throw new TimeoutException(String.Format("Failed to acquire a lock named '{0}' within the specified timeout ('{1}').", name, timeout)); throw new TimeoutException(String.Format("Failed to acquire lock '{0}' within the specified timeout ({1}).", name, timeout));
}
return l; return dLock;
} }
public void ReleaseDistributedLock(DistributedLock l) { public void ReleaseDistributedLock(DistributedLock dLock) {
try { try {
var record = GetDistributedLockRecordByName(l.Name); var record = GetDistributedLockRecordByName(dLock.Name);
if (record == null)
if (record == null) { throw new OrchardException(T("No lock record could be found in the database for lock '{0}'.", dLock.Name));
throw new OrchardException(T("No lock record could be found for the specified lock to be released."));
}
TryCommitNewTransaction(repository => repository.Delete(record)); TryCommitNewTransaction(repository => repository.Delete(record));
} }
catch (Exception ex) { catch (Exception ex) {
if (ex.IsFatal()) throw; if (ex.IsFatal())
Logger.Error(ex, "An non-fatal error occurred while trying to dispose a distributed lock with name '{0}'.", l.Name); throw;
Logger.Error(ex, "An non-fatal error occurred while releasing lock '{0}'.", dLock.Name);
} }
} }
@@ -105,8 +100,7 @@ namespace Orchard.Tasks.Locking.Services {
DistributedLockRecord result = null; DistributedLockRecord result = null;
TryCommitNewTransaction(repository => { TryCommitNewTransaction(repository => {
result = repository.Table.FirstOrDefault(x => result = repository.Table.FirstOrDefault(x =>
x.Name == name && x.Name == name && (x.ValidUntilUtc == null || x.ValidUntilUtc >= _clock.UtcNow)
(x.ValidUntilUtc == null || x.ValidUntilUtc >= _clock.UtcNow)
); );
}); });
@@ -117,19 +111,18 @@ namespace Orchard.Tasks.Locking.Services {
try { try {
name = GetTenantLockName(name); name = GetTenantLockName(name);
if (!DisableMonitorLock && !Monitor.TryEnter(String.Intern(name))) { if (!DisableMonitorLock && !Monitor.TryEnter(String.Intern(name)))
return null; return null;
}
DistributedLock dLock = null; DistributedLock dLock = null;
// Returns the existing lock in case of reentrancy. // Return the existing lock in case of reentrancy.
if(!DisableMonitorLock && _locks.TryGetValue(name, out dLock)) { if(!DisableMonitorLock && _locks.TryGetValue(name, out dLock)) {
dLock.IncreaseReferenceCount(); dLock.Increment();
return dLock; return dLock;
} }
// Find an existing, active lock, if any. // Find an existing active lock, if any.
var record = GetValidDistributedLockRecordByName(name); var record = GetValidDistributedLockRecordByName(name);
// The current owner name (based on machine name). // The current owner name (based on machine name).
@@ -151,8 +144,7 @@ namespace Orchard.Tasks.Locking.Services {
ValidUntilUtc = maxValidFor != null ? _clock.UtcNow + maxValidFor : null ValidUntilUtc = maxValidFor != null ? _clock.UtcNow + maxValidFor : null
}; };
canAcquireLock = TryCommitNewTransaction(repository => {
canAcquireLock = TryCommitNewTransaction( repository => {
repository.Create(record); repository.Create(record);
}); });
} }
@@ -201,9 +193,8 @@ namespace Orchard.Tasks.Locking.Services {
} }
private bool TryCommitNewTransaction(Action<IRepository<DistributedLockRecord>> action) { private bool TryCommitNewTransaction(Action<IRepository<DistributedLockRecord>> action) {
if (action == null) { if (action == null)
throw new ArgumentNullException(); throw new ArgumentNullException();
}
try { try {
using (var childLifetimeScope = _lifetimeScope.BeginLifetimeScope()) { using (var childLifetimeScope = _lifetimeScope.BeginLifetimeScope()) {
@@ -218,7 +209,6 @@ namespace Orchard.Tasks.Locking.Services {
catch { catch {
return false; return false;
} }
} }
} }
} }

View File

@@ -3,7 +3,9 @@
namespace Orchard.Tasks.Locking.Services { namespace Orchard.Tasks.Locking.Services {
/// <summary> /// <summary>
/// Represents a distributed lock. /> /// Represents a distributed lock returned by <c>IDistributedLockService</c>. The owner of the
/// lock should call <c>Dispose()</c> on an instance of this interface to release the distributed
/// lock.
/// </summary> /// </summary>
public interface IDistributedLock : IDisposable { public interface IDistributedLock : IDisposable {
string Name { get; } string Name { get; }

View File

@@ -1,71 +1,82 @@
using System; using System;
namespace Orchard.Tasks.Locking.Services { namespace Orchard.Tasks.Locking.Services {
/// <summary>
/// Provides distributed locking functionality. /// <summary>
/// </summary> /// Provides functionality to acquire and release tenant-wide locks which are
public interface IDistributedLockService : IDependency { /// distributed across all instances in a web farm.
/// <summary> /// </summary>
/// Tries to acquire a distributed lock on a named resource for the current tenant. /// <remarks>
/// </summary> /// Distributed locks can be used to protect critical sections that should only ever
/// <param name="name">The name to use for the lock.</param> /// be executed by a single thread of execution across a whole web farm. The distributed
/// <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> /// locks returned by this service are reentrant, i.e. the owner of a lock can reacquire it
/// <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> /// multiple times, and must also release it (dispose of it) as many times as it was
/// <param name="lock">The acquired lock.</param> /// acquired for the lock to be released.
/// <returns>Returns true if a lock was successfully acquired, false otherwise.</returns> /// </remarks>
bool TryAcquireLock(string name, TimeSpan? maxValidFor, TimeSpan? timeout, out IDistributedLock @lock); public interface IDistributedLockService : IDependency {
/// <summary> /// <summary>
/// Acquires a distributed lock on a named resource for the current tenant. /// Tries to acquire a named distributed lock within the current tenant.
/// </summary> /// </summary>
/// <param name="name">The name to use for the lock.</param> /// <param name="name">The name of the lock to acquire.</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="maxValidFor">The maximum amount of time the lock should be held before automatically expiring. This is a safeguard in case the owner fails to release the lock. If <c>null</c> is specified, the lock never automatically expires.</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="timeout">The amount of time to wait for the lock to be acquired. Passing <c>TimeSpan.Zero</c> will cause the method to return immediately. Passing <c>null</c> will cause the method to block indefinitely until a lock can be acquired.</param>
/// <returns>Returns a lock.</returns> /// <param name="lock">This out parameter will be assigned the acquired lock if successful.</param>
/// <exception cref="TimeoutException">Throws a TimeoutException if no lock could be acquired in time.</exception> /// <returns><c>true</c> if the lock was successfully acquired, otherwise <c>false</c>.</returns>
IDistributedLock AcquireLock(string name, TimeSpan? maxValidFor, TimeSpan? timeout); bool TryAcquireLock(string name, TimeSpan? maxValidFor, TimeSpan? timeout, out IDistributedLock @lock);
/// <summary>
/// Acquires a named distributed lock within the current tenant or throws if the lock cannot be acquired.
/// </summary>
/// <param name="name">The name of the lock to acquire.</param>
/// <param name="maxValidFor">The maximum amount of time the lock should be held before automatically expiring. This is a safeguard in case the owner fails to release the lock. If <c>null</c> is specified, the lock never automatically expires.</param>
/// <param name="timeout">The amount of time to wait for the lock to be acquired. Passing <c>TimeSpan.Zero</c> will cause the method to return immediately. Passing <c>null</c> will cause the method to block indefinitely until a lock can be acquired.</param>
/// <returns>The acquired lock.</returns>
/// <exception cref="TimeoutException">This method throws a TimeoutException if the lock could not be acquired within the specified timeout period.</exception>
IDistributedLock AcquireLock(string name, TimeSpan? maxValidFor, TimeSpan? timeout);
} }
public static class DistributedLockServiceExtensions { public static class DistributedLockServiceExtensions {
/// <summary>
/// Tries to acquire a lock on the specified name for the current machine. /// <summary>
/// </summary> /// Tries to immediately acquire a named distributed lock with a given expiration time within the current tenant.
/// <param name="name">The name to use for the lock.</param> /// </summary>
/// <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="name">The name of the lock to acquire.</param>
/// <param name="lock">The acquired lock.</param> /// <param name="maxValidFor">The maximum amount of time the lock should be held before automatically expiring. This is a safeguard in case the owner fails to release the lock. If <c>null</c> is specified, the lock never automatically expires.</param>
/// <returns>Returns true if a lock was successfully acquired, false otherwise.</returns> /// <param name="lock">This out parameter will be assigned the acquired lock if successful.</param>
public static bool TryAcquireLock(this IDistributedLockService service, string name, TimeSpan? maxValidFor, out IDistributedLock @lock) { /// <returns><c>true</c> if the lock could be immediately acquired, otherwise <c>false</c>.</returns>
public static bool TryAcquireLock(this IDistributedLockService service, string name, TimeSpan? maxValidFor, out IDistributedLock @lock) {
return service.TryAcquireLock(name, maxValidFor, TimeSpan.Zero, out @lock); return service.TryAcquireLock(name, maxValidFor, TimeSpan.Zero, out @lock);
} }
/// <summary> /// <summary>
/// Tries to acquire a lock on the specified name for the current machine. /// Tries to immediately acquire a named distributed lock with no expiration time within the current tenant.
/// </summary> /// </summary>
/// <param name="name">The name to use for the lock.</param> /// <param name="name">The name of the lock to acquire.</param>
/// <param name="lock">The acquired lock.</param> /// <param name="lock">This out parameter will be assigned the acquired lock if successful.</param>
/// <returns>Returns true if a lock was successfully acquired, false otherwise.</returns> /// <returns><c>true</c> if the lock could be immediately acquired, otherwise <c>false</c>.</returns>
public static bool TryAcquireLock(this IDistributedLockService service, string name, out IDistributedLock @lock) { public static bool TryAcquireLock(this IDistributedLockService service, string name, out IDistributedLock @lock) {
return service.TryAcquireLock(name, null, TimeSpan.Zero, out @lock); return service.TryAcquireLock(name, null, TimeSpan.Zero, out @lock);
} }
/// <summary> /// <summary>
/// Acquires a lock with the specified parameters for the current machine. /// Acquires a named distributed lock with a given expiration time within the current tenant.
/// </summary> /// </summary>
/// <param name="name">The name to use for the lock.</param> /// <param name="name">The name of the lock to acquire.</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 should be held before automatically expiring. This is a safeguard in case the owner fails to release the lock. If <c>null</c> is specified, the lock never automatically expires.</param>
/// <returns>Returns a lock.</returns> /// <returns>The acquired lock.</returns>
/// <exception cref="TimeoutException">Throws a TimeoutException if no lock could be acquired in time.</exception> /// <remarks>This method blocks indefinitely until the lock can be acquired.</remarks>
public static IDistributedLock AcquireLock(this IDistributedLockService service, string name, TimeSpan? maxValidFor) { public static IDistributedLock AcquireLock(this IDistributedLockService service, string name, TimeSpan? maxValidFor) {
return service.AcquireLock(name, maxValidFor, null); return service.AcquireLock(name, maxValidFor, null);
} }
/// <summary> /// <summary>
/// Acquires a lock with the specified parameters for the current machine. /// Acquires a named distributed lock with no expiration time within the current tenant.
/// </summary> /// </summary>
/// <param name="name">The name to use for the lock.</param> /// <param name="name">The name to use for the lock.</param>
/// <returns>Returns a lock.</returns> /// <returns>The acquired lock.</returns>
/// <exception cref="TimeoutException">Throws a TimeoutException if no lock could be acquired in time.</exception> /// <remarks>This method blocks indefinitely until the lock can be acquired.</remarks>
public static IDistributedLock AcquireLock(this IDistributedLockService service, string name) { public static IDistributedLock AcquireLock(this IDistributedLockService service, string name) {
return service.AcquireLock(name, null, null); return service.AcquireLock(name, null, null);
} }
} }