diff --git a/AsyncKeyedLock.Tests/AsyncKeyedLocker/KeyedSemaphores/NonPooled/TestsForAsyncKeyedLock.cs b/AsyncKeyedLock.Tests/AsyncKeyedLocker/KeyedSemaphores/NonPooled/TestsForAsyncKeyedLock.cs index 4d01041..152a2a4 100644 --- a/AsyncKeyedLock.Tests/AsyncKeyedLocker/KeyedSemaphores/NonPooled/TestsForAsyncKeyedLock.cs +++ b/AsyncKeyedLock.Tests/AsyncKeyedLocker/KeyedSemaphores/NonPooled/TestsForAsyncKeyedLock.cs @@ -8,6 +8,8 @@ namespace AsyncKeyedLock.Tests.AsyncKeyedLocker.KeyedSemaphores.NonPooled; /// /// Adapted from https://raw.githubusercontent.com/amoerie/keyed-semaphores/main/KeyedSemaphores.Tests/TestsForKeyedSemaphore.cs and https://raw.githubusercontent.com/amoerie/keyed-semaphores/main/KeyedSemaphores.Tests/TestsForKeyedSemaphoresCollection.cs /// +[Collection("KeyedSemaphores Tests")] +[CollectionDefinition("KeyedSemaphores Tests", DisableParallelization = false)] public class TestsForAsyncKeyedLock { private readonly ITestOutputHelper _output; @@ -19,6 +21,8 @@ public TestsForAsyncKeyedLock(ITestOutputHelper output) _output = output ?? throw new ArgumentNullException(nameof(output)); } + [Collection("KeyedSemaphores Tests")] + [CollectionDefinition("KeyedSemaphores Tests", DisableParallelization = false)] public class Async : TestsForAsyncKeyedLock { public Async(ITestOutputHelper output) : base(output) { } @@ -93,6 +97,8 @@ async Task OccupyTheLockALittleBit(int key) } } + [Collection("KeyedSemaphores Tests")] + [CollectionDefinition("KeyedSemaphores Tests", DisableParallelization = false)] public class Sync : TestsForAsyncKeyedLock { public Sync(ITestOutputHelper output) : base(output) { } diff --git a/AsyncKeyedLock.Tests/AsyncKeyedLocker/KeyedSemaphores/Pooled/TestsForAsyncKeyedLock.cs b/AsyncKeyedLock.Tests/AsyncKeyedLocker/KeyedSemaphores/Pooled/TestsForAsyncKeyedLock.cs index 49b54fc..b855bdb 100644 --- a/AsyncKeyedLock.Tests/AsyncKeyedLocker/KeyedSemaphores/Pooled/TestsForAsyncKeyedLock.cs +++ b/AsyncKeyedLock.Tests/AsyncKeyedLocker/KeyedSemaphores/Pooled/TestsForAsyncKeyedLock.cs @@ -8,6 +8,8 @@ namespace AsyncKeyedLock.Tests.AsyncKeyedLocker.KeyedSemaphores.Pooled; /// /// Adapted from https://raw.githubusercontent.com/amoerie/keyed-semaphores/main/KeyedSemaphores.Tests/TestsForKeyedSemaphore.cs and https://raw.githubusercontent.com/amoerie/keyed-semaphores/main/KeyedSemaphores.Tests/TestsForAsyncKeyedLocker.cs /// +[Collection("KeyedSemaphores Tests")] +[CollectionDefinition("KeyedSemaphores Tests", DisableParallelization = false)] public class TestsForAsyncKeyedLock { private readonly ITestOutputHelper _output; @@ -23,6 +25,8 @@ public TestsForAsyncKeyedLock(ITestOutputHelper output) _output = output ?? throw new ArgumentNullException(nameof(output)); } + [Collection("KeyedSemaphores Tests")] + [CollectionDefinition("KeyedSemaphores Tests", DisableParallelization = false)] public class Async : TestsForAsyncKeyedLock { public Async(ITestOutputHelper output) : base(output) { } @@ -97,6 +101,8 @@ async Task OccupyTheLockALittleBit(int key) } } + [Collection("KeyedSemaphores Tests")] + [CollectionDefinition("KeyedSemaphores Tests", DisableParallelization = false)] public class Sync : TestsForAsyncKeyedLock { public Sync(ITestOutputHelper output) : base(output) { } diff --git a/AsyncKeyedLock.Tests/AsyncKeyedLocker/OriginalTests.cs b/AsyncKeyedLock.Tests/AsyncKeyedLocker/OriginalTests.cs index 883199a..d194f93 100644 --- a/AsyncKeyedLock.Tests/AsyncKeyedLocker/OriginalTests.cs +++ b/AsyncKeyedLock.Tests/AsyncKeyedLocker/OriginalTests.cs @@ -1,11 +1,11 @@ using AsyncKeyedLock.Tests.Helpers; using FluentAssertions; -using ListShuffle; -using System.Collections.Concurrent; using Xunit; namespace AsyncKeyedLock.Tests.AsyncKeyedLocker { + [Collection("Original Tests")] + [CollectionDefinition("Original Tests", DisableParallelization = false)] public class OriginalTests { [Fact] @@ -644,561 +644,6 @@ public void TestTimeoutTryLock() Assert.False(asyncKeyedLocker.IsInUse("test")); } - [Fact] - public async Task BasicTest() - { - var locks = 5000; - var concurrency = 50; - var asyncKeyedLocker = new AsyncKeyedLocker(o => { o.PoolSize = 0; }); - var concurrentQueue = new ConcurrentQueue<(bool entered, int key)>(); - - var tasks = Enumerable.Range(1, locks * concurrency) - .Select(async i => - { - var key = Convert.ToInt32(Math.Ceiling((double)i / concurrency)); - using (await asyncKeyedLocker.LockAsync(key)) - { - await Task.Delay(20); - concurrentQueue.Enqueue((true, key)); - await Task.Delay(80); - concurrentQueue.Enqueue((false, key)); - } - }); - await Task.WhenAll(tasks.AsParallel()); - - bool valid = concurrentQueue.Count == locks * concurrency * 2; - - var entered = new HashSet(); - - while (valid && !concurrentQueue.IsEmpty) - { - concurrentQueue.TryDequeue(out var result); - if (result.entered) - { - if (entered.Contains(result.key)) - { - valid = false; - break; - } - entered.Add(result.key); - } - else - { - if (!entered.Contains(result.key)) - { - valid = false; - break; - } - entered.Remove(result.key); - } - } - - Assert.True(valid); - } - - [Fact] - public async Task BasicTestGenerics() - { - var locks = 5000; - var concurrency = 50; - var asyncKeyedLocker = new AsyncKeyedLocker(o => { o.PoolSize = 0; }); - var concurrentQueue = new ConcurrentQueue<(bool entered, int key)>(); - - var tasks = Enumerable.Range(1, locks * concurrency) - .Select(async i => - { - var key = Convert.ToInt32(Math.Ceiling((double)i / concurrency)); - using (await asyncKeyedLocker.LockAsync(key)) - { - await Task.Delay(20); - concurrentQueue.Enqueue((true, key)); - await Task.Delay(80); - concurrentQueue.Enqueue((false, key)); - } - }); - await Task.WhenAll(tasks.AsParallel()); - - bool valid = concurrentQueue.Count == locks * concurrency * 2; - - var entered = new HashSet(); - - while (valid && !concurrentQueue.IsEmpty) - { - concurrentQueue.TryDequeue(out var result); - if (result.entered) - { - if (entered.Contains(result.key)) - { - valid = false; - break; - } - entered.Add(result.key); - } - else - { - if (!entered.Contains(result.key)) - { - valid = false; - break; - } - entered.Remove(result.key); - } - } - - Assert.True(valid); - } - - [Fact] - public async Task BenchmarkSimulationTest() - { - AsyncKeyedLocker AsyncKeyedLocker; - ParallelQuery? AsyncKeyedLockerTasks = null; - Dictionary> _shuffledIntegers = new(); - - var NumberOfLocks = 200; - var Contention = 100; - var GuidReversals = 0; - - if (!_shuffledIntegers.TryGetValue(Contention * NumberOfLocks, out var ShuffledIntegers)) - { - ShuffledIntegers = Enumerable.Range(0, Contention * NumberOfLocks).ToList(); - ShuffledIntegers.Shuffle(); - _shuffledIntegers[Contention * NumberOfLocks] = ShuffledIntegers; - } - - if (NumberOfLocks != Contention) - { - AsyncKeyedLocker = new AsyncKeyedLocker(o => o.PoolSize = NumberOfLocks, Environment.ProcessorCount, NumberOfLocks); - AsyncKeyedLockerTasks = ShuffledIntegers - .Select(async i => - { - var key = i % NumberOfLocks; - - using (var myLock = await AsyncKeyedLocker.LockAsync(key.ToString())) - { - for (int j = 0; j < GuidReversals; j++) - { - Guid guid = Guid.NewGuid(); - var guidString = guid.ToString(); - guidString = guidString.Reverse().ToString(); -#pragma warning disable CS8602 // Dereference of a possibly null reference. - if (guidString.Length != 53) - { - throw new Exception($"Not 53 but {guidString?.Length}"); - } -#pragma warning restore CS8602 // Dereference of a possibly null reference. - } - } - - await Task.Yield(); - }).AsParallel(); - - await Task.WhenAll(AsyncKeyedLockerTasks); - } - } - - [Fact] - public async Task BasicTestGenericsPooling50k() - { - var locks = 50_000; - var concurrency = 50; - var asyncKeyedLocker = new AsyncKeyedLocker(o => o.PoolSize = 50_000, Environment.ProcessorCount, 50_000); - var concurrentQueue = new ConcurrentQueue<(bool entered, int key)>(); - - var tasks = Enumerable.Range(1, locks * concurrency) - .Select(async i => - { - var key = Convert.ToInt32(Math.Ceiling((double)i / concurrency)); - using (await asyncKeyedLocker.LockAsync(key)) - { - await Task.Delay(20); - concurrentQueue.Enqueue((true, key)); - await Task.Delay(80); - concurrentQueue.Enqueue((false, key)); - } - }); - await Task.WhenAll(tasks.AsParallel()); - - bool valid = concurrentQueue.Count == locks * concurrency * 2; - - var entered = new HashSet(); - - while (valid && !concurrentQueue.IsEmpty) - { - concurrentQueue.TryDequeue(out var result); - if (result.entered) - { - if (entered.Contains(result.key)) - { - valid = false; - break; - } - entered.Add(result.key); - } - else - { - if (!entered.Contains(result.key)) - { - valid = false; - break; - } - entered.Remove(result.key); - } - } - - Assert.True(valid); - } - - [Fact] - public async Task BasicTestGenericsPooling50kUnfilled() - { - var locks = 50_000; - var concurrency = 50; - var asyncKeyedLocker = new AsyncKeyedLocker(o => - { - o.PoolSize = 50_000; - o.PoolInitialFill = 0; - }, Environment.ProcessorCount, 50_000); - var concurrentQueue = new ConcurrentQueue<(bool entered, int key)>(); - - var tasks = Enumerable.Range(1, locks * concurrency) - .Select(async i => - { - var key = Convert.ToInt32(Math.Ceiling((double)i / concurrency)); - using (await asyncKeyedLocker.LockAsync(key)) - { - await Task.Delay(20); - concurrentQueue.Enqueue((true, key)); - await Task.Delay(80); - concurrentQueue.Enqueue((false, key)); - } - }); - await Task.WhenAll(tasks.AsParallel()); - - bool valid = concurrentQueue.Count == locks * concurrency * 2; - - var entered = new HashSet(); - - while (valid && !concurrentQueue.IsEmpty) - { - concurrentQueue.TryDequeue(out var result); - if (result.entered) - { - if (entered.Contains(result.key)) - { - valid = false; - break; - } - entered.Add(result.key); - } - else - { - if (!entered.Contains(result.key)) - { - valid = false; - break; - } - entered.Remove(result.key); - } - } - - Assert.True(valid); - } - - [Fact] - public async Task BasicTestGenericsPoolingProcessorCount() - { - var locks = 50_000; - var concurrency = 50; - var asyncKeyedLocker = new AsyncKeyedLocker(o => o.PoolSize = Environment.ProcessorCount, Environment.ProcessorCount, 50_000); - var concurrentQueue = new ConcurrentQueue<(bool entered, int key)>(); - - var tasks = Enumerable.Range(1, locks * concurrency) - .Select(async i => - { - var key = Convert.ToInt32(Math.Ceiling((double)i / concurrency)); - using (await asyncKeyedLocker.LockAsync(key)) - { - await Task.Delay(20); - concurrentQueue.Enqueue((true, key)); - await Task.Delay(80); - concurrentQueue.Enqueue((false, key)); - } - }); - await Task.WhenAll(tasks.AsParallel()); - - bool valid = concurrentQueue.Count == locks * concurrency * 2; - - var entered = new HashSet(); - - while (valid && !concurrentQueue.IsEmpty) - { - concurrentQueue.TryDequeue(out var result); - if (result.entered) - { - if (entered.Contains(result.key)) - { - valid = false; - break; - } - entered.Add(result.key); - } - else - { - if (!entered.Contains(result.key)) - { - valid = false; - break; - } - entered.Remove(result.key); - } - } - - Assert.True(valid); - } - - [Fact] - public async Task BasicTestGenericsPooling10k() - { - var locks = 50_000; - var concurrency = 50; - var asyncKeyedLocker = new AsyncKeyedLocker(o => o.PoolSize = 10_000, Environment.ProcessorCount, 50_000); - var concurrentQueue = new ConcurrentQueue<(bool entered, int key)>(); - - var tasks = Enumerable.Range(1, locks * concurrency) - .Select(async i => - { - var key = Convert.ToInt32(Math.Ceiling((double)i / concurrency)); - using (await asyncKeyedLocker.LockAsync(key)) - { - await Task.Delay(20); - concurrentQueue.Enqueue((true, key)); - await Task.Delay(80); - concurrentQueue.Enqueue((false, key)); - } - }); - await Task.WhenAll(tasks.AsParallel()); - - bool valid = concurrentQueue.Count == locks * concurrency * 2; - - var entered = new HashSet(); - - while (valid && !concurrentQueue.IsEmpty) - { - concurrentQueue.TryDequeue(out var result); - if (result.entered) - { - if (entered.Contains(result.key)) - { - valid = false; - break; - } - entered.Add(result.key); - } - else - { - if (!entered.Contains(result.key)) - { - valid = false; - break; - } - entered.Remove(result.key); - } - } - - Assert.True(valid); - } - - [Fact] - public async Task BasicTestGenericsString() - { - var locks = 5000; - var concurrency = 50; - var asyncKeyedLocker = new AsyncKeyedLocker(o => { o.PoolSize = 0; }); - var concurrentQueue = new ConcurrentQueue<(bool entered, string key)>(); - - var tasks = Enumerable.Range(1, locks * concurrency) - .Select(async i => - { - var key = Convert.ToInt32(Math.Ceiling((double)i / 5)).ToString(); - using (await asyncKeyedLocker.LockAsync(key)) - { - await Task.Delay(20); - concurrentQueue.Enqueue((true, key)); - await Task.Delay(80); - concurrentQueue.Enqueue((false, key)); - } - }); - await Task.WhenAll(tasks.AsParallel()); - - bool valid = concurrentQueue.Count == locks * concurrency * 2; - - var entered = new HashSet(); - - while (valid && !concurrentQueue.IsEmpty) - { - concurrentQueue.TryDequeue(out var result); - if (result.entered) - { - if (entered.Contains(result.key)) - { - valid = false; - break; - } - entered.Add(result.key); - } - else - { - if (!entered.Contains(result.key)) - { - valid = false; - break; - } - entered.Remove(result.key); - } - } - - Assert.True(valid); - } - - [Fact] - public async Task Test1AtATime() - { - var range = 25000; - var asyncKeyedLocker = new AsyncKeyedLocker(o => { o.PoolSize = 0; }); - var concurrentQueue = new ConcurrentQueue(); - - int threadNum = 0; - - var tasks = Enumerable.Range(1, range * 2) - .Select(async i => - { - var key = Convert.ToInt32(Math.Ceiling((double)Interlocked.Increment(ref threadNum) / 2)); - using (await asyncKeyedLocker.LockAsync(key)) - { - concurrentQueue.Enqueue(key); - } - }); - await Task.WhenAll(tasks.AsParallel()); - - bool valid = true; - var list = concurrentQueue.ToList(); - - for (int i = 0; i < range; i++) - { - if (list[i] == list[i + range]) - { - valid = false; - break; - } - } - - Assert.True(valid); - } - - [Fact] - public async Task Test2AtATime() - { - var range = 4; - var asyncKeyedLocker = new AsyncKeyedLocker(o => { o.MaxCount = 2; o.PoolSize = 0; }); - var concurrentQueue = new ConcurrentQueue(); - - var tasks = Enumerable.Range(1, range * 4) - .Select(async i => - { - var key = Convert.ToInt32(Math.Ceiling((double)i / 4)); - using (await asyncKeyedLocker.LockAsync(key)) - { - concurrentQueue.Enqueue(key); - await Task.Delay((100 * key) + 1000); - } - }).ToList(); - await Task.WhenAll(tasks); - - bool valid = true; - var list = concurrentQueue.ToList(); - - for (int i = 0; i < range * 2; i++) - { - if (list[i] != list[i + (range * 2)]) - { - valid = false; - break; - } - } - - Assert.True(valid); - } - - [Fact] - public async Task Test1AtATimeGenerics() - { - var range = 25000; - var asyncKeyedLocker = new AsyncKeyedLocker(o => { o.PoolSize = 0; }); - var concurrentQueue = new ConcurrentQueue(); - - int threadNum = 0; - - var tasks = Enumerable.Range(1, range * 2) - .Select(async i => - { - var key = Convert.ToInt32(Math.Ceiling((double)Interlocked.Increment(ref threadNum) / 2)); - using (await asyncKeyedLocker.LockAsync(key)) - { - concurrentQueue.Enqueue(key); - } - }); - await Task.WhenAll(tasks.AsParallel()); - - bool valid = true; - var list = concurrentQueue.ToList(); - - for (int i = 0; i < range; i++) - { - if (list[i] == list[i + range]) - { - valid = false; - break; - } - } - - Assert.True(valid); - } - - [Fact] - public async Task Test2AtATimeGenerics() - { - var range = 4; - var asyncKeyedLocker = new AsyncKeyedLocker(o => { o.MaxCount = 2; o.PoolSize = 0; }); - var concurrentQueue = new ConcurrentQueue(); - - var tasks = Enumerable.Range(1, range * 4) - .Select(async i => - { - var key = Convert.ToInt32(Math.Ceiling((double)i / 4)); - using (await asyncKeyedLocker.LockAsync(key)) - { - concurrentQueue.Enqueue(key); - await Task.Delay((100 * key) + 1000); - } - }).ToList(); - await Task.WhenAll(tasks); - - bool valid = true; - var list = concurrentQueue.ToList(); - - for (int i = 0; i < range * 2; i++) - { - if (list[i] != list[i + (range * 2)]) - { - valid = false; - break; - } - } - - Assert.True(valid); - } - [Fact] public Task TestContinueOnCapturedContextTrue() => TestContinueOnCapturedContext(true); diff --git a/AsyncKeyedLock.Tests/AsyncKeyedLocker/StressTests.cs b/AsyncKeyedLock.Tests/AsyncKeyedLocker/StressTests.cs new file mode 100644 index 0000000..c28e2cc --- /dev/null +++ b/AsyncKeyedLock.Tests/AsyncKeyedLocker/StressTests.cs @@ -0,0 +1,566 @@ +using ListShuffle; +using System.Collections.Concurrent; +using Xunit; + +namespace AsyncKeyedLock.Tests.AsyncKeyedLocker +{ + [Collection("Stress Tests")] + [CollectionDefinition("Stress Tests", DisableParallelization = true)] + public class StressTests + { + [Fact] + public async Task StressTest() + { + var locks = 5000; + var concurrency = 50; + var asyncKeyedLocker = new AsyncKeyedLocker(o => { o.PoolSize = 0; }); + var concurrentQueue = new ConcurrentQueue<(bool entered, int key)>(); + + var tasks = Enumerable.Range(1, locks * concurrency) + .Select(async i => + { + var key = Convert.ToInt32(Math.Ceiling((double)i / concurrency)); + using (await asyncKeyedLocker.LockAsync(key)) + { + await Task.Delay(20); + concurrentQueue.Enqueue((true, key)); + await Task.Delay(80); + concurrentQueue.Enqueue((false, key)); + } + }); + await Task.WhenAll(tasks.AsParallel()); + + bool valid = concurrentQueue.Count == locks * concurrency * 2; + + var entered = new HashSet(); + + while (valid && !concurrentQueue.IsEmpty) + { + concurrentQueue.TryDequeue(out var result); + if (result.entered) + { + if (entered.Contains(result.key)) + { + valid = false; + break; + } + entered.Add(result.key); + } + else + { + if (!entered.Contains(result.key)) + { + valid = false; + break; + } + entered.Remove(result.key); + } + } + + Assert.True(valid); + } + + [Fact] + public async Task StressTestGenerics() + { + var locks = 5000; + var concurrency = 50; + var asyncKeyedLocker = new AsyncKeyedLocker(o => { o.PoolSize = 0; }); + var concurrentQueue = new ConcurrentQueue<(bool entered, int key)>(); + + var tasks = Enumerable.Range(1, locks * concurrency) + .Select(async i => + { + var key = Convert.ToInt32(Math.Ceiling((double)i / concurrency)); + using (await asyncKeyedLocker.LockAsync(key)) + { + await Task.Delay(20); + concurrentQueue.Enqueue((true, key)); + await Task.Delay(80); + concurrentQueue.Enqueue((false, key)); + } + }); + await Task.WhenAll(tasks.AsParallel()); + + bool valid = concurrentQueue.Count == locks * concurrency * 2; + + var entered = new HashSet(); + + while (valid && !concurrentQueue.IsEmpty) + { + concurrentQueue.TryDequeue(out var result); + if (result.entered) + { + if (entered.Contains(result.key)) + { + valid = false; + break; + } + entered.Add(result.key); + } + else + { + if (!entered.Contains(result.key)) + { + valid = false; + break; + } + entered.Remove(result.key); + } + } + + Assert.True(valid); + } + + [Fact] + public async Task BenchmarkSimulationTest() + { + AsyncKeyedLocker AsyncKeyedLocker; + ParallelQuery? AsyncKeyedLockerTasks = null; + Dictionary> _shuffledIntegers = new(); + + var NumberOfLocks = 200; + var Contention = 100; + var GuidReversals = 0; + + if (!_shuffledIntegers.TryGetValue(Contention * NumberOfLocks, out var ShuffledIntegers)) + { + ShuffledIntegers = Enumerable.Range(0, Contention * NumberOfLocks).ToList(); + ShuffledIntegers.Shuffle(); + _shuffledIntegers[Contention * NumberOfLocks] = ShuffledIntegers; + } + + if (NumberOfLocks != Contention) + { + AsyncKeyedLocker = new AsyncKeyedLocker(o => o.PoolSize = NumberOfLocks, Environment.ProcessorCount, NumberOfLocks); + AsyncKeyedLockerTasks = ShuffledIntegers + .Select(async i => + { + var key = i % NumberOfLocks; + + using (var myLock = await AsyncKeyedLocker.LockAsync(key.ToString())) + { + for (int j = 0; j < GuidReversals; j++) + { + Guid guid = Guid.NewGuid(); + var guidString = guid.ToString(); + guidString = guidString.Reverse().ToString(); +#pragma warning disable CS8602 // Dereference of a possibly null reference. + if (guidString.Length != 53) + { + throw new Exception($"Not 53 but {guidString?.Length}"); + } +#pragma warning restore CS8602 // Dereference of a possibly null reference. + } + } + + await Task.Yield(); + }).AsParallel(); + + await Task.WhenAll(AsyncKeyedLockerTasks); + } + } + + [Fact] + public async Task StressTestGenericsPooling50k() + { + var locks = 50_000; + var concurrency = 50; + var asyncKeyedLocker = new AsyncKeyedLocker(o => o.PoolSize = 50_000, Environment.ProcessorCount, 50_000); + var concurrentQueue = new ConcurrentQueue<(bool entered, int key)>(); + + var tasks = Enumerable.Range(1, locks * concurrency) + .Select(async i => + { + var key = Convert.ToInt32(Math.Ceiling((double)i / concurrency)); + using (await asyncKeyedLocker.LockAsync(key)) + { + await Task.Delay(20); + concurrentQueue.Enqueue((true, key)); + await Task.Delay(80); + concurrentQueue.Enqueue((false, key)); + } + }); + await Task.WhenAll(tasks.AsParallel()); + + bool valid = concurrentQueue.Count == locks * concurrency * 2; + + var entered = new HashSet(); + + while (valid && !concurrentQueue.IsEmpty) + { + concurrentQueue.TryDequeue(out var result); + if (result.entered) + { + if (entered.Contains(result.key)) + { + valid = false; + break; + } + entered.Add(result.key); + } + else + { + if (!entered.Contains(result.key)) + { + valid = false; + break; + } + entered.Remove(result.key); + } + } + + Assert.True(valid); + } + + [Fact] + public async Task StressTestGenericsPooling50kUnfilled() + { + var locks = 50_000; + var concurrency = 50; + var asyncKeyedLocker = new AsyncKeyedLocker(o => + { + o.PoolSize = 50_000; + o.PoolInitialFill = 0; + }, Environment.ProcessorCount, 50_000); + var concurrentQueue = new ConcurrentQueue<(bool entered, int key)>(); + + var tasks = Enumerable.Range(1, locks * concurrency) + .Select(async i => + { + var key = Convert.ToInt32(Math.Ceiling((double)i / concurrency)); + using (await asyncKeyedLocker.LockAsync(key)) + { + await Task.Delay(20); + concurrentQueue.Enqueue((true, key)); + await Task.Delay(80); + concurrentQueue.Enqueue((false, key)); + } + }); + await Task.WhenAll(tasks.AsParallel()); + + bool valid = concurrentQueue.Count == locks * concurrency * 2; + + var entered = new HashSet(); + + while (valid && !concurrentQueue.IsEmpty) + { + concurrentQueue.TryDequeue(out var result); + if (result.entered) + { + if (entered.Contains(result.key)) + { + valid = false; + break; + } + entered.Add(result.key); + } + else + { + if (!entered.Contains(result.key)) + { + valid = false; + break; + } + entered.Remove(result.key); + } + } + + Assert.True(valid); + } + + [Fact] + public async Task StressTestGenericsPoolingProcessorCount() + { + var locks = 50_000; + var concurrency = 50; + var asyncKeyedLocker = new AsyncKeyedLocker(o => o.PoolSize = Environment.ProcessorCount, Environment.ProcessorCount, 50_000); + var concurrentQueue = new ConcurrentQueue<(bool entered, int key)>(); + + var tasks = Enumerable.Range(1, locks * concurrency) + .Select(async i => + { + var key = Convert.ToInt32(Math.Ceiling((double)i / concurrency)); + using (await asyncKeyedLocker.LockAsync(key)) + { + await Task.Delay(20); + concurrentQueue.Enqueue((true, key)); + await Task.Delay(80); + concurrentQueue.Enqueue((false, key)); + } + }); + await Task.WhenAll(tasks.AsParallel()); + + bool valid = concurrentQueue.Count == locks * concurrency * 2; + + var entered = new HashSet(); + + while (valid && !concurrentQueue.IsEmpty) + { + concurrentQueue.TryDequeue(out var result); + if (result.entered) + { + if (entered.Contains(result.key)) + { + valid = false; + break; + } + entered.Add(result.key); + } + else + { + if (!entered.Contains(result.key)) + { + valid = false; + break; + } + entered.Remove(result.key); + } + } + + Assert.True(valid); + } + + [Fact] + public async Task StressTestGenericsPooling10k() + { + var locks = 50_000; + var concurrency = 50; + var asyncKeyedLocker = new AsyncKeyedLocker(o => o.PoolSize = 10_000, Environment.ProcessorCount, 50_000); + var concurrentQueue = new ConcurrentQueue<(bool entered, int key)>(); + + var tasks = Enumerable.Range(1, locks * concurrency) + .Select(async i => + { + var key = Convert.ToInt32(Math.Ceiling((double)i / concurrency)); + using (await asyncKeyedLocker.LockAsync(key)) + { + await Task.Delay(20); + concurrentQueue.Enqueue((true, key)); + await Task.Delay(80); + concurrentQueue.Enqueue((false, key)); + } + }); + await Task.WhenAll(tasks.AsParallel()); + + bool valid = concurrentQueue.Count == locks * concurrency * 2; + + var entered = new HashSet(); + + while (valid && !concurrentQueue.IsEmpty) + { + concurrentQueue.TryDequeue(out var result); + if (result.entered) + { + if (entered.Contains(result.key)) + { + valid = false; + break; + } + entered.Add(result.key); + } + else + { + if (!entered.Contains(result.key)) + { + valid = false; + break; + } + entered.Remove(result.key); + } + } + + Assert.True(valid); + } + + [Fact] + public async Task StressTestGenericsString() + { + var locks = 5000; + var concurrency = 50; + var asyncKeyedLocker = new AsyncKeyedLocker(o => { o.PoolSize = 0; }); + var concurrentQueue = new ConcurrentQueue<(bool entered, string key)>(); + + var tasks = Enumerable.Range(1, locks * concurrency) + .Select(async i => + { + var key = Convert.ToInt32(Math.Ceiling((double)i / 5)).ToString(); + using (await asyncKeyedLocker.LockAsync(key)) + { + await Task.Delay(20); + concurrentQueue.Enqueue((true, key)); + await Task.Delay(80); + concurrentQueue.Enqueue((false, key)); + } + }); + await Task.WhenAll(tasks.AsParallel()); + + bool valid = concurrentQueue.Count == locks * concurrency * 2; + + var entered = new HashSet(); + + while (valid && !concurrentQueue.IsEmpty) + { + concurrentQueue.TryDequeue(out var result); + if (result.entered) + { + if (entered.Contains(result.key)) + { + valid = false; + break; + } + entered.Add(result.key); + } + else + { + if (!entered.Contains(result.key)) + { + valid = false; + break; + } + entered.Remove(result.key); + } + } + + Assert.True(valid); + } + + [Fact] + public async Task Test1AtATime() + { + var range = 25000; + var asyncKeyedLocker = new AsyncKeyedLocker(o => { o.PoolSize = 0; }); + var concurrentQueue = new ConcurrentQueue(); + + int threadNum = 0; + + var tasks = Enumerable.Range(1, range * 2) + .Select(async i => + { + var key = Convert.ToInt32(Math.Ceiling((double)Interlocked.Increment(ref threadNum) / 2)); + using (await asyncKeyedLocker.LockAsync(key)) + { + concurrentQueue.Enqueue(key); + } + }); + await Task.WhenAll(tasks.AsParallel()); + + bool valid = true; + var list = concurrentQueue.ToList(); + + for (int i = 0; i < range; i++) + { + if (list[i] == list[i + range]) + { + valid = false; + break; + } + } + + Assert.True(valid); + } + + [Fact] + public async Task Test2AtATime() + { + var range = 4; + var asyncKeyedLocker = new AsyncKeyedLocker(o => { o.MaxCount = 2; o.PoolSize = 0; }); + var concurrentQueue = new ConcurrentQueue(); + + var tasks = Enumerable.Range(1, range * 4) + .Select(async i => + { + var key = Convert.ToInt32(Math.Ceiling((double)i / 4)); + using (await asyncKeyedLocker.LockAsync(key)) + { + concurrentQueue.Enqueue(key); + await Task.Delay((100 * key) + 1000); + } + }).ToList(); + await Task.WhenAll(tasks); + + bool valid = true; + var list = concurrentQueue.ToList(); + + for (int i = 0; i < range * 2; i++) + { + if (list[i] != list[i + (range * 2)]) + { + valid = false; + break; + } + } + + Assert.True(valid); + } + + [Fact] + public async Task Test1AtATimeGenerics() + { + var range = 25000; + var asyncKeyedLocker = new AsyncKeyedLocker(o => { o.PoolSize = 0; }); + var concurrentQueue = new ConcurrentQueue(); + + int threadNum = 0; + + var tasks = Enumerable.Range(1, range * 2) + .Select(async i => + { + var key = Convert.ToInt32(Math.Ceiling((double)Interlocked.Increment(ref threadNum) / 2)); + using (await asyncKeyedLocker.LockAsync(key)) + { + concurrentQueue.Enqueue(key); + } + }); + await Task.WhenAll(tasks.AsParallel()); + + bool valid = true; + var list = concurrentQueue.ToList(); + + for (int i = 0; i < range; i++) + { + if (list[i] == list[i + range]) + { + valid = false; + break; + } + } + + Assert.True(valid); + } + + [Fact] + public async Task Test2AtATimeGenerics() + { + var range = 4; + var asyncKeyedLocker = new AsyncKeyedLocker(o => { o.MaxCount = 2; o.PoolSize = 0; }); + var concurrentQueue = new ConcurrentQueue(); + + var tasks = Enumerable.Range(1, range * 4) + .Select(async i => + { + var key = Convert.ToInt32(Math.Ceiling((double)i / 4)); + using (await asyncKeyedLocker.LockAsync(key)) + { + concurrentQueue.Enqueue(key); + await Task.Delay((100 * key) + 1000); + } + }).ToList(); + await Task.WhenAll(tasks); + + bool valid = true; + var list = concurrentQueue.ToList(); + + for (int i = 0; i < range * 2; i++) + { + if (list[i] != list[i + (range * 2)]) + { + valid = false; + break; + } + } + + Assert.True(valid); + } + } +} \ No newline at end of file diff --git a/AsyncKeyedLock.Tests/AsyncNonKeyedLockerTests/OriginalTests.cs b/AsyncKeyedLock.Tests/AsyncNonKeyedLockerTests/OriginalTests.cs index 8b653a2..e3c1aea 100644 --- a/AsyncKeyedLock.Tests/AsyncNonKeyedLockerTests/OriginalTests.cs +++ b/AsyncKeyedLock.Tests/AsyncNonKeyedLockerTests/OriginalTests.cs @@ -3,6 +3,8 @@ namespace AsyncKeyedLock.Tests.AsyncNonKeyedLockerTests { + [Collection("NonKeyed Tests")] + [CollectionDefinition("NonKeyed Tests", DisableParallelization = false)] public class OriginalTests { [Fact] diff --git a/AsyncKeyedLock.Tests/StripedAsyncKeyedLocker/KeyedSemaphores/TestsForStripedAsyncKeyedLock.cs b/AsyncKeyedLock.Tests/StripedAsyncKeyedLocker/KeyedSemaphores/TestsForStripedAsyncKeyedLock.cs index 7b38b05..826f801 100644 --- a/AsyncKeyedLock.Tests/StripedAsyncKeyedLocker/KeyedSemaphores/TestsForStripedAsyncKeyedLock.cs +++ b/AsyncKeyedLock.Tests/StripedAsyncKeyedLocker/KeyedSemaphores/TestsForStripedAsyncKeyedLock.cs @@ -8,6 +8,8 @@ namespace AsyncKeyedLock.Tests.StripedAsyncKeyedLocker.KeyedSemaphores; /// /// Adapted from https://raw.githubusercontent.com/amoerie/keyed-semaphores/main/KeyedSemaphores.Tests/TestsForKeyedSemaphore.cs and https://raw.githubusercontent.com/amoerie/keyed-semaphores/main/KeyedSemaphores.Tests/TestsForKeyedSemaphoresCollection.cs /// +[Collection("KeyedSemaphore Tests")] +[CollectionDefinition("KeyedSemaphore Tests", DisableParallelization = false)] public class TestsForStripedAsyncKeyedLock { private readonly ITestOutputHelper _output; @@ -19,6 +21,8 @@ public TestsForStripedAsyncKeyedLock(ITestOutputHelper output) _output = output ?? throw new ArgumentNullException(nameof(output)); } + [Collection("KeyedSemaphore Tests")] + [CollectionDefinition("KeyedSemaphore Tests", DisableParallelization = false)] public class Async : TestsForStripedAsyncKeyedLock { public Async(ITestOutputHelper output) : base(output) { } @@ -93,6 +97,8 @@ async Task OccupyTheLockALittleBit(int key) } } + [Collection("KeyedSemaphore Tests")] + [CollectionDefinition("KeyedSemaphore Tests", DisableParallelization = false)] public class Sync : TestsForStripedAsyncKeyedLock { public Sync(ITestOutputHelper output) : base(output) { } diff --git a/AsyncKeyedLock.Tests/StripedAsyncKeyedLocker/OriginalTests.cs b/AsyncKeyedLock.Tests/StripedAsyncKeyedLocker/OriginalTests.cs index c153997..6dea88b 100644 --- a/AsyncKeyedLock.Tests/StripedAsyncKeyedLocker/OriginalTests.cs +++ b/AsyncKeyedLock.Tests/StripedAsyncKeyedLocker/OriginalTests.cs @@ -1,12 +1,12 @@ using AsyncKeyedLock.Tests.Helpers; using FluentAssertions; -using ListShuffle; using System.Collections; -using System.Collections.Concurrent; using Xunit; namespace AsyncKeyedLock.Tests.StripedAsyncKeyedLocker { + [Collection("Original Tests")] + [CollectionDefinition("Original Tests", DisableParallelization = false)] public class OriginalTests { [Fact] @@ -542,349 +542,6 @@ public void TestTimeoutTryLock() Assert.False(stripedAsyncKeyedLocker.IsInUse("test")); } - [Fact] - public async Task BasicTest() - { - var locks = 5000; - var concurrency = 50; - var stripedAsyncKeyedLocker = new StripedAsyncKeyedLocker(); - var concurrentQueue = new ConcurrentQueue<(bool entered, int key)>(); - - var tasks = Enumerable.Range(1, locks * concurrency) - .Select(async i => - { - var key = Convert.ToInt32(Math.Ceiling((double)i / concurrency)); - using (await stripedAsyncKeyedLocker.LockAsync(key)) - { - await Task.Delay(20); - concurrentQueue.Enqueue((true, key)); - await Task.Delay(80); - concurrentQueue.Enqueue((false, key)); - } - }); - await Task.WhenAll(tasks.AsParallel()); - - bool valid = concurrentQueue.Count == locks * concurrency * 2; - - var entered = new HashSet(); - - while (valid && !concurrentQueue.IsEmpty) - { - concurrentQueue.TryDequeue(out var result); - if (result.entered) - { - if (entered.Contains(result.key)) - { - valid = false; - break; - } - entered.Add(result.key); - } - else - { - if (!entered.Contains(result.key)) - { - valid = false; - break; - } - entered.Remove(result.key); - } - } - - Assert.True(valid); - } - - [Fact] - public async Task BasicTestGenerics() - { - var locks = 5000; - var concurrency = 50; - var stripedAsyncKeyedLocker = new StripedAsyncKeyedLocker(); - var concurrentQueue = new ConcurrentQueue<(bool entered, int key)>(); - - var tasks = Enumerable.Range(1, locks * concurrency) - .Select(async i => - { - var key = Convert.ToInt32(Math.Ceiling((double)i / concurrency)); - using (await stripedAsyncKeyedLocker.LockAsync(key)) - { - await Task.Delay(20); - concurrentQueue.Enqueue((true, key)); - await Task.Delay(80); - concurrentQueue.Enqueue((false, key)); - } - }); - await Task.WhenAll(tasks.AsParallel()); - - bool valid = concurrentQueue.Count == locks * concurrency * 2; - - var entered = new HashSet(); - - while (valid && !concurrentQueue.IsEmpty) - { - concurrentQueue.TryDequeue(out var result); - if (result.entered) - { - if (entered.Contains(result.key)) - { - valid = false; - break; - } - entered.Add(result.key); - } - else - { - if (!entered.Contains(result.key)) - { - valid = false; - break; - } - entered.Remove(result.key); - } - } - - Assert.True(valid); - } - - [Fact] - public async Task BenchmarkSimulationTest() - { - StripedAsyncKeyedLocker AsyncKeyedLocker; - ParallelQuery? AsyncKeyedLockerTasks = null; - Dictionary> _shuffledIntegers = new(); - - var NumberOfLocks = 200; - var Contention = 100; - var GuidReversals = 0; - - if (!_shuffledIntegers.TryGetValue(Contention * NumberOfLocks, out var ShuffledIntegers)) - { - ShuffledIntegers = Enumerable.Range(0, Contention * NumberOfLocks).ToList(); - ShuffledIntegers.Shuffle(); - _shuffledIntegers[Contention * NumberOfLocks] = ShuffledIntegers; - } - - if (NumberOfLocks != Contention) - { - AsyncKeyedLocker = new StripedAsyncKeyedLocker(NumberOfLocks); - AsyncKeyedLockerTasks = ShuffledIntegers - .Select(async i => - { - var key = i % NumberOfLocks; - - using (var myLock = await AsyncKeyedLocker.LockAsync(key.ToString())) - { - for (int j = 0; j < GuidReversals; j++) - { - Guid guid = Guid.NewGuid(); - var guidString = guid.ToString(); - guidString = guidString.Reverse().ToString(); -#pragma warning disable CS8602 // Dereference of a possibly null reference. - if (guidString.Length != 53) - { - throw new Exception($"Not 53 but {guidString?.Length}"); - } -#pragma warning restore CS8602 // Dereference of a possibly null reference. - } - } - - await Task.Yield(); - }).AsParallel(); - - await Task.WhenAll(AsyncKeyedLockerTasks); - } - } - - [Fact] - public async Task BasicTestGenericsString() - { - var locks = 5000; - var concurrency = 50; - var stripedAsyncKeyedLocker = new StripedAsyncKeyedLocker(); - var concurrentQueue = new ConcurrentQueue<(bool entered, string key)>(); - - var tasks = Enumerable.Range(1, locks * concurrency) - .Select(async i => - { - var key = Convert.ToInt32(Math.Ceiling((double)i / 5)).ToString(); - using (await stripedAsyncKeyedLocker.LockAsync(key)) - { - await Task.Delay(20); - concurrentQueue.Enqueue((true, key)); - await Task.Delay(80); - concurrentQueue.Enqueue((false, key)); - } - }); - await Task.WhenAll(tasks.AsParallel()); - - bool valid = concurrentQueue.Count == locks * concurrency * 2; - - var entered = new HashSet(); - - while (valid && !concurrentQueue.IsEmpty) - { - concurrentQueue.TryDequeue(out var result); - if (result.entered) - { - if (entered.Contains(result.key)) - { - valid = false; - break; - } - entered.Add(result.key); - } - else - { - if (!entered.Contains(result.key)) - { - valid = false; - break; - } - entered.Remove(result.key); - } - } - - Assert.True(valid); - } - - [Fact] - public async Task Test1AtATime() - { - var range = 25000; - var stripedAsyncKeyedLocker = new StripedAsyncKeyedLocker(); - var concurrentQueue = new ConcurrentQueue(); - - int threadNum = 0; - - var tasks = Enumerable.Range(1, range * 2) - .Select(async i => - { - var key = Convert.ToInt32(Math.Ceiling((double)Interlocked.Increment(ref threadNum) / 2)); - using (await stripedAsyncKeyedLocker.LockAsync(key)) - { - concurrentQueue.Enqueue(key); - } - }); - await Task.WhenAll(tasks.AsParallel()); - - bool valid = true; - var list = concurrentQueue.ToList(); - - for (int i = 0; i < range; i++) - { - if (list[i] == list[i + range]) - { - valid = false; - break; - } - } - - Assert.True(valid); - } - - [Fact] - public async Task Test2AtATime() - { - var range = 4; - var stripedAsyncKeyedLocker = new StripedAsyncKeyedLocker(maxCount: 2); - var concurrentQueue = new ConcurrentQueue(); - - var tasks = Enumerable.Range(1, range * 4) - .Select(async i => - { - var key = Convert.ToInt32(Math.Ceiling((double)i / 4)); - using (await stripedAsyncKeyedLocker.LockAsync(key)) - { - concurrentQueue.Enqueue(key); - await Task.Delay((100 * key) + 1000); - } - }).ToList(); - await Task.WhenAll(tasks); - - bool valid = true; - var list = concurrentQueue.ToList(); - - for (int i = 0; i < range * 2; i++) - { - if (list[i] != list[i + (range * 2)]) - { - valid = false; - break; - } - } - - Assert.True(valid); - } - - [Fact] - public async Task Test1AtATimeGenerics() - { - var range = 25000; - var stripedAsyncKeyedLocker = new StripedAsyncKeyedLocker(); - var concurrentQueue = new ConcurrentQueue(); - - int threadNum = 0; - - var tasks = Enumerable.Range(1, range * 2) - .Select(async i => - { - var key = Convert.ToInt32(Math.Ceiling((double)Interlocked.Increment(ref threadNum) / 2)); - using (await stripedAsyncKeyedLocker.LockAsync(key)) - { - concurrentQueue.Enqueue(key); - } - }); - await Task.WhenAll(tasks.AsParallel()); - - bool valid = true; - var list = concurrentQueue.ToList(); - - for (int i = 0; i < range; i++) - { - if (list[i] == list[i + range]) - { - valid = false; - break; - } - } - - Assert.True(valid); - } - - [Fact] - public async Task Test2AtATimeGenerics() - { - var range = 4; - var stripedAsyncKeyedLocker = new StripedAsyncKeyedLocker(maxCount: 2); - var concurrentQueue = new ConcurrentQueue(); - - var tasks = Enumerable.Range(1, range * 4) - .Select(async i => - { - var key = Convert.ToInt32(Math.Ceiling((double)i / 4)); - using (await stripedAsyncKeyedLocker.LockAsync(key)) - { - concurrentQueue.Enqueue(key); - await Task.Delay((100 * key) + 1000); - } - }).ToList(); - await Task.WhenAll(tasks); - - bool valid = true; - var list = concurrentQueue.ToList(); - - for (int i = 0; i < range * 2; i++) - { - if (list[i] != list[i + (range * 2)]) - { - valid = false; - break; - } - } - - Assert.True(valid); - } - [Fact] public Task TestContinueOnCapturedContextTrue() => TestContinueOnCapturedContext(true); diff --git a/AsyncKeyedLock.Tests/StripedAsyncKeyedLocker/StressTests.cs b/AsyncKeyedLock.Tests/StripedAsyncKeyedLocker/StressTests.cs new file mode 100644 index 0000000..6e1b41b --- /dev/null +++ b/AsyncKeyedLock.Tests/StripedAsyncKeyedLocker/StressTests.cs @@ -0,0 +1,354 @@ +using ListShuffle; +using System.Collections.Concurrent; +using Xunit; + +namespace AsyncKeyedLock.Tests.StripedAsyncKeyedLocker +{ + [Collection("Stress Tests")] + [CollectionDefinition("Stress Tests", DisableParallelization = true)] + public class StressTests + { + [Fact] + public async Task StressTest() + { + var locks = 5000; + var concurrency = 50; + var stripedAsyncKeyedLocker = new StripedAsyncKeyedLocker(); + var concurrentQueue = new ConcurrentQueue<(bool entered, int key)>(); + + var tasks = Enumerable.Range(1, locks * concurrency) + .Select(async i => + { + var key = Convert.ToInt32(Math.Ceiling((double)i / concurrency)); + using (await stripedAsyncKeyedLocker.LockAsync(key)) + { + await Task.Delay(20); + concurrentQueue.Enqueue((true, key)); + await Task.Delay(80); + concurrentQueue.Enqueue((false, key)); + } + }); + await Task.WhenAll(tasks.AsParallel()); + + bool valid = concurrentQueue.Count == locks * concurrency * 2; + + var entered = new HashSet(); + + while (valid && !concurrentQueue.IsEmpty) + { + concurrentQueue.TryDequeue(out var result); + if (result.entered) + { + if (entered.Contains(result.key)) + { + valid = false; + break; + } + entered.Add(result.key); + } + else + { + if (!entered.Contains(result.key)) + { + valid = false; + break; + } + entered.Remove(result.key); + } + } + + Assert.True(valid); + } + + [Fact] + public async Task StressTestGenerics() + { + var locks = 5000; + var concurrency = 50; + var stripedAsyncKeyedLocker = new StripedAsyncKeyedLocker(); + var concurrentQueue = new ConcurrentQueue<(bool entered, int key)>(); + + var tasks = Enumerable.Range(1, locks * concurrency) + .Select(async i => + { + var key = Convert.ToInt32(Math.Ceiling((double)i / concurrency)); + using (await stripedAsyncKeyedLocker.LockAsync(key)) + { + await Task.Delay(20); + concurrentQueue.Enqueue((true, key)); + await Task.Delay(80); + concurrentQueue.Enqueue((false, key)); + } + }); + await Task.WhenAll(tasks.AsParallel()); + + bool valid = concurrentQueue.Count == locks * concurrency * 2; + + var entered = new HashSet(); + + while (valid && !concurrentQueue.IsEmpty) + { + concurrentQueue.TryDequeue(out var result); + if (result.entered) + { + if (entered.Contains(result.key)) + { + valid = false; + break; + } + entered.Add(result.key); + } + else + { + if (!entered.Contains(result.key)) + { + valid = false; + break; + } + entered.Remove(result.key); + } + } + + Assert.True(valid); + } + + [Fact] + public async Task BenchmarkSimulationTest() + { + StripedAsyncKeyedLocker AsyncKeyedLocker; + ParallelQuery? AsyncKeyedLockerTasks = null; + Dictionary> _shuffledIntegers = new(); + + var NumberOfLocks = 200; + var Contention = 100; + var GuidReversals = 0; + + if (!_shuffledIntegers.TryGetValue(Contention * NumberOfLocks, out var ShuffledIntegers)) + { + ShuffledIntegers = Enumerable.Range(0, Contention * NumberOfLocks).ToList(); + ShuffledIntegers.Shuffle(); + _shuffledIntegers[Contention * NumberOfLocks] = ShuffledIntegers; + } + + if (NumberOfLocks != Contention) + { + AsyncKeyedLocker = new StripedAsyncKeyedLocker(NumberOfLocks); + AsyncKeyedLockerTasks = ShuffledIntegers + .Select(async i => + { + var key = i % NumberOfLocks; + + using (var myLock = await AsyncKeyedLocker.LockAsync(key.ToString())) + { + for (int j = 0; j < GuidReversals; j++) + { + Guid guid = Guid.NewGuid(); + var guidString = guid.ToString(); + guidString = guidString.Reverse().ToString(); +#pragma warning disable CS8602 // Dereference of a possibly null reference. + if (guidString.Length != 53) + { + throw new Exception($"Not 53 but {guidString?.Length}"); + } +#pragma warning restore CS8602 // Dereference of a possibly null reference. + } + } + + await Task.Yield(); + }).AsParallel(); + + await Task.WhenAll(AsyncKeyedLockerTasks); + } + } + + [Fact] + public async Task StressTestGenericsString() + { + var locks = 5000; + var concurrency = 50; + var stripedAsyncKeyedLocker = new StripedAsyncKeyedLocker(); + var concurrentQueue = new ConcurrentQueue<(bool entered, string key)>(); + + var tasks = Enumerable.Range(1, locks * concurrency) + .Select(async i => + { + var key = Convert.ToInt32(Math.Ceiling((double)i / 5)).ToString(); + using (await stripedAsyncKeyedLocker.LockAsync(key)) + { + await Task.Delay(20); + concurrentQueue.Enqueue((true, key)); + await Task.Delay(80); + concurrentQueue.Enqueue((false, key)); + } + }); + await Task.WhenAll(tasks.AsParallel()); + + bool valid = concurrentQueue.Count == locks * concurrency * 2; + + var entered = new HashSet(); + + while (valid && !concurrentQueue.IsEmpty) + { + concurrentQueue.TryDequeue(out var result); + if (result.entered) + { + if (entered.Contains(result.key)) + { + valid = false; + break; + } + entered.Add(result.key); + } + else + { + if (!entered.Contains(result.key)) + { + valid = false; + break; + } + entered.Remove(result.key); + } + } + + Assert.True(valid); + } + + [Fact] + public async Task Test1AtATime() + { + var range = 25000; + var stripedAsyncKeyedLocker = new StripedAsyncKeyedLocker(); + var concurrentQueue = new ConcurrentQueue(); + + int threadNum = 0; + + var tasks = Enumerable.Range(1, range * 2) + .Select(async i => + { + var key = Convert.ToInt32(Math.Ceiling((double)Interlocked.Increment(ref threadNum) / 2)); + using (await stripedAsyncKeyedLocker.LockAsync(key)) + { + concurrentQueue.Enqueue(key); + } + }); + await Task.WhenAll(tasks.AsParallel()); + + bool valid = true; + var list = concurrentQueue.ToList(); + + for (int i = 0; i < range; i++) + { + if (list[i] == list[i + range]) + { + valid = false; + break; + } + } + + Assert.True(valid); + } + + [Fact] + public async Task Test2AtATime() + { + var range = 4; + var stripedAsyncKeyedLocker = new StripedAsyncKeyedLocker(maxCount: 2); + var concurrentQueue = new ConcurrentQueue(); + + var tasks = Enumerable.Range(1, range * 4) + .Select(async i => + { + var key = Convert.ToInt32(Math.Ceiling((double)i / 4)); + using (await stripedAsyncKeyedLocker.LockAsync(key)) + { + concurrentQueue.Enqueue(key); + await Task.Delay((100 * key) + 1000); + } + }).ToList(); + await Task.WhenAll(tasks); + + bool valid = true; + var list = concurrentQueue.ToList(); + + for (int i = 0; i < range * 2; i++) + { + if (list[i] != list[i + (range * 2)]) + { + valid = false; + break; + } + } + + Assert.True(valid); + } + + [Fact] + public async Task Test1AtATimeGenerics() + { + var range = 25000; + var stripedAsyncKeyedLocker = new StripedAsyncKeyedLocker(); + var concurrentQueue = new ConcurrentQueue(); + + int threadNum = 0; + + var tasks = Enumerable.Range(1, range * 2) + .Select(async i => + { + var key = Convert.ToInt32(Math.Ceiling((double)Interlocked.Increment(ref threadNum) / 2)); + using (await stripedAsyncKeyedLocker.LockAsync(key)) + { + concurrentQueue.Enqueue(key); + } + }); + await Task.WhenAll(tasks.AsParallel()); + + bool valid = true; + var list = concurrentQueue.ToList(); + + for (int i = 0; i < range; i++) + { + if (list[i] == list[i + range]) + { + valid = false; + break; + } + } + + Assert.True(valid); + } + + [Fact] + public async Task Test2AtATimeGenerics() + { + var range = 4; + var stripedAsyncKeyedLocker = new StripedAsyncKeyedLocker(maxCount: 2); + var concurrentQueue = new ConcurrentQueue(); + + var tasks = Enumerable.Range(1, range * 4) + .Select(async i => + { + var key = Convert.ToInt32(Math.Ceiling((double)i / 4)); + using (await stripedAsyncKeyedLocker.LockAsync(key)) + { + concurrentQueue.Enqueue(key); + await Task.Delay((100 * key) + 1000); + } + }).ToList(); + await Task.WhenAll(tasks); + + bool valid = true; + var list = concurrentQueue.ToList(); + + for (int i = 0; i < range * 2; i++) + { + if (list[i] != list[i + (range * 2)]) + { + valid = false; + break; + } + } + + Assert.True(valid); + } + } +} \ No newline at end of file diff --git a/AsyncKeyedLock.Tests/xunit.runner.json b/AsyncKeyedLock.Tests/xunit.runner.json index e8f3498..5d3bf60 100644 --- a/AsyncKeyedLock.Tests/xunit.runner.json +++ b/AsyncKeyedLock.Tests/xunit.runner.json @@ -1,9 +1,9 @@ { "$schema": "https://xunit.net/schema/current/xunit.runner.schema.json", "parallelAlgorithm": "aggressive", - "parallelizeAssembly": false, + "parallelizeAssembly": true, "parallelizeTestCollections": true, "stopOnFail": true, "showLiveOutput": true, - "maxParallelThreads": "0.25x" + "maxParallelThreads": "0.5x" }