Example #1
0
            public ValueTask DisposeAsync()
            {
                if (_objAsyncLock._intDisposedStatus > 1)
                {
                    throw new ObjectDisposedException(nameof(_objAsyncLock));
                }
                DebuggableSemaphoreSlim objNextSemaphoreSlim = _objAsyncLock._objCurrentSemaphore.Value;

                if (_objNextSemaphore != objNextSemaphoreSlim)
                {
                    if (objNextSemaphoreSlim == _objCurrentSemaphore)
                    {
                        throw new InvalidOperationException(
                                  "_objNextSemaphore was expected to be the current semaphore. Instead, the old semaphore was never unset.");
                    }
                    if (objNextSemaphoreSlim == null)
                    {
                        throw new InvalidOperationException(
                                  "_objNextSemaphore was expected to be the current semaphore. Instead, the current semaphore is null.\n\n"
                                  + "This may be because AsyncLocal's control flow is the inverse of what one expects, so acquiring "
                                  + "the lock inside a function and then leaving the function before exiting the lock can produce this situation.");
                    }
                    throw new InvalidOperationException("_objNextSemaphore was expected to be the current semaphore");
                }

                // Update _objAsyncLock._objCurrentSemaphore in the calling ExecutionContext
                // and defer any awaits to DisposeCoreAsync(). If this isn't done, the
                // update will happen in a copy of the ExecutionContext and the caller
                // won't see the changes.
                _objAsyncLock._objCurrentSemaphore.Value = _objCurrentSemaphore;

                return(DisposeCoreAsync());
            }
        private async Task <IAsyncDisposable> TakeWriteLockCoreAsync(DebuggableSemaphoreSlim objCurrentSemaphore, SafeWriterSemaphoreRelease objRelease, CancellationToken token)
        {
            try
            {
                await objCurrentSemaphore.WaitAsync(token);

                if (Interlocked.Increment(ref _intCountActiveReaders) == 1)
                {
                    try
                    {
                        // Wait for the reader lock only if there have been no other write locks before us
                        if (_objTopLevelWriterSemaphore.CurrentCount != 0 || objCurrentSemaphore == _objTopLevelWriterSemaphore)
                        {
                            await _objReaderSemaphore.WaitAsync(token);
                        }
                    }
                    catch
                    {
                        Interlocked.Decrement(ref _intCountActiveReaders);
                        throw;
                    }
                }
            }
            catch (OperationCanceledException)
            {
                //swallow this because it must be handled as a disposal in the original ExecutionContext
            }
            return(objRelease);
        }
 public SafeWriterSemaphoreRelease(DebuggableSemaphoreSlim objLastSemaphore, DebuggableSemaphoreSlim objCurrentSemaphore, DebuggableSemaphoreSlim objNextSemaphore, AsyncFriendlyReaderWriterLock objReaderWriterLock)
 {
     if (objCurrentSemaphore == null)
     {
         throw new ArgumentNullException(nameof(objCurrentSemaphore));
     }
     if (objNextSemaphore == null)
     {
         throw new ArgumentNullException(nameof(objNextSemaphore));
     }
     if (objLastSemaphore == objCurrentSemaphore)
     {
         throw new InvalidOperationException(
                   "Last and current semaphores are identical, this should not happen.");
     }
     if (objCurrentSemaphore == objNextSemaphore)
     {
         throw new InvalidOperationException(
                   "Current and next semaphores are identical, this should not happen.");
     }
     if (objLastSemaphore == objNextSemaphore)
     {
         throw new InvalidOperationException(
                   "Last and next semaphores are identical, this should not happen.");
     }
     _objLastSemaphore    = objLastSemaphore;
     _objCurrentSemaphore = objCurrentSemaphore;
     _objNextSemaphore    = objNextSemaphore;
     _objReaderWriterLock = objReaderWriterLock;
 }
Example #4
0
 private static async Task <IAsyncDisposable> TakeLockCoreAsync(DebuggableSemaphoreSlim objCurrentSemaphore, SafeSemaphoreRelease objRelease, CancellationToken token)
 {
     try
     {
         await objCurrentSemaphore.WaitAsync(token);
     }
     catch (OperationCanceledException)
     {
         //swallow this because it must be handled as a disposal in the original ExecutionContext
     }
     return(objRelease);
 }
