mirror of
https://github.com/JKorf/CryptoExchange.Net
synced 2025-06-07 16:06:15 +00:00
Removed HandleUpdatesBeforeConfirmation flag, allow messages to trigger listeners even if not confirmed and mark as confirmed then. Updated websocket reconnection delay handling
This commit is contained in:
parent
c8c98e13d0
commit
d27f394b46
@ -52,11 +52,6 @@ namespace CryptoExchange.Net.Clients
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
protected internal bool UnhandledMessageExpected { get; set; }
|
protected internal bool UnhandledMessageExpected { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// If true a subscription will accept message before the confirmation of a subscription has been received
|
|
||||||
/// </summary>
|
|
||||||
protected bool HandleMessageBeforeConfirmation { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The rate limiters
|
/// The rate limiters
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -203,7 +198,6 @@ namespace CryptoExchange.Net.Clients
|
|||||||
return socketResult.As<UpdateSubscription>(null);
|
return socketResult.As<UpdateSubscription>(null);
|
||||||
|
|
||||||
socketConnection = socketResult.Data;
|
socketConnection = socketResult.Data;
|
||||||
subscription.HandleUpdatesBeforeConfirmation = subscription.HandleUpdatesBeforeConfirmation || HandleMessageBeforeConfirmation;
|
|
||||||
|
|
||||||
// Add a subscription on the socket connection
|
// Add a subscription on the socket connection
|
||||||
var success = socketConnection.AddSubscription(subscription);
|
var success = socketConnection.AddSubscription(subscription);
|
||||||
@ -250,12 +244,19 @@ namespace CryptoExchange.Net.Clients
|
|||||||
if (!subResult)
|
if (!subResult)
|
||||||
{
|
{
|
||||||
waitEvent?.Set();
|
waitEvent?.Set();
|
||||||
|
var isTimeout = subResult.Error is CancellationRequestedError;
|
||||||
|
if (isTimeout && subscription.Confirmed)
|
||||||
|
{
|
||||||
|
// No response received, but the subscription did receive updates. We'll assume success
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
_logger.FailedToSubscribe(socketConnection.SocketId, subResult.Error?.ToString());
|
_logger.FailedToSubscribe(socketConnection.SocketId, subResult.Error?.ToString());
|
||||||
// If this was a timeout we still need to send an unsubscribe to prevent messages coming in later
|
// If this was a timeout we still need to send an unsubscribe to prevent messages coming in later
|
||||||
var unsubscribe = subResult.Error is CancellationRequestedError;
|
await socketConnection.CloseAsync(subscription, isTimeout).ConfigureAwait(false);
|
||||||
await socketConnection.CloseAsync(subscription, unsubscribe).ConfigureAwait(false);
|
|
||||||
return new CallResult<UpdateSubscription>(subResult.Error!);
|
return new CallResult<UpdateSubscription>(subResult.Error!);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
subscription.HandleSubQueryResponse(subQuery.Response!);
|
subscription.HandleSubQueryResponse(subQuery.Response!);
|
||||||
}
|
}
|
||||||
@ -517,7 +518,7 @@ namespace CryptoExchange.Net.Clients
|
|||||||
/// <param name="address">The address to connect to</param>
|
/// <param name="address">The address to connect to</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
protected virtual WebSocketParameters GetWebSocketParameters(string address)
|
protected virtual WebSocketParameters GetWebSocketParameters(string address)
|
||||||
=> new(new Uri(address), ClientOptions.AutoReconnect)
|
=> new(new Uri(address), ClientOptions.ReconnectPolicy)
|
||||||
{
|
{
|
||||||
KeepAliveInterval = KeepAliveInterval,
|
KeepAliveInterval = KeepAliveInterval,
|
||||||
ReconnectInterval = ClientOptions.ReconnectInterval,
|
ReconnectInterval = ClientOptions.ReconnectInterval,
|
||||||
|
@ -16,10 +16,6 @@ namespace CryptoExchange.Net.Interfaces
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public int Id { get; }
|
public int Id { get; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether this listener can handle data
|
|
||||||
/// </summary>
|
|
||||||
public bool CanHandleData { get; }
|
|
||||||
/// <summary>
|
|
||||||
/// The identifiers for this processor
|
/// The identifiers for this processor
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public HashSet<string> ListenerIdentifiers { get; }
|
public HashSet<string> ListenerIdentifiers { get; }
|
||||||
|
@ -169,4 +169,23 @@
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
Snapshot
|
Snapshot
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reconnect policy
|
||||||
|
/// </summary>
|
||||||
|
public enum ReconnectPolicy
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Reconnect is disabled
|
||||||
|
/// </summary>
|
||||||
|
Disabled,
|
||||||
|
/// <summary>
|
||||||
|
/// Fixed delay of `ReconnectInterval` between retries
|
||||||
|
/// </summary>
|
||||||
|
FixedDelay,
|
||||||
|
/// <summary>
|
||||||
|
/// Backof policy of 2^`reconnectAttempt`, where `reconnectAttempt` has a max value of 5
|
||||||
|
/// </summary>
|
||||||
|
ExponentialBackoff
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
using CryptoExchange.Net.Authentication;
|
using CryptoExchange.Net.Authentication;
|
||||||
|
using CryptoExchange.Net.Objects.Sockets;
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace CryptoExchange.Net.Objects.Options
|
namespace CryptoExchange.Net.Objects.Options
|
||||||
@ -9,15 +10,15 @@ namespace CryptoExchange.Net.Objects.Options
|
|||||||
public class SocketExchangeOptions : ExchangeOptions
|
public class SocketExchangeOptions : ExchangeOptions
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether or not the socket should automatically reconnect when losing connection
|
/// The fixed time to wait between reconnect attempts, only used when `ReconnectPolicy` is set to `ReconnectPolicy.ExponentialBackoff`
|
||||||
/// </summary>
|
|
||||||
public bool AutoReconnect { get; set; } = true;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Time to wait between reconnect attempts
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public TimeSpan ReconnectInterval { get; set; } = TimeSpan.FromSeconds(5);
|
public TimeSpan ReconnectInterval { get; set; } = TimeSpan.FromSeconds(5);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reconnect policy
|
||||||
|
/// </summary>
|
||||||
|
public ReconnectPolicy ReconnectPolicy { get; set; } = ReconnectPolicy.FixedDelay;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Max number of concurrent resubscription tasks per socket after reconnecting a socket
|
/// Max number of concurrent resubscription tasks per socket after reconnecting a socket
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -57,7 +58,7 @@ namespace CryptoExchange.Net.Objects.Options
|
|||||||
{
|
{
|
||||||
ApiCredentials = ApiCredentials?.Copy(),
|
ApiCredentials = ApiCredentials?.Copy(),
|
||||||
OutputOriginalData = OutputOriginalData,
|
OutputOriginalData = OutputOriginalData,
|
||||||
AutoReconnect = AutoReconnect,
|
ReconnectPolicy = ReconnectPolicy,
|
||||||
DelayAfterConnect = DelayAfterConnect,
|
DelayAfterConnect = DelayAfterConnect,
|
||||||
MaxConcurrentResubscriptionsPerSocket = MaxConcurrentResubscriptionsPerSocket,
|
MaxConcurrentResubscriptionsPerSocket = MaxConcurrentResubscriptionsPerSocket,
|
||||||
ReconnectInterval = ReconnectInterval,
|
ReconnectInterval = ReconnectInterval,
|
||||||
|
@ -26,20 +26,20 @@ namespace CryptoExchange.Net.Objects.Sockets
|
|||||||
public IDictionary<string, string> Cookies { get; set; } = new Dictionary<string, string>();
|
public IDictionary<string, string> Cookies { get; set; } = new Dictionary<string, string>();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The time to wait between reconnect attempts
|
/// The fixed time to wait between reconnect attempts, only used when `ReconnectPolicy` is set to `ReconnectPolicy.ExponentialBackoff`
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public TimeSpan ReconnectInterval { get; set; } = TimeSpan.FromSeconds(5);
|
public TimeSpan ReconnectInterval { get; set; } = TimeSpan.FromSeconds(5);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reconnect policy
|
||||||
|
/// </summary>
|
||||||
|
public ReconnectPolicy ReconnectPolicy { get; set; } = ReconnectPolicy.FixedDelay;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Proxy for the connection
|
/// Proxy for the connection
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ApiProxy? Proxy { get; set; }
|
public ApiProxy? Proxy { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Whether the socket should automatically reconnect when connection is lost
|
|
||||||
/// </summary>
|
|
||||||
public bool AutoReconnect { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The maximum time of no data received before considering the connection lost and closting/reconnecting the socket
|
/// The maximum time of no data received before considering the connection lost and closting/reconnecting the socket
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -68,11 +68,11 @@ namespace CryptoExchange.Net.Objects.Sockets
|
|||||||
/// ctor
|
/// ctor
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="uri">Uri</param>
|
/// <param name="uri">Uri</param>
|
||||||
/// <param name="autoReconnect">Auto reconnect</param>
|
/// <param name="policy">Reconnect policy</param>
|
||||||
public WebSocketParameters(Uri uri, bool autoReconnect)
|
public WebSocketParameters(Uri uri, ReconnectPolicy policy)
|
||||||
{
|
{
|
||||||
Uri = uri;
|
Uri = uri;
|
||||||
AutoReconnect = autoReconnect;
|
ReconnectPolicy = policy;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -47,6 +47,7 @@ namespace CryptoExchange.Net.Sockets
|
|||||||
private ProcessState _processState;
|
private ProcessState _processState;
|
||||||
private DateTime _lastReconnectTime;
|
private DateTime _lastReconnectTime;
|
||||||
private string _baseAddress;
|
private string _baseAddress;
|
||||||
|
private int _reconnectAttempt;
|
||||||
|
|
||||||
private const int _receiveBufferSize = 1048576;
|
private const int _receiveBufferSize = 1048576;
|
||||||
private const int _sendBufferSize = 4096;
|
private const int _sendBufferSize = 4096;
|
||||||
@ -246,7 +247,7 @@ namespace CryptoExchange.Net.Sockets
|
|||||||
await _closeTask.ConfigureAwait(false);
|
await _closeTask.ConfigureAwait(false);
|
||||||
_closeTask = null;
|
_closeTask = null;
|
||||||
|
|
||||||
if (!Parameters.AutoReconnect)
|
if (Parameters.ReconnectPolicy == ReconnectPolicy.Disabled)
|
||||||
{
|
{
|
||||||
_processState = ProcessState.Idle;
|
_processState = ProcessState.Idle;
|
||||||
await (OnClose?.Invoke() ?? Task.CompletedTask).ConfigureAwait(false);
|
await (OnClose?.Invoke() ?? Task.CompletedTask).ConfigureAwait(false);
|
||||||
@ -259,9 +260,9 @@ namespace CryptoExchange.Net.Sockets
|
|||||||
await (OnReconnecting?.Invoke() ?? Task.CompletedTask).ConfigureAwait(false);
|
await (OnReconnecting?.Invoke() ?? Task.CompletedTask).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
var sinceLastReconnect = DateTime.UtcNow - _lastReconnectTime;
|
// Delay here to prevent very repid looping when a connection to the server is accepted and immediately disconnected
|
||||||
if (sinceLastReconnect < Parameters.ReconnectInterval)
|
var initialDelay = GetReconnectDelay();
|
||||||
await Task.Delay(Parameters.ReconnectInterval - sinceLastReconnect).ConfigureAwait(false);
|
await Task.Delay(initialDelay).ConfigureAwait(false);
|
||||||
|
|
||||||
while (!_stopRequested)
|
while (!_stopRequested)
|
||||||
{
|
{
|
||||||
@ -282,13 +283,17 @@ namespace CryptoExchange.Net.Sockets
|
|||||||
_ctsSource = new CancellationTokenSource();
|
_ctsSource = new CancellationTokenSource();
|
||||||
while (_sendBuffer.TryDequeue(out _)) { } // Clear send buffer
|
while (_sendBuffer.TryDequeue(out _)) { } // Clear send buffer
|
||||||
|
|
||||||
|
_reconnectAttempt++;
|
||||||
var connected = await ConnectInternalAsync().ConfigureAwait(false);
|
var connected = await ConnectInternalAsync().ConfigureAwait(false);
|
||||||
if (!connected)
|
if (!connected)
|
||||||
{
|
{
|
||||||
await Task.Delay(Parameters.ReconnectInterval).ConfigureAwait(false);
|
// Delay between reconnect attempts
|
||||||
|
var delay = GetReconnectDelay();
|
||||||
|
await Task.Delay(delay).ConfigureAwait(false);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_reconnectAttempt = 0;
|
||||||
_lastReconnectTime = DateTime.UtcNow;
|
_lastReconnectTime = DateTime.UtcNow;
|
||||||
await (OnReconnected?.Invoke() ?? Task.CompletedTask).ConfigureAwait(false);
|
await (OnReconnected?.Invoke() ?? Task.CompletedTask).ConfigureAwait(false);
|
||||||
break;
|
break;
|
||||||
@ -298,6 +303,24 @@ namespace CryptoExchange.Net.Sockets
|
|||||||
_processState = ProcessState.Idle;
|
_processState = ProcessState.Idle;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private TimeSpan GetReconnectDelay()
|
||||||
|
{
|
||||||
|
if (_reconnectAttempt == 0)
|
||||||
|
{
|
||||||
|
// Means this is directly after disconnecting. Only delay if the last reconnect time is very recent
|
||||||
|
var sinceLastReconnect = DateTime.UtcNow - _lastReconnectTime;
|
||||||
|
if (sinceLastReconnect < TimeSpan.FromSeconds(5))
|
||||||
|
return TimeSpan.FromSeconds(5) - sinceLastReconnect;
|
||||||
|
|
||||||
|
return TimeSpan.FromMilliseconds(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
var delay = Parameters.ReconnectPolicy == ReconnectPolicy.FixedDelay ? Parameters.ReconnectInterval : TimeSpan.FromSeconds(Math.Pow(2, Math.Min(5, _reconnectAttempt)));
|
||||||
|
if (delay > TimeSpan.Zero)
|
||||||
|
return delay;
|
||||||
|
return TimeSpan.FromMilliseconds(1);
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public virtual void Send(int id, string data, int weight)
|
public virtual void Send(int id, string data, int weight)
|
||||||
{
|
{
|
||||||
|
@ -19,11 +19,6 @@ namespace CryptoExchange.Net.Sockets
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public int Id { get; } = ExchangeHelpers.NextId();
|
public int Id { get; } = ExchangeHelpers.NextId();
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Can handle data
|
|
||||||
/// </summary>
|
|
||||||
public bool CanHandleData => true;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Has this query been completed
|
/// Has this query been completed
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -443,7 +443,7 @@ namespace CryptoExchange.Net.Sockets
|
|||||||
// 4. Get the listeners interested in this message
|
// 4. Get the listeners interested in this message
|
||||||
List<IMessageProcessor> processors;
|
List<IMessageProcessor> processors;
|
||||||
lock (_listenersLock)
|
lock (_listenersLock)
|
||||||
processors = _listeners.Where(s => s.ListenerIdentifiers.Contains(listenId) && s.CanHandleData).ToList();
|
processors = _listeners.Where(s => s.ListenerIdentifiers.Contains(listenId)).ToList();
|
||||||
|
|
||||||
if (processors.Count == 0)
|
if (processors.Count == 0)
|
||||||
{
|
{
|
||||||
@ -451,7 +451,7 @@ namespace CryptoExchange.Net.Sockets
|
|||||||
{
|
{
|
||||||
List<string> listenerIds;
|
List<string> listenerIds;
|
||||||
lock (_listenersLock)
|
lock (_listenersLock)
|
||||||
listenerIds = _listeners.Where(l => l.CanHandleData).SelectMany(l => l.ListenerIdentifiers).ToList();
|
listenerIds = _listeners.SelectMany(l => l.ListenerIdentifiers).ToList();
|
||||||
_logger.ReceivedMessageNotMatchedToAnyListener(SocketId, listenId, string.Join(",", listenerIds));
|
_logger.ReceivedMessageNotMatchedToAnyListener(SocketId, listenId, string.Join(",", listenerIds));
|
||||||
UnhandledMessage?.Invoke(_accessor);
|
UnhandledMessage?.Invoke(_accessor);
|
||||||
}
|
}
|
||||||
@ -478,6 +478,10 @@ namespace CryptoExchange.Net.Sockets
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (processor is Subscription subscriptionProcessor && !subscriptionProcessor.Confirmed)
|
||||||
|
// If this message is for this listener then it is automatically confirmed, even if the subscription is not (yet) confirmed
|
||||||
|
subscriptionProcessor.Confirmed = true;
|
||||||
|
|
||||||
// 6. Deserialize the message
|
// 6. Deserialize the message
|
||||||
object? deserialized = null;
|
object? deserialized = null;
|
||||||
desCache?.TryGetValue(messageType, out deserialized);
|
desCache?.TryGetValue(messageType, out deserialized);
|
||||||
@ -861,6 +865,8 @@ namespace CryptoExchange.Net.Sockets
|
|||||||
{
|
{
|
||||||
subscription.HandleSubQueryResponse(subQuery.Response!);
|
subscription.HandleSubQueryResponse(subQuery.Response!);
|
||||||
waitEvent.Set();
|
waitEvent.Set();
|
||||||
|
if (r.Result.Success)
|
||||||
|
subscription.Confirmed = true;
|
||||||
return r.Result;
|
return r.Result;
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
@ -870,9 +876,6 @@ namespace CryptoExchange.Net.Sockets
|
|||||||
return taskList.First(t => !t.Result.Success).Result;
|
return taskList.First(t => !t.Result.Success).Result;
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var subscription in subList)
|
|
||||||
subscription.Confirmed = true;
|
|
||||||
|
|
||||||
if (!_socket.IsOpen)
|
if (!_socket.IsOpen)
|
||||||
return new CallResult(new WebError("Socket not connected"));
|
return new CallResult(new WebError("Socket not connected"));
|
||||||
|
|
||||||
|
@ -18,11 +18,6 @@ namespace CryptoExchange.Net.Sockets
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public int Id { get; set; }
|
public int Id { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Can handle data
|
|
||||||
/// </summary>
|
|
||||||
public bool CanHandleData => Confirmed || HandleUpdatesBeforeConfirmation;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Total amount of invocations
|
/// Total amount of invocations
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -43,11 +38,6 @@ namespace CryptoExchange.Net.Sockets
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public bool Confirmed { get; set; }
|
public bool Confirmed { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Whether this subscription should handle update messages before confirmation
|
|
||||||
/// </summary>
|
|
||||||
public bool HandleUpdatesBeforeConfirmation { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Is the subscription closed
|
/// Is the subscription closed
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user