< Summary

Class:Azure.Identity.AsyncLockWithValue`1
Assembly:Azure.Identity
File(s):C:\Git\azure-sdk-for-net\sdk\identity\Azure.Identity\src\AsyncLockWithValue.cs
Covered lines:58
Uncovered lines:2
Coverable lines:60
Total lines:180
Line coverage:96.6% (58 of 60)
Covered branches:30
Total branches:32
Branch coverage:93.7% (30 of 32)

Metrics

MethodCyclomatic complexity Line coverage Branch coverage
.ctor()-100%100%
GetLockOrValueAsync()-95%92.86%
SetValue(...)-100%100%
Reset()-75%75%
UnlockOrGetNextWaiter()-100%100%
get_HasValue()-100%100%
get_Value()-100%100%
.ctor(...)-100%100%
.ctor(...)-100%100%
SetValue(...)-100%100%
Dispose()-100%100%

File(s)

C:\Git\azure-sdk-for-net\sdk\identity\Azure.Identity\src\AsyncLockWithValue.cs

#LineLine coverage
 1// Copyright (c) Microsoft Corporation. All rights reserved.
 2// Licensed under the MIT License.
 3
 4using System;
 5using System.Collections.Generic;
 6using System.Threading;
 7using System.Threading.Tasks;
 8using Azure.Core.Pipeline;
 9
 10namespace Azure.Identity
 11{
 12    /// <summary>
 13    /// Primitive that combines async lock and value cache
 14    /// </summary>
 15    /// <typeparam name="T"></typeparam>
 16    internal sealed class AsyncLockWithValue<T>
 17    {
 120418        private readonly object _syncObj = new object();
 19        private Queue<TaskCompletionSource<Lock>> _waiters;
 20        private bool _isLocked;
 21        private bool _hasValue;
 22        private T _value;
 23
 24        /// <summary>
 25        /// Method that either returns cached value or acquire a lock.
 26        /// If one caller has acquired a lock, other callers will be waiting for the lock to be released.
 27        /// If value is set, lock is released and all waiters get that value.
 28        /// If value isn't set, the next waiter in the queue will get the lock.
 29        /// </summary>
 30        /// <param name="async"></param>
 31        /// <param name="cancellationToken"></param>
 32        /// <returns></returns>
 33        public async ValueTask<Lock> GetLockOrValueAsync(bool async, CancellationToken cancellationToken = default)
 34        {
 35            TaskCompletionSource<Lock> valueTcs;
 194836            lock (_syncObj)
 37            {
 38                // If there is a value, just return it
 194839                if (_hasValue)
 40                {
 44041                    return new Lock(_value);
 42                }
 43
 44                // If lock isn't acquire yet, acquire it and return to the caller
 150845                if (!_isLocked)
 46                {
 83047                    _isLocked = true;
 83048                    return new Lock(this);
 49                }
 50
 51                // Check cancellationToken before instantiating waiter
 67852                cancellationToken.ThrowIfCancellationRequested();
 53
 54                // If lock is already taken, create a waiter and wait either until value is set or lock can be acquired 
 49055                _waiters ??= new Queue<TaskCompletionSource<Lock>>();
 56                // if async == false, valueTcs will be waited only in this thread and only synchronously, so RunContinua
 49057                valueTcs = new TaskCompletionSource<Lock>(async ? TaskCreationOptions.RunContinuationsAsynchronously : T
 49058                _waiters.Enqueue(valueTcs);
 49059            }
 60
 61            try
 62            {
 49063                if (async)
 64                {
 34265                    return await valueTcs.Task.AwaitWithCancellation(cancellationToken);
 66                }
 67
 68#pragma warning disable AZC0104 // Use EnsureCompleted() directly on asynchronous method return value.
 69#pragma warning disable AZC0111 // DO NOT use EnsureCompleted in possibly asynchronous scope.
 14870                valueTcs.Task.Wait(cancellationToken);
 14071                return valueTcs.Task.EnsureCompleted();
 72#pragma warning restore AZC0111 // DO NOT use EnsureCompleted in possibly asynchronous scope.
 73#pragma warning restore AZC0104 // Use EnsureCompleted() directly on asynchronous method return value.
 74            }
 1875            catch (OperationCanceledException)
 76            {
 77                // Throw OperationCanceledException only if another thread hasn't set a value to this waiter
 78                // by calling either Reset or SetValue
 1879                if (valueTcs.TrySetCanceled())
 80                {
 1881                    throw;
 82                }
 83
 084                return valueTcs.Task.Result;
 85            }
 174286        }
 87
 88        /// <summary>
 89        /// Set value to the cache and to all the waiters
 90        /// </summary>
 91        /// <param name="value"></param>
 92        private void SetValue(T value)
 93        {
 94            Queue<TaskCompletionSource<Lock>> waiters;
 6895            lock (_syncObj)
 96            {
 6897                _value = value;
 6898                _hasValue = true;
 6899                _isLocked = false;
 68100                if (_waiters == default)
 101                {
 56102                    return;
 103                }
 104
 12105                waiters = _waiters;
 12106                _waiters = default;
 12107            }
 108
 82109            while (waiters.Count > 0)
 110            {
 70111                waiters.Dequeue().TrySetResult(new Lock(value));
 112            }
 68113        }
 114
 115        /// <summary>
 116        /// Release the lock and allow next waiter acquire it
 117        /// </summary>
 118        private void Reset()
 119        {
 1224120            TaskCompletionSource<Lock> nextWaiter = UnlockOrGetNextWaiter();
 1224121            while (nextWaiter != default && !nextWaiter.TrySetResult(new Lock(this)))
 122            {
 0123                nextWaiter = UnlockOrGetNextWaiter();
 124            }
 1224125        }
 126
 127        private TaskCompletionSource<Lock> UnlockOrGetNextWaiter()
 128        {
 1224129            lock (_syncObj)
 130            {
 1224131                if (!_isLocked)
 132                {
 60133                    return default;
 134                }
 135
 1164136                if (_waiters == default)
 137                {
 756138                    _isLocked = false;
 756139                    return default;
 140                }
 141
 426142                while (_waiters.Count > 0)
 143                {
 420144                    var nextWaiter = _waiters.Dequeue();
 420145                    if (!nextWaiter.Task.IsCompleted)
 146                    {
 147                        // Return the waiter only if it wasn't canceled already
 402148                        return nextWaiter;
 149                    }
 150                }
 151
 6152                _isLocked = false;
 6153                return default;
 154            }
 1224155        }
 156
 157        public readonly struct Lock : IDisposable
 158        {
 159            private readonly AsyncLockWithValue<T> _owner;
 1710160            public bool HasValue => _owner == default;
 510161            public T Value { get; }
 162
 163            public Lock(T value)
 164            {
 510165                _owner = default;
 510166                Value = value;
 510167            }
 168
 169            public Lock(AsyncLockWithValue<T> owner)
 170            {
 1232171                _owner = owner;
 1232172                Value = default;
 1232173            }
 174
 68175            public void SetValue(T value) => _owner.SetValue(value);
 176
 1730177            public void Dispose() => _owner?.Reset();
 178        }
 179    }
 180}