public void Return(IKeyedSemaphore keyedSemaphore)
        {
            if (keyedSemaphore == null)
            {
                throw new ArgumentNullException(nameof(keyedSemaphore));
            }

            // Do not throw ObjectDisposedException here, because this method is only called from InternalKeyedSemaphore.Dispose
            if (_isDisposed)
            {
                return;
            }

            lock (keyedSemaphore)
            {
                var remainingConsumers = keyedSemaphore.DecreaseConsumers();

                if (remainingConsumers == 0)
                {
                    if (!_index.TryRemove(keyedSemaphore.Key, out _))
                    {
                        throw new KeyedSemaphoresException($"Failed to remove a keyed semaphore because it has already been deleted by someone else! Key: {keyedSemaphore.Key}");
                    }

                    keyedSemaphore.InternalDispose();
                }
            }
        }
Beispiel #2
0
        public async Task DisposingTheKeyedSemaphoresCollectionShouldInterruptAllThreads()
        {
            // Arrange
            var runningTasksIndex  = new ConcurrentDictionary <int, int>();
            var parallelismLock    = new object();
            var currentParallelism = 0;
            var maxParallelism     = 0;
            var random             = new Random();
            var index = new ConcurrentDictionary <string, IKeyedSemaphore>();

            using var keyedSemaphores = new KeyedSemaphoresCollection(index);

            // 50 threads, 1 key
            var numberOfThreads = 50;

            Log($"Starting {numberOfThreads} threads");
            var threads = Enumerable.Range(0, numberOfThreads)
                          .Select(i => Task.Run(async() => await OccupyTheLockALittleBit(i, 1).ConfigureAwait(false)))
                          .ToList();

            // Act + Assert
            await Task.Delay(100);

            Log($"[WAITING] keyedSemaphores.Dispose");
            keyedSemaphores.Dispose();
            Log($"[OK]      keyedSemaphores.Dispose");

            await Task.WhenAll(threads).ConfigureAwait(false);

            maxParallelism.Should().Be(1);
            index.Should().BeEmpty();

            async Task OccupyTheLockALittleBit(int thread, int key)
            {
                var currentTaskId = Task.CurrentId ?? -1;
                var delay         = random.Next(0, 200);

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

                IKeyedSemaphore keyedSemaphore = null;

                try
                {
                    try
                    {
                        Log($"[{thread, 2}] [WAITING] KeyedSemaphores.Provide    : {key,3}");
                        keyedSemaphore = keyedSemaphores.Provide(key.ToString());
                        Log($"[{thread, 2}] [OK]      KeyedSemaphores.Provide    : {key,3}");
                    }
                    catch (ObjectDisposedException)
                    {
                        Log($"[{thread, 2}] [DISPOSED]KeyedSemaphores.Provide    : {key,3}");
                        return;
                    }

                    try
                    {
                        Log($"[{thread, 2}] [WAITING] KeyedSemaphores.WaitAsync  : {key,3}");
                        await keyedSemaphore.WaitAsync();

                        Log($"[{thread, 2}] [OK]      KeyedSemaphores.WaitAsync  : {key,3}");
                    }
                    catch (OperationCanceledException)
                    {
                        Log($"[{thread, 2}] [CANCELED]KeyedSemaphores.WaitAsync  : {key,3}");
                        return;
                    }

                    try
                    {
                        var incrementedCurrentParallelism = Interlocked.Increment(ref currentParallelism);

                        lock (parallelismLock)
                        {
                            maxParallelism = Math.Max(incrementedCurrentParallelism, maxParallelism);
                        }

                        if (runningTasksIndex.TryGetValue(key, out var otherThread))
                        {
                            throw new Exception($"[{thread, 2}] Task [{currentTaskId,3}] has a lock for key ${key} " +
                                                $"but another task [{otherThread,3}] also has an active lock for this key!");
                        }

                        runningTasksIndex[key] = currentTaskId;

                        if (!runningTasksIndex.TryRemove(key, out var value))
                        {
                            var ex = new Exception($"[{thread, 2}] Task [{currentTaskId,3}] has finished " +
                                                   $"but when trying to cleanup the running tasks index, the value is already gone");

                            throw ex;
                        }

                        if (value != currentTaskId)
                        {
                            var ex = new Exception($"[{thread, 2}] Task [{currentTaskId,3}] has finished and has removed itself from the running tasks index," +
                                                   $" but that index contained a task ID of another task: [{value}]!");

                            throw ex;
                        }

                        Interlocked.Decrement(ref currentParallelism);
                    }
                    finally
                    {
                        try
                        {
                            Log($"[{thread, 2}] [WAITING] KeyedSemaphore.Release     : {key,3}");
                            keyedSemaphore.Release();
                            Log($"[{thread, 2}] [OK]      KeyedSemaphore.Release     : {key,3}");
                        }
                        catch (ObjectDisposedException e)
                        {
                            Log($"[{thread, 2}] [DISPOSED]KeyedSemaphore.Release     : {e}");
                        }
                    }
                }
                finally
                {
                    if (keyedSemaphore != null)
                    {
                        Log($"[{thread, 2}] [WAITING] KeyedSemaphore.Dispose     : {key,3}");
                        keyedSemaphore.Dispose();
                        Log($"[{thread, 2}] [OK]      KeyedSemaphore.Dispose     : {key,3}");
                    }
                }
            }
        }
