Skip to content

Commit

Permalink
Added a simple, non-keyed async lock through AsyncNonKeyedLocker.
Browse files Browse the repository at this point in the history
  • Loading branch information
MarkCiliaVincenti committed Jan 14, 2024
1 parent cb7355c commit 6585036
Show file tree
Hide file tree
Showing 6 changed files with 601 additions and 4 deletions.
166 changes: 166 additions & 0 deletions AsyncKeyedLock.Tests/AsyncNonKeyedLockerTests/OriginalTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
using FluentAssertions;
using Xunit;

namespace AsyncKeyedLock.Tests.AsyncNonKeyedLockerTests
{
public class OriginalTests
{
[Fact]
public void TestLock()
{
var asyncNonKeyedLocker = new AsyncNonKeyedLocker();
Assert.Equal(0, asyncNonKeyedLocker.GetRemainingCount());
Assert.Equal(1, asyncNonKeyedLocker.GetCurrentCount());
using (asyncNonKeyedLocker.Lock())
{
Assert.Equal(1, asyncNonKeyedLocker.GetRemainingCount());
Assert.Equal(0, asyncNonKeyedLocker.GetCurrentCount());
}
Assert.Equal(0, asyncNonKeyedLocker.GetRemainingCount());
Assert.Equal(1, asyncNonKeyedLocker.GetCurrentCount());
}

[Fact]
public void TestLockAndCancellationToken()
{
var asyncNonKeyedLocker = new AsyncNonKeyedLocker();
Assert.Equal(0, asyncNonKeyedLocker.GetRemainingCount());
Assert.Equal(1, asyncNonKeyedLocker.GetCurrentCount());
using (asyncNonKeyedLocker.Lock(CancellationToken.None))
{
Assert.Equal(1, asyncNonKeyedLocker.GetRemainingCount());
Assert.Equal(0, asyncNonKeyedLocker.GetCurrentCount());
}
Assert.Equal(0, asyncNonKeyedLocker.GetRemainingCount());
Assert.Equal(1, asyncNonKeyedLocker.GetCurrentCount());
}

[Fact]
public void TestLockAndCancelledCancellationToken()
{
Action action = () =>
{
var asyncNonKeyedLocker = new AsyncNonKeyedLocker();
using (asyncNonKeyedLocker.Lock(new CancellationToken(true)))
{ }
};
action.Should().Throw<OperationCanceledException>();
}

[Fact]
public void TestLockAndMillisecondsTimeout()
{
var asyncNonKeyedLocker = new AsyncNonKeyedLocker();
Assert.Equal(0, asyncNonKeyedLocker.GetRemainingCount());
Assert.Equal(1, asyncNonKeyedLocker.GetCurrentCount());
using (var myLock = asyncNonKeyedLocker.Lock(0, out bool entered))
{
Assert.True(entered);
Assert.True(myLock.EnteredSemaphore);
Assert.Equal(1, asyncNonKeyedLocker.GetRemainingCount());
Assert.Equal(0, asyncNonKeyedLocker.GetCurrentCount());
using (var myLock2 = asyncNonKeyedLocker.Lock(0, out entered))
{
Assert.False(entered);
Assert.False(myLock2.EnteredSemaphore);
}
}
Assert.Equal(0, asyncNonKeyedLocker.GetRemainingCount());
Assert.Equal(1, asyncNonKeyedLocker.GetCurrentCount());
}

[Fact]
public void TestLockAndTimeout()
{
var asyncNonKeyedLocker = new AsyncNonKeyedLocker();
Assert.Equal(0, asyncNonKeyedLocker.GetRemainingCount());
Assert.Equal(1, asyncNonKeyedLocker.GetCurrentCount());
using (var myLock = asyncNonKeyedLocker.Lock(TimeSpan.FromMilliseconds(0), out bool entered))
{
Assert.True(entered);
Assert.True(myLock.EnteredSemaphore);
Assert.Equal(1, asyncNonKeyedLocker.GetRemainingCount());
Assert.Equal(0, asyncNonKeyedLocker.GetCurrentCount());
using (var myLock2 = asyncNonKeyedLocker.Lock(TimeSpan.FromMilliseconds(0), out entered))
{
Assert.False(entered);
Assert.False(myLock2.EnteredSemaphore);
}
}
Assert.Equal(0, asyncNonKeyedLocker.GetRemainingCount());
Assert.Equal(1, asyncNonKeyedLocker.GetCurrentCount());
}

[Fact]
public void TestLockAndMillisecondsTimeoutAndCancellationToken()
{
var asyncNonKeyedLocker = new AsyncNonKeyedLocker();
Assert.Equal(0, asyncNonKeyedLocker.GetRemainingCount());
Assert.Equal(1, asyncNonKeyedLocker.GetCurrentCount());
using (var myLock = asyncNonKeyedLocker.Lock(0, CancellationToken.None, out bool entered))
{
Assert.True(entered);
Assert.True(myLock.EnteredSemaphore);
Assert.Equal(1, asyncNonKeyedLocker.GetRemainingCount());
Assert.Equal(0, asyncNonKeyedLocker.GetCurrentCount());
using (var myLock2 = asyncNonKeyedLocker.Lock(0, CancellationToken.None, out entered))
{
Assert.False(entered);
Assert.False(myLock2.EnteredSemaphore);
}
}
Assert.Equal(0, asyncNonKeyedLocker.GetRemainingCount());
Assert.Equal(1, asyncNonKeyedLocker.GetCurrentCount());
}

[Fact]
public void TestLockAndMillisecondsTimeoutAndCancelledCancellationToken()
{
bool entered = false;
Action action = () =>
{
var asyncNonKeyedLocker = new AsyncNonKeyedLocker();
using (asyncNonKeyedLocker.Lock(0, new CancellationToken(true), out entered))
{ }
};
action.Should().Throw<OperationCanceledException>();
entered.Should().BeFalse();
}

[Fact]
public void TestLockAndTimeoutAndCancellationToken()
{
var asyncNonKeyedLocker = new AsyncNonKeyedLocker();
Assert.Equal(0, asyncNonKeyedLocker.GetRemainingCount());
Assert.Equal(1, asyncNonKeyedLocker.GetCurrentCount());
using (var myLock = asyncNonKeyedLocker.Lock(TimeSpan.FromMilliseconds(0), CancellationToken.None, out bool entered))
{
Assert.True(entered);
Assert.True(myLock.EnteredSemaphore);
Assert.Equal(1, asyncNonKeyedLocker.GetRemainingCount());
Assert.Equal(0, asyncNonKeyedLocker.GetCurrentCount());
using (var myLock2 = asyncNonKeyedLocker.Lock(TimeSpan.FromMilliseconds(0), CancellationToken.None, out entered))
{
Assert.False(entered);
Assert.False(myLock2.EnteredSemaphore);
}
}
Assert.Equal(0, asyncNonKeyedLocker.GetRemainingCount());
Assert.Equal(1, asyncNonKeyedLocker.GetCurrentCount());
}

[Fact]
public void TestLockAndTimeoutAndCancelledCancellationToken()
{
bool entered = false;
Action action = () =>
{
var asyncNonKeyedLocker = new AsyncNonKeyedLocker();
using (asyncNonKeyedLocker.Lock(TimeSpan.FromMilliseconds(0), new CancellationToken(true), out entered))
{ }
};
action.Should().Throw<OperationCanceledException>();
entered.Should().BeFalse();
}
}
}
8 changes: 4 additions & 4 deletions AsyncKeyedLock/AsyncKeyedLock.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,16 @@
<PackageProjectUrl>https://github.com/MarkCiliaVincenti/AsyncKeyedLock</PackageProjectUrl>
<Copyright>MIT</Copyright>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<Version>6.2.6</Version>
<Version>6.3.0</Version>
<PackageIcon>logo.png</PackageIcon>
<PackageReleaseNotes>Cleaning pooling and aggressively inlining locking methods.</PackageReleaseNotes>
<PackageReleaseNotes>Added a simple, non-keyed async lock through AsyncNonKeyedLocker.</PackageReleaseNotes>
<Description>An asynchronous .NET Standard 2.0 library that allows you to lock based on a key (keyed semaphores), limiting concurrent threads sharing the same key to a specified number, with optional pooling for reducing memory allocations.</Description>
<Copyright>© 2024 Mark Cilia Vincenti</Copyright>
<PackageTags>async,lock,key,keyed,semaphore,striped,dictionary,concurrentdictionary,pooling,duplicate,synchronization</PackageTags>
<RepositoryType>git</RepositoryType>
<GeneratePackageOnBuild>false</GeneratePackageOnBuild>
<AssemblyVersion>6.2.6.0</AssemblyVersion>
<FileVersion>6.2.6.0</FileVersion>
<AssemblyVersion>6.3.0.0</AssemblyVersion>
<FileVersion>6.3.0.0</FileVersion>
<PackageReadmeFile>README.md</PackageReadmeFile>
<IsPackable>true</IsPackable>
<EnforceCodeStyleInBuild>True</EnforceCodeStyleInBuild>
Expand Down
33 changes: 33 additions & 0 deletions AsyncKeyedLock/AsyncNonKeyedLockReleaser.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using System;
using System.Runtime.CompilerServices;
using System.Threading;

