mirror of
https://github.com/JKorf/CryptoExchange.Net.git
synced 2026-10-13 22:51:39 +00:00
Added WithRateLimitAdmissionAsync for REST and Socket clients
This commit is contained in:
@@ -1,10 +1,13 @@
|
||||
using System;
|
||||
using CryptoExchange.Net.Interfaces.Clients;
|
||||
using CryptoExchange.Net.Objects.Errors;
|
||||
using CryptoExchange.Net.Objects.Options;
|
||||
using CryptoExchange.Net.RateLimiting;
|
||||
using CryptoExchange.Net.SharedApis;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace CryptoExchange.Net.Clients
|
||||
{
|
||||
@@ -78,6 +81,11 @@ namespace CryptoExchange.Net.Clients
|
||||
/// </summary>
|
||||
protected internal virtual ErrorMapping ErrorMapping { get; } = new ErrorMapping([]);
|
||||
|
||||
/// <summary>
|
||||
/// Current rate limit admission override
|
||||
/// </summary>
|
||||
protected readonly AsyncLocal<RateLimitAdmission?> AdmissionOverride = new();
|
||||
|
||||
/// <summary>
|
||||
/// ctor
|
||||
/// </summary>
|
||||
@@ -122,6 +130,29 @@ namespace CryptoExchange.Net.Clients
|
||||
/// </summary>
|
||||
public ErrorInfo GetErrorInfo(string code, string? message = null) => ErrorMapping.GetErrorInfo(code.ToString(), message);
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<TResult> WithRateLimitAdmissionAsync<TResult>(
|
||||
RateLimitAdmission admission,
|
||||
Func<Task<TResult>> operation)
|
||||
{
|
||||
if (admission == null)
|
||||
throw new ArgumentNullException(nameof(admission));
|
||||
if (operation == null)
|
||||
throw new ArgumentNullException(nameof(operation));
|
||||
|
||||
var previous = AdmissionOverride.Value;
|
||||
AdmissionOverride.Value = admission;
|
||||
|
||||
try
|
||||
{
|
||||
return await operation().ConfigureAwait(false);
|
||||
}
|
||||
finally
|
||||
{
|
||||
AdmissionOverride.Value = previous;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Dispose
|
||||
/// </summary>
|
||||
|
||||
@@ -350,9 +350,8 @@ namespace CryptoExchange.Net.Clients
|
||||
|
||||
if (ClientOptions.RateLimiterEnabled)
|
||||
{
|
||||
var rateRatio = 1.0;
|
||||
if (ClientOptions.RateLimitAdmission != null)
|
||||
rateRatio = ClientOptions.RateLimitAdmission(definition, requestWeight).MaxUtilization;
|
||||
var admissionOverride = AdmissionOverride.Value ?? ClientOptions.RateLimitAdmission?.Invoke(definition, requestWeight);
|
||||
var rateRatio = admissionOverride?.MaxUtilizationRatio ?? 1.0;
|
||||
|
||||
var limitResult = await definition.RateLimitGate.ProcessAsync(
|
||||
_logger,
|
||||
@@ -379,9 +378,8 @@ namespace CryptoExchange.Net.Clients
|
||||
if (ClientOptions.RateLimiterEnabled)
|
||||
{
|
||||
var singleRequestWeight = weightSingleLimiter ?? 1;
|
||||
var rateRatio = 1.0;
|
||||
if (ClientOptions.RateLimitAdmission != null)
|
||||
rateRatio = ClientOptions.RateLimitAdmission(definition, singleRequestWeight).MaxUtilization;
|
||||
var admissionOverride = AdmissionOverride.Value ?? ClientOptions.RateLimitAdmission?.Invoke(definition, singleRequestWeight);
|
||||
var rateRatio = admissionOverride?.MaxUtilizationRatio ?? 1.0;
|
||||
|
||||
var limitResult = await definition.RateLimitGate.ProcessSingleAsync(
|
||||
_logger,
|
||||
|
||||
@@ -858,7 +858,8 @@ namespace CryptoExchange.Net.Clients
|
||||
ReconnectInterval = ClientOptions.ReconnectInterval,
|
||||
RateLimiter = ClientOptions.RateLimiterEnabled ? RateLimiter : null,
|
||||
RateLimitingBehavior = ClientOptions.RateLimitingBehaviour,
|
||||
RateLimitAdmission = ClientOptions.RateLimitAdmission,
|
||||
RateLimitAdmissionCallbackOptions = (def, weight) => ClientOptions.RateLimitAdmission?.Invoke(def, weight),
|
||||
RateLimitAdmissionCallbackRequest = () => AdmissionOverride.Value,
|
||||
Proxy = ClientOptions.Proxy,
|
||||
Timeout = ApiOptions.SocketNoDataTimeout ?? ClientOptions.SocketNoDataTimeout,
|
||||
ReceiveBufferSize = ClientOptions.ReceiveBufferSize,
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
using CryptoExchange.Net.Interfaces;
|
||||
using CryptoExchange.Net.Clients;
|
||||
using CryptoExchange.Net.Interfaces;
|
||||
using CryptoExchange.Net.Interfaces.Clients;
|
||||
using CryptoExchange.Net.Objects;
|
||||
using CryptoExchange.Net.RateLimiting;
|
||||
using CryptoExchange.Net.SharedApis;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using System;
|
||||
@@ -11,6 +14,7 @@ using System.IO.Compression;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web;
|
||||
|
||||
namespace CryptoExchange.Net
|
||||
@@ -510,6 +514,23 @@ namespace CryptoExchange.Net
|
||||
|
||||
return bytes;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Execute an operation with a specific rate limit admission rule
|
||||
/// </summary>
|
||||
/// <param name="client">Client</param>
|
||||
/// <param name="admission">Rate limit admission rule</param>
|
||||
/// <param name="operation">Operation to execute</param>
|
||||
public static Task<TResult> WithRateLimitAdmissionAsync<TClient, TResult>(
|
||||
this TClient client,
|
||||
RateLimitAdmission admission,
|
||||
Func<TClient, Task<TResult>> operation)
|
||||
where TClient : IRateLimitAdmissionClient
|
||||
{
|
||||
return client.WithRateLimitAdmissionAsync(
|
||||
admission,
|
||||
() => operation(client));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
using CryptoExchange.Net.Authentication;
|
||||
using CryptoExchange.Net.Objects.Options;
|
||||
using CryptoExchange.Net.SharedApis;
|
||||
using CryptoExchange.Net.SharedApis;
|
||||
using System;
|
||||
|
||||
namespace CryptoExchange.Net.Interfaces.Clients
|
||||
@@ -8,7 +6,7 @@ namespace CryptoExchange.Net.Interfaces.Clients
|
||||
/// <summary>
|
||||
/// Base api client
|
||||
/// </summary>
|
||||
public interface IBaseApiClient
|
||||
public interface IBaseApiClient : IRateLimitAdmissionClient
|
||||
{
|
||||
/// <summary>
|
||||
/// Exchange name
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
using CryptoExchange.Net.RateLimiting;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace CryptoExchange.Net.Interfaces
|
||||
{
|
||||
/// <summary>
|
||||
/// A client supporting rate limit admission rules
|
||||
/// </summary>
|
||||
public interface IRateLimitAdmissionClient
|
||||
{
|
||||
/// <summary>
|
||||
/// Execute an operation with a specific rate limit admission rule
|
||||
/// </summary>
|
||||
Task<TResult> WithRateLimitAdmissionAsync<TResult>(
|
||||
RateLimitAdmission admission,
|
||||
Func<Task<TResult>> operation);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -69,7 +69,12 @@ namespace CryptoExchange.Net.Objects.Sockets
|
||||
/// A delegate receiving the request definition and the request weight that can be used to determine whether a request should be admitted or rejected based
|
||||
/// on the request definition and the current rate limit usage ratio. This allows for custom rate limiting logic to be implemented.
|
||||
/// </summary>
|
||||
public Func<RequestDefinition, int, RateLimitAdmission>? RateLimitAdmission { get; set; }
|
||||
public Func<RequestDefinition, int, RateLimitAdmission?>? RateLimitAdmissionCallbackOptions { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// A delegate for retrieving the current rate limit admission decision for a specific request. This allows for custom rate limiting logic to be implemented.
|
||||
/// </summary>
|
||||
public Func<RateLimitAdmission?>? RateLimitAdmissionCallbackRequest { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Encoding for sending/receiving data
|
||||
|
||||
@@ -32,7 +32,7 @@ namespace CryptoExchange.Net.RateLimiting.Guards
|
||||
public string Name => "EndpointLimitGuard";
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Description => $"Limit requests to endpoint";
|
||||
public string Description => _windowType == RateLimitWindowType.Decay ? $"Endpoint limit of {_limit} with a decay rate of {_decayRate}" : $"Limit of {_limit} per {_period}";
|
||||
|
||||
/// <summary>
|
||||
/// ctor
|
||||
|
||||
@@ -12,13 +12,13 @@ namespace CryptoExchange.Net.RateLimiting
|
||||
/// <summary>
|
||||
/// Value ratio between 0 and 1
|
||||
/// </summary>
|
||||
public double MaxUtilization { get; }
|
||||
public double MaxUtilizationRatio { get; }
|
||||
|
||||
private RateLimitAdmission(double maxUtilizationValue) {
|
||||
if (maxUtilizationValue <= 0 || maxUtilizationValue > 1)
|
||||
throw new ArgumentOutOfRangeException(nameof(maxUtilizationValue), "Max utilization value must be bigger than 0 and less than or equal to 1");
|
||||
|
||||
MaxUtilization = maxUtilizationValue;
|
||||
MaxUtilizationRatio = maxUtilizationValue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -26,7 +26,7 @@ namespace CryptoExchange.Net.RateLimiting
|
||||
/// 1 means it's allowed to use the full rate limit.
|
||||
/// </summary>
|
||||
/// <param name="value">0.5 means a max use 50% of the rate limit, 1 means the request is allowed to use the full rate limit</param>
|
||||
public static RateLimitAdmission WithMaxUtilization(double value)
|
||||
public static RateLimitAdmission WithMaxUtilizationRatio(double value)
|
||||
=> new RateLimitAdmission(value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -131,7 +131,10 @@ namespace CryptoExchange.Net.RateLimiting
|
||||
logger.RateLimitRequestFailed(itemId, definition.Path, guard.Name, guard.Description);
|
||||
|
||||
RateLimitTriggered?.Invoke(new RateLimitEvent(itemId, _name, guard.Description, definition, result.Current, requestWeight, result.Limit, result.Period, result.Delay, rateLimitingBehaviour));
|
||||
return CallResult.Fail(new ClientRateLimitError($"Rate limit check failed on guard {guard.Name}; {guard.Description}"));
|
||||
var errorMsg = $"Rate limit check failed on guard {guard.Name}; {guard.Description}";
|
||||
if (allowedRateRatio < 1)
|
||||
errorMsg += $" with allowed ratio {allowedRateRatio}";
|
||||
return CallResult.Fail(new ClientRateLimitError(errorMsg));
|
||||
}
|
||||
|
||||
if (result.Delay != TimeSpan.Zero)
|
||||
@@ -139,7 +142,14 @@ namespace CryptoExchange.Net.RateLimiting
|
||||
// Delay is needed and limit behaviour is to wait for the request to be under the limit
|
||||
_semaphore.Release();
|
||||
|
||||
var description = result.Limit == null ? guard.Description : $"{guard.Description}, Request weight: {requestWeight}, Current: {result.Current}, Limit: {result.Limit}, requests now being limited: {_waitingCount}";
|
||||
var description =
|
||||
result.Limit == null
|
||||
? guard.Description
|
||||
: $"{guard.Description}, Request weight: {requestWeight}," +
|
||||
$" Current: {result.Current}" +
|
||||
$", Limit: {result.Limit}" +
|
||||
(allowedRateRatio < 1 ? $", Allowed ratio: {allowedRateRatio}" : "") +
|
||||
$", requests now being limited: {_waitingCount}";
|
||||
if (type == RateLimitItemType.Connection)
|
||||
logger.RateLimitDelayingConnection(itemId, result.Delay, guard.Name, description);
|
||||
else
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
using CryptoExchange.Net.Clients;
|
||||
using CryptoExchange.Net.Interfaces.Clients;
|
||||
using CryptoExchange.Net.RateLimiting;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace CryptoExchange.Net.SharedApis
|
||||
{
|
||||
@@ -12,7 +15,7 @@ namespace CryptoExchange.Net.SharedApis
|
||||
{
|
||||
private readonly Func<string, string, TradingMode, DateTime?, string> _symbolFormatter;
|
||||
private readonly Func<bool> _authDelegate;
|
||||
|
||||
private readonly IBaseApiClient _apiClient;
|
||||
private IReadOnlyCollection<CapabilityOptions> _capabilities = Array.Empty<CapabilityOptions>();
|
||||
IReadOnlyCollection<CapabilityOptions> ISharedApi.Capabilities => _capabilities;
|
||||
|
||||
@@ -33,16 +36,17 @@ namespace CryptoExchange.Net.SharedApis
|
||||
/// </summary>
|
||||
public SharedApiBase(
|
||||
SharedTransport transport,
|
||||
string exchange,
|
||||
IBaseApiClient apiClient,
|
||||
TradingMode[] supportedTradingModes,
|
||||
Func<bool> authenticated,
|
||||
Func<string, string, TradingMode, DateTime?, string> formatSymbol)
|
||||
{
|
||||
Transport = transport;
|
||||
Exchange = exchange;
|
||||
SupportedTradingModes = supportedTradingModes;
|
||||
_apiClient = apiClient;
|
||||
_authDelegate = authenticated;
|
||||
_symbolFormatter = formatSymbol;
|
||||
Transport = transport;
|
||||
Exchange = apiClient.Exchange;
|
||||
SupportedTradingModes = supportedTradingModes;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -64,6 +68,12 @@ namespace CryptoExchange.Net.SharedApis
|
||||
/// <inheritdoc />
|
||||
public void ResetDefaultExchangeParameters() => ExchangeParameters.ResetStaticExchangeParameters(Exchange);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<TResult> WithRateLimitAdmissionAsync<TResult>(
|
||||
RateLimitAdmission admission,
|
||||
Func<Task<TResult>> operation)
|
||||
=> _apiClient.WithRateLimitAdmissionAsync(admission, operation);
|
||||
|
||||
/// <inheritdoc />
|
||||
public abstract SharedClientInfo Discover();
|
||||
}
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
using System;
|
||||
using CryptoExchange.Net.Interfaces;
|
||||
using CryptoExchange.Net.RateLimiting;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace CryptoExchange.Net.SharedApis
|
||||
{
|
||||
/// <summary>
|
||||
/// Shared API
|
||||
/// </summary>
|
||||
public interface ISharedApi
|
||||
public interface ISharedApi : IRateLimitAdmissionClient
|
||||
{
|
||||
/// <summary>
|
||||
/// Name of the exchange
|
||||
|
||||
@@ -208,11 +208,17 @@ namespace CryptoExchange.Net.Sockets.Default
|
||||
{
|
||||
if (Parameters.RateLimiter != null)
|
||||
{
|
||||
var rateRatio = 1.0;
|
||||
if (Parameters.RateLimitAdmission != null)
|
||||
rateRatio = Parameters.RateLimitAdmission(_requestDefinition, 1).MaxUtilization;
|
||||
|
||||
var limitResult = await Parameters.RateLimiter.ProcessAsync(_logger, Id, RateLimitItemType.Connection, _requestDefinition, null, 1, Parameters.RateLimitingBehavior, null, rateRatio, _ctsSource.Token).ConfigureAwait(false);
|
||||
var limitResult = await Parameters.RateLimiter.ProcessAsync(
|
||||
_logger,
|
||||
Id,
|
||||
RateLimitItemType.Connection,
|
||||
_requestDefinition,
|
||||
null,
|
||||
1,
|
||||
Parameters.RateLimitingBehavior,
|
||||
null,
|
||||
1.0,
|
||||
_ctsSource.Token).ConfigureAwait(false);
|
||||
if (!limitResult.Success)
|
||||
return CallResult.Fail(new ClientRateLimitError("Connection limit reached"));
|
||||
}
|
||||
@@ -386,7 +392,15 @@ namespace CryptoExchange.Net.Sockets.Default
|
||||
|
||||
var bytes = Parameters.Encoding.GetBytes(data);
|
||||
_logger.SocketAddingBytesToSendBuffer(Id, id, bytes);
|
||||
_sendBuffer.Enqueue(new SendItem { Id = id, Type = WebSocketMessageType.Text, Weight = weight, Bytes = bytes });
|
||||
_sendBuffer.Enqueue(
|
||||
new SendItem
|
||||
{
|
||||
Id = id,
|
||||
Type = WebSocketMessageType.Text,
|
||||
Weight = weight,
|
||||
Bytes = bytes,
|
||||
RateLimitAdmission = Parameters.RateLimitAdmissionCallbackRequest?.Invoke()
|
||||
});
|
||||
_sendEvent.Set();
|
||||
return true;
|
||||
}
|
||||
@@ -398,7 +412,14 @@ namespace CryptoExchange.Net.Sockets.Default
|
||||
return false;
|
||||
|
||||
_logger.SocketAddingBytesToSendBuffer(Id, id, data);
|
||||
_sendBuffer.Enqueue(new SendItem { Id = id, Type = WebSocketMessageType.Binary, Weight = weight, Bytes = data });
|
||||
_sendBuffer.Enqueue(
|
||||
new SendItem {
|
||||
Id = id,
|
||||
Type = WebSocketMessageType.Binary,
|
||||
Weight = weight,
|
||||
Bytes = data,
|
||||
RateLimitAdmission = Parameters.RateLimitAdmissionCallbackRequest?.Invoke()
|
||||
});
|
||||
_sendEvent.Set();
|
||||
return true;
|
||||
}
|
||||
@@ -534,9 +555,8 @@ namespace CryptoExchange.Net.Sockets.Default
|
||||
{
|
||||
if (Parameters.RateLimiter != null)
|
||||
{
|
||||
var rateRatio = 1.0;
|
||||
if (Parameters.RateLimitAdmission != null)
|
||||
rateRatio = Parameters.RateLimitAdmission(_requestDefinition, data.Weight).MaxUtilization;
|
||||
var admission = data.RateLimitAdmission ?? Parameters.RateLimitAdmissionCallbackOptions?.Invoke(_requestDefinition, data.Weight);
|
||||
var rateRatio = admission?.MaxUtilizationRatio ?? 1.0;
|
||||
|
||||
try
|
||||
{
|
||||
@@ -1035,6 +1055,11 @@ namespace CryptoExchange.Net.Sockets.Default
|
||||
/// </summary>
|
||||
public WebSocketMessageType Type { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Rate limit admission override
|
||||
/// </summary>
|
||||
public RateLimitAdmission? RateLimitAdmission { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The bytes to send
|
||||
/// </summary>
|
||||
|
||||
Reference in New Issue
Block a user