Example #5
0
        public Task <IAsyncDisposable> TakeLockAsync()
        {
            if (_intDisposedStatus != 0)
            {
                return(Task.FromException <IAsyncDisposable>(new ObjectDisposedException(nameof(AsyncLock))));
            }
            DebuggableSemaphoreSlim objNextSemaphore    = Utils.SemaphorePool.Get();
            DebuggableSemaphoreSlim objCurrentSemaphore = _objCurrentSemaphore.Value ?? _objTopLevelSemaphore;

            _objCurrentSemaphore.Value = objNextSemaphore;
            SafeSemaphoreRelease objRelease = new SafeSemaphoreRelease(objCurrentSemaphore, objNextSemaphore, this);

            return(TakeLockCoreAsync(objCurrentSemaphore, objRelease));
        }
        /// <summary>
        /// Try to asynchronously obtain a lock for reading.
        /// </summary>
        public Task EnterReadLockAsync(CancellationToken token = default)
        {
            if (_intDisposedStatus != 0)
            {
                return(Task.FromException(new ObjectDisposedException(nameof(AsyncFriendlyReaderWriterLock))));
            }
            token.ThrowIfCancellationRequested();
            if (_objTopLevelWriterSemaphore.CurrentCount != 0)
            {
                return(TakeReadLockCoreLightAsync(token));
            }
            DebuggableSemaphoreSlim objCurrentSemaphore = _objCurrentWriterSemaphore.Value?.Item2 ?? _objTopLevelWriterSemaphore;

            return(TakeReadLockCoreAsync(objCurrentSemaphore, token));
        }
        /// <summary>
        /// Try to synchronously obtain a lock for reading.
        /// </summary>
        public void EnterReadLock(CancellationToken token = default)
        {
            if (_intDisposedStatus != 0)
            {
                throw new ObjectDisposedException(nameof(AsyncFriendlyReaderWriterLock));
            }

            token.ThrowIfCancellationRequested();
            // Only do the complicated steps if any write lock is currently being held, otherwise skip it and just process the read lock
            if (_objTopLevelWriterSemaphore.CurrentCount == 0)
            {
                // Temporarily acquiring a write lock just to mess with the read locks is a bottleneck, so don't do any such setting unless we need it
                DebuggableSemaphoreSlim objCurrentSemaphore = _objCurrentWriterSemaphore.Value?.Item2 ?? _objTopLevelWriterSemaphore;
                objCurrentSemaphore.SafeWait(token);
                try
                {
                    if (Interlocked.Increment(ref _intCountActiveReaders) == 1)
                    {
                        try
                        {
                            _objReaderSemaphore.SafeWait(token);
                        }
                        catch
                        {
                            Interlocked.Decrement(ref _intCountActiveReaders);
                            throw;
                        }
                    }
                }
                finally
                {
                    objCurrentSemaphore.Release();
                }
            }
            else if (Interlocked.Increment(ref _intCountActiveReaders) == 1)
            {
                try
                {
                    _objReaderSemaphore.SafeWait(token);
                }
                catch
                {
                    Interlocked.Decrement(ref _intCountActiveReaders);
                    throw;
                }
            }
        }