namespace AsyncKeyedLock
{
/// <summary>
/// Represents an <see cref="IDisposable"/> for AsyncNonKeyedLocker.
/// </summary>
public readonly struct AsyncNonKeyedLockReleaser : IDisposable
{
private readonly SemaphoreSlim _semaphoreSlim;

/// <summary>
/// The exposed <see cref="SemaphoreSlim"/> instance used to limit the number of threads that can access the lock concurrently.
/// </summary>
public readonly SemaphoreSlim SemaphoreSlim => _semaphoreSlim;

internal AsyncNonKeyedLockReleaser(SemaphoreSlim semaphoreSlim)
{
_semaphoreSlim = semaphoreSlim;
}

/// <summary>
/// Releases the <see cref="SemaphoreSlim"/> object once.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly void Dispose()
{
_semaphoreSlim.Release();
}
}
}
44 changes: 44 additions & 0 deletions AsyncKeyedLock/AsyncNonKeyedLockTimeoutReleaser.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
using System;
using System.Runtime.CompilerServices;
using System.Threading;

namespace AsyncKeyedLock
{
/// <summary>
/// Represents an <see cref="IDisposable"/> for AsyncNonKeyedLocker with timeouts.
/// </summary>
public readonly struct AsyncNonKeyedLockTimeoutReleaser : IDisposable
{
private readonly bool _enteredSemaphore;

/// <summary>
/// True if the timeout was reached, false if not.
/// </summary>
public readonly bool EnteredSemaphore => _enteredSemaphore;

private readonly SemaphoreSlim _semaphoreSlim;

/// <summary>
/// The exposed <see cref="SemaphoreSlim"/> instance used to limit the number of threads that can access the lock concurrently.
/// </summary>
public readonly SemaphoreSlim SemaphoreSlim => _semaphoreSlim;

internal AsyncNonKeyedLockTimeoutReleaser(SemaphoreSlim semaphoreSlim, bool enteredSemaphore)
{
_enteredSemaphore = enteredSemaphore;
_semaphoreSlim = semaphoreSlim;
}

/// <summary>
/// Releases the <see cref="SemaphoreSlim"/> object once, depending on whether or not the semaphore was entered.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly void Dispose()
{
if (_enteredSemaphore)
{
_semaphoreSlim.Release();
}
}
}
}
Loading

0 comments on commit 6585036

Please sign in to comment.