diff --git a/CryptoExchange.Net/RateLimiting/Guards/RateLimitGuard.cs b/CryptoExchange.Net/RateLimiting/Guards/RateLimitGuard.cs index 6d5b971..5fd5d38 100644 --- a/CryptoExchange.Net/RateLimiting/Guards/RateLimitGuard.cs +++ b/CryptoExchange.Net/RateLimiting/Guards/RateLimitGuard.cs @@ -3,6 +3,7 @@ using CryptoExchange.Net.RateLimiting.Interfaces; using CryptoExchange.Net.RateLimiting.Trackers; using System; using System.Collections.Generic; +using System.Threading; namespace CryptoExchange.Net.RateLimiting.Guards { @@ -36,6 +37,7 @@ namespace CryptoExchange.Net.RateLimiting.Guards private readonly double? _decayRate; private readonly int? _connectionWeight; private readonly Func _keySelector; + private readonly SemaphoreSlim? _sharedGuardSemaphore; /// public string Name => "RateLimitGuard"; @@ -52,6 +54,11 @@ namespace CryptoExchange.Net.RateLimiting.Guards /// public TimeSpan TimeSpan { get; } + /// + /// Whether this guard is shared between multiple gates + /// + public bool SharedGuard { get; } + /// /// ctor /// @@ -62,8 +69,9 @@ namespace CryptoExchange.Net.RateLimiting.Guards /// Type of rate limit window /// The decay per timespan if windowType is DecayWindowTracker /// The weight of a new connection - public RateLimitGuard(Func keySelector, IGuardFilter filter, int limit, TimeSpan timeSpan, RateLimitWindowType windowType, double? decayPerTimeSpan = null, int? connectionWeight = null) - : this(keySelector, new[] { filter }, limit, timeSpan, windowType, decayPerTimeSpan, connectionWeight) + /// Whether this guard is shared between multiple gates + public RateLimitGuard(Func keySelector, IGuardFilter filter, int limit, TimeSpan timeSpan, RateLimitWindowType windowType, double? decayPerTimeSpan = null, int? connectionWeight = null, bool shared = false) + : this(keySelector, new[] { filter }, limit, timeSpan, windowType, decayPerTimeSpan, connectionWeight, shared) { } @@ -77,22 +85,27 @@ namespace CryptoExchange.Net.RateLimiting.Guards /// Type of rate limit window /// The decay per timespan if windowType is DecayWindowTracker /// The weight of a new connection - public RateLimitGuard(Func keySelector, IEnumerable filters, int limit, TimeSpan timeSpan, RateLimitWindowType windowType, double? decayPerTimeSpan = null, int? connectionWeight = null) + /// Whether this guard is shared between multiple gates + public RateLimitGuard(Func keySelector, IEnumerable filters, int limit, TimeSpan timeSpan, RateLimitWindowType windowType, double? decayPerTimeSpan = null, int? connectionWeight = null, bool shared = false) { _filters = filters; _trackers = new Dictionary(); _windowType = windowType; Limit = limit; TimeSpan = timeSpan; + SharedGuard = shared; _keySelector = keySelector; _decayRate = decayPerTimeSpan; _connectionWeight = connectionWeight; + + if (SharedGuard) + _sharedGuardSemaphore = new SemaphoreSlim(1, 1); } /// public LimitCheck Check(RateLimitItemType type, RequestDefinition definition, string host, string? apiKey, int requestWeight, string? keySuffix) { - foreach(var filter in _filters) + foreach (var filter in _filters) { if (!filter.Passes(type, definition, host, apiKey)) return LimitCheck.NotApplicable; @@ -101,18 +114,30 @@ namespace CryptoExchange.Net.RateLimiting.Guards if (type == RateLimitItemType.Connection) requestWeight = _connectionWeight ?? requestWeight; - var key = _keySelector(definition, host, apiKey) + keySuffix; - if (!_trackers.TryGetValue(key, out var tracker)) + if (SharedGuard) + _sharedGuardSemaphore!.Wait(); + + try { - tracker = CreateTracker(); - _trackers.Add(key, tracker); + var key = _keySelector(definition, host, apiKey) + keySuffix; + if (!_trackers.TryGetValue(key, out var tracker)) + { + tracker = CreateTracker(); + _trackers.Add(key, tracker); + } + + + var delay = tracker.GetWaitTime(requestWeight); + if (delay == default) + return LimitCheck.NotNeeded(Limit, TimeSpan, tracker.Current); + + return LimitCheck.Needed(delay, Limit, TimeSpan, tracker.Current); + } + finally + { + if (SharedGuard) + _sharedGuardSemaphore!.Release(); } - - var delay = tracker.GetWaitTime(requestWeight); - if (delay == default) - return LimitCheck.NotNeeded(Limit, TimeSpan, tracker.Current); - - return LimitCheck.Needed(delay, Limit, TimeSpan, tracker.Current); } /// @@ -127,9 +152,23 @@ namespace CryptoExchange.Net.RateLimiting.Guards if (type == RateLimitItemType.Connection) requestWeight = _connectionWeight ?? requestWeight; + var key = _keySelector(definition, host, apiKey) + keySuffix; var tracker = _trackers[key]; - tracker.ApplyWeight(requestWeight); + + if (SharedGuard) + _sharedGuardSemaphore!.Wait(); + + try + { + tracker.ApplyWeight(requestWeight); + } + finally + { + if (SharedGuard) + _sharedGuardSemaphore!.Release(); + } + return RateLimitState.Applied(Limit, TimeSpan, tracker.Current); }