mirror of
				https://github.com/JKorf/CryptoExchange.Net
				synced 2025-10-27 08:27:19 +00:00 
			
		
		
		
	Added support for ratelimiting key suffix, allowing parameter based ratelimiting
This commit is contained in:
		
							parent
							
								
									cd78dbf575
								
							
						
					
					
						commit
						3b15c35a02
					
				| @ -176,12 +176,12 @@ namespace CryptoExchange.Net.UnitTests | |||||||
| 
 | 
 | ||||||
|             for (var i = 0; i < requests + 1; i++) |             for (var i = 0; i < requests + 1; i++) | ||||||
|             { |             { | ||||||
|                 var result1 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition, "https://test.com", "123", 1, RateLimitingBehaviour.Wait, default);              |                 var result1 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition, "https://test.com", "123", 1, RateLimitingBehaviour.Wait, null, default);              | ||||||
|                 Assert.That(i == requests? triggered : !triggered); |                 Assert.That(i == requests? triggered : !triggered); | ||||||
|             } |             } | ||||||
|             triggered = false; |             triggered = false; | ||||||
|             await Task.Delay((int)Math.Round(perSeconds * 1000) + 10); |             await Task.Delay((int)Math.Round(perSeconds * 1000) + 10); | ||||||
|             var result2 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition, "https://test.com", "123", 1,  RateLimitingBehaviour.Wait, default); |             var result2 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition, "https://test.com", "123", 1,  RateLimitingBehaviour.Wait, null, default); | ||||||
|             Assert.That(!triggered); |             Assert.That(!triggered); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
| @ -201,7 +201,7 @@ namespace CryptoExchange.Net.UnitTests | |||||||
|             rateLimiter.RateLimitTriggered += (x) => { evnt = x; }; |             rateLimiter.RateLimitTriggered += (x) => { evnt = x; }; | ||||||
|             for (var i = 0; i < 2; i++) |             for (var i = 0; i < 2; i++) | ||||||
|             { |             { | ||||||
|                 var result1 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition, "https://test.com", "123", 1, RateLimitingBehaviour.Wait, default); |                 var result1 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition, "https://test.com", "123", 1, RateLimitingBehaviour.Wait, null, default); | ||||||
|                 bool expected = i == 1 ? (expectLimiting ? evnt.DelayTime > TimeSpan.Zero : evnt == null) : evnt == null; |                 bool expected = i == 1 ? (expectLimiting ? evnt.DelayTime > TimeSpan.Zero : evnt == null) : evnt == null; | ||||||
|                 Assert.That(expected); |                 Assert.That(expected); | ||||||
|             } |             } | ||||||
| @ -222,9 +222,9 @@ namespace CryptoExchange.Net.UnitTests | |||||||
|             RateLimitEvent evnt = null; |             RateLimitEvent evnt = null; | ||||||
|             rateLimiter.RateLimitTriggered += (x) => { evnt = x; }; |             rateLimiter.RateLimitTriggered += (x) => { evnt = x; }; | ||||||
| 
 | 
 | ||||||
|             var result1 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition1, "https://test.com", "123", 1, RateLimitingBehaviour.Wait, default); |             var result1 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition1, "https://test.com", "123", 1, RateLimitingBehaviour.Wait, null, default); | ||||||
|             Assert.That(evnt == null); |             Assert.That(evnt == null); | ||||||
|             var result2 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition2, "https://test.com", "123", 1, RateLimitingBehaviour.Wait, default); |             var result2 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition2, "https://test.com", "123", 1, RateLimitingBehaviour.Wait, null, default); | ||||||
|             Assert.That(expectLimiting ? evnt != null : evnt == null); |             Assert.That(expectLimiting ? evnt != null : evnt == null); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
| @ -243,12 +243,12 @@ namespace CryptoExchange.Net.UnitTests | |||||||
| 
 | 
 | ||||||
|             for (var i = 0; i < requests + 1; i++) |             for (var i = 0; i < requests + 1; i++) | ||||||
|             { |             { | ||||||
|                 var result1 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition, "https://test.com", "123", 1, RateLimitingBehaviour.Wait, default); |                 var result1 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition, "https://test.com", "123", 1, RateLimitingBehaviour.Wait, null, default); | ||||||
|                 Assert.That(i == requests ? triggered : !triggered); |                 Assert.That(i == requests ? triggered : !triggered); | ||||||
|             } |             } | ||||||
|             triggered = false; |             triggered = false; | ||||||
|             await Task.Delay((int)Math.Round(perSeconds * 1000) + 10); |             await Task.Delay((int)Math.Round(perSeconds * 1000) + 10); | ||||||
|             var result2 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition, "https://test.com", "123", 1, RateLimitingBehaviour.Wait, default); |             var result2 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition, "https://test.com", "123", 1, RateLimitingBehaviour.Wait, null, default); | ||||||
|             Assert.That(!triggered); |             Assert.That(!triggered); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
| @ -266,7 +266,7 @@ namespace CryptoExchange.Net.UnitTests | |||||||
|             rateLimiter.RateLimitTriggered += (x) => { evnt = x; }; |             rateLimiter.RateLimitTriggered += (x) => { evnt = x; }; | ||||||
|             for (var i = 0; i < 2; i++) |             for (var i = 0; i < 2; i++) | ||||||
|             { |             { | ||||||
|                 var result1 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition, "https://test.com", "123", 1, RateLimitingBehaviour.Wait, default); |                 var result1 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition, "https://test.com", "123", 1, RateLimitingBehaviour.Wait, null, default); | ||||||
|                 bool expected = i == 1 ? (expectLimited ? evnt.DelayTime > TimeSpan.Zero : evnt == null) : evnt == null; |                 bool expected = i == 1 ? (expectLimited ? evnt.DelayTime > TimeSpan.Zero : evnt == null) : evnt == null; | ||||||
|                 Assert.That(expected); |                 Assert.That(expected); | ||||||
|             } |             } | ||||||
| @ -286,7 +286,7 @@ namespace CryptoExchange.Net.UnitTests | |||||||
|             rateLimiter.RateLimitTriggered += (x) => { evnt = x; }; |             rateLimiter.RateLimitTriggered += (x) => { evnt = x; }; | ||||||
|             for (var i = 0; i < 2; i++) |             for (var i = 0; i < 2; i++) | ||||||
|             { |             { | ||||||
|                 var result1 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition, "https://test.com", "123", 1, RateLimitingBehaviour.Wait, default); |                 var result1 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition, "https://test.com", "123", 1, RateLimitingBehaviour.Wait, null, default); | ||||||
|                 bool expected = i == 1 ? (expectLimited ? evnt.DelayTime > TimeSpan.Zero : evnt == null) : evnt == null; |                 bool expected = i == 1 ? (expectLimited ? evnt.DelayTime > TimeSpan.Zero : evnt == null) : evnt == null; | ||||||
|                 Assert.That(expected); |                 Assert.That(expected); | ||||||
|             } |             } | ||||||
| @ -309,9 +309,9 @@ namespace CryptoExchange.Net.UnitTests | |||||||
|             RateLimitEvent evnt = null; |             RateLimitEvent evnt = null; | ||||||
|             rateLimiter.RateLimitTriggered += (x) => { evnt = x; }; |             rateLimiter.RateLimitTriggered += (x) => { evnt = x; }; | ||||||
| 
 | 
 | ||||||
