Skip to content

Commit

Permalink
Threading 5.15.0
Browse files Browse the repository at this point in the history
  • Loading branch information
sakno committed Oct 16, 2024
1 parent 5bdb434 commit c282cf4
Show file tree
Hide file tree
Showing 16 changed files with 548 additions and 59 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
Release Notes
====

# 10-16-2023
<a href="https://www.nuget.org/packages/dotnext.threading/5.15.0">DotNext.Threading 5.15.0</a>
* Added support of synchronous lock acquisition to `AsyncExclusiveLock`, `AsyncReaderWriterLock`, `AsyncManualResetEvent`, `AsyncAutoResetEvent` so the users can easily migrate step-by-step from monitors and other synchronization primitives to async-friendly primitives
* Fixed random `InvalidOperationException` caused by `RandomAccessCache<TKey, TValue>`
* Added synchronous methods to `RandomAccessCache<TKey, TValue>` to support [251](https://github.com/dotnet/dotNext/issues/251) feature request

# 10-13-2024
<a href="https://www.nuget.org/packages/dotnext/5.14.0">DotNext 5.14.0</a>
* Added helpers to `DelegateHelpers` class to convert delegates with synchronous signature to their asynchronous counterparts
Expand Down
30 changes: 5 additions & 25 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,32 +44,12 @@ All these things are implemented in 100% managed code on top of existing .NET AP
* [NuGet Packages](https://www.nuget.org/profiles/rvsakno)

# What's new
Release Date: 10-13-2024
Release Date: 10-16-2024

<a href="https://www.nuget.org/packages/dotnext/5.14.0">DotNext 5.14.0</a>
* Added helpers to `DelegateHelpers` class to convert delegates with synchronous signature to their asynchronous counterparts
* Added support of async enumerator to `SingletonList<T>`
* Fixed exception propagation in `DynamicTaskAwaitable`
* Added support of [ConfigureAwaitOptions](https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.configureawaitoptions) to `DynamicTaskAwaitable`

<a href="https://www.nuget.org/packages/dotnext.metaprogramming/5.14.0">DotNext.Metaprogramming 5.14.0</a>
* Updated dependencies

<a href="https://www.nuget.org/packages/dotnext.unsafe/5.14.0">DotNext.Unsafe 5.14.0</a>
* Updated dependencies

<a href="https://www.nuget.org/packages/dotnext.threading/5.14.0">DotNext.Threading 5.14.0</a>
* Updated dependencies

<a href="https://www.nuget.org/packages/dotnext.io/5.14.0">DotNext.IO 5.14.0</a>
* Updated dependencies

<a href="https://www.nuget.org/packages/dotnext.net.cluster/5.14.0">DotNext.Net.Cluster 5.14.0</a>
* Fixed graceful shutdown of Raft TCP listener
* Updated vulnerable dependencies

<a href="https://www.nuget.org/packages/dotnext.aspnetcore.cluster/5.14.0">DotNext.AspNetCore.Cluster 5.14.0</a>
* Updated vulnerable dependencies
<a href="https://www.nuget.org/packages/dotnext.threading/5.15.0">DotNext.Threading 5.15.0</a>
* Added support of synchronous lock acquisition to `AsyncExclusiveLock`, `AsyncReaderWriterLock`, `AsyncManualResetEvent`, `AsyncAutoResetEvent` so the users can easily migrate step-by-step from monitors and other synchronization primitives to async-friendly primitives
* Fixed random `InvalidOperationException` caused by `RandomAccessCache<TKey, TValue>`
* Added synchronous methods to `RandomAccessCache<TKey, TValue>` to support [251](https://github.com/dotnet/dotNext/issues/251) feature request

Changelog for previous versions located [here](./CHANGELOG.md).

Expand Down
39 changes: 37 additions & 2 deletions src/DotNext.Tests/Runtime/Caching/RandomAccessCacheTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ static async Task RequestLoop(RandomAccessCache<long, string> cache)
}

[Fact]
public static async Task AddRemove()
public static async Task AddRemoveAsync()
{
await using var cache = new RandomAccessCache<long, string>(15);

Expand All @@ -122,9 +122,29 @@ public static async Task AddRemove()
Equal("10", session.Value);
}
}

[Fact]
public static void AddRemove()
{
using var cache = new RandomAccessCache<long, string>(15);

using (var writeSession = cache.Change(10L, DefaultTimeout))
{
False(writeSession.TryGetValue(out _));
writeSession.SetValue("10");
}

False(cache.TryRemove(11L, DefaultTimeout, out _));
True(cache.TryRemove(10L, DefaultTimeout, out var session));

using (session)
{
Equal("10", session.Value);
}
}

[Fact]
public static async Task AddInvalidate()
public static async Task AddInvalidateAsync()
{
await using var cache = new RandomAccessCache<long, string>(15);

Expand All @@ -137,6 +157,21 @@ public static async Task AddInvalidate()
False(await cache.InvalidateAsync(11L));
True(await cache.InvalidateAsync(10L));
}

[Fact]
public static void AddInvalidate()
{
using var cache = new RandomAccessCache<long, string>(15);

using (var session = cache.Change(10L, DefaultTimeout))
{
False(session.TryGetValue(out _));
session.SetValue("10");
}

False(cache.Invalidate(11L, DefaultTimeout));
True(cache.Invalidate(10L, DefaultTimeout));
}

[Fact]
public static async Task AddTwice()
Expand Down
35 changes: 35 additions & 0 deletions src/DotNext.Tests/Threading/AsyncExclusiveLockTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -179,4 +179,39 @@ public static async Task LockStealing2()
l.Release();
await task3;
}

