File: System\Diagnostics\RateLimiter.cs
Web Access
Project: src\src\libraries\System.Diagnostics.DiagnosticSource\src\System.Diagnostics.DiagnosticSource.csproj (System.Diagnostics.DiagnosticSource)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System;
using System.Threading;
 
namespace System.Diagnostics
{
    /// <summary>
    /// A class that limits operations to a maximum count per second.
    /// </summary>
    internal sealed class RateLimiter
    {
        private readonly int _maxOperationsPerSecond;
        private readonly Stopwatch _stopwatch;
        private readonly long _ticksPerSecond;
        private int _currentOperationCount;
        private long _intervalStartTicks;
 
        /// <summary>
        /// Initializes a new instance of the RateLimiter class.
        /// </summary>
        /// <param name="maxOperationsPerSecond">Maximum number of operations allowed per second.</param>
        internal RateLimiter(int maxOperationsPerSecond)
        {
            Debug.Assert(maxOperationsPerSecond > 0, "maxOperationsPerSecond must be greater than 0");
 
            _maxOperationsPerSecond = maxOperationsPerSecond;
            _stopwatch = new Stopwatch();
            _stopwatch.Start();
            _intervalStartTicks = _stopwatch.ElapsedTicks;
            _currentOperationCount = 0;
            _ticksPerSecond = Stopwatch.Frequency;
        }
 
        /// <summary>
        /// Tries to perform an operation if the rate limit allows it.
        /// </summary>
        /// <returns>True if the operation is allowed within the rate limit, otherwise false.</returns>
        internal bool TryAcquire()
        {
            do
            {
                long currentTicks = _stopwatch.ElapsedTicks;
                long intervalStartTicks = Interlocked.Read(ref _intervalStartTicks);
                int currentOperationCount = Volatile.Read(ref _currentOperationCount);
                long elapsedTicks = currentTicks - intervalStartTicks;
 
                // If a second has elapsed, reset the counter
                if (elapsedTicks >= _ticksPerSecond)
                {
                    if (Interlocked.CompareExchange(ref _currentOperationCount, 1, currentOperationCount) != currentOperationCount)
                    {
                        // Another thread has already reset the counter, so we need to check again
                        continue;
                    }
 
                    // Update the _intervalStartTicks if no-one else updated it in the meantime.
                    Interlocked.CompareExchange(ref _intervalStartTicks, currentTicks, intervalStartTicks);
 
                    return true; // Allow the operation
                }
 
                return Interlocked.Increment(ref _currentOperationCount) <= _maxOperationsPerSecond;
            } while (true);
        }
    }
}