|             var result1 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition1, "https://test.com", key1, 1, RateLimitingBehaviour.Wait, default); |             var result1 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition1, "https://test.com", key1, 1, RateLimitingBehaviour.Wait, null, default); | ||||||
|             Assert.That(evnt == null); |             Assert.That(evnt == null); | ||||||
|             var result2 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition2, "https://test.com", key2, 1, RateLimitingBehaviour.Wait, default); |             var result2 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition2, "https://test.com", key2, 1, RateLimitingBehaviour.Wait, null, default); | ||||||
|             Assert.That(expectLimited ? evnt != null : evnt == null); |             Assert.That(expectLimited ? evnt != null : evnt == null); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
| @ -328,9 +328,9 @@ namespace CryptoExchange.Net.UnitTests | |||||||
|             RateLimitEvent evnt = null; |             RateLimitEvent evnt = null; | ||||||
|             rateLimiter.RateLimitTriggered += (x) => { evnt = x; }; |             rateLimiter.RateLimitTriggered += (x) => { evnt = x; }; | ||||||
| 
 | 
 | ||||||
|             var result1 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition1, "https://test.com", "123", 1, RateLimitingBehaviour.Wait, default); |             var result1 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition1, "https://test.com", "123", 1, RateLimitingBehaviour.Wait, null, default); | ||||||
|             Assert.That(evnt == null); |             Assert.That(evnt == null); | ||||||
|             var result2 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition2, "https://test.com", null, 1, RateLimitingBehaviour.Wait, default); |             var result2 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition2, "https://test.com", null, 1, RateLimitingBehaviour.Wait, null, default); | ||||||
|             Assert.That(expectLimited ? evnt != null : evnt == null); |             Assert.That(expectLimited ? evnt != null : evnt == null); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
| @ -348,9 +348,9 @@ namespace CryptoExchange.Net.UnitTests | |||||||
|             RateLimitEvent evnt = null; |             RateLimitEvent evnt = null; | ||||||
|             rateLimiter.RateLimitTriggered += (x) => { evnt = x; }; |             rateLimiter.RateLimitTriggered += (x) => { evnt = x; }; | ||||||
| 
 | 
 | ||||||
|             var result1 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition1, host1, "123", 1, RateLimitingBehaviour.Wait, default); |             var result1 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition1, host1, "123", 1, RateLimitingBehaviour.Wait, null, default); | ||||||
|             Assert.That(evnt == null); |             Assert.That(evnt == null); | ||||||
|             var result2 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition1, host2, "123", 1, RateLimitingBehaviour.Wait, default); |             var result2 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition1, host2, "123", 1, RateLimitingBehaviour.Wait, null, default); | ||||||
|             Assert.That(expectLimited ? evnt != null : evnt == null); |             Assert.That(expectLimited ? evnt != null : evnt == null); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
| @ -365,9 +365,9 @@ namespace CryptoExchange.Net.UnitTests | |||||||
|             RateLimitEvent evnt = null; |             RateLimitEvent evnt = null; | ||||||
|             rateLimiter.RateLimitTriggered += (x) => { evnt = x; }; |             rateLimiter.RateLimitTriggered += (x) => { evnt = x; }; | ||||||
| 
 | 
 | ||||||
|             var result1 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Connection, new RequestDefinition("1", HttpMethod.Get), host1, "123", 1, RateLimitingBehaviour.Wait, default); |             var result1 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Connection, new RequestDefinition("1", HttpMethod.Get), host1, "123", 1, RateLimitingBehaviour.Wait, null, default); | ||||||
|             Assert.That(evnt == null); |             Assert.That(evnt == null); | ||||||
|             var result2 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Connection, new RequestDefinition("1", HttpMethod.Get), host2, "123", 1, RateLimitingBehaviour.Wait, default); |             var result2 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Connection, new RequestDefinition("1", HttpMethod.Get), host2, "123", 1, RateLimitingBehaviour.Wait, null, default); | ||||||
|             Assert.That(expectLimited ? evnt != null : evnt == null); |             Assert.That(expectLimited ? evnt != null : evnt == null); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
| @ -381,8 +381,8 @@ namespace CryptoExchange.Net.UnitTests | |||||||
|             rateLimiter.RateLimitTriggered += (x) => { evnt = x; }; |             rateLimiter.RateLimitTriggered += (x) => { evnt = x; }; | ||||||
|             var ct = new CancellationTokenSource(TimeSpan.FromSeconds(0.2)); |             var ct = new CancellationTokenSource(TimeSpan.FromSeconds(0.2)); | ||||||
| 
 | 
 | ||||||
