< Summary

Information
Class: System.Net.Http.CreditManager
Assembly: System.Net.Http
File(s): D:\runner\runtime\src\libraries\System.Net.Http\src\System\Net\Http\SocketsHttpHandler\CreditManager.cs
Line coverage
0%
Covered lines: 0
Uncovered lines: 96
Coverable lines: 96
Total lines: 177
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 32
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Cyclomatic complexity NPath complexity Sequence coverage
.ctor(...)0%220%
RequestCreditAsync(...)0%660%
AdjustCredit(...)0%14140%
Dispose()0%660%
TryRequestCreditNoLock(...)0%440%

File(s)

D:\runner\runtime\src\libraries\System.Net.Http\src\System\Net\Http\SocketsHttpHandler\CreditManager.cs

#LineLine coverage
 1// Licensed to the .NET Foundation under one or more agreements.
 2// The .NET Foundation licenses this file to you under the MIT license.
 3
 4using System.Diagnostics;
 5using System.Threading;
 6using System.Threading.Tasks;
 7
 8namespace System.Net.Http
 9{
 10    internal sealed class CreditManager
 11    {
 12        private readonly IHttpTrace _owner;
 13        private readonly string _name;
 14        private int _current;
 15        private bool _disposed;
 16        /// <summary>Circular singly-linked list of active waiters.</summary>
 17        /// <remarks>If null, the list is empty.  If non-null, this is the tail.  If the list has one item, its Next is 
 18        private CreditWaiter? _waitersTail;
 19
 020        public CreditManager(IHttpTrace owner, string name, int initialCredit)
 021        {
 022            Debug.Assert(owner != null);
 023            Debug.Assert(!string.IsNullOrWhiteSpace(name));
 24
 025            if (NetEventSource.Log.IsEnabled()) owner.Trace($"{name}. {nameof(initialCredit)}={initialCredit}");
 026            _owner = owner;
 027            _name = name;
 028            _current = initialCredit;
 029        }
 30
 031        public bool IsCreditAvailable => Volatile.Read(ref _current) > 0;
 32
 33        private object SyncObject
 34        {
 35            // Generally locking on "this" is considered poor form, but this type is internal,
 36            // and it's unnecessary overhead to allocate another object just for this purpose.
 037            get => this;
 38        }
 39
 40        public bool TryRequestCreditNoWait(int amount)
 41        {
 42            lock (SyncObject)
 43            {
 44                return TryRequestCreditNoLock(amount) > 0;
 45            }
 46        }
 47
 48        public ValueTask<int> RequestCreditAsync(int amount, CancellationToken cancellationToken)
 049        {
 050            lock (SyncObject)
 051            {
 52                // If we can satisfy the request with credit already available, do so synchronously.
 053                int granted = TryRequestCreditNoLock(amount);
 54
 055                if (granted > 0)
 056                {
 057                    return new ValueTask<int>(granted);
 58                }
 59
 060                if (NetEventSource.Log.IsEnabled()) _owner.Trace($"{_name}. requested={amount}, no credit available.");
 61
 62                // Otherwise, create a new waiter.
 063                var waiter = new CreditWaiter(cancellationToken);
 064                waiter.Amount = amount;
 65
 66                // Add the waiter at the tail of the queue.
 067                if (_waitersTail is null)
 068                {
 069                    _waitersTail = waiter.Next = waiter;
 070                }
 71                else
 072                {
 073                    waiter.Next = _waitersTail.Next;
 074                    _waitersTail.Next = waiter;
 075                    _waitersTail = waiter;
 076                }
 77
 78                // And return a ValueTask<int> for it.
 079                return waiter.AsValueTask();
 80            }
 081        }
 82
 83        public void AdjustCredit(int amount)
 084        {
 85            // Note credit can be adjusted *downward* as well.
 86            // This can cause the current credit to become negative.
 87
 088            lock (SyncObject)
 089            {
 090                if (NetEventSource.Log.IsEnabled()) _owner.Trace($"{_name}. {nameof(amount)}={amount}, current={_current
 91
 092                if (_disposed)
 093                {
 094                    return;
 95                }
 96
 097                Debug.Assert(_current <= 0 || _waitersTail is null, "Shouldn't have waiters when credit is available");
 98
 099                _current = checked(_current + amount);
 100
 0101                while (_current > 0 && _waitersTail != null)
 0102                {
 103                    // Get the waiter from the head of the queue.
 0104                    CreditWaiter? waiter = _waitersTail.Next;
 0105                    Debug.Assert(waiter != null);
 0106                    int granted = Math.Min(waiter.Amount, _current);
 107
 108                    // Remove the waiter from the list.
 0109                    if (waiter.Next == waiter)
 0110                    {
 0111                        Debug.Assert(_waitersTail == waiter);
 0112                        _waitersTail = null;
 0113                    }
 114                    else
 0115                    {
 0116                        _waitersTail.Next = waiter.Next;
 0117                    }
 0118                    waiter.Next = null;
 119
 120                    // Ensure that we grant credit only if the task has not been canceled.
 0121                    if (waiter.TrySetResult(granted))
 0122                    {
 0123                        _current -= granted;
 0124                    }
 125
 0126                    waiter.Dispose();
 0127                }
 0128            }
 0129        }
 130
 131        public void Dispose()
 0132        {
 0133            lock (SyncObject)
 0134            {
 0135                if (_disposed)
 0136                {
 0137                    return;
 138                }
 139
 0140                _disposed = true;
 141
 0142                CreditWaiter? waiter = _waitersTail;
 0143                if (waiter != null)
 0144                {
 145                    do
 0146                    {
 0147                        CreditWaiter? next = waiter!.Next;
 0148                        waiter.Next = null;
 0149                        waiter.Dispose();
 0150                        waiter = next;
 0151                    }
 0152                    while (waiter != _waitersTail);
 153
 0154                    _waitersTail = null;
 0155                }
 0156            }
 0157        }
 158
 159        private int TryRequestCreditNoLock(int amount)
 0160        {
 0161            Debug.Assert(Monitor.IsEntered(SyncObject), "Shouldn't be called outside lock.");
 162
 0163            ObjectDisposedException.ThrowIf(_disposed, this);
 164
 0165            if (_current > 0)
 0166            {
 0167                Debug.Assert(_waitersTail is null, "Shouldn't have waiters when credit is available");
 168
 0169                int granted = Math.Min(amount, _current);
 0170                if (NetEventSource.Log.IsEnabled()) _owner.Trace($"{_name}. requested={amount}, current={_current}, gran
 0171                _current -= granted;
 0172                return granted;
 173            }
 0174            return 0;
 0175        }
 176    }
 177}