Example #8
0
            public void Dispose()
            {
                if (_objAsyncLock._intDisposedStatus > 1)
                {
                    throw new ObjectDisposedException(nameof(_objAsyncLock));
                }
                DebuggableSemaphoreSlim objNextSemaphoreSlim = _objAsyncLock._objCurrentSemaphore.Value;

                if (_objNextSemaphore != objNextSemaphoreSlim)
                {
                    if (objNextSemaphoreSlim == _objCurrentSemaphore)
                    {
                        throw new InvalidOperationException(
                                  "_objNextSemaphore was expected to be the current semaphore. Instead, the old semaphore was never unset.");
                    }
                    if (objNextSemaphoreSlim == null)
                    {
                        throw new InvalidOperationException(
                                  "_objNextSemaphore was expected to be the current semaphore. Instead, the current semaphore is null.\n\n"
                                  + "This may be because AsyncLocal's control flow is the inverse of what one expects, so acquiring "
                                  + "the lock inside a function and then leaving the function before exiting the lock can produce this situation.");
                    }
                    throw new InvalidOperationException("_objNextSemaphore was expected to be the current semaphore");
                }

                _objAsyncLock._objCurrentSemaphore.Value = _objCurrentSemaphore;
                try
                {
                    if (_objCurrentSemaphore.CurrentCount == 0)
                    {
                        _objNextSemaphore.SafeWait();
                        try
                        {
                            _objCurrentSemaphore.Release();
                        }
                        finally
                        {
                            _objNextSemaphore.Release();
                        }
                    }
                }
                finally
                {
                    Utils.SemaphorePool.Return(ref _objNextSemaphore);
                }
            }
        private async Task <IAsyncDisposable> TakeWriteLockCoreAsync(DebuggableSemaphoreSlim objCurrentSemaphore, SafeWriterSemaphoreRelease objRelease)
        {
            await objCurrentSemaphore.WaitAsync();

            if (Interlocked.Increment(ref _intCountActiveReaders) == 1)
            {
                try
                {
                    // Wait for the reader lock only if there have been no other write locks before us
                    if (_objTopLevelWriterSemaphore.CurrentCount != 0 || objCurrentSemaphore == _objTopLevelWriterSemaphore)
                    {
                        await _objReaderSemaphore.WaitAsync();
                    }
                }
                catch
                {
                    Interlocked.Decrement(ref _intCountActiveReaders);
                    throw;
                }
            }
            return(objRelease);
        }
        /// <summary>
        /// Try to asynchronously obtain a lock for writing.
        /// The returned SafeSemaphoreWriterRelease must be stored for when the write lock is to be released.
        /// </summary>
        public Task <IAsyncDisposable> EnterWriteLockAsync()
        {
            if (_intDisposedStatus != 0)
            {
                return(Task.FromException <IAsyncDisposable>(new ObjectDisposedException(nameof(AsyncFriendlyReaderWriterLock))));
            }
            (DebuggableSemaphoreSlim objLastSemaphore, DebuggableSemaphoreSlim objCurrentSemaphore)
                = _objCurrentWriterSemaphore.Value
                  ?? new Tuple <DebuggableSemaphoreSlim, DebuggableSemaphoreSlim>(null, _objTopLevelWriterSemaphore);
            DebuggableSemaphoreSlim objNextSemaphore = Utils.SemaphorePool.Get();

            // Extremely hacky solution to buggy semaphore (re)cycling in AsyncLocal
            // TODO: Fix this properly. The problem is that after an AsyncLocal shallow-copy in a different context, the semaphores can get returned in the copy without altering the original AsyncLocal
            while (objNextSemaphore == objCurrentSemaphore || objNextSemaphore == objLastSemaphore)
            {
                objNextSemaphore = Utils.SemaphorePool.Get();
            }
            _objCurrentWriterSemaphore.Value = new Tuple <DebuggableSemaphoreSlim, DebuggableSemaphoreSlim>(objCurrentSemaphore, objNextSemaphore);
            SafeWriterSemaphoreRelease objRelease = new SafeWriterSemaphoreRelease(objLastSemaphore, objCurrentSemaphore, objNextSemaphore, this);

            return(TakeWriteLockCoreAsync(objCurrentSemaphore, objRelease));
        }
Example #11
0
        public IDisposable TakeLock(CancellationToken token = default)
        {
            if (_intDisposedStatus != 0)
            {
                throw new ObjectDisposedException(nameof(AsyncLock));
            }
            token.ThrowIfCancellationRequested();
            DebuggableSemaphoreSlim objNextSemaphore    = Utils.SemaphorePool.Get();
            DebuggableSemaphoreSlim objCurrentSemaphore = _objCurrentSemaphore.Value ?? _objTopLevelSemaphore;

            _objCurrentSemaphore.Value = objNextSemaphore;
            try
            {
                objCurrentSemaphore.SafeWait(token);
            }
            catch
            {
                _objCurrentSemaphore.Value = objCurrentSemaphore;
                Utils.SemaphorePool.Return(ref objNextSemaphore);
                throw;
            }
            return(new SafeSemaphoreRelease(objCurrentSemaphore, objNextSemaphore, this));
        }
        /// <summary>
        /// Heavier read lock entrant, used if a write lock is already being held somewhere
        /// </summary>
        private async Task TakeReadLockCoreAsync(DebuggableSemaphoreSlim objCurrentSemaphore, CancellationToken token = default)
        {
            await objCurrentSemaphore.WaitAsync(token);

            try
            {
                if (Interlocked.Increment(ref _intCountActiveReaders) == 1)
                {
                    try
                    {
                        await _objReaderSemaphore.WaitAsync(token);
                    }
                    catch
                    {
                        Interlocked.Decrement(ref _intCountActiveReaders);
                        throw;
                    }
                }
            }
            finally
            {
                objCurrentSemaphore.Release();
            }
        }