[Fact]
public static void SynchronousLock()
{
using var l = new AsyncExclusiveLock();
True(l.TryAcquire(DefaultTimeout));

False(l.TryAcquire(TimeSpan.Zero));
}

[Fact]
public static async Task MixedLock()
{
await using var l = new AsyncExclusiveLock();
True(await l.TryAcquireAsync(DefaultTimeout));

var t = Task.Factory.StartNew(() => l.TryAcquire(DefaultTimeout), TaskCreationOptions.LongRunning);
l.Release();

True(await t);
False(l.TryAcquire());
l.Release();
}

[Fact]
public static async Task DisposedWhenSynchronousLockAcquired()
{
var l = new AsyncExclusiveLock();
True(l.TryAcquire());

var t = Task.Factory.StartNew(() => l.TryAcquire(DefaultTimeout), TaskCreationOptions.LongRunning);

l.Dispose();
await ThrowsAsync<ObjectDisposedException>(Func.Constant(t));
}
}
63 changes: 62 additions & 1 deletion src/DotNext.Tests/Threading/AsyncReaderWriterLockTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ public static async Task TrivialLock()
True(await rwLock.TryEnterReadLockAsync(DefaultTimeout));
True(await rwLock.TryUpgradeToWriteLockAsync(DefaultTimeout));
False(rwLock.TryEnterWriteLock());
False(rwLock.TryUpgradeToWriteLock());
rwLock.DowngradeFromWriteLock();
True(await rwLock.TryEnterReadLockAsync(DefaultTimeout));
}
Expand Down Expand Up @@ -77,7 +78,7 @@ public static void OptimisticRead()
True(rwLock.Validate(stamp));
rwLock.Release();
Equal(stamp, rwLock.TryOptimisticRead());
True(rwLock.TryEnterWriteLock());
True(rwLock.TryEnterWriteLock(stamp));
False(rwLock.IsReadLockHeld);
True(rwLock.IsWriteLockHeld);
False(rwLock.Validate(stamp));
Expand Down Expand Up @@ -195,4 +196,64 @@ public static async Task LockStealing2()
@lock.Release();
await task3;
}

[Fact]
public static async Task DisposedWhenSynchronousReadLockAcquired()
{
var l = new AsyncReaderWriterLock();
True(l.TryEnterReadLock());

var t = Task.Factory.StartNew(() => l.TryEnterWriteLock(DefaultTimeout), TaskCreationOptions.LongRunning);

l.Dispose();
await ThrowsAsync<ObjectDisposedException>(Func.Constant(t));
}

[Fact]
public static async Task DisposedWhenSynchronousWriteLockAcquired()
{
var l = new AsyncReaderWriterLock();
True(l.TryEnterWriteLock());

var t = Task.Factory.StartNew(() => l.TryEnterReadLock(DefaultTimeout), TaskCreationOptions.LongRunning);

l.Dispose();
await ThrowsAsync<ObjectDisposedException>(Func.Constant(t));
}

[Fact]
public static async Task AcquireReadWriteLockSynchronously()
{
using var l = new AsyncReaderWriterLock();
True(l.TryEnterReadLock(DefaultTimeout));
True(l.TryEnterReadLock(DefaultTimeout));
Equal(2L, l.CurrentReadCount);

var t = Task.Factory.StartNew(() => l.TryEnterWriteLock(DefaultTimeout), TaskCreationOptions.LongRunning);

l.Release();
l.Release();

True(await t);
True(l.IsWriteLockHeld);

l.Release();
False(l.IsWriteLockHeld);
}

