Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions src/DistributedLock.Core/Chaining/ChainedDistributedLock.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
namespace Medallion.Threading.Chaining;

using Medallion.Threading.Internal;

/// <summary>
/// A chained distributed lock which acquires multiple underlying locks
/// </summary>
public class ChainedDistributedLock : IDistributedLock
{
private readonly IDistributedLock[] _distributedLocks;

internal ChainedDistributedLock(string name, IDistributedLock[] distributedLocks)
{
this.Name = name ?? throw new ArgumentNullException(nameof(name));
this._distributedLocks = ChainedHelpers.Validate(distributedLocks, nameof(distributedLocks));
}

public string Name { get; }

public IDistributedSynchronizationHandle Acquire(TimeSpan? timeout = null, CancellationToken cancellationToken = default) =>
SyncViaAsync.Run(static s => s.@this.AcquireAsync(s.timeout, s.cancellationToken), (@this: this, timeout, cancellationToken));

public ValueTask<IDistributedSynchronizationHandle> AcquireAsync(TimeSpan? timeout = null, CancellationToken cancellationToken = default) =>
this.TryAcquireInternalAsync(timeout, cancellationToken).GetHandleOrTimeout();

public IDistributedSynchronizationHandle? TryAcquire(TimeSpan timeout = default, CancellationToken cancellationToken = default) =>
SyncViaAsync.Run(static s => s.@this.TryAcquireAsync(s.timeout, s.cancellationToken), (@this: this, timeout, cancellationToken));

public ValueTask<IDistributedSynchronizationHandle?> TryAcquireAsync(TimeSpan timeout = default, CancellationToken cancellationToken = default) =>
this.TryAcquireInternalAsync(timeout, cancellationToken).GetHandleOrDefault();

private ValueTask<CompositeDistributedSynchronizationHandle.AcquireResult> TryAcquireInternalAsync(
TimeoutValue timeout,
CancellationToken cancellationToken) =>
CompositeDistributedSynchronizationHandle.TryAcquireAllAsync(
this._distributedLocks,
static (l, t, c) => SyncViaAsync.IsSynchronous ? l.TryAcquire(t, c).AsValueTask() : l.TryAcquireAsync(t, c),
timeout,
cancellationToken);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
namespace Medallion.Threading.Chaining;
public class ChainedDistributedLockProvider : IDistributedLockProvider
{
public ChainedDistributedLockProvider(IDistributedLockProvider firstProvider, IDistributedLockProvider secondProvider, params IDistributedLockProvider[] additionalProviders) : this(lockProviders: [firstProvider, secondProvider, .. additionalProviders])
{
}
internal ChainedDistributedLockProvider(IDistributedLockProvider[] lockProviders)
{
this.Providers = lockProviders;
}

public IDistributedLockProvider[] Providers { get; }

public IDistributedLock CreateLock(string name) => new ChainedDistributedLock(name, this.Providers.Select(p => p.CreateLock(name)).ToArray());
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
namespace Medallion.Threading.Chaining;

using Medallion.Threading.Internal;

/// <summary>
/// A chained reader-writer lock which acquires multiple underlying reader-writer locks
/// </summary>
public class ChainedDistributedReaderWriterLock : IDistributedReaderWriterLock
{
private readonly IDistributedReaderWriterLock[] _distributedReaderWriterLocks;

internal ChainedDistributedReaderWriterLock(
string name,
IDistributedReaderWriterLock[] distributedReaderWriterLocks)
{
this.Name = name ?? throw new ArgumentNullException(nameof(name));
this._distributedReaderWriterLocks = ChainedHelpers.Validate(distributedReaderWriterLocks, nameof(distributedReaderWriterLocks));
}

public string Name { get; }

public IDistributedSynchronizationHandle AcquireReadLock(TimeSpan? timeout = null, CancellationToken cancellationToken = default) =>
SyncViaAsync.Run(static s => s.@this.AcquireReadLockAsync(s.timeout, s.cancellationToken), (@this: this, timeout, cancellationToken));

public ValueTask<IDistributedSynchronizationHandle> AcquireReadLockAsync(TimeSpan? timeout = null, CancellationToken cancellationToken = default) =>
this.TryAcquireReadLockInternalAsync(timeout, cancellationToken).GetHandleOrTimeout();

public IDistributedSynchronizationHandle? TryAcquireReadLock(TimeSpan timeout = default, CancellationToken cancellationToken = default) =>
SyncViaAsync.Run(static s => s.@this.TryAcquireReadLockAsync(s.timeout, s.cancellationToken), (@this: this, timeout, cancellationToken));

public ValueTask<IDistributedSynchronizationHandle?> TryAcquireReadLockAsync(TimeSpan timeout = default, CancellationToken cancellationToken = default) =>
this.TryAcquireReadLockInternalAsync(timeout, cancellationToken).GetHandleOrDefault();

public IDistributedSynchronizationHandle AcquireWriteLock(TimeSpan? timeout = null, CancellationToken cancellationToken = default) =>
SyncViaAsync.Run(static s => s.@this.AcquireWriteLockAsync(s.timeout, s.cancellationToken), (@this: this, timeout, cancellationToken));

public ValueTask<IDistributedSynchronizationHandle> AcquireWriteLockAsync(TimeSpan? timeout = null, CancellationToken cancellationToken = default) =>
this.TryAcquireWriteLockInternalAsync(timeout, cancellationToken).GetHandleOrTimeout();

public IDistributedSynchronizationHandle? TryAcquireWriteLock(TimeSpan timeout = default, CancellationToken cancellationToken = default) =>
SyncViaAsync.Run(static s => s.@this.TryAcquireWriteLockAsync(s.timeout, s.cancellationToken), (@this: this, timeout, cancellationToken));

public ValueTask<IDistributedSynchronizationHandle?> TryAcquireWriteLockAsync(TimeSpan timeout = default, CancellationToken cancellationToken = default) =>
this.TryAcquireWriteLockInternalAsync(timeout, cancellationToken).GetHandleOrDefault();

private ValueTask<CompositeDistributedSynchronizationHandle.AcquireResult> TryAcquireReadLockInternalAsync(
TimeoutValue timeout,
CancellationToken cancellationToken) =>
CompositeDistributedSynchronizationHandle.TryAcquireAllAsync(
this._distributedReaderWriterLocks,
static (l, t, c) => SyncViaAsync.IsSynchronous ? l.TryAcquireReadLock(t, c).AsValueTask() : l.TryAcquireReadLockAsync(t, c),
timeout,
cancellationToken);

private ValueTask<CompositeDistributedSynchronizationHandle.AcquireResult> TryAcquireWriteLockInternalAsync(
TimeoutValue timeout,
CancellationToken cancellationToken) =>
CompositeDistributedSynchronizationHandle.TryAcquireAllAsync(
this._distributedReaderWriterLocks,
static (l, t, c) => SyncViaAsync.IsSynchronous ? l.TryAcquireWriteLock(t, c).AsValueTask() : l.TryAcquireWriteLockAsync(t, c),
timeout,
cancellationToken);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
namespace Medallion.Threading.Chaining;

public class ChainedDistributedReaderWriterLockProvider : IDistributedReaderWriterLockProvider
{
public ChainedDistributedReaderWriterLockProvider(IDistributedReaderWriterLockProvider firstProvider, IDistributedReaderWriterLockProvider secondProvider, params IDistributedReaderWriterLockProvider[] additionalProviders) : this(lockProviders: [firstProvider, secondProvider, .. additionalProviders])
{
}
internal ChainedDistributedReaderWriterLockProvider(IDistributedReaderWriterLockProvider[] lockProviders)
{
this.Providers = lockProviders;
}

public IDistributedReaderWriterLockProvider[] Providers { get; }
public IDistributedReaderWriterLock CreateReaderWriterLock(string name) => new ChainedDistributedReaderWriterLock(name, this.Providers.Select(p => p.CreateReaderWriterLock(name)).ToArray());
}
43 changes: 43 additions & 0 deletions src/DistributedLock.Core/Chaining/ChainedDistributedSemaphore.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
using Medallion.Threading.Internal;

namespace Medallion.Threading.Chaining;

/// <summary>
/// A chained semaphore which acquires multiple underlying semaphores
/// </summary>
public class ChainedDistributedSemaphore : IDistributedSemaphore
{
private readonly IDistributedSemaphore[] _distributedSemaphores;

internal ChainedDistributedSemaphore(string name, int maxCount, IDistributedSemaphore[] distributedSemaphores)
{
this.Name = name ?? throw new ArgumentNullException(nameof(name));
this.MaxCount = maxCount;
this._distributedSemaphores = ChainedHelpers.Validate(distributedSemaphores, nameof(distributedSemaphores));
}

public string Name { get; }

public int MaxCount { get; }

public IDistributedSynchronizationHandle Acquire(TimeSpan? timeout = null, CancellationToken cancellationToken = default) =>
SyncViaAsync.Run(static s => s.@this.AcquireAsync(s.timeout, s.cancellationToken), (@this: this, timeout, cancellationToken));

public ValueTask<IDistributedSynchronizationHandle> AcquireAsync(TimeSpan? timeout = null, CancellationToken cancellationToken = default) =>
this.TryAcquireInternalAsync(timeout, cancellationToken).GetHandleOrTimeout();

public IDistributedSynchronizationHandle? TryAcquire(TimeSpan timeout = default, CancellationToken cancellationToken = default) =>
SyncViaAsync.Run(static s => s.@this.TryAcquireAsync(s.timeout, s.cancellationToken), (@this: this, timeout, cancellationToken));

public ValueTask<IDistributedSynchronizationHandle?> TryAcquireAsync(TimeSpan timeout = default, CancellationToken cancellationToken = default) =>
this.TryAcquireInternalAsync(timeout, cancellationToken).GetHandleOrDefault();

private ValueTask<CompositeDistributedSynchronizationHandle.AcquireResult> TryAcquireInternalAsync(
TimeoutValue timeout,
CancellationToken cancellationToken) =>
CompositeDistributedSynchronizationHandle.TryAcquireAllAsync(
this._distributedSemaphores,
static (s, t, c) => SyncViaAsync.IsSynchronous ? s.TryAcquire(t, c).AsValueTask() : s.TryAcquireAsync(t, c),
timeout,
cancellationToken);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
namespace Medallion.Threading.Chaining;

public class ChainedDistributedSemaphoreProvider : IDistributedSemaphoreProvider
{
public ChainedDistributedSemaphoreProvider(IDistributedSemaphoreProvider firstProvider, IDistributedSemaphoreProvider secondProvider, params IDistributedSemaphoreProvider[] additionalProviders) : this(lockProviders: [firstProvider, secondProvider, .. additionalProviders])
{
}
internal ChainedDistributedSemaphoreProvider(IDistributedSemaphoreProvider[] lockProviders)
{
this.Providers = lockProviders;
}

internal IDistributedSemaphoreProvider[] Providers { get; }

public IDistributedSemaphore CreateSemaphore(string name, int maxCount) => new ChainedDistributedSemaphore(name, maxCount, this.Providers.Select(p => p.CreateSemaphore(name, maxCount)).ToArray());
}
14 changes: 14 additions & 0 deletions src/DistributedLock.Core/Chaining/ChainedHelpers.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
namespace Medallion.Threading.Chaining;

internal static class ChainedHelpers
{
public static T[] Validate<T>(T[] primitives, string paramName)
where T : class
{
if (primitives is null) { throw new ArgumentNullException(paramName); }
if (primitives.Length == 0) { throw new ArgumentException("At least one underlying synchronization primitive is required.", paramName); }
if (Array.IndexOf(primitives, null) >= 0) { throw new ArgumentException("Must not contain null", paramName); }

return primitives;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
namespace Medallion.Threading.Chaining;

public static class DistributedLockProviderExtensions
{

/// <summary>
/// Chains <paramref name="provider"/> with <paramref name="precedingProvider"/> such that locks created from the resulting provider are acquired first by <paramref name="precedingProvider"/> then by <paramref name="provider"/>.
/// </summary>
/// <param name="provider"></param>
/// <param name="precedingProvider"></param>
/// <returns></returns>
public static IDistributedLockProvider PrecededBy(this IDistributedLockProvider provider, IDistributedLockProvider precedingProvider)
{
var providers = new List<IDistributedLockProvider>();
if (precedingProvider is ChainedDistributedLockProvider chained)
{
providers.AddRange(chained.Providers);
}
else
{
providers.Add(precedingProvider);
}
if (provider is ChainedDistributedLockProvider chained2)
{
providers.AddRange(chained2.Providers);
}
else
{
providers.Add(provider);
}
return new ChainedDistributedLockProvider(lockProviders: [.. providers]);
}

/// <summary>
/// Chains <paramref name="provider"/> with <paramref name="followingProvider"/> such that locks created from the resulting provider are acquired first by <paramref name="provider"/>, then by <paramref name="followingProvider"/>.
/// </summary>
/// <param name="provider"></param>
/// <param name="followingProvider"></param>
/// <returns></returns>
public static IDistributedLockProvider FollowedBy(this IDistributedLockProvider provider, IDistributedLockProvider followingProvider)
{
var providers = new List<IDistributedLockProvider>();
if (provider is ChainedDistributedLockProvider chained)
{
providers.AddRange(chained.Providers);
}
else
{
providers.Add(provider);
}
if (followingProvider is ChainedDistributedLockProvider chained2)
{
providers.AddRange(chained2.Providers);
}
else
{
providers.Add(followingProvider);
}
return new ChainedDistributedLockProvider([.. providers]);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
namespace Medallion.Threading.Chaining;

public static class DistributedReaderWriterLockProviderExtensions
{

/// <summary>
/// Chains <paramref name="provider"/> with <paramref name="precedingProvider"/> such that locks created from the resulting provider are acquired first by <paramref name="precedingProvider"/> then by <paramref name="provider"/>.
/// </summary>
/// <param name="provider"></param>
/// <param name="precedingProvider"></param>
/// <returns></returns>
public static IDistributedReaderWriterLockProvider PrecededBy(this IDistributedReaderWriterLockProvider provider, IDistributedReaderWriterLockProvider precedingProvider)
{
var providers = new List<IDistributedReaderWriterLockProvider>();
if (precedingProvider is ChainedDistributedReaderWriterLockProvider chained)
{
providers.AddRange(chained.Providers);
}
else
{
providers.Add(precedingProvider);
}
if (provider is ChainedDistributedReaderWriterLockProvider chained2)
{
providers.AddRange(chained2.Providers);
}
else
{
providers.Add(provider);
}
return new ChainedDistributedReaderWriterLockProvider(lockProviders: [.. providers]);
}

/// <summary>
/// Chains <paramref name="provider"/> with <paramref name="followingProvider"/> such that locks created from the resulting provider are acquired first by <paramref name="provider"/>, then by <paramref name="followingProvider"/>.
/// </summary>
/// <param name="provider"></param>
/// <param name="followingProvider"></param>
/// <returns></returns>
public static IDistributedReaderWriterLockProvider FollowedBy(this IDistributedReaderWriterLockProvider provider, IDistributedReaderWriterLockProvider followingProvider)
{
var providers = new List<IDistributedReaderWriterLockProvider>();
if (provider is ChainedDistributedReaderWriterLockProvider chained)
{
providers.AddRange(chained.Providers);
}
else
{
providers.Add(provider);
}
if (followingProvider is ChainedDistributedReaderWriterLockProvider chained2)
{
providers.AddRange(chained2.Providers);
}
else
{
providers.Add(followingProvider);
}
return new ChainedDistributedReaderWriterLockProvider([.. providers]);
}
}
Loading