mirror of
https://github.com/JKorf/CryptoExchange.Net
synced 2026-04-12 16:13:12 +00:00
Compare commits
5 Commits
b29cdc41f3
...
89a73747b0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
89a73747b0 | ||
|
|
02b70398b3 | ||
|
|
73fcb47b17 | ||
|
|
d41ca3459e | ||
|
|
bea2b2bd7b |
@ -6,9 +6,9 @@
|
||||
<PackageId>CryptoExchange.Net</PackageId>
|
||||
<Authors>JKorf</Authors>
|
||||
<Description>CryptoExchange.Net is a base library which is used to implement different cryptocurrency (exchange) API's. It provides a standardized way of implementing different API's, which results in a very similar experience for users of the API implementations.</Description>
|
||||
<PackageVersion>10.6.2</PackageVersion>
|
||||
<AssemblyVersion>10.6.2</AssemblyVersion>
|
||||
<FileVersion>10.6.2</FileVersion>
|
||||
<PackageVersion>10.7.0</PackageVersion>
|
||||
<AssemblyVersion>10.7.0</AssemblyVersion>
|
||||
<FileVersion>10.7.0</FileVersion>
|
||||
<PackageRequireLicenseAcceptance>false</PackageRequireLicenseAcceptance>
|
||||
<PackageTags>OKX;OKX.Net;Mexc;Mexc.Net;Kucoin;Kucoin.Net;Kraken;Kraken.Net;Huobi;Huobi.Net;CoinEx;CoinEx.Net;Bybit;Bybit.Net;Bitget;Bitget.Net;Bitfinex;Bitfinex.Net;Binance;Binance.Net;CryptoCurrency;CryptoCurrency Exchange;CryptoExchange.Net</PackageTags>
|
||||
<RepositoryType>git</RepositoryType>
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
using CryptoExchange.Net.Objects;
|
||||
using CryptoExchange.Net.Objects.Options;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@ -105,31 +106,36 @@ namespace CryptoExchange.Net
|
||||
/// <summary>
|
||||
/// Create a new HttpMessageHandler instance
|
||||
/// </summary>
|
||||
public static HttpMessageHandler CreateHttpClientMessageHandler(ApiProxy? proxy, TimeSpan? keepAliveInterval)
|
||||
public static HttpMessageHandler CreateHttpClientMessageHandler(RestExchangeOptions options)
|
||||
{
|
||||
#if NET5_0_OR_GREATER
|
||||
var socketHandler = new SocketsHttpHandler();
|
||||
try
|
||||
{
|
||||
if (keepAliveInterval != null && keepAliveInterval != TimeSpan.Zero)
|
||||
if (options.HttpKeepAliveInterval != null && options.HttpKeepAliveInterval != TimeSpan.Zero)
|
||||
{
|
||||
socketHandler.KeepAlivePingPolicy = HttpKeepAlivePingPolicy.Always;
|
||||
socketHandler.KeepAlivePingDelay = keepAliveInterval.Value;
|
||||
socketHandler.KeepAlivePingDelay = options.HttpKeepAliveInterval.Value;
|
||||
socketHandler.KeepAlivePingTimeout = TimeSpan.FromSeconds(10);
|
||||
}
|
||||
|
||||
socketHandler.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate;
|
||||
socketHandler.DefaultProxyCredentials = CredentialCache.DefaultCredentials;
|
||||
|
||||
socketHandler.EnableMultipleHttp2Connections = options.HttpEnableMultipleHttp2Connections;
|
||||
socketHandler.PooledConnectionLifetime = options.HttpPooledConnectionLifetime;
|
||||
socketHandler.PooledConnectionIdleTimeout = options.HttpPooledConnectionIdleTimeout;
|
||||
socketHandler.MaxConnectionsPerServer = options.HttpMaxConnectionsPerServer;
|
||||
}
|
||||
catch (PlatformNotSupportedException) { }
|
||||
catch (NotImplementedException) { } // Mono runtime throws NotImplementedException
|
||||
|
||||
if (proxy != null)
|
||||
if (options.Proxy != null)
|
||||
{
|
||||
socketHandler.Proxy = new WebProxy
|
||||
{
|
||||
Address = new Uri($"{proxy.Host}:{proxy.Port}"),
|
||||
Credentials = proxy.Password == null ? null : new NetworkCredential(proxy.Login, proxy.Password)
|
||||
Address = new Uri($"{options.Proxy.Host}:{options.Proxy.Port}"),
|
||||
Credentials = options.Proxy.Password == null ? null : new NetworkCredential(options.Proxy.Login, options.Proxy.Password)
|
||||
};
|
||||
}
|
||||
return socketHandler;
|
||||
@ -143,12 +149,12 @@ namespace CryptoExchange.Net
|
||||
catch (PlatformNotSupportedException) { }
|
||||
catch (NotImplementedException) { } // Mono runtime throws NotImplementedException
|
||||
|
||||
if (proxy != null)
|
||||
if (options.Proxy != null)
|
||||
{
|
||||
httpHandler.Proxy = new WebProxy
|
||||
{
|
||||
Address = new Uri($"{proxy.Host}:{proxy.Port}"),
|
||||
Credentials = proxy.Password == null ? null : new NetworkCredential(proxy.Login, proxy.Password)
|
||||
Address = new Uri($"{options.Proxy.Host}:{options.Proxy.Port}"),
|
||||
Credentials = options.Proxy.Password == null ? null : new NetworkCredential(options.Proxy.Login, options.Proxy.Password)
|
||||
};
|
||||
}
|
||||
return httpHandler;
|
||||
|
||||
@ -32,10 +32,29 @@ namespace CryptoExchange.Net.Objects.Options
|
||||
#else
|
||||
= new Version(1, 1);
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Http client keep alive interval for keeping connections open
|
||||
/// Http client keep alive interval for keeping connections open. Only applied when using dotnet8.0 or higher and dependency injection
|
||||
/// </summary>
|
||||
public TimeSpan? HttpKeepAliveInterval { get; set; } = TimeSpan.FromSeconds(15);
|
||||
#if NET5_0_OR_GREATER
|
||||
/// <summary>
|
||||
/// Enable multiple simultaneous HTTP 2 connections. Only applied when using dependency injection
|
||||
/// </summary>
|
||||
public bool HttpEnableMultipleHttp2Connections { get; set; } = false;
|
||||
/// <summary>
|
||||
/// Lifetime of pooled HTTP connections; the time before a connection is recreated. Only applied when using dependency injection
|
||||
/// </summary>
|
||||
public TimeSpan HttpPooledConnectionLifetime { get; set; } = TimeSpan.FromMinutes(15);
|
||||
/// <summary>
|
||||
/// Idle timeout of pooled HTTP connections; the time before an open connection is closed when there are no requests. Only applied when using dependency injection
|
||||
/// </summary>
|
||||
public TimeSpan HttpPooledConnectionIdleTimeout { get; set; } = TimeSpan.FromMinutes(2);
|
||||
/// <summary>
|
||||
/// Max number of connections per server. Only applied when using dependency injection
|
||||
/// </summary>
|
||||
public int HttpMaxConnectionsPerServer { get; set; } = int.MaxValue;
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Set the values of this options on the target options
|
||||
@ -54,6 +73,12 @@ namespace CryptoExchange.Net.Objects.Options
|
||||
item.CachingMaxAge = CachingMaxAge;
|
||||
item.HttpVersion = HttpVersion;
|
||||
item.HttpKeepAliveInterval = HttpKeepAliveInterval;
|
||||
#if NET5_0_OR_GREATER
|
||||
item.HttpMaxConnectionsPerServer = HttpMaxConnectionsPerServer;
|
||||
item.HttpPooledConnectionLifetime = HttpPooledConnectionLifetime;
|
||||
item.HttpPooledConnectionIdleTimeout = HttpPooledConnectionIdleTimeout;
|
||||
item.HttpEnableMultipleHttp2Connections = HttpEnableMultipleHttp2Connections;
|
||||
#endif
|
||||
return item;
|
||||
}
|
||||
}
|
||||
|
||||
@ -12,14 +12,16 @@ namespace CryptoExchange.Net.Requests
|
||||
public class RequestFactory : IRequestFactory
|
||||
{
|
||||
private HttpClient? _httpClient;
|
||||
private RestExchangeOptions? _options;
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Configure(RestExchangeOptions options, HttpClient? client = null)
|
||||
{
|
||||
if (client == null)
|
||||
client = CreateClient(options.Proxy, options.RequestTimeout, options.HttpKeepAliveInterval);
|
||||
client = CreateClient(options);
|
||||
|
||||
_httpClient = client;
|
||||
_options = options;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@ -39,15 +41,20 @@ namespace CryptoExchange.Net.Requests
|
||||
/// <inheritdoc />
|
||||
public void UpdateSettings(ApiProxy? proxy, TimeSpan requestTimeout, TimeSpan? httpKeepAliveInterval)
|
||||
{
|
||||
_httpClient = CreateClient(proxy, requestTimeout, httpKeepAliveInterval);
|
||||
var newOptions = new RestExchangeOptions();
|
||||
_options!.Set(newOptions);
|
||||
newOptions.Proxy = proxy;
|
||||
newOptions.RequestTimeout = requestTimeout;
|
||||
newOptions.HttpKeepAliveInterval = httpKeepAliveInterval;
|
||||
_httpClient = CreateClient(newOptions);
|
||||
}
|
||||
|
||||
private static HttpClient CreateClient(ApiProxy? proxy, TimeSpan requestTimeout, TimeSpan? httpKeepAliveInterval)
|
||||
private static HttpClient CreateClient(RestExchangeOptions options)
|
||||
{
|
||||
var handler = LibraryHelpers.CreateHttpClientMessageHandler(proxy, httpKeepAliveInterval);
|
||||
var handler = LibraryHelpers.CreateHttpClientMessageHandler(options);
|
||||
var client = new HttpClient(handler)
|
||||
{
|
||||
Timeout = requestTimeout
|
||||
Timeout = options.RequestTimeout
|
||||
};
|
||||
return client;
|
||||
}
|
||||
|
||||
@ -123,8 +123,15 @@ namespace CryptoExchange.Net.Sockets.Default
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (_listenersLock)
|
||||
_listenersLock.EnterReadLock();
|
||||
try
|
||||
{
|
||||
return _listeners.OfType<Subscription>().Count(h => h.UserSubscription);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_listenersLock.ExitReadLock();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -135,8 +142,16 @@ namespace CryptoExchange.Net.Sockets.Default
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (_listenersLock)
|
||||
_listenersLock.EnterReadLock();
|
||||
try
|
||||
{
|
||||
return _listeners.OfType<Subscription>().Where(h => h.UserSubscription).ToArray();
|
||||
|
||||
}
|
||||
finally
|
||||
{
|
||||
_listenersLock.ExitReadLock();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -240,8 +255,15 @@ namespace CryptoExchange.Net.Sockets.Default
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (_listenersLock)
|
||||
_listenersLock.EnterReadLock();
|
||||
try
|
||||
{
|
||||
return _listeners.OfType<Subscription>().Select(x => x.Topic).Where(t => t != null).ToArray()!;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_listenersLock.ExitReadLock();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -252,18 +274,21 @@ namespace CryptoExchange.Net.Sockets.Default
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (_listenersLock)
|
||||
_listenersLock.EnterReadLock();
|
||||
try
|
||||
{
|
||||
return _listeners.OfType<Query>().Where(x => !x.Completed).Count();
|
||||
}
|
||||
finally
|
||||
{
|
||||
_listenersLock.ExitReadLock();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private bool _pausedActivity;
|
||||
#if NET9_0_OR_GREATER
|
||||
private readonly Lock _listenersLock = new Lock();
|
||||
#else
|
||||
private readonly object _listenersLock = new object();
|
||||
#endif
|
||||
private readonly ReaderWriterLockSlim _listenersLock = new ReaderWriterLockSlim();
|
||||
private readonly List<IMessageProcessor> _listeners;
|
||||
private readonly ILogger _logger;
|
||||
private SocketStatus _status;
|
||||
@ -340,7 +365,8 @@ namespace CryptoExchange.Net.Sockets.Default
|
||||
if (ApiClient._socketConnections.ContainsKey(SocketId))
|
||||
ApiClient._socketConnections.TryRemove(SocketId, out _);
|
||||
|
||||
lock (_listenersLock)
|
||||
_listenersLock.EnterWriteLock();
|
||||
try
|
||||
{
|
||||
foreach (var subscription in _listeners.OfType<Subscription>().Where(l => l.UserSubscription && !l.IsClosingConnection))
|
||||
{
|
||||
@ -354,6 +380,10 @@ namespace CryptoExchange.Net.Sockets.Default
|
||||
_listeners.Remove(query);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_listenersLock.ExitWriteLock();
|
||||
}
|
||||
|
||||
_ = Task.Run(() => ConnectionClosed?.Invoke());
|
||||
return Task.CompletedTask;
|
||||
@ -369,7 +399,8 @@ namespace CryptoExchange.Net.Sockets.Default
|
||||
Authenticated = false;
|
||||
_lastSequenceNumber = 0;
|
||||
|
||||
lock (_listenersLock)
|
||||
_listenersLock.EnterWriteLock();
|
||||
try
|
||||
{
|
||||
foreach (var subscription in _listeners.OfType<Subscription>().Where(l => l.UserSubscription))
|
||||
subscription.Reset();
|
||||
@ -380,6 +411,10 @@ namespace CryptoExchange.Net.Sockets.Default
|
||||
_listeners.Remove(query);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_listenersLock.ExitWriteLock();
|
||||
}
|
||||
|
||||
_ = Task.Run(() => ConnectionLost?.Invoke());
|
||||
return Task.CompletedTask;
|
||||
@ -401,7 +436,8 @@ namespace CryptoExchange.Net.Sockets.Default
|
||||
{
|
||||
Status = SocketStatus.Resubscribing;
|
||||
|
||||
lock (_listenersLock)
|
||||
_listenersLock.EnterWriteLock();
|
||||
try
|
||||
{
|
||||
foreach (var query in _listeners.OfType<Query>().ToList())
|
||||
{
|
||||
@ -409,6 +445,10 @@ namespace CryptoExchange.Net.Sockets.Default
|
||||
_listeners.Remove(query);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_listenersLock.ExitWriteLock();
|
||||
}
|
||||
|
||||
// Can't wait for this as it would cause a deadlock
|
||||
_ = Task.Run(async () =>
|
||||
@ -464,10 +504,15 @@ namespace CryptoExchange.Net.Sockets.Default
|
||||
protected virtual Task HandleRequestRateLimitedAsync(int requestId)
|
||||
{
|
||||
Query? query;
|
||||
lock (_listenersLock)
|
||||
_listenersLock.EnterReadLock();
|
||||
try
|
||||
{
|
||||
query = _listeners.OfType<Query>().FirstOrDefault(x => x.Id == requestId);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_listenersLock.ExitReadLock();
|
||||
}
|
||||
|
||||
if (query == null)
|
||||
return Task.CompletedTask;
|
||||
@ -493,10 +538,15 @@ namespace CryptoExchange.Net.Sockets.Default
|
||||
protected virtual Task HandleRequestSentAsync(int requestId)
|
||||
{
|
||||
Query? query;
|
||||
lock (_listenersLock)
|
||||
_listenersLock.EnterReadLock();
|
||||
try
|
||||
{
|
||||
query = _listeners.OfType<Query>().FirstOrDefault(x => x.Id == requestId);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_listenersLock.ExitReadLock();
|
||||
}
|
||||
|
||||
if (query == null)
|
||||
return Task.CompletedTask;
|
||||
@ -543,7 +593,8 @@ namespace CryptoExchange.Net.Sockets.Default
|
||||
}
|
||||
|
||||
Type? deserializationType = null;
|
||||
lock (_listenersLock)
|
||||
_listenersLock.EnterReadLock();
|
||||
try
|
||||
{
|
||||
foreach (var subscription in _listeners)
|
||||
{
|
||||
@ -560,6 +611,10 @@ namespace CryptoExchange.Net.Sockets.Default
|
||||
break;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_listenersLock.ExitReadLock();
|
||||
}
|
||||
|
||||
if (deserializationType == null)
|
||||
{
|
||||
@ -605,7 +660,8 @@ namespace CryptoExchange.Net.Sockets.Default
|
||||
var topicFilter = messageConverter.GetTopicFilter(result);
|
||||
|
||||
bool processed = false;
|
||||
lock (_listenersLock)
|
||||
_listenersLock.EnterReadLock();
|
||||
try
|
||||
{
|
||||
var currentCount = _listeners.Count;
|
||||
for(var i = 0; i < _listeners.Count; i++)
|
||||
@ -670,12 +726,17 @@ namespace CryptoExchange.Net.Sockets.Default
|
||||
break;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_listenersLock.ExitReadLock();
|
||||
}
|
||||
|
||||
if (!processed)
|
||||
{
|
||||
if (!ApiClient.HandleUnhandledMessage(this, typeIdentifier, data))
|
||||
{
|
||||
lock (_listenersLock)
|
||||
_listenersLock.EnterReadLock();
|
||||
try
|
||||
{
|
||||
_logger.ReceivedMessageNotMatchedToAnyListener(
|
||||
SocketId,
|
||||
@ -683,6 +744,10 @@ namespace CryptoExchange.Net.Sockets.Default
|
||||
topicFilter!,
|
||||
string.Join(",", _listeners.Select(x => string.Join(",", x.MessageRouter.Routes.Where(x => x.TypeIdentifier == typeIdentifier).Select(x => x.TopicFilter != null ? string.Join(",", x.TopicFilter) : "[null]")))));
|
||||
}
|
||||
finally
|
||||
{
|
||||
_listenersLock.ExitReadLock();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -727,7 +792,8 @@ namespace CryptoExchange.Net.Sockets.Default
|
||||
if (ApiClient._socketConnections.ContainsKey(SocketId))
|
||||
ApiClient._socketConnections.TryRemove(SocketId, out _);
|
||||
|
||||
lock (_listenersLock)
|
||||
_listenersLock.EnterReadLock();
|
||||
try
|
||||
{
|
||||
foreach (var subscription in _listeners.OfType<Subscription>())
|
||||
{
|
||||
@ -735,6 +801,10 @@ namespace CryptoExchange.Net.Sockets.Default
|
||||
subscription.CancellationTokenRegistration.Value.Dispose();
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_listenersLock.ExitReadLock();
|
||||
}
|
||||
|
||||
await _socket.CloseAsync().ConfigureAwait(false);
|
||||
_socket.Dispose();
|
||||
@ -764,18 +834,30 @@ namespace CryptoExchange.Net.Sockets.Default
|
||||
subscription.CancellationTokenRegistration.Value.Dispose();
|
||||
|
||||
bool anyDuplicateSubscription;
|
||||
lock (_listenersLock)
|
||||
anyDuplicateSubscription = _listeners.OfType<Subscription>().Any(x => x != subscription && x.MessageRouter.Routes.All(l => subscription.MessageRouter.ContainsCheck(l)));
|
||||
|
||||
bool shouldCloseConnection;
|
||||
lock (_listenersLock)
|
||||
_listenersLock.EnterReadLock();
|
||||
try
|
||||
{
|
||||
anyDuplicateSubscription = _listeners.OfType<Subscription>().Any(x => x != subscription && x.MessageRouter.Routes.All(l => subscription.MessageRouter.ContainsCheck(l)));
|
||||
shouldCloseConnection = _listeners.OfType<Subscription>().All(r => !r.UserSubscription || r.Status == SubscriptionStatus.Closing || r.Status == SubscriptionStatus.Closed) && !DedicatedRequestConnection.IsDedicatedRequestConnection;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_listenersLock.ExitReadLock();
|
||||
}
|
||||
|
||||
if (!anyDuplicateSubscription)
|
||||
{
|
||||
bool needUnsub;
|
||||
lock (_listenersLock)
|
||||
_listenersLock.EnterReadLock();
|
||||
try
|
||||
{
|
||||
needUnsub = _listeners.Contains(subscription) && !shouldCloseConnection;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_listenersLock.ExitReadLock();
|
||||
}
|
||||
|
||||
if (needUnsub && _socket.IsOpen)
|
||||
await UnsubscribeAsync(subscription).ConfigureAwait(false);
|
||||
@ -800,8 +882,15 @@ namespace CryptoExchange.Net.Sockets.Default
|
||||
await CloseAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
lock (_listenersLock)
|
||||
_listenersLock.EnterWriteLock();
|
||||
try
|
||||
{
|
||||
_listeners.Remove(subscription);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_listenersLock.ExitWriteLock();
|
||||
}
|
||||
|
||||
subscription.Status = SubscriptionStatus.Closed;
|
||||
}
|
||||
@ -825,8 +914,15 @@ namespace CryptoExchange.Net.Sockets.Default
|
||||
if (Status != SocketStatus.None && Status != SocketStatus.Connected)
|
||||
return false;
|
||||
|
||||
lock (_listenersLock)
|
||||
_listenersLock.EnterWriteLock();
|
||||
try
|
||||
{
|
||||
_listeners.Add(subscription);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_listenersLock.ExitWriteLock();
|
||||
}
|
||||
|
||||
if (subscription.UserSubscription)
|
||||
_logger.AddingNewSubscription(SocketId, subscription.Id, UserSubscriptionCount);
|
||||
@ -839,8 +935,15 @@ namespace CryptoExchange.Net.Sockets.Default
|
||||
/// <param name="id"></param>
|
||||
public Subscription? GetSubscription(int id)
|
||||
{
|
||||
lock (_listenersLock)
|
||||
_listenersLock.EnterReadLock();
|
||||
try
|
||||
{
|
||||
return _listeners.OfType<Subscription>().SingleOrDefault(s => s.Id == id);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_listenersLock.ExitReadLock();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -888,15 +991,29 @@ namespace CryptoExchange.Net.Sockets.Default
|
||||
|
||||
private async Task SendAndWaitIntAsync(Query query, CancellationToken ct = default)
|
||||
{
|
||||
lock (_listenersLock)
|
||||
_listenersLock.EnterWriteLock();
|
||||
try
|
||||
{
|
||||
_listeners.Add(query);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_listenersLock.ExitWriteLock();
|
||||
}
|
||||
|
||||
var sendResult = await SendAsync(query.Id, query.Request, query.Weight).ConfigureAwait(false);
|
||||
if (!sendResult)
|
||||
{
|
||||
query.Fail(sendResult.Error!);
|
||||
lock (_listenersLock)
|
||||
_listenersLock.EnterWriteLock();
|
||||
try
|
||||
{
|
||||
_listeners.Remove(query);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_listenersLock.ExitWriteLock();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@ -927,8 +1044,15 @@ namespace CryptoExchange.Net.Sockets.Default
|
||||
}
|
||||
finally
|
||||
{
|
||||
lock (_listenersLock)
|
||||
_listenersLock.EnterWriteLock();
|
||||
try
|
||||
{
|
||||
_listeners.Remove(query);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_listenersLock.ExitWriteLock();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1035,8 +1159,16 @@ namespace CryptoExchange.Net.Sockets.Default
|
||||
if (!DedicatedRequestConnection.IsDedicatedRequestConnection)
|
||||
{
|
||||
bool anySubscriptions;
|
||||
lock (_listenersLock)
|
||||
_listenersLock.EnterReadLock();
|
||||
try
|
||||
{
|
||||
anySubscriptions = _listeners.OfType<Subscription>().Any(s => s.UserSubscription);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_listenersLock.ExitReadLock();
|
||||
}
|
||||
|
||||
if (!anySubscriptions)
|
||||
{
|
||||
// No need to resubscribe anything
|
||||
@ -1047,11 +1179,16 @@ namespace CryptoExchange.Net.Sockets.Default
|
||||
}
|
||||
|
||||
bool anyAuthenticated;
|
||||
lock (_listenersLock)
|
||||
_listenersLock.EnterReadLock();
|
||||
try
|
||||
{
|
||||
anyAuthenticated = _listeners.OfType<Subscription>().Any(s => s.Authenticated)
|
||||
|| DedicatedRequestConnection.IsDedicatedRequestConnection && DedicatedRequestConnection.Authenticated;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_listenersLock.ExitReadLock();
|
||||
}
|
||||
|
||||
if (anyAuthenticated)
|
||||
{
|
||||
@ -1076,8 +1213,15 @@ namespace CryptoExchange.Net.Sockets.Default
|
||||
return new CallResult(new WebError("Socket not connected"));
|
||||
|
||||
List<Subscription> subList;
|
||||
lock (_listenersLock)
|
||||
_listenersLock.EnterReadLock();
|
||||
try
|
||||
{
|
||||
subList = _listeners.OfType<Subscription>().Where(x => x.Active).Skip(batch * batchSize).Take(batchSize).ToList();
|
||||
}
|
||||
finally
|
||||
{
|
||||
_listenersLock.ExitReadLock();
|
||||
}
|
||||
|
||||
if (subList.Count == 0)
|
||||
break;
|
||||
|
||||
@ -112,6 +112,8 @@ namespace CryptoExchange.Net.Testing.Implementations
|
||||
|
||||
public async Task ReconnectAsync()
|
||||
{
|
||||
await Task.Delay(1).ConfigureAwait(false);
|
||||
|
||||
if (OnReconnecting != null)
|
||||
await OnReconnecting().ConfigureAwait(false);
|
||||
|
||||
|
||||
18
README.md
18
README.md
@ -67,6 +67,24 @@ Make a one time donation in a crypto currency of your choice. If you prefer to d
|
||||
Alternatively, sponsor me on Github using [Github Sponsors](https://github.com/sponsors/JKorf).
|
||||
|
||||
## Release notes
|
||||
* Version 10.7.0 - 24 Feb 2026
|
||||
* Added parsing of REST response data up to 128 characters for error responses
|
||||
* Added check for invalid json in JsonSocketMessageHandler
|
||||
* Added virtual GetTypeIdentifierNonJson for handling non-json messages in JsonSocketMessageHandler
|
||||
* Added additional options to Rest client options for configuring HttpClient
|
||||
* Updated INextPageToken parameter on Shared interfaces to PageRequest type, functionality unchanged
|
||||
* Added SupportsAscending and SupportsDescending properties to PaginatedEndpointOptions to expose supported data directions
|
||||
* Added MaxAge property to PaginatedEndpointOptions to expose the max age of data that can be requested
|
||||
* Added Direction property to Shared interfaces paginated requests to configure pagination data direction
|
||||
* Removed PaginationSupport property from PaginatedEndpointOptions, replaced by above new properties
|
||||
* Updated Shared GetTradeHistoryRequest EndTime property to be optional
|
||||
* Updated I(Futures/Spot)OrderRestClient.GetClosed(Futures/Spot)OrdersOptions from PaginatedEndpointOptions<GetClosedOrdersRequest> to GetClosedOrdersOptions
|
||||
* Updated I(Futures/Spot)OrderRestClient.Get(Futures/Spot)UserTradesOptions from PaginatedEndpointOptions<GetUserTradesRequest> to GetUserTradesOptions
|
||||
* Updated rate limiting PathStartFilter to ignore added or missing slash before the path
|
||||
* Updated internal lock for subscription to ReaderWriterLockSlim on SocketConnection
|
||||
* Removed check for OnlyTrackProvidedSymbols in combination with empty TrackedSymbols list
|
||||
* Fixed KlineTracker throwing exception if there is no data in the initial snapshot
|
||||
|
||||
* Version 10.6.2 - 17 Feb 2026
|
||||
* Fix for websocket queries which don't expects response getting stuck in subscribing state
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user