|             var result1 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Connection, new RequestDefinition("1", HttpMethod.Get), "https://test.com", "123", 1, RateLimitingBehaviour.Wait, ct.Token); |             var result1 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Connection, new RequestDefinition("1", HttpMethod.Get), "https://test.com", "123", 1, RateLimitingBehaviour.Wait, null, ct.Token); | ||||||
|             var result2 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Connection, new RequestDefinition("1", HttpMethod.Get), "https://test.com", "123", 1, RateLimitingBehaviour.Wait, ct.Token); |             var result2 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Connection, new RequestDefinition("1", HttpMethod.Get), "https://test.com", "123", 1, RateLimitingBehaviour.Wait, null, ct.Token); | ||||||
|             Assert.That(result2.Error, Is.TypeOf<CancellationRequestedError>()); |             Assert.That(result2.Error, Is.TypeOf<CancellationRequestedError>()); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -155,6 +155,7 @@ namespace CryptoExchange.Net.Clients | |||||||
|         /// <param name="additionalHeaders">Additional headers for this request</param> |         /// <param name="additionalHeaders">Additional headers for this request</param> | ||||||
|         /// <param name="weight">Override the request weight for this request definition, for example when the weight depends on the parameters</param> |         /// <param name="weight">Override the request weight for this request definition, for example when the weight depends on the parameters</param> | ||||||
|         /// <param name="weightSingleLimiter">Specify the weight to apply to the individual rate limit guard for this request</param> |         /// <param name="weightSingleLimiter">Specify the weight to apply to the individual rate limit guard for this request</param> | ||||||
|  |         /// <param name="rateLimitKeySuffix">An additional optional suffix for the key selector. Can be used to make rate limiting work based on parameters.</param> | ||||||
|         /// <returns></returns> |         /// <returns></returns> | ||||||
|         protected virtual Task<WebCallResult<T>> SendAsync<T>( |         protected virtual Task<WebCallResult<T>> SendAsync<T>( | ||||||
|             string baseAddress, |             string baseAddress, | ||||||
| @ -163,7 +164,8 @@ namespace CryptoExchange.Net.Clients | |||||||
|             CancellationToken cancellationToken, |             CancellationToken cancellationToken, | ||||||
|             Dictionary<string, string>? additionalHeaders = null, |             Dictionary<string, string>? additionalHeaders = null, | ||||||
|             int? weight = null, |             int? weight = null, | ||||||
|             int? weightSingleLimiter = null) |             int? weightSingleLimiter = null, | ||||||
|  |             string? rateLimitKeySuffix = null) | ||||||
|         { |         { | ||||||
|             var parameterPosition = definition.ParameterPosition ?? ParameterPositions[definition.Method]; |             var parameterPosition = definition.ParameterPosition ?? ParameterPositions[definition.Method]; | ||||||
|             return SendAsync<T>( |             return SendAsync<T>( | ||||||
| @ -174,7 +176,8 @@ namespace CryptoExchange.Net.Clients | |||||||
|                 cancellationToken, |                 cancellationToken, | ||||||
|                 additionalHeaders, |                 additionalHeaders, | ||||||
|                 weight, |                 weight, | ||||||
|                 weightSingleLimiter); |                 weightSingleLimiter, | ||||||
|  |                 rateLimitKeySuffix); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         /// <summary> |         /// <summary> | ||||||
| @ -189,6 +192,7 @@ namespace CryptoExchange.Net.Clients | |||||||
|         /// <param name="additionalHeaders">Additional headers for this request</param> |         /// <param name="additionalHeaders">Additional headers for this request</param> | ||||||
|         /// <param name="weight">Override the request weight for this request definition, for example when the weight depends on the parameters</param> |         /// <param name="weight">Override the request weight for this request definition, for example when the weight depends on the parameters</param> | ||||||
|         /// <param name="weightSingleLimiter">Specify the weight to apply to the individual rate limit guard for this request</param> |         /// <param name="weightSingleLimiter">Specify the weight to apply to the individual rate limit guard for this request</param> | ||||||
|  |         /// <param name="rateLimitKeySuffix">An additional optional suffix for the key selector. Can be used to make rate limiting work based on parameters.</param> | ||||||
|         /// <returns></returns> |         /// <returns></returns> | ||||||
|         protected virtual async Task<WebCallResult<T>> SendAsync<T>( |         protected virtual async Task<WebCallResult<T>> SendAsync<T>( | ||||||
|             string baseAddress, |             string baseAddress, | ||||||
| @ -198,7 +202,8 @@ namespace CryptoExchange.Net.Clients | |||||||
|             CancellationToken cancellationToken, |             CancellationToken cancellationToken, | ||||||
|             Dictionary<string, string>? additionalHeaders = null, |             Dictionary<string, string>? additionalHeaders = null, | ||||||
|             int? weight = null, |             int? weight = null, | ||||||
|             int? weightSingleLimiter = null) |             int? weightSingleLimiter = null, | ||||||
|  |             string? rateLimitKeySuffix = null) | ||||||
|         { |         { | ||||||
|             string? cacheKey = null; |             string? cacheKey = null; | ||||||
|             if (ShouldCache(definition)) |             if (ShouldCache(definition)) | ||||||
| @ -222,7 +227,7 @@ namespace CryptoExchange.Net.Clients | |||||||
|                 currentTry++; |                 currentTry++; | ||||||
|                 var requestId = ExchangeHelpers.NextId(); |                 var requestId = ExchangeHelpers.NextId(); | ||||||
| 
 | 
 | ||||||
|                 var prepareResult = await PrepareAsync(requestId, baseAddress, definition, cancellationToken, additionalHeaders, weight, weightSingleLimiter).ConfigureAwait(false); |                 var prepareResult = await PrepareAsync(requestId, baseAddress, definition, cancellationToken, additionalHeaders, weight, weightSingleLimiter, rateLimitKeySuffix).ConfigureAwait(false); | ||||||
|                 if (!prepareResult) |                 if (!prepareResult) | ||||||
|                     return new WebCallResult<T>(prepareResult.Error!); |                     return new WebCallResult<T>(prepareResult.Error!); | ||||||
| 
 | 
 | ||||||
| @ -264,6 +269,7 @@ namespace CryptoExchange.Net.Clients | |||||||
|         /// <param name="additionalHeaders">Additional headers for this request</param> |         /// <param name="additionalHeaders">Additional headers for this request</param> | ||||||
|         /// <param name="weight">Override the request weight for this request</param> |         /// <param name="weight">Override the request weight for this request</param> | ||||||
|         /// <param name="weightSingleLimiter">Specify the weight to apply to the individual rate limit guard for this request</param> |         /// <param name="weightSingleLimiter">Specify the weight to apply to the individual rate limit guard for this request</param> | ||||||
|  |         /// <param name="rateLimitKeySuffix">An additional optional suffix for the key selector</param> | ||||||
|         /// <returns></returns> |         /// <returns></returns> | ||||||
|         /// <exception cref="Exception"></exception> |         /// <exception cref="Exception"></exception> | ||||||
|         protected virtual async Task<CallResult> PrepareAsync( |         protected virtual async Task<CallResult> PrepareAsync( | ||||||
| @ -273,7 +279,8 @@ namespace CryptoExchange.Net.Clients | |||||||
|             CancellationToken cancellationToken, |             CancellationToken cancellationToken, | ||||||
|             Dictionary<string, string>? additionalHeaders = null, |             Dictionary<string, string>? additionalHeaders = null, | ||||||
|             int? weight = null, |             int? weight = null, | ||||||
|             int? weightSingleLimiter = null) |             int? weightSingleLimiter = null, | ||||||
|  |             string? rateLimitKeySuffix = null) | ||||||
|         { |         { | ||||||
|             // Time sync |             // Time sync | ||||||
|             if (definition.Authenticated) |             if (definition.Authenticated) | ||||||
| @ -308,7 +315,7 @@ namespace CryptoExchange.Net.Clients | |||||||
| 
 | 
 | ||||||
|                 if (ClientOptions.RateLimiterEnabled) |                 if (ClientOptions.RateLimiterEnabled) | ||||||
|                 { |                 { | ||||||
|                     var limitResult = await definition.RateLimitGate.ProcessAsync(_logger, requestId, RateLimitItemType.Request, definition, baseAddress, AuthenticationProvider?._credentials.Key, requestWeight, ClientOptions.RateLimitingBehaviour, cancellationToken).ConfigureAwait(false); |                     var limitResult = await definition.RateLimitGate.ProcessAsync(_logger, requestId, RateLimitItemType.Request, definition, baseAddress, AuthenticationProvider?._credentials.Key, requestWeight, ClientOptions.RateLimitingBehaviour, rateLimitKeySuffix, cancellationToken).ConfigureAwait(false); | ||||||
|                     if (!limitResult) |                     if (!limitResult) | ||||||
|                         return new CallResult(limitResult.Error!); |                         return new CallResult(limitResult.Error!); | ||||||
|                 } |                 } | ||||||
| @ -323,7 +330,7 @@ namespace CryptoExchange.Net.Clients | |||||||
|                 if (ClientOptions.RateLimiterEnabled) |                 if (ClientOptions.RateLimiterEnabled) | ||||||
|                 { |                 { | ||||||
|                     var singleRequestWeight = weightSingleLimiter ?? 1; |                     var singleRequestWeight = weightSingleLimiter ?? 1; | ||||||
|                     var limitResult = await definition.RateLimitGate.ProcessSingleAsync(_logger, requestId, definition.LimitGuard, RateLimitItemType.Request, definition, baseAddress, AuthenticationProvider?._credentials.Key, singleRequestWeight, ClientOptions.RateLimitingBehaviour, cancellationToken).ConfigureAwait(false); |                     var limitResult = await definition.RateLimitGate.ProcessSingleAsync(_logger, requestId, definition.LimitGuard, RateLimitItemType.Request, definition, baseAddress, AuthenticationProvider?._credentials.Key, singleRequestWeight, ClientOptions.RateLimitingBehaviour, rateLimitKeySuffix, cancellationToken).ConfigureAwait(false); | ||||||
|                     if (!limitResult) |                     if (!limitResult) | ||||||
|                         return new CallResult(limitResult.Error!); |                         return new CallResult(limitResult.Error!); | ||||||
|                 } |                 } | ||||||
| @ -609,7 +616,7 @@ namespace CryptoExchange.Net.Clients | |||||||
| 
 | 
 | ||||||
|                 if (ClientOptions.RateLimiterEnabled) |                 if (ClientOptions.RateLimiterEnabled) | ||||||
|                 { |                 { | ||||||
|                     var limitResult = await gate.ProcessAsync(_logger, requestId, RateLimitItemType.Request, new RequestDefinition(uri.AbsolutePath.TrimStart('/'), method) { Authenticated = signed }, uri.Host, AuthenticationProvider?._credentials.Key, requestWeight, ClientOptions.RateLimitingBehaviour, cancellationToken).ConfigureAwait(false); |                     var limitResult = await gate.ProcessAsync(_logger, requestId, RateLimitItemType.Request, new RequestDefinition(uri.AbsolutePath.TrimStart('/'), method) { Authenticated = signed }, uri.Host, AuthenticationProvider?._credentials.Key, requestWeight, ClientOptions.RateLimitingBehaviour, null, cancellationToken).ConfigureAwait(false); | ||||||
|                     if (!limitResult) |                     if (!limitResult) | ||||||
|                         return new CallResult<IRequest>(limitResult.Error!); |                         return new CallResult<IRequest>(limitResult.Error!); | ||||||
|                 } |                 } | ||||||
|  | |||||||
| @ -90,7 +90,7 @@ namespace CryptoExchange.Net.RateLimiting.Guards | |||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         /// <inheritdoc /> |         /// <inheritdoc /> | ||||||
|         public LimitCheck Check(RateLimitItemType type, RequestDefinition definition, string host, string? apiKey, int requestWeight) |         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) | ||||||
|             { |             { | ||||||
| @ -101,7 +101,7 @@ namespace CryptoExchange.Net.RateLimiting.Guards | |||||||
|             if (type == RateLimitItemType.Connection) |             if (type == RateLimitItemType.Connection) | ||||||
|                 requestWeight = _connectionWeight ?? requestWeight; |                 requestWeight = _connectionWeight ?? requestWeight; | ||||||
| 
 | 
 | ||||||
|             var key = _keySelector(definition, host, apiKey); |             var key = _keySelector(definition, host, apiKey) + keySuffix; | ||||||
|             if (!_trackers.TryGetValue(key, out var tracker)) |             if (!_trackers.TryGetValue(key, out var tracker)) | ||||||
|             { |             { | ||||||
|                 tracker = CreateTracker(); |                 tracker = CreateTracker(); | ||||||
| @ -116,7 +116,7 @@ namespace CryptoExchange.Net.RateLimiting.Guards | |||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         /// <inheritdoc /> |         /// <inheritdoc /> | ||||||
|         public RateLimitState ApplyWeight(RateLimitItemType type, RequestDefinition definition, string host, string? apiKey, int requestWeight) |         public RateLimitState ApplyWeight(RateLimitItemType type, RequestDefinition definition, string host, string? apiKey, int requestWeight, string? keySuffix) | ||||||
|         { |         { | ||||||
|             foreach (var filter in _filters) |             foreach (var filter in _filters) | ||||||
|             { |             { | ||||||
| @ -127,7 +127,7 @@ namespace CryptoExchange.Net.RateLimiting.Guards | |||||||
|             if (type == RateLimitItemType.Connection) |             if (type == RateLimitItemType.Connection) | ||||||
|                 requestWeight = _connectionWeight ?? requestWeight; |                 requestWeight = _connectionWeight ?? requestWeight; | ||||||
| 
 | 
 | ||||||
|             var key = _keySelector(definition, host, apiKey); |             var key = _keySelector(definition, host, apiKey) + keySuffix; | ||||||
|             var tracker = _trackers[key]; |             var tracker = _trackers[key]; | ||||||
|             tracker.ApplyWeight(requestWeight); |             tracker.ApplyWeight(requestWeight); | ||||||
|             return RateLimitState.Applied(Limit, TimeSpan, tracker.Current); |             return RateLimitState.Applied(Limit, TimeSpan, tracker.Current); | ||||||
|  | |||||||
| @ -42,7 +42,7 @@ namespace CryptoExchange.Net.RateLimiting.Guards | |||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         /// <inheritdoc /> |         /// <inheritdoc /> | ||||||
|         public LimitCheck Check(RateLimitItemType type, RequestDefinition definition, string host, string? apiKey, int requestWeight) |         public LimitCheck Check(RateLimitItemType type, RequestDefinition definition, string host, string? apiKey, int requestWeight, string? keySuffix) | ||||||
|         { |         { | ||||||
|             if (type != Type) |             if (type != Type) | ||||||
|                 return LimitCheck.NotApplicable; |                 return LimitCheck.NotApplicable; | ||||||
| @ -55,7 +55,7 @@ namespace CryptoExchange.Net.RateLimiting.Guards | |||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         /// <inheritdoc /> |         /// <inheritdoc /> | ||||||
|         public RateLimitState ApplyWeight(RateLimitItemType type, RequestDefinition definition, string host, string? apiKey, int requestWeight) |         public RateLimitState ApplyWeight(RateLimitItemType type, RequestDefinition definition, string host, string? apiKey, int requestWeight, string? keySuffix) | ||||||
|         { |         { | ||||||
|             return RateLimitState.NotApplied; |             return RateLimitState.NotApplied; | ||||||
|         } |         } | ||||||
|  | |||||||
| @ -19,7 +19,7 @@ namespace CryptoExchange.Net.RateLimiting.Guards | |||||||
|         /// <summary> |         /// <summary> | ||||||
|         /// Endpoint limit per API key |         /// Endpoint limit per API key | ||||||
|         /// </summary> |         /// </summary> | ||||||
|         public static Func<RequestDefinition, string, string?, string> PerApiKey { get; } = new Func<RequestDefinition, string, string?, string>((def, host, key) => def.Path + def.Method); |         public static Func<RequestDefinition, string, string?, string> PerApiKey { get; } = new Func<RequestDefinition, string, string?, string>((def, host, key) => def.Path + def.Method + key); | ||||||
| 
 | 
 | ||||||
|         private readonly Dictionary<string, IWindowTracker> _trackers; |         private readonly Dictionary<string, IWindowTracker> _trackers; | ||||||
|         private readonly RateLimitWindowType _windowType; |         private readonly RateLimitWindowType _windowType; | ||||||
| @ -53,9 +53,9 @@ namespace CryptoExchange.Net.RateLimiting.Guards | |||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         /// <inheritdoc /> |         /// <inheritdoc /> | ||||||
|         public LimitCheck Check(RateLimitItemType type, RequestDefinition definition, string host, string? apiKey, int requestWeight) |         public LimitCheck Check(RateLimitItemType type, RequestDefinition definition, string host, string? apiKey, int requestWeight, string? keySuffix) | ||||||
|         { |         { | ||||||
|             var key = _keySelector(definition, host, apiKey); |             var key = _keySelector(definition, host, apiKey) + keySuffix; | ||||||
|             if (!_trackers.TryGetValue(key, out var tracker)) |             if (!_trackers.TryGetValue(key, out var tracker)) | ||||||
|             { |             { | ||||||
|                 tracker = CreateTracker(); |                 tracker = CreateTracker(); | ||||||
| @ -70,9 +70,9 @@ namespace CryptoExchange.Net.RateLimiting.Guards | |||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         /// <inheritdoc /> |         /// <inheritdoc /> | ||||||
|         public RateLimitState ApplyWeight(RateLimitItemType type, RequestDefinition definition, string host, string? apiKey, int requestWeight) |         public RateLimitState ApplyWeight(RateLimitItemType type, RequestDefinition definition, string host, string? apiKey, int requestWeight, string? keySuffix) | ||||||
|         { |         { | ||||||
|             var key = _keySelector(definition, host, apiKey); |             var key = _keySelector(definition, host, apiKey) + keySuffix; | ||||||
|             var tracker = _trackers[key]; |             var tracker = _trackers[key]; | ||||||
|             tracker.ApplyWeight(requestWeight); |             tracker.ApplyWeight(requestWeight); | ||||||
|             return RateLimitState.Applied(_limit, _period, tracker.Current); |             return RateLimitState.Applied(_limit, _period, tracker.Current); | ||||||
|  | |||||||
| @ -53,9 +53,10 @@ namespace CryptoExchange.Net.RateLimiting.Interfaces | |||||||
|         /// <param name="apiKey">The API key</param> |         /// <param name="apiKey">The API key</param> | ||||||
|         /// <param name="requestWeight">Request weight</param> |         /// <param name="requestWeight">Request weight</param> | ||||||
|         /// <param name="behaviour">Behaviour when rate limit is hit</param> |         /// <param name="behaviour">Behaviour when rate limit is hit</param> | ||||||
|  |         /// <param name="keySuffix">An additional optional suffix for the key selector. Can be used to make rate limiting work based on parameters.</param> | ||||||
|         /// <param name="ct">Cancelation token</param> |         /// <param name="ct">Cancelation token</param> | ||||||
|         /// <returns>Error if RateLimitingBehaviour is Fail and rate limit is hit</returns> |         /// <returns>Error if RateLimitingBehaviour is Fail and rate limit is hit</returns> | ||||||
|         Task<CallResult> ProcessAsync(ILogger logger, int itemId, RateLimitItemType type, RequestDefinition definition, string baseAddress, string? apiKey, int requestWeight, RateLimitingBehaviour behaviour, CancellationToken ct); |         Task<CallResult> ProcessAsync(ILogger logger, int itemId, RateLimitItemType type, RequestDefinition definition, string baseAddress, string? apiKey, int requestWeight, RateLimitingBehaviour behaviour, string? keySuffix, CancellationToken ct); | ||||||
| 
 | 
 | ||||||
|         /// <summary> |         /// <summary> | ||||||
|         /// Enforces the rate limit as defined in the request definition. When a rate limit is hit will wait for the rate limit to pass if RateLimitingBehaviour is Wait, or return an error if it is set to Fail |         /// Enforces the rate limit as defined in the request definition. When a rate limit is hit will wait for the rate limit to pass if RateLimitingBehaviour is Wait, or return an error if it is set to Fail | ||||||
| @ -69,8 +70,9 @@ namespace CryptoExchange.Net.RateLimiting.Interfaces | |||||||
|         /// <param name="apiKey">The API key</param> |         /// <param name="apiKey">The API key</param> | ||||||
|         /// <param name="behaviour">Behaviour when rate limit is hit</param> |         /// <param name="behaviour">Behaviour when rate limit is hit</param> | ||||||
|         /// <param name="requestWeight">The weight to apply to the limit guard</param> |         /// <param name="requestWeight">The weight to apply to the limit guard</param> | ||||||
|  |         /// <param name="keySuffix">An additional optional suffix for the key selector. Can be used to make rate limiting work based on parameters.</param> | ||||||
|         /// <param name="ct">Cancelation token</param> |         /// <param name="ct">Cancelation token</param> | ||||||
|         /// <returns>Error if RateLimitingBehaviour is Fail and rate limit is hit</returns> |         /// <returns>Error if RateLimitingBehaviour is Fail and rate limit is hit</returns> | ||||||
|         Task<CallResult> ProcessSingleAsync(ILogger logger, int itemId, IRateLimitGuard guard, RateLimitItemType type, RequestDefinition definition, string baseAddress, string? apiKey, int requestWeight, RateLimitingBehaviour behaviour, CancellationToken ct); |         Task<CallResult> ProcessSingleAsync(ILogger logger, int itemId, IRateLimitGuard guard, RateLimitItemType type, RequestDefinition definition, string baseAddress, string? apiKey, int requestWeight, RateLimitingBehaviour behaviour, string? keySuffix, CancellationToken ct); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -25,8 +25,9 @@ namespace CryptoExchange.Net.RateLimiting.Interfaces | |||||||
|         /// <param name="host">The host address</param> |         /// <param name="host">The host address</param> | ||||||
|         /// <param name="apiKey">The API key</param> |         /// <param name="apiKey">The API key</param> | ||||||
|         /// <param name="requestWeight">The request weight</param> |         /// <param name="requestWeight">The request weight</param> | ||||||
|  |         /// <param name="keySuffix">An additional optional suffix for the key selector. Can be used to make rate limiting work based on parameters.</param> | ||||||
|         /// <returns></returns> |         /// <returns></returns> | ||||||
|         LimitCheck Check(RateLimitItemType type, RequestDefinition definition, string host, string? apiKey, int requestWeight); |         LimitCheck Check(RateLimitItemType type, RequestDefinition definition, string host, string? apiKey, int requestWeight, string? keySuffix); | ||||||
| 
 | 
 | ||||||
|         /// <summary> |         /// <summary> | ||||||
|         /// Apply the request to this guard with the specified weight |         /// Apply the request to this guard with the specified weight | ||||||
| @ -36,7 +37,8 @@ namespace CryptoExchange.Net.RateLimiting.Interfaces | |||||||
|         /// <param name="host">The host address</param> |         /// <param name="host">The host address</param> | ||||||
|         /// <param name="apiKey">The API key</param> |         /// <param name="apiKey">The API key</param> | ||||||
|         /// <param name="requestWeight">The request weight</param> |         /// <param name="requestWeight">The request weight</param> | ||||||
|  |         /// <param name="keySuffix">An additional optional suffix for the key selector. Can be used to make rate limiting work based on parameters.</param> | ||||||
|         /// <returns></returns> |         /// <returns></returns> | ||||||
|         RateLimitState ApplyWeight(RateLimitItemType type, RequestDefinition definition, string host, string? apiKey, int requestWeight); |         RateLimitState ApplyWeight(RateLimitItemType type, RequestDefinition definition, string host, string? apiKey, int requestWeight, string? keySuffix); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -37,14 +37,14 @@ namespace CryptoExchange.Net.RateLimiting | |||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         /// <inheritdoc /> |         /// <inheritdoc /> | ||||||
|         public async Task<CallResult> ProcessAsync(ILogger logger, int itemId, RateLimitItemType type, RequestDefinition definition, string host, string? apiKey, int requestWeight, RateLimitingBehaviour rateLimitingBehaviour, CancellationToken ct) |         public async Task<CallResult> ProcessAsync(ILogger logger, int itemId, RateLimitItemType type, RequestDefinition definition, string host, string? apiKey, int requestWeight, RateLimitingBehaviour rateLimitingBehaviour, string? keySuffix, CancellationToken ct) | ||||||
|         { |         { | ||||||
|             await _semaphore.WaitAsync(ct).ConfigureAwait(false); |             await _semaphore.WaitAsync(ct).ConfigureAwait(false); | ||||||
|             bool release = true; |             bool release = true; | ||||||
|             _waitingCount++; |             _waitingCount++; | ||||||
|             try |             try | ||||||
|             { |             { | ||||||
|                 return await CheckGuardsAsync(_guards, logger, itemId, type, definition, host, apiKey, requestWeight, rateLimitingBehaviour, ct).ConfigureAwait(false); |                 return await CheckGuardsAsync(_guards, logger, itemId, type, definition, host, apiKey, requestWeight, rateLimitingBehaviour, keySuffix, ct).ConfigureAwait(false); | ||||||
|             } |             } | ||||||
|             catch (TaskCanceledException) |             catch (TaskCanceledException) | ||||||
|             { |             { | ||||||
| @ -71,6 +71,7 @@ namespace CryptoExchange.Net.RateLimiting | |||||||
|             string? apiKey, |             string? apiKey, | ||||||
|              int requestWeight, |              int requestWeight, | ||||||
|             RateLimitingBehaviour rateLimitingBehaviour, |             RateLimitingBehaviour rateLimitingBehaviour, | ||||||
|  |             string? keySuffix, | ||||||
|             CancellationToken ct) |             CancellationToken ct) | ||||||
|         { |         { | ||||||
|             await _semaphore.WaitAsync(ct).ConfigureAwait(false); |             await _semaphore.WaitAsync(ct).ConfigureAwait(false); | ||||||
| @ -78,7 +79,7 @@ namespace CryptoExchange.Net.RateLimiting | |||||||
|             _waitingCount++; |             _waitingCount++; | ||||||
|             try |             try | ||||||
|             { |             { | ||||||
|                 return await CheckGuardsAsync(new IRateLimitGuard[] { guard }, logger, itemId, type, definition, host, apiKey, requestWeight, rateLimitingBehaviour, ct).ConfigureAwait(false); |                 return await CheckGuardsAsync(new IRateLimitGuard[] { guard }, logger, itemId, type, definition, host, apiKey, requestWeight, rateLimitingBehaviour, keySuffix, ct).ConfigureAwait(false); | ||||||
|             } |             } | ||||||
|             catch (TaskCanceledException) |             catch (TaskCanceledException) | ||||||
|             { |             { | ||||||
| @ -94,12 +95,12 @@ namespace CryptoExchange.Net.RateLimiting | |||||||
|             } |             } | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         private async Task<CallResult> CheckGuardsAsync(IEnumerable<IRateLimitGuard> guards, ILogger logger, int itemId, RateLimitItemType type, RequestDefinition definition, string host, string? apiKey, int requestWeight, RateLimitingBehaviour rateLimitingBehaviour, CancellationToken ct) |         private async Task<CallResult> CheckGuardsAsync(IEnumerable<IRateLimitGuard> guards, ILogger logger, int itemId, RateLimitItemType type, RequestDefinition definition, string host, string? apiKey, int requestWeight, RateLimitingBehaviour rateLimitingBehaviour, string? keySuffix, CancellationToken ct) | ||||||
|         { |         { | ||||||
|             foreach (var guard in guards) |             foreach (var guard in guards) | ||||||
|             { |             { | ||||||
|                 // Check if a wait is needed for this guard |                 // Check if a wait is needed for this guard | ||||||
|                 var result = guard.Check(type, definition, host, apiKey, requestWeight); |                 var result = guard.Check(type, definition, host, apiKey, requestWeight, keySuffix); | ||||||
|                 if (result.Delay != TimeSpan.Zero && rateLimitingBehaviour == RateLimitingBehaviour.Fail) |                 if (result.Delay != TimeSpan.Zero && rateLimitingBehaviour == RateLimitingBehaviour.Fail) | ||||||
|                 { |                 { | ||||||
|                     // Delay is needed and limit behaviour is to fail the request |                     // Delay is needed and limit behaviour is to fail the request | ||||||
| @ -126,14 +127,14 @@ namespace CryptoExchange.Net.RateLimiting | |||||||
|                     RateLimitTriggered?.Invoke(new RateLimitEvent(itemId, _name, guard.Description, definition, host, result.Current, requestWeight, result.Limit, result.Period, result.Delay, rateLimitingBehaviour)); |                     RateLimitTriggered?.Invoke(new RateLimitEvent(itemId, _name, guard.Description, definition, host, result.Current, requestWeight, result.Limit, result.Period, result.Delay, rateLimitingBehaviour)); | ||||||
|                     await Task.Delay((int)result.Delay.TotalMilliseconds + 1, ct).ConfigureAwait(false); |                     await Task.Delay((int)result.Delay.TotalMilliseconds + 1, ct).ConfigureAwait(false); | ||||||
|                     await _semaphore.WaitAsync(ct).ConfigureAwait(false); |                     await _semaphore.WaitAsync(ct).ConfigureAwait(false); | ||||||
|                     return await CheckGuardsAsync(guards, logger, itemId, type, definition, host, apiKey, requestWeight, rateLimitingBehaviour, ct).ConfigureAwait(false); |                     return await CheckGuardsAsync(guards, logger, itemId, type, definition, host, apiKey, requestWeight, rateLimitingBehaviour, keySuffix, ct).ConfigureAwait(false); | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             // Apply the weight on each guard |             // Apply the weight on each guard | ||||||
|             foreach (var guard in guards) |             foreach (var guard in guards) | ||||||
|             { |             { | ||||||
|                 var result = guard.ApplyWeight(type, definition, host, apiKey, requestWeight); |                 var result = guard.ApplyWeight(type, definition, host, apiKey, requestWeight, keySuffix); | ||||||
|                 if (result.IsApplied) |                 if (result.IsApplied) | ||||||
|                 { |                 { | ||||||
|                     RateLimitUpdated?.Invoke(new RateLimitUpdateEvent(itemId, _name, guard.Description, result.Current, result.Limit, result.Period)); |                     RateLimitUpdated?.Invoke(new RateLimitUpdateEvent(itemId, _name, guard.Description, result.Current, result.Limit, result.Period)); | ||||||
|  | |||||||
| @ -219,7 +219,7 @@ namespace CryptoExchange.Net.Sockets | |||||||
|                 if (Parameters.RateLimiter != null) |                 if (Parameters.RateLimiter != null) | ||||||
|                 { |                 { | ||||||
|                     var definition = new RequestDefinition(Uri.AbsolutePath, HttpMethod.Get) { ConnectionId = Id }; |                     var definition = new RequestDefinition(Uri.AbsolutePath, HttpMethod.Get) { ConnectionId = Id }; | ||||||
|                     var limitResult = await Parameters.RateLimiter.ProcessAsync(_logger, Id, RateLimitItemType.Connection, definition, _baseAddress, null, 1, Parameters.RateLimitingBehavior, _ctsSource.Token).ConfigureAwait(false); |                     var limitResult = await Parameters.RateLimiter.ProcessAsync(_logger, Id, RateLimitItemType.Connection, definition, _baseAddress, null, 1, Parameters.RateLimitingBehavior, null, _ctsSource.Token).ConfigureAwait(false); | ||||||
|                     if (!limitResult) |                     if (!limitResult) | ||||||
|                         return new CallResult(new ClientRateLimitError("Connection limit reached")); |                         return new CallResult(new ClientRateLimitError("Connection limit reached")); | ||||||
|                 } |                 } | ||||||
| @ -508,7 +508,7 @@ namespace CryptoExchange.Net.Sockets | |||||||
|                         { |                         { | ||||||
|                             try |                             try | ||||||
|                             { |                             { | ||||||
|                                 var limitResult = await Parameters.RateLimiter.ProcessAsync(_logger, data.Id, RateLimitItemType.Request, requestDefinition, _baseAddress, null, data.Weight, Parameters.RateLimitingBehavior, _ctsSource.Token).ConfigureAwait(false); |                                 var limitResult = await Parameters.RateLimiter.ProcessAsync(_logger, data.Id, RateLimitItemType.Request, requestDefinition, _baseAddress, null, data.Weight, Parameters.RateLimitingBehavior, null, _ctsSource.Token).ConfigureAwait(false); | ||||||
|                                 if (!limitResult) |                                 if (!limitResult) | ||||||
|                                 { |                                 { | ||||||
|                                     await (OnRequestRateLimited?.Invoke(data.Id) ?? Task.CompletedTask).ConfigureAwait(false); |                                     await (OnRequestRateLimited?.Invoke(data.Id) ?? Task.CompletedTask).ConfigureAwait(false); | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user