Skip to content

Commit

Permalink
Created an alternate method for locking using striped locks. For a li…
Browse files Browse the repository at this point in the history
…st of pros and cons of both techniques, please visit the wiki at https://github.com/MarkCiliaVincenti/AsyncKeyedLock/wiki
  • Loading branch information
MarkCiliaVincenti committed Feb 25, 2023
1 parent 50c7f21 commit 4589301
Show file tree
Hide file tree
Showing 16 changed files with 2,067 additions and 32 deletions.
2 changes: 1 addition & 1 deletion AsyncKeyedLock.Tests/AsyncKeyedLock.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
<ItemGroup>
<PackageReference Include="FluentAssertions" Version="6.10.0" />
<PackageReference Include="ListShuffle" Version="1.1.5" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.4.1" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.5.0" />
<PackageReference Include="xunit" Version="2.4.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
using ListShuffle;
using System.Collections.Concurrent;
using System.Threading.Tasks;
using Xunit;

namespace AsyncKeyedLock.Tests
namespace AsyncKeyedLock.Tests.AsyncKeyedLocker
{
public class OriginalTests
{
Expand All @@ -15,8 +14,7 @@ public async Task TestTimeout()
{
using (var myLock = await asyncKeyedLocker.LockAsync("test", 0))
{
var myTimeoutReleaser = (AsyncKeyedLockTimeoutReleaser<string>)myLock;
Assert.False(myTimeoutReleaser.EnteredSemaphore);
Assert.False(myLock.EnteredSemaphore);
}
Assert.True(asyncKeyedLocker.IsInUse("test"));
}
Expand Down Expand Up @@ -46,8 +44,7 @@ public async Task TestTimeoutWithTimeSpan()
{
using (var myLock = await asyncKeyedLocker.LockAsync("test", TimeSpan.Zero))
{
var myTimeoutReleaser = (AsyncKeyedLockTimeoutReleaser<string>)myLock;
Assert.False(myTimeoutReleaser.EnteredSemaphore);
Assert.False(myLock.EnteredSemaphore);
}
Assert.True(asyncKeyedLocker.IsInUse("test"));
}
Expand All @@ -59,7 +56,7 @@ public async Task BasicTest()
{
var locks = 5000;
var concurrency = 50;
var asyncKeyedLocker = new AsyncKeyedLocker();
var asyncKeyedLocker = new AsyncKeyedLock.AsyncKeyedLocker();
var concurrentQueue = new ConcurrentQueue<(bool entered, int key)>();

var tasks = Enumerable.Range(1, locks * concurrency)
Expand Down Expand Up @@ -463,7 +460,7 @@ public async Task BasicTestGenericsString()
public async Task Test1AtATime()
{
var range = 25;
var asyncKeyedLocker = new AsyncKeyedLocker();
var asyncKeyedLocker = new AsyncKeyedLock.AsyncKeyedLocker();
var concurrentQueue = new ConcurrentQueue<int>();

var tasks = Enumerable.Range(1, range * 2)
Expand Down Expand Up @@ -497,7 +494,7 @@ public async Task Test1AtATime()
public async Task Test2AtATime()
{
var range = 4;
var asyncKeyedLocker = new AsyncKeyedLocker(o => o.MaxCount = 2);
var asyncKeyedLocker = new AsyncKeyedLock.AsyncKeyedLocker(o => o.MaxCount = 2);
var concurrentQueue = new ConcurrentQueue<int>();

var tasks = Enumerable.Range(1, range * 4)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
using System.Collections.Concurrent;
using Xunit;

namespace AsyncKeyedLock.Tests;
namespace AsyncKeyedLock.Tests.AsyncKeyedLocker;

/// <summary>
/// Adapted from https://github.com/amoerie/keyed-semaphores/blob/main/KeyedSemaphores.Tests/TestsForKeyedSemaphore.cs
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
using System.Collections.Concurrent;
using Xunit;

namespace AsyncKeyedLock.Tests;
namespace AsyncKeyedLock.Tests.AsyncKeyedLocker;

/// <summary>
/// Adapted from https://github.com/amoerie/keyed-semaphores/blob/main/KeyedSemaphores.Tests/TestsForKeyedSemaphoresCollection.cs
Expand All @@ -16,8 +16,7 @@ public async Task ShouldRunThreadsWithDistinctKeysInParallel()
var currentParallelism = 0;
var maxParallelism = 0;
var parallelismLock = new object();
var asyncKeyedLocks = new AsyncKeyedLocker<string>();
var index = asyncKeyedLocks.Index;
var asyncKeyedLocks = new AsyncKeyedLocker<int>();

// 100 threads, 100 keys
var threads = Enumerable.Range(0, 100)
Expand All @@ -28,11 +27,14 @@ public async Task ShouldRunThreadsWithDistinctKeysInParallel()
await Task.WhenAll(threads).ConfigureAwait(false);

maxParallelism.Should().BeGreaterThan(10);
index.Should().BeEmpty();
foreach (var key in Enumerable.Range(0, 100))
{
asyncKeyedLocks.IsInUse(key).Should().BeFalse();
}

async Task OccupyTheLockALittleBit(int key)
{
using (await asyncKeyedLocks.LockAsync(key.ToString()))
using (await asyncKeyedLocks.LockAsync(key))
{
var incrementedCurrentParallelism = Interlocked.Increment(ref currentParallelism);

Expand Down Expand Up @@ -60,7 +62,6 @@ public async Task ShouldRunThreadsWithSameKeysLinearly()
var currentParallelism = 0;
var maxParallelism = 0;
var asyncKeyedLocks = new AsyncKeyedLocker<int>();
var index = asyncKeyedLocks.Index;

// 100 threads, 10 keys
var threads = Enumerable.Range(0, 100)
Expand All @@ -71,7 +72,10 @@ public async Task ShouldRunThreadsWithSameKeysLinearly()
await Task.WhenAll(threads).ConfigureAwait(false);

maxParallelism.Should().BeLessOrEqualTo(10);
index.Should().BeEmpty();
foreach (var key in Enumerable.Range(0, 100))
{
asyncKeyedLocks.IsInUse(key % 10).Should().BeFalse();
}

async Task OccupyTheLockALittleBit(int key)
{
Expand Down Expand Up @@ -126,7 +130,6 @@ public async Task ShouldNeverCreateTwoSemaphoresForTheSameKey()
var maxParallelism = 0;
var random = new Random();
var asyncKeyedLocks = new AsyncKeyedLocker<int>();
var index = asyncKeyedLocks.Index;

// Many threads, 1 key
var threads = Enumerable.Range(0, 100)
Expand All @@ -137,7 +140,7 @@ public async Task ShouldNeverCreateTwoSemaphoresForTheSameKey()
await Task.WhenAll(threads).ConfigureAwait(false);

maxParallelism.Should().Be(1);
index.Should().BeEmpty();
asyncKeyedLocks.IsInUse(1).Should().BeFalse();


async Task OccupyTheLockALittleBit(int key)
Expand Down Expand Up @@ -190,8 +193,7 @@ public async Task ShouldRunThreadsWithDistinctStringKeysInParallel()
var currentParallelism = 0;
var maxParallelism = 0;
var parallelismLock = new object();
var asyncKeyedLocks = new AsyncKeyedLocker<string>();
var index = asyncKeyedLocks.Index;
var asyncKeyedLocks = new AsyncKeyedLocker<int>();

// 100 threads, 100 keys
var threads = Enumerable.Range(0, 100)
Expand All @@ -202,11 +204,14 @@ public async Task ShouldRunThreadsWithDistinctStringKeysInParallel()
await Task.WhenAll(threads).ConfigureAwait(false);

maxParallelism.Should().BeGreaterThan(10);
index.Should().BeEmpty();
foreach (var key in Enumerable.Range(0, 100))
{
asyncKeyedLocks.IsInUse(key).Should().BeFalse();
}

async Task OccupyTheLockALittleBit(int key)
{
using (await asyncKeyedLocks.LockAsync(key.ToString()))
using (await asyncKeyedLocks.LockAsync(key))
{
var incrementedCurrentParallelism = Interlocked.Increment(ref currentParallelism);

Expand All @@ -223,4 +228,39 @@ async Task OccupyTheLockALittleBit(int key)
}
}
}

[Fact]
public async Task IsInUseShouldReturnTrueWhenLockedAndFalseWhenNotLocked()
{
// Arrange
var asyncKeyedLocks = new AsyncKeyedLocker<int>();

// 10 threads, 10 keys
var threads = Enumerable.Range(0, 10)
.Select(i => Task.Run(async () => await OccupyTheLockALittleBit(i).ConfigureAwait(false)))
.ToList();

// Act
await Task.WhenAll(threads).ConfigureAwait(false);
foreach (var key in Enumerable.Range(0, 10))
{
asyncKeyedLocks.IsInUse(key).Should().BeFalse();
}

async Task OccupyTheLockALittleBit(int key)
{
asyncKeyedLocks.IsInUse(key).Should().BeFalse();

using (await asyncKeyedLocks.LockAsync(key))
{
const int delay = 250;

await Task.Delay(TimeSpan.FromMilliseconds(delay)).ConfigureAwait(false);

asyncKeyedLocks.IsInUse(key).Should().BeTrue();
}

asyncKeyedLocks.IsInUse(key).Should().BeFalse();
}
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
using FluentAssertions;
using Xunit;

namespace AsyncKeyedLock.Tests;
namespace AsyncKeyedLock.Tests.AsyncKeyedLocker;

/// <summary>
/// Adapted from https://github.com/amoerie/keyed-semaphores/blob/main/KeyedSemaphores.Tests/TestsForCancellationAndTimeout.cs
Expand Down
Loading

0 comments on commit 4589301

Please sign in to comment.