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; }
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); }
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; } } }
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)); }
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(); } }
private static async Task <IAsyncDisposable> TakeLockCoreAsync(DebuggableSemaphoreSlim objCurrentSemaphore, SafeSemaphoreRelease objRelease) { await objCurrentSemaphore.WaitAsync(); return(objRelease); }
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); }