[Fact]
public static async Task ResumeMultipleReadersSynchronously()
{
using var l = new AsyncReaderWriterLock();
True(l.TryEnterWriteLock());

var t1 = Task.Factory.StartNew(TryEnterReadLock, TaskCreationOptions.LongRunning);
var t2 = Task.Factory.StartNew(TryEnterReadLock, TaskCreationOptions.LongRunning);

l.Release();
Equal(new[] { true, true }, await Task.WhenAll(t1, t2));
Equal(2L, l.CurrentReadCount);

bool TryEnterReadLock() => l.TryEnterReadLock(DefaultTimeout);
}
}
61 changes: 61 additions & 0 deletions src/DotNext.Tests/Threading/AsyncResetEventTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -104,4 +104,65 @@ public static async Task RegressionIssue82()
ev.Set();
await consumer;
}

public static TheoryData<IAsyncResetEvent> GetResetEvents() => new()
{
new AsyncAutoResetEvent(false),
new AsyncManualResetEvent(false),
};

[Theory]
[MemberData(nameof(GetResetEvents))]
public static async Task ManualResetEventSynchronousCompletion(IAsyncResetEvent resetEvent)
{
using (resetEvent)
{
False(resetEvent.IsSet);

var t = Task.Factory.StartNew(() => resetEvent.Wait(DefaultTimeout), TaskCreationOptions.LongRunning);

True(resetEvent.Signal());
True(await t);
Equal(resetEvent.ResetMode is EventResetMode.ManualReset, resetEvent.IsSet);
}
}

[Theory]
[MemberData(nameof(GetResetEvents))]
public static void AlreadySignaledEvents(IAsyncResetEvent resetEvent)
{
using (resetEvent)
{
True(resetEvent.Signal());
True(resetEvent.Wait(DefaultTimeout));
}
}

[Fact]
public static async Task AutoResetOnSyncWait()
{
using var are = new AsyncAutoResetEvent(false);
var t = Task.Factory.StartNew(() => are.Wait(DefaultTimeout), TaskCreationOptions.LongRunning);
True(are.Set());

True(await t);
False(are.IsSet);
}

[Fact]
public static async Task ResumeSuspendedCallersSequentially()
{
using var are = new AsyncAutoResetEvent(false);
var t1 = Task.Factory.StartNew(Wait, TaskCreationOptions.LongRunning);
var t2 = Task.Factory.StartNew(Wait, TaskCreationOptions.LongRunning);

True(are.Set());

True(await Task.WhenAny(t1, t2).Unwrap());

True(are.Set());
Equal(new[] { true, true }, await Task.WhenAll(t1, t2));

bool Wait() => are.Wait(DefaultTimeout);
}
}
2 changes: 1 addition & 1 deletion src/DotNext.Threading/DotNext.Threading.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<ImplicitUsings>true</ImplicitUsings>
<IsTrimmable>true</IsTrimmable>
<Features>nullablePublicOnly</Features>
<VersionPrefix>5.14.0</VersionPrefix>
<VersionPrefix>5.15.0</VersionPrefix>
<VersionSuffix></VersionSuffix>
<Authors>.NET Foundation and Contributors</Authors>
<Product>.NEXT Family of Libraries</Product>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -159,27 +159,32 @@ internal required TValue Value
[DebuggerDisplay($"NumberOfItems = {{{nameof(Count)}}}")]
internal sealed class Bucket : AsyncExclusiveLock
{
private bool newPairAdded;
private volatile KeyValuePair? first; // volatile

[ExcludeFromCodeCoverage]
private (int Alive, int Dead) Count => first?.BucketNodesCount ?? default;

internal KeyValuePair? TryAdd(IEqualityComparer<TKey>? keyComparer, TKey key, int hashCode, TValue value)
internal KeyValuePair? TryAdd(TKey key, int hashCode, TValue value)
{
var firstCopy = first;
if (firstCopy is not null && firstCopy.KeyHashCode == hashCode
&& (keyComparer?.Equals(key, firstCopy.Key)
?? EqualityComparer<TKey>.Default.Equals(key, firstCopy.Key)))
KeyValuePair? result;
if (newPairAdded)
{
return null;
result = null;
}
else
{
result = CreatePair(key, value, hashCode);
result.NextInBucket = first;
first = result;
newPairAdded = true;
}

var newPair = CreatePair(key, value, hashCode);
newPair.NextInBucket = firstCopy;
first = newPair;
return newPair;
return result;
}

internal void MarkAsReadyToAdd() => newPairAdded = false;

private void Remove(KeyValuePair? previous, KeyValuePair current)
{
ref var location = ref previous is null ? ref first : ref previous.NextInBucket;
Expand Down
Loading

0 comments on commit c282cf4

Please sign in to comment.