using Microsoft.Extensions.Caching.Memory; using System.Runtime.CompilerServices; namespace Remotely.Shared.Helpers; public static class RateLimiter { private static readonly MemoryCache _cache = new(new MemoryCacheOptions()); private static readonly SemaphoreSlim _cacheLock = new(1, 1); /// /// Clears the RateLimiter cache so it is reset to a fresh state. /// /// /// public static async Task Reset(CancellationToken cancellationToken = default) { await _cacheLock.WaitAsync(cancellationToken); try { _cache.Clear(); } finally { _cacheLock.Release(); } } /// /// /// Throttles a given func so it is only called once every duration. /// /// /// The key used to identify and debounce the action will be derived from /// the caller information. Those parameters will be populated automatically /// and can be left empty. /// /// public static async Task Throttle( Func func, TimeSpan duration, [CallerMemberName] string callerMemberName = "", [CallerFilePath] string callerFilePath = "", [CallerLineNumber] int callerLineNumber = -1, CancellationToken cancellationToken = default) { var key = $"{callerMemberName}-{callerFilePath}-{callerLineNumber}"; await Throttle(func, duration, key, cancellationToken); } /// /// /// Throttles a given func so it is only called once every duration. /// /// /// The provided key will be used to identify and throttle the func. /// /// public static async Task Throttle( Func func, TimeSpan duration, string key, CancellationToken cancellationToken = default) { await _cacheLock.WaitAsync(cancellationToken); try { if (_cache.TryGetValue(key, out _)) { return; } await func.Invoke(); _cache.Set(key, string.Empty, duration); } finally { _cacheLock.Release(); } } }