mirror of
https://github.com/OrchardCMS/Orchard.git
synced 2026-02-09 09:16:41 +08:00
Cleaned up naming, formatting and comments in distributed locks.
This commit is contained in:
@@ -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);
|
||||||
|
|||||||
@@ -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++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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 dLock;
|
||||||
}
|
}
|
||||||
|
|
||||||
return l;
|
public void ReleaseDistributedLock(DistributedLock dLock) {
|
||||||
}
|
|
||||||
|
|
||||||
public void ReleaseDistributedLock(DistributedLock l) {
|
|
||||||
|
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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; }
|
||||||
|
|||||||
@@ -1,70 +1,81 @@
|
|||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace Orchard.Tasks.Locking.Services {
|
namespace Orchard.Tasks.Locking.Services {
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Provides distributed locking functionality.
|
/// Provides functionality to acquire and release tenant-wide locks which are
|
||||||
|
/// distributed across all instances in a web farm.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Distributed locks can be used to protect critical sections that should only ever
|
||||||
|
/// be executed by a single thread of execution across a whole web farm. The distributed
|
||||||
|
/// locks returned by this service are reentrant, i.e. the owner of a lock can reacquire it
|
||||||
|
/// multiple times, and must also release it (dispose of it) as many times as it was
|
||||||
|
/// acquired for the lock to be released.
|
||||||
|
/// </remarks>
|
||||||
public interface IDistributedLockService : IDependency {
|
public interface IDistributedLockService : IDependency {
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Tries to acquire 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>
|
||||||
/// <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 was successfully acquired, otherwise <c>false</c>.</returns>
|
||||||
bool TryAcquireLock(string name, TimeSpan? maxValidFor, TimeSpan? timeout, out IDistributedLock @lock);
|
bool TryAcquireLock(string name, TimeSpan? maxValidFor, TimeSpan? timeout, out IDistributedLock @lock);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Acquires a distributed lock on a named resource for the current tenant.
|
/// Acquires a named distributed lock within the current tenant or throws if the lock cannot be acquired.
|
||||||
/// </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>
|
/// <returns>The acquired lock.</returns>
|
||||||
/// <exception cref="TimeoutException">Throws a TimeoutException if no lock could be acquired in time.</exception>
|
/// <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);
|
IDistributedLock AcquireLock(string name, TimeSpan? maxValidFor, TimeSpan? timeout);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class DistributedLockServiceExtensions {
|
public static class DistributedLockServiceExtensions {
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Tries to acquire a lock on the specified name for the current machine.
|
/// Tries to immediately acquire 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>
|
||||||
/// <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, TimeSpan? maxValidFor, out IDistributedLock @lock) {
|
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);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user