< Summary

Information
Class: System.Net.Http.FailedProxyCache
Assembly: System.Net.Http
File(s): D:\runner\runtime\src\libraries\System.Net.Http\src\System\Net\Http\SocketsHttpHandler\FailedProxyCache.cs
Line coverage
0%
Covered lines: 0
Uncovered lines: 51
Coverable lines: 51
Total lines: 142
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 20
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()100%110%
GetProxyRenewTicks(...)0%880%
SetProxyFailed(...)100%110%
TryRenewProxy(...)100%110%
Cleanup()0%440%
CleanupHelper()0%880%

File(s)

D:\runner\runtime\src\libraries\System.Net.Http\src\System\Net\Http\SocketsHttpHandler\FailedProxyCache.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.Collections.Concurrent;
 5using System.Collections.Generic;
 6using System.Runtime.CompilerServices;
 7using System.Threading;
 8
 9namespace System.Net.Http
 10{
 11    /// <summary>
 12    /// Holds a cache of failing proxies and manages when they should be retried.
 13    /// </summary>
 14    internal sealed class FailedProxyCache
 15    {
 16        /// <summary>
 17        /// When returned by <see cref="GetProxyRenewTicks"/>, indicates a proxy is immediately usable.
 18        /// </summary>
 19        public const long Immediate = 0;
 20
 21        // If a proxy fails, time out 30 minutes. WinHTTP and Firefox both use this.
 22        private const int FailureTimeoutInMilliseconds = 1000 * 60 * 30;
 23
 24        // Scan through the failures and flush any that have expired every 5 minutes.
 25        private const int FlushFailuresTimerInMilliseconds = 1000 * 60 * 5;
 26
 27        // _failedProxies will only be flushed (rare but somewhat expensive) if we have more than this number of proxies
 28        private const int LargeProxyConfigBoundary = 8;
 29
 30        // Value is the Environment.TickCount64 to remove the proxy from the failure list.
 031        private readonly ConcurrentDictionary<Uri, long> _failedProxies = new ConcurrentDictionary<Uri, long>();
 32
 33        // When Environment.TickCount64 >= _nextFlushTicks, cause a flush.
 034        private long _nextFlushTicks = Environment.TickCount64 + FlushFailuresTimerInMilliseconds;
 35
 36        // This lock can be folded into _nextFlushTicks for space optimization, but
 37        // this class should only have a single instance so would rather have clarity.
 038        private SpinLock _flushLock = new SpinLock(enableThreadOwnerTracking: false); // mutable struct; do not make thi
 39
 40        /// <summary>
 41        /// Checks when a proxy will become usable.
 42        /// </summary>
 43        /// <param name="uri">The <see cref="Uri"/> of the proxy to check.</param>
 44        /// <returns>If the proxy can be used, <see cref="Immediate"/>. Otherwise, the next <see cref="Environment.TickC
 45        public long GetProxyRenewTicks(Uri uri)
 046        {
 047            Cleanup();
 48
 49            // If not failed, ready immediately.
 050            if (!_failedProxies.TryGetValue(uri, out long renewTicks))
 051            {
 052                return Immediate;
 53            }
 54
 55            // If we haven't reached out renew time, the proxy can't be used.
 056            if (Environment.TickCount64 < renewTicks)
 057            {
 058                return renewTicks;
 59            }
 60
 61            // Renew time reached, we can remove the proxy from the cache.
 062            if (TryRenewProxy(uri, renewTicks))
 063            {
 064                return Immediate;
 65            }
 66
 67            // Another thread updated the cache before we could remove it.
 68            // We can't know if this is a removal or an update, so check again.
 069            return _failedProxies.TryGetValue(uri, out renewTicks) ? renewTicks : Immediate;
 070        }
 71
 72        /// <summary>
 73        /// Sets a proxy as failed, to avoid trying it again for some time.
 74        /// </summary>
 75        /// <param name="uri">The URI of the proxy.</param>
 76        public void SetProxyFailed(Uri uri)
 077        {
 078            _failedProxies[uri] = Environment.TickCount64 + FailureTimeoutInMilliseconds;
 079            Cleanup();
 080        }
 81
 82        /// <summary>
 83        /// Renews a proxy prior to its period expiring. Used when all proxies are failed to renew the proxy closest to 
 84        /// </summary>
 85        /// <param name="uri">The <paramref name="uri"/> of the proxy to renew.</param>
 86        /// <param name="renewTicks">The current renewal time for the proxy. If the value has changed from this, the pro
 87        public bool TryRenewProxy(Uri uri, long renewTicks) =>
 088            _failedProxies.TryRemove(new KeyValuePair<Uri, long>(uri, renewTicks));
 89
 90        /// <summary>
 91        /// Cleans up any old proxies that should no longer be marked as failing.
 92        /// </summary>
 93        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 94        private void Cleanup()
 095        {
 096            if (_failedProxies.Count > LargeProxyConfigBoundary && Environment.TickCount64 >= Interlocked.Read(ref _next
 097            {
 098                CleanupHelper();
 099            }
 0100        }
 101
 102        /// <summary>
 103        /// Cleans up any old proxies that should no longer be marked as failing.
 104        /// </summary>
 105        /// <remarks>
 106        /// I expect this to never be called by <see cref="Cleanup"/> in a production system. It is only needed in the c
 107        /// that a system has a very large number of proxies that the PAC script cycles through. It is moderately expens
 108        /// so it's only run periodically and is disabled until we exceed <see cref="LargeProxyConfigBoundary"/> failed 
 109        /// </remarks>
 110        [MethodImpl(MethodImplOptions.NoInlining)]
 111        private void CleanupHelper()
 0112        {
 0113            bool lockTaken = false;
 114            try
 0115            {
 0116                _flushLock.TryEnter(ref lockTaken);
 0117                if (!lockTaken)
 0118                {
 0119                    return;
 120                }
 121
 0122                long curTicks = Environment.TickCount64;
 123
 0124                foreach (KeyValuePair<Uri, long> kvp in _failedProxies)
 0125                {
 0126                    if (curTicks >= kvp.Value)
 0127                    {
 0128                        ((ICollection<KeyValuePair<Uri, long>>)_failedProxies).Remove(kvp);
 0129                    }
 0130                }
 0131            }
 132            finally
 0133            {
 0134                if (lockTaken)
 0135                {
 0136                    Interlocked.Exchange(ref _nextFlushTicks, Environment.TickCount64 + FlushFailuresTimerInMilliseconds
 0137                    _flushLock.Exit(false);
 0138                }
 0139            }
 0140        }
 141    }
 142}