Beispiel #3
0
        public async Task ShouldNeverCreateTwoSemaphoresForTheSameKey()
        {
            // Arrange
            var runningTasksIndex  = new ConcurrentDictionary <int, int>();
            var parallelismLock    = new object();
            var currentParallelism = 0;
            var maxParallelism     = 0;
            var random             = new Random();
            var index = new ConcurrentDictionary <string, IKeyedSemaphore>();

            using var keyedSemaphores = new KeyedSemaphoresCollection(index);

            // Many threads, 1 key
            var threads = Enumerable.Range(0, 100)
                          .Select(i => Task.Run(async() => await OccupyTheLockALittleBit(1).ConfigureAwait(false)))
                          .ToList();

            // Act + Assert
            await Task.WhenAll(threads).ConfigureAwait(false);

            maxParallelism.Should().Be(1);
            index.Should().BeEmpty();


            async Task OccupyTheLockALittleBit(int key)
            {
                var currentTaskId = Task.CurrentId ?? -1;
                var delay         = random.Next(500);


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


                IKeyedSemaphore keyedSemaphore = null;

                try
                {
                    keyedSemaphore = keyedSemaphores.Provide(key.ToString());


                    await keyedSemaphore.WaitAsync().ConfigureAwait(false);

                    try
                    {
                        var incrementedCurrentParallelism = Interlocked.Increment(ref currentParallelism);


                        lock (parallelismLock)
                        {
                            maxParallelism = Math.Max(incrementedCurrentParallelism, maxParallelism);
                        }

                        if (runningTasksIndex.TryGetValue(key, out var otherThread))
                        {
                            throw new Exception($"Task [{currentTaskId,3}] has a lock for key ${key} " +
                                                $"but another task [{otherThread,3}] also has an active lock for this key!");
                        }

                        runningTasksIndex[key] = currentTaskId;

                        if (!runningTasksIndex.TryRemove(key, out var value))
                        {
                            var ex = new Exception($"Task [{currentTaskId,3}] has finished " +
                                                   $"but when trying to cleanup the running tasks index, the value is already gone");

                            throw ex;
                        }

                        if (value != currentTaskId)
                        {
                            var ex = new Exception($"Task [{currentTaskId,3}] has finished and has removed itself from the running tasks index," +
                                                   $" but that index contained a task ID of another task: [{value}]!");

                            throw ex;
                        }

                        Interlocked.Decrement(ref currentParallelism);
                    }
                    finally
                    {
                        keyedSemaphore.Release();
                    }
                }
                finally
                {
                    keyedSemaphore?.Dispose();
                }
            }
        }
 internal LockedKeyedSemaphore(IKeyedSemaphore keyedSemaphore)
 {
     _keyedSemaphore = keyedSemaphore ?? throw new ArgumentNullException(nameof(keyedSemaphore));
 }