| File: System\Net\Http\SocketsHttpHandler\CreditManager.cs | Web Access |
| Project: src\runtime\src\libraries\System.Net.Http\src\System.Net.Http.csproj (System.Net.Http) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics; using System.Threading; using System.Threading.Tasks; namespace System.Net.Http { internal sealed class CreditManager { private readonly IHttpTrace _owner; private readonly string _name; private int _current; private bool _disposed; /// <summary>Circular singly-linked list of active waiters.</summary> /// <remarks>If null, the list is empty. If non-null, this is the tail. If the list has one item, its Next is itself.</remarks> private CreditWaiter? _waitersTail; public CreditManager(IHttpTrace owner, string name, int initialCredit) { Debug.Assert(owner != null); Debug.Assert(!string.IsNullOrWhiteSpace(name)); if (NetEventSource.Log.IsEnabled()) owner.Trace($"{name}. {nameof(initialCredit)}={initialCredit}"); _owner = owner; _name = name; _current = initialCredit; } public bool IsCreditAvailable => Volatile.Read(ref _current) > 0; private object SyncObject { // Generally locking on "this" is considered poor form, but this type is internal, // and it's unnecessary overhead to allocate another object just for this purpose. get => this; } public bool TryRequestCreditNoWait(int amount) { lock (SyncObject) { return TryRequestCreditNoLock(amount) > 0; } } public ValueTask<int> RequestCreditAsync(int amount, CancellationToken cancellationToken) { lock (SyncObject) { // If we can satisfy the request with credit already available, do so synchronously. int granted = TryRequestCreditNoLock(amount); if (granted > 0) { return new ValueTask<int>(granted); } if (NetEventSource.Log.IsEnabled()) _owner.Trace($"{_name}. requested={amount}, no credit available."); // Otherwise, create a new waiter. var waiter = new CreditWaiter(cancellationToken); waiter.Amount = amount; // Add the waiter at the tail of the queue. if (_waitersTail is null) { _waitersTail = waiter.Next = waiter; } else { waiter.Next = _waitersTail.Next; _waitersTail.Next = waiter; _waitersTail = waiter; } // And return a ValueTask<int> for it. return waiter.AsValueTask(); } } public void AdjustCredit(int amount) { // Note credit can be adjusted *downward* as well. // This can cause the current credit to become negative. lock (SyncObject) { if (NetEventSource.Log.IsEnabled()) _owner.Trace($"{_name}. {nameof(amount)}={amount}, current={_current}"); if (_disposed) { return; } Debug.Assert(_current <= 0 || _waitersTail is null, "Shouldn't have waiters when credit is available"); _current = checked(_current + amount); while (_current > 0 && _waitersTail != null) { // Get the waiter from the head of the queue. CreditWaiter? waiter = _waitersTail.Next; Debug.Assert(waiter != null); int granted = Math.Min(waiter.Amount, _current); // Remove the waiter from the list. if (waiter.Next == waiter) { Debug.Assert(_waitersTail == waiter); _waitersTail = null; } else { _waitersTail.Next = waiter.Next; } waiter.Next = null; // Ensure that we grant credit only if the task has not been canceled. if (waiter.TrySetResult(granted)) { _current -= granted; } waiter.Dispose(); } } } public void Dispose() { lock (SyncObject) { if (_disposed) { return; } _disposed = true; CreditWaiter? waiter = _waitersTail; if (waiter != null) { do { CreditWaiter? next = waiter!.Next; waiter.Next = null; waiter.Dispose(); waiter = next; } while (waiter != _waitersTail); _waitersTail = null; } } } private int TryRequestCreditNoLock(int amount) { Debug.Assert(Monitor.IsEntered(SyncObject), "Shouldn't be called outside lock."); ObjectDisposedException.ThrowIf(_disposed, this); if (_current > 0) { Debug.Assert(_waitersTail is null, "Shouldn't have waiters when credit is available"); int granted = Math.Min(amount, _current); if (NetEventSource.Log.IsEnabled()) _owner.Trace($"{_name}. requested={amount}, current={_current}, granted={granted}"); _current -= granted; return granted; } return 0; } } }