Example #13
0
        private static async Task <IAsyncDisposable> TakeLockCoreAsync(DebuggableSemaphoreSlim objCurrentSemaphore, SafeSemaphoreRelease objRelease)
        {
            await objCurrentSemaphore.WaitAsync();

            return(objRelease);
        }
Example #14
0
 public SafeSemaphoreRelease(DebuggableSemaphoreSlim objCurrentSemaphore, DebuggableSemaphoreSlim objNextSemaphore, AsyncLock objAsyncLock)
 {
     _objCurrentSemaphore = objCurrentSemaphore;
     _objNextSemaphore    = objNextSemaphore;
     _objAsyncLock        = objAsyncLock;
 }
        /// <summary>
        /// Try to synchronously obtain a lock for writing.
        /// The returned SafeSemaphoreWriterRelease must be stored for when the write lock is to be released.
        /// </summary>
        public IDisposable EnterWriteLock(CancellationToken token = default)
        {
            if (_intDisposedStatus != 0)
            {
                throw new ObjectDisposedException(nameof(AsyncFriendlyReaderWriterLock));
            }

            token.ThrowIfCancellationRequested();
            (DebuggableSemaphoreSlim objLastSemaphore, DebuggableSemaphoreSlim objCurrentSemaphore)
                = _objCurrentWriterSemaphore.Value
                  ?? new Tuple <DebuggableSemaphoreSlim, DebuggableSemaphoreSlim>(null, _objTopLevelWriterSemaphore);
            DebuggableSemaphoreSlim objNextSemaphore = Utils.SemaphorePool.Get();

            // Extremely hacky solution to buggy semaphore (re)cycling in AsyncLocal
            // TODO: Fix this properly. The problem is that after an AsyncLocal shallow-copy in a different context, the semaphores can get returned in the copy without altering the original AsyncLocal
            while (objNextSemaphore == objCurrentSemaphore || objNextSemaphore == objLastSemaphore)
            {
                objNextSemaphore = Utils.SemaphorePool.Get();
            }
            _objCurrentWriterSemaphore.Value = new Tuple <DebuggableSemaphoreSlim, DebuggableSemaphoreSlim>(objCurrentSemaphore, objNextSemaphore);
            SafeWriterSemaphoreRelease objRelease = new SafeWriterSemaphoreRelease(objLastSemaphore, objCurrentSemaphore, objNextSemaphore, this);

            try
            {
                objCurrentSemaphore.SafeWait(token);
            }
            catch
            {
                _objCurrentWriterSemaphore.Value = new Tuple <DebuggableSemaphoreSlim, DebuggableSemaphoreSlim>(objLastSemaphore, objCurrentSemaphore);
                Utils.SemaphorePool.Return(ref objNextSemaphore);
                throw;
            }
            try
            {
                if (Interlocked.Increment(ref _intCountActiveReaders) == 1)
                {
                    try
                    {
                        // Wait for the reader lock only if there have been no other write locks before us
                        if (_objTopLevelWriterSemaphore.CurrentCount != 0 || objCurrentSemaphore == _objTopLevelWriterSemaphore)
                        {
                            _objReaderSemaphore.SafeWait(token);
                        }
                    }
                    catch
                    {
                        Interlocked.Decrement(ref _intCountActiveReaders);
                        throw;
                    }
                }
            }
            catch
            {
                _objCurrentWriterSemaphore.Value = new Tuple <DebuggableSemaphoreSlim, DebuggableSemaphoreSlim>(objLastSemaphore, objCurrentSemaphore);
                try
                {
                    // ReSharper disable once MethodSupportsCancellation
                    objNextSemaphore.SafeWait();
                    try
                    {
                        objCurrentSemaphore.Release();
                    }
                    finally
                    {
                        objNextSemaphore.Release();
                    }
                }
                finally
                {
                    Utils.SemaphorePool.Return(ref objNextSemaphore);
                }
                throw;
            }
            return(objRelease);
        }