mirror of
https://github.com/JKorf/CryptoExchange.Net.git
synced 2026-08-15 02:13:01 +00:00
Squashed commit of the following:
commit0571ed17a0Author: JKorf <jankorf91@gmail.com> Date: Sun Jul 10 19:56:27 2022 +0200 Fixed tests commit99c331b389Author: JKorf <jankorf91@gmail.com> Date: Sun Jul 10 16:41:14 2022 +0200 Updated version commit70f8bd203aAuthor: JKorf <jankorf91@gmail.com> Date: Sun Jul 10 16:36:00 2022 +0200 Finished up websocket refactoring commit89b517c936Author: JKorf <jankorf91@gmail.com> Date: Fri Jul 8 20:24:58 2022 +0200 wip commit91e33cc42cAuthor: JKorf <jankorf91@gmail.com> Date: Thu Jul 7 22:17:55 2022 +0200 wip
This commit is contained in:
@@ -3,13 +3,13 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using CryptoExchange.Net.Logging;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using CryptoExchange.Net.Objects;
|
||||
using System.Net.WebSockets;
|
||||
|
||||
namespace CryptoExchange.Net.Sockets
|
||||
{
|
||||
@@ -57,10 +57,22 @@ namespace CryptoExchange.Net.Sockets
|
||||
return subscriptions.Count(h => h.UserSubscription); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a copy of the current subscriptions
|
||||
/// </summary>
|
||||
public SocketSubscription[] Subscriptions
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (subscriptionLock)
|
||||
return subscriptions.Where(h => h.UserSubscription).ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If the connection has been authenticated
|
||||
/// </summary>
|
||||
public bool Authenticated { get; set; }
|
||||
public bool Authenticated { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// If connection is made
|
||||
@@ -80,28 +92,13 @@ namespace CryptoExchange.Net.Sockets
|
||||
/// <summary>
|
||||
/// The connection uri
|
||||
/// </summary>
|
||||
public Uri Uri => _socket.Uri;
|
||||
public Uri ConnectionUri => _socket.Uri;
|
||||
|
||||
/// <summary>
|
||||
/// The API client the connection is for
|
||||
/// </summary>
|
||||
public SocketApiClient ApiClient { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// If the socket should be reconnected upon closing
|
||||
/// </summary>
|
||||
public bool ShouldReconnect { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Current reconnect try, reset when a successful connection is made
|
||||
/// </summary>
|
||||
public int ReconnectTry { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Current resubscribe try, reset when a successful connection is made
|
||||
/// </summary>
|
||||
public int ResubscribeTry { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Time of disconnecting
|
||||
/// </summary>
|
||||
@@ -110,7 +107,7 @@ namespace CryptoExchange.Net.Sockets
|
||||
/// <summary>
|
||||
/// Tag for identificaion
|
||||
/// </summary>
|
||||
public string? Tag { get; set; }
|
||||
public string Tag { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// If activity is paused
|
||||
@@ -124,27 +121,12 @@ namespace CryptoExchange.Net.Sockets
|
||||
{
|
||||
pausedActivity = value;
|
||||
log.Write(LogLevel.Information, $"Socket {SocketId} Paused activity: " + value);
|
||||
if(pausedActivity) ActivityPaused?.Invoke();
|
||||
else ActivityUnpaused?.Invoke();
|
||||
if(pausedActivity) _ = Task.Run(() => ActivityPaused?.Invoke());
|
||||
else _ = Task.Run(() => ActivityUnpaused?.Invoke());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool pausedActivity;
|
||||
private readonly List<SocketSubscription> subscriptions;
|
||||
private readonly object subscriptionLock = new();
|
||||
|
||||
private bool lostTriggered;
|
||||
private readonly Log log;
|
||||
private readonly BaseSocketClient socketClient;
|
||||
|
||||
private readonly List<PendingRequest> pendingRequests;
|
||||
private Task? _socketProcessTask;
|
||||
private Task? _socketReconnectTask;
|
||||
private readonly AsyncResetEvent _reconnectWaitEvent;
|
||||
|
||||
private SocketStatus _status;
|
||||
|
||||
/// <summary>
|
||||
/// Status of the socket connection
|
||||
/// </summary>
|
||||
@@ -153,12 +135,26 @@ namespace CryptoExchange.Net.Sockets
|
||||
get => _status;
|
||||
private set
|
||||
{
|
||||
if (_status == value)
|
||||
return;
|
||||
|
||||
var oldStatus = _status;
|
||||
_status = value;
|
||||
log.Write(LogLevel.Trace, $"Socket {SocketId} status changed from {oldStatus} to {_status}");
|
||||
log.Write(LogLevel.Debug, $"Socket {SocketId} status changed from {oldStatus} to {_status}");
|
||||
}
|
||||
}
|
||||
|
||||
private bool pausedActivity;
|
||||
private readonly List<SocketSubscription> subscriptions;
|
||||
private readonly object subscriptionLock = new();
|
||||
|
||||
private readonly Log log;
|
||||
private readonly BaseSocketClient socketClient;
|
||||
|
||||
private readonly List<PendingRequest> pendingRequests;
|
||||
|
||||
private SocketStatus _status;
|
||||
|
||||
/// <summary>
|
||||
/// The underlying websocket
|
||||
/// </summary>
|
||||
@@ -170,137 +166,72 @@ namespace CryptoExchange.Net.Sockets
|
||||
/// <param name="client">The socket client</param>
|
||||
/// <param name="apiClient">The api client</param>
|
||||
/// <param name="socket">The socket</param>
|
||||
public SocketConnection(BaseSocketClient client, SocketApiClient apiClient, IWebsocket socket)
|
||||
/// <param name="tag"></param>
|
||||
public SocketConnection(BaseSocketClient client, SocketApiClient apiClient, IWebsocket socket, string tag)
|
||||
{
|
||||
log = client.log;
|
||||
socketClient = client;
|
||||
ApiClient = apiClient;
|
||||
Tag = tag;
|
||||
|
||||
pendingRequests = new List<PendingRequest>();
|
||||
|
||||
subscriptions = new List<SocketSubscription>();
|
||||
|
||||
_socket = socket;
|
||||
|
||||
_reconnectWaitEvent = new AsyncResetEvent(false, true);
|
||||
|
||||
_socket.Timeout = client.ClientOptions.SocketNoDataTimeout;
|
||||
_socket.OnMessage += ProcessMessage;
|
||||
_socket.OnOpen += SocketOnOpen;
|
||||
_socket.OnClose += () => _reconnectWaitEvent.Set();
|
||||
|
||||
_socket.OnMessage += HandleMessage;
|
||||
_socket.OnOpen += HandleOpen;
|
||||
_socket.OnClose += HandleClose;
|
||||
_socket.OnReconnecting += HandleReconnecting;
|
||||
_socket.OnReconnected += HandleReconnected;
|
||||
_socket.OnError += HandleError;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Connect the websocket and start processing
|
||||
/// Handler for a socket opening
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public async Task<bool> ConnectAsync()
|
||||
protected virtual void HandleOpen()
|
||||
{
|
||||
var connected = await _socket.ConnectAsync().ConfigureAwait(false);
|
||||
if (connected)
|
||||
Status = SocketStatus.Connected;
|
||||
PausedActivity = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handler for a socket closing without reconnect
|
||||
/// </summary>
|
||||
protected virtual void HandleClose()
|
||||
{
|
||||
Status = SocketStatus.Closed;
|
||||
Authenticated = false;
|
||||
lock(subscriptionLock)
|
||||
{
|
||||
Status = SocketStatus.Connected;
|
||||
_socketReconnectTask = ReconnectWatcherAsync();
|
||||
_socketProcessTask = _socket.ProcessAsync();
|
||||
}
|
||||
|
||||
return connected;
|
||||
foreach (var sub in subscriptions)
|
||||
sub.Confirmed = false;
|
||||
}
|
||||
Task.Run(() => ConnectionClosed?.Invoke());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieve the underlying socket
|
||||
/// Handler for a socket losing conenction and starting reconnect
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public IWebsocket GetSocket()
|
||||
protected virtual void HandleReconnecting()
|
||||
{
|
||||
return _socket;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Trigger a reconnect of the socket connection
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public async Task TriggerReconnectAsync()
|
||||
{
|
||||
await _socket.CloseAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Close the connection
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public async Task CloseAsync()
|
||||
{
|
||||
if (Status == SocketStatus.Closed || Status == SocketStatus.Disposed)
|
||||
return;
|
||||
|
||||
ShouldReconnect = false;
|
||||
if (socketClient.socketConnections.ContainsKey(SocketId))
|
||||
socketClient.socketConnections.TryRemove(SocketId, out _);
|
||||
|
||||
Status = SocketStatus.Reconnecting;
|
||||
DisconnectTime = DateTime.UtcNow;
|
||||
Authenticated = false;
|
||||
lock (subscriptionLock)
|
||||
{
|
||||
foreach (var subscription in subscriptions)
|
||||
{
|
||||
if (subscription.CancellationTokenRegistration.HasValue)
|
||||
subscription.CancellationTokenRegistration.Value.Dispose();
|
||||
}
|
||||
foreach (var sub in subscriptions)
|
||||
sub.Confirmed = false;
|
||||
}
|
||||
|
||||
while (Status == SocketStatus.Reconnecting)
|
||||
// Wait for reconnecting to finish
|
||||
await Task.Delay(100).ConfigureAwait(false);
|
||||
|
||||
await _socket.CloseAsync().ConfigureAwait(false);
|
||||
if(_socketProcessTask != null)
|
||||
await _socketProcessTask.ConfigureAwait(false);
|
||||
_socket.Dispose();
|
||||
Task.Run(() => ConnectionLost?.Invoke());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Close a subscription on this connection. If all subscriptions on this connection are closed the connection gets closed as well
|
||||
/// Handler for a socket which has reconnected
|
||||
/// </summary>
|
||||
/// <param name="subscription">Subscription to close</param>
|
||||
/// <returns></returns>
|
||||
public async Task CloseAsync(SocketSubscription subscription)
|
||||
protected virtual async void HandleReconnected()
|
||||
{
|
||||
if (Status == SocketStatus.Closing || Status == SocketStatus.Closed || Status == SocketStatus.Disposed)
|
||||
return;
|
||||
|
||||
log.Write(LogLevel.Trace, $"Socket {SocketId} closing subscription {subscription.Id}");
|
||||
if (subscription.CancellationTokenRegistration.HasValue)
|
||||
subscription.CancellationTokenRegistration.Value.Dispose();
|
||||
|
||||
if (subscription.Confirmed && _socket.IsOpen)
|
||||
await socketClient.UnsubscribeAsync(this, subscription).ConfigureAwait(false);
|
||||
|
||||
bool shouldCloseConnection;
|
||||
lock (subscriptionLock)
|
||||
{
|
||||
if (Status == SocketStatus.Closing)
|
||||
{
|
||||
log.Write(LogLevel.Trace, $"Socket {SocketId} already closing");
|
||||
return;
|
||||
}
|
||||
|
||||
shouldCloseConnection = subscriptions.All(r => !r.UserSubscription || r == subscription);
|
||||
if (shouldCloseConnection)
|
||||
Status = SocketStatus.Closing;
|
||||
}
|
||||
|
||||
if (shouldCloseConnection)
|
||||
{
|
||||
log.Write(LogLevel.Trace, $"Socket {SocketId} closing as there are no more subscriptions");
|
||||
await CloseAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
lock (subscriptionLock)
|
||||
subscriptions.Remove(subscription);
|
||||
}
|
||||
|
||||
private async Task ReconnectAsync()
|
||||
{
|
||||
// Fail all pending requests
|
||||
Status = SocketStatus.Resubscribing;
|
||||
lock (pendingRequests)
|
||||
{
|
||||
foreach (var pendingRequest in pendingRequests.ToList())
|
||||
@@ -310,137 +241,37 @@ namespace CryptoExchange.Net.Sockets
|
||||
}
|
||||
}
|
||||
|
||||
if (socketClient.ClientOptions.AutoReconnect && ShouldReconnect)
|
||||
{
|
||||
// Should reconnect
|
||||
DisconnectTime = DateTime.UtcNow;
|
||||
log.Write(LogLevel.Warning, $"Socket {SocketId} Connection lost, will try to reconnect");
|
||||
if (!lostTriggered)
|
||||
{
|
||||
lostTriggered = true;
|
||||
_ = Task.Run(() => ConnectionLost?.Invoke());
|
||||
}
|
||||
|
||||
while (ShouldReconnect)
|
||||
{
|
||||
if (ReconnectTry > 0)
|
||||
{
|
||||
// Wait a bit before attempting reconnect
|
||||
await Task.Delay(socketClient.ClientOptions.ReconnectInterval).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (!ShouldReconnect)
|
||||
{
|
||||
// Should reconnect changed to false while waiting to reconnect
|
||||
return;
|
||||
}
|
||||
|
||||
_socket.Reset();
|
||||
if (!await _socket.ConnectAsync().ConfigureAwait(false))
|
||||
{
|
||||
// Reconnect failed
|
||||
ReconnectTry++;
|
||||
ResubscribeTry = 0;
|
||||
if (socketClient.ClientOptions.MaxReconnectTries != null
|
||||
&& ReconnectTry >= socketClient.ClientOptions.MaxReconnectTries)
|
||||
{
|
||||
log.Write(LogLevel.Warning, $"Socket {SocketId} failed to reconnect after {ReconnectTry} tries, closing");
|
||||
ShouldReconnect = false;
|
||||
|
||||
if (socketClient.socketConnections.ContainsKey(SocketId))
|
||||
socketClient.socketConnections.TryRemove(SocketId, out _);
|
||||
|
||||
_ = Task.Run(() => ConnectionClosed?.Invoke());
|
||||
// Reached max tries, break loop and leave connection closed
|
||||
break;
|
||||
}
|
||||
|
||||
// Continue to try again
|
||||
log.Write(LogLevel.Debug, $"Socket {SocketId} failed to reconnect{(socketClient.ClientOptions.MaxReconnectTries != null ? $", try {ReconnectTry}/{socketClient.ClientOptions.MaxReconnectTries}" : "")}, will try again in {socketClient.ClientOptions.ReconnectInterval}");
|
||||
continue;
|
||||
}
|
||||
|
||||
// Successfully reconnected, start processing
|
||||
Status = SocketStatus.Connected;
|
||||
_socketProcessTask = _socket.ProcessAsync();
|
||||
|
||||
ReconnectTry = 0;
|
||||
var time = DisconnectTime;
|
||||
DisconnectTime = null;
|
||||
|
||||
log.Write(LogLevel.Information, $"Socket {SocketId} reconnected after {DateTime.UtcNow - time}");
|
||||
|
||||
var reconnectResult = await ProcessReconnectAsync().ConfigureAwait(false);
|
||||
if (!reconnectResult)
|
||||
{
|
||||
// Failed to resubscribe everything
|
||||
ResubscribeTry++;
|
||||
DisconnectTime = time;
|
||||
|
||||
if (socketClient.ClientOptions.MaxResubscribeTries != null &&
|
||||
ResubscribeTry >= socketClient.ClientOptions.MaxResubscribeTries)
|
||||
{
|
||||
log.Write(LogLevel.Warning, $"Socket {SocketId} failed to resubscribe after {ResubscribeTry} tries, closing. Last resubscription error: {reconnectResult.Error}");
|
||||
ShouldReconnect = false;
|
||||
|
||||
if (socketClient.socketConnections.ContainsKey(SocketId))
|
||||
socketClient.socketConnections.TryRemove(SocketId, out _);
|
||||
|
||||
_ = Task.Run(() => ConnectionClosed?.Invoke());
|
||||
}
|
||||
else
|
||||
log.Write(LogLevel.Debug, $"Socket {SocketId} resubscribing all subscriptions failed on reconnected socket{(socketClient.ClientOptions.MaxResubscribeTries != null ? $", try {ResubscribeTry}/{socketClient.ClientOptions.MaxResubscribeTries}" : "")}. Error: {reconnectResult.Error}. Disconnecting and reconnecting.");
|
||||
|
||||
// Failed resubscribe, close socket if it is still open
|
||||
if (_socket.IsOpen)
|
||||
await _socket.CloseAsync().ConfigureAwait(false);
|
||||
else
|
||||
DisconnectTime = DateTime.UtcNow;
|
||||
|
||||
// Break out of the loop, the new processing task should reconnect again
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Succesfully reconnected
|
||||
log.Write(LogLevel.Information, $"Socket {SocketId} data connection restored.");
|
||||
ResubscribeTry = 0;
|
||||
if (lostTriggered)
|
||||
{
|
||||
lostTriggered = false;
|
||||
_ = Task.Run(() => ConnectionRestored?.Invoke(time.HasValue ? DateTime.UtcNow - time.Value : TimeSpan.FromSeconds(0)));
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
var reconnectSuccessful = await ProcessReconnectAsync().ConfigureAwait(false);
|
||||
if (!reconnectSuccessful)
|
||||
await _socket.ReconnectAsync().ConfigureAwait(false);
|
||||
else
|
||||
{
|
||||
if (!socketClient.ClientOptions.AutoReconnect && ShouldReconnect)
|
||||
_ = Task.Run(() => ConnectionClosed?.Invoke());
|
||||
|
||||
// No reconnecting needed
|
||||
log.Write(LogLevel.Information, $"Socket {SocketId} closed");
|
||||
if (socketClient.socketConnections.ContainsKey(SocketId))
|
||||
socketClient.socketConnections.TryRemove(SocketId, out _);
|
||||
Status = SocketStatus.Connected;
|
||||
_ = Task.Run(() =>
|
||||
{
|
||||
ConnectionRestored?.Invoke(DateTime.UtcNow - DisconnectTime!.Value);
|
||||
DisconnectTime = null;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Dispose the connection
|
||||
/// Handler for an error on a websocket
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
/// <param name="e">The exception</param>
|
||||
protected virtual void HandleError(Exception e)
|
||||
{
|
||||
Status = SocketStatus.Disposed;
|
||||
_socket.Dispose();
|
||||
if (e is WebSocketException wse)
|
||||
log.Write(LogLevel.Warning, $"Socket {SocketId} error: Websocket error code {wse.WebSocketErrorCode}, details: " + e.ToLogString());
|
||||
else
|
||||
log.Write(LogLevel.Warning, $"Socket {SocketId} error: " + e.ToLogString());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Process a message received by the socket
|
||||
/// </summary>
|
||||
/// <param name="data">The received data</param>
|
||||
private void ProcessMessage(string data)
|
||||
protected virtual void HandleMessage(string data)
|
||||
{
|
||||
var timestamp = DateTime.UtcNow;
|
||||
log.Write(LogLevel.Trace, $"Socket {SocketId} received data: " + data);
|
||||
@@ -456,15 +287,13 @@ namespace CryptoExchange.Net.Sockets
|
||||
}
|
||||
|
||||
var handledResponse = false;
|
||||
PendingRequest[] requests;
|
||||
lock(pendingRequests)
|
||||
requests = pendingRequests.ToArray();
|
||||
|
||||
// Remove any timed out requests
|
||||
foreach (var request in requests.Where(r => r.Completed))
|
||||
PendingRequest[] requests;
|
||||
lock (pendingRequests)
|
||||
{
|
||||
lock (pendingRequests)
|
||||
pendingRequests.Remove(request);
|
||||
pendingRequests.RemoveAll(r => r.Completed);
|
||||
requests = pendingRequests.ToArray();
|
||||
}
|
||||
|
||||
// Check if this message is an answer on any pending requests
|
||||
@@ -472,7 +301,7 @@ namespace CryptoExchange.Net.Sockets
|
||||
{
|
||||
if (pendingRequest.CheckData(tokenData))
|
||||
{
|
||||
lock (pendingRequests)
|
||||
lock (pendingRequests)
|
||||
pendingRequests.Remove(pendingRequest);
|
||||
|
||||
if (!socketClient.ContinueOnQueryResponse)
|
||||
@@ -484,8 +313,8 @@ namespace CryptoExchange.Net.Sockets
|
||||
}
|
||||
|
||||
// Message was not a request response, check data handlers
|
||||
var messageEvent = new MessageEvent(this, tokenData, socketClient.ClientOptions.OutputOriginalData ? data: null, timestamp);
|
||||
var (handled, userProcessTime) = HandleData(messageEvent);
|
||||
var messageEvent = new MessageEvent(this, tokenData, socketClient.ClientOptions.OutputOriginalData ? data : null, timestamp);
|
||||
var (handled, userProcessTime, subscription) = HandleData(messageEvent);
|
||||
if (!handled && !handledResponse)
|
||||
{
|
||||
if (!socketClient.UnhandledMessageExpected)
|
||||
@@ -495,10 +324,108 @@ namespace CryptoExchange.Net.Sockets
|
||||
|
||||
var total = DateTime.UtcNow - timestamp;
|
||||
if (userProcessTime.TotalMilliseconds > 500)
|
||||
log.Write(LogLevel.Debug, $"Socket {SocketId} message processing slow ({(int)total.TotalMilliseconds}ms, {(int)userProcessTime.TotalMilliseconds}ms user code), consider offloading data handling to another thread. " +
|
||||
log.Write(LogLevel.Debug, $"Socket {SocketId}{(subscription == null ? "" : " subscription " + subscription!.Id)} message processing slow ({(int)total.TotalMilliseconds}ms, {(int)userProcessTime.TotalMilliseconds}ms user code), consider offloading data handling to another thread. " +
|
||||
"Data from this socket may arrive late or not at all if message processing is continuously slow.");
|
||||
|
||||
log.Write(LogLevel.Trace, $"Socket {SocketId} message processed in {(int)total.TotalMilliseconds}ms, ({(int)userProcessTime.TotalMilliseconds}ms user code)");
|
||||
|
||||
log.Write(LogLevel.Trace, $"Socket {SocketId}{(subscription == null ? "" : " subscription " + subscription!.Id)} message processed in {(int)total.TotalMilliseconds}ms, ({(int)userProcessTime.TotalMilliseconds}ms user code)");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Connect the websocket
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public async Task<bool> ConnectAsync() => await _socket.ConnectAsync().ConfigureAwait(false);
|
||||
|
||||
/// <summary>
|
||||
/// Retrieve the underlying socket
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public IWebsocket GetSocket() => _socket;
|
||||
|
||||
/// <summary>
|
||||
/// Trigger a reconnect of the socket connection
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public async Task TriggerReconnectAsync() => await _socket.ReconnectAsync().ConfigureAwait(false);
|
||||
|
||||
/// <summary>
|
||||
/// Close the connection
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public async Task CloseAsync()
|
||||
{
|
||||
if (Status == SocketStatus.Closed || Status == SocketStatus.Disposed)
|
||||
return;
|
||||
|
||||
if (socketClient.socketConnections.ContainsKey(SocketId))
|
||||
socketClient.socketConnections.TryRemove(SocketId, out _);
|
||||
|
||||
lock (subscriptionLock)
|
||||
{
|
||||
foreach (var subscription in subscriptions)
|
||||
{
|
||||
if (subscription.CancellationTokenRegistration.HasValue)
|
||||
subscription.CancellationTokenRegistration.Value.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
await _socket.CloseAsync().ConfigureAwait(false);
|
||||
_socket.Dispose();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Close a subscription on this connection. If all subscriptions on this connection are closed the connection gets closed as well
|
||||
/// </summary>
|
||||
/// <param name="subscription">Subscription to close</param>
|
||||
/// <returns></returns>
|
||||
public async Task CloseAsync(SocketSubscription subscription)
|
||||
{
|
||||
lock (subscriptionLock)
|
||||
{
|
||||
if (!subscriptions.Contains(subscription))
|
||||
return;
|
||||
|
||||
subscriptions.Remove(subscription);
|
||||
}
|
||||
|
||||
if (Status == SocketStatus.Closing || Status == SocketStatus.Closed || Status == SocketStatus.Disposed)
|
||||
return;
|
||||
|
||||
log.Write(LogLevel.Debug, $"Socket {SocketId} closing subscription {subscription.Id}");
|
||||
if (subscription.CancellationTokenRegistration.HasValue)
|
||||
subscription.CancellationTokenRegistration.Value.Dispose();
|
||||
|
||||
if (subscription.Confirmed && _socket.IsOpen)
|
||||
await socketClient.UnsubscribeAsync(this, subscription).ConfigureAwait(false);
|
||||
|
||||
bool shouldCloseConnection;
|
||||
lock (subscriptionLock)
|
||||
{
|
||||
if (Status == SocketStatus.Closing)
|
||||
{
|
||||
log.Write(LogLevel.Debug, $"Socket {SocketId} already closing");
|
||||
return;
|
||||
}
|
||||
|
||||
shouldCloseConnection = subscriptions.All(r => !r.UserSubscription);
|
||||
if (shouldCloseConnection)
|
||||
Status = SocketStatus.Closing;
|
||||
}
|
||||
|
||||
if (shouldCloseConnection)
|
||||
{
|
||||
log.Write(LogLevel.Debug, $"Socket {SocketId} closing as there are no more subscriptions");
|
||||
await CloseAsync().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Dispose the connection
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
Status = SocketStatus.Disposed;
|
||||
_socket.Dispose();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -513,7 +440,8 @@ namespace CryptoExchange.Net.Sockets
|
||||
return false;
|
||||
|
||||
subscriptions.Add(subscription);
|
||||
log.Write(LogLevel.Trace, $"Socket {SocketId} adding new subscription with id {subscription.Id}, total subscriptions on connection: {subscriptions.Count}");
|
||||
if(subscription.UserSubscription)
|
||||
log.Write(LogLevel.Debug, $"Socket {SocketId} adding new subscription with id {subscription.Id}, total subscriptions on connection: {subscriptions.Count(s => s.UserSubscription)}");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -544,7 +472,7 @@ namespace CryptoExchange.Net.Sockets
|
||||
/// </summary>
|
||||
/// <param name="messageEvent"></param>
|
||||
/// <returns>True if the data was successfully handled</returns>
|
||||
private (bool, TimeSpan) HandleData(MessageEvent messageEvent)
|
||||
private (bool, TimeSpan, SocketSubscription?) HandleData(MessageEvent messageEvent)
|
||||
{
|
||||
SocketSubscription? currentSubscription = null;
|
||||
try
|
||||
@@ -585,13 +513,13 @@ namespace CryptoExchange.Net.Sockets
|
||||
}
|
||||
}
|
||||
|
||||
return (handled, userCodeDuration);
|
||||
return (handled, userCodeDuration, currentSubscription);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
log.Write(LogLevel.Error, $"Socket {SocketId} Exception during message processing\r\nException: {ex.ToLogString()}\r\nData: {messageEvent.JsonData}");
|
||||
currentSubscription?.InvokeExceptionHandler(ex);
|
||||
return (false, TimeSpan.Zero);
|
||||
return (false, TimeSpan.Zero, null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -649,37 +577,24 @@ namespace CryptoExchange.Net.Sockets
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handler for a socket opening
|
||||
/// </summary>
|
||||
protected virtual void SocketOnOpen()
|
||||
{
|
||||
ReconnectTry = 0;
|
||||
PausedActivity = false;
|
||||
}
|
||||
|
||||
private async Task ReconnectWatcherAsync()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
await _reconnectWaitEvent.WaitAsync().ConfigureAwait(false);
|
||||
if (!ShouldReconnect)
|
||||
return;
|
||||
|
||||
Status = SocketStatus.Reconnecting;
|
||||
await ReconnectAsync().ConfigureAwait(false);
|
||||
|
||||
if (!ShouldReconnect)
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<CallResult<bool>> ProcessReconnectAsync()
|
||||
{
|
||||
if (!_socket.IsOpen)
|
||||
return new CallResult<bool>(new WebError("Socket not connected"));
|
||||
|
||||
if (Authenticated)
|
||||
bool anySubscriptions = false;
|
||||
lock (subscriptionLock)
|
||||
anySubscriptions = subscriptions.Any(s => s.UserSubscription);
|
||||
|
||||
if (!anySubscriptions)
|
||||
{
|
||||
// No need to resubscribe anything
|
||||
log.Write(LogLevel.Debug, $"Socket {SocketId} Nothing to resubscribe, closing connection");
|
||||
_ = _socket.CloseAsync();
|
||||
return new CallResult<bool>(true);
|
||||
}
|
||||
|
||||
if (subscriptions.Any(s => s.Authenticated))
|
||||
{
|
||||
// If we reconnected a authenticated connection we need to re-authenticate
|
||||
var authResult = await socketClient.AuthenticateSocketAsync(this).ConfigureAwait(false);
|
||||
@@ -689,13 +604,22 @@ namespace CryptoExchange.Net.Sockets
|
||||
return authResult;
|
||||
}
|
||||
|
||||
Authenticated = true;
|
||||
log.Write(LogLevel.Debug, $"Socket {SocketId} authentication succeeded on reconnected socket.");
|
||||
}
|
||||
|
||||
// Get a list of all subscriptions on the socket
|
||||
List<SocketSubscription> subscriptionList;
|
||||
List<SocketSubscription> subscriptionList = new List<SocketSubscription>();
|
||||
lock (subscriptionLock)
|
||||
subscriptionList = subscriptions.Where(h => h.Request != null).ToList();
|
||||
{
|
||||
foreach (var subscription in subscriptions)
|
||||
{
|
||||
if (subscription.Request != null)
|
||||
subscriptionList.Add(subscription);
|
||||
else
|
||||
subscription.Confirmed = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Foreach subscription which is subscribed by a subscription request we will need to resend that request to resubscribe
|
||||
for (var i = 0; i < subscriptionList.Count; i += socketClient.ClientOptions.MaxConcurrentResubscriptionsPerSocket)
|
||||
@@ -705,13 +629,16 @@ namespace CryptoExchange.Net.Sockets
|
||||
|
||||
var taskList = new List<Task<CallResult<bool>>>();
|
||||
foreach (var subscription in subscriptionList.Skip(i).Take(socketClient.ClientOptions.MaxConcurrentResubscriptionsPerSocket))
|
||||
taskList.Add(socketClient.SubscribeAndWaitAsync(this, subscription.Request!, subscription));
|
||||
taskList.Add(socketClient.SubscribeAndWaitAsync(this, subscription.Request!, subscription));
|
||||
|
||||
await Task.WhenAll(taskList).ConfigureAwait(false);
|
||||
if (taskList.Any(t => !t.Result.Success))
|
||||
return taskList.First(t => !t.Result.Success).Result;
|
||||
}
|
||||
|
||||
foreach (var subscription in subscriptionList)
|
||||
subscription.Confirmed = true;
|
||||
|
||||
if (!_socket.IsOpen)
|
||||
return new CallResult<bool>(new WebError("Socket not connected"));
|
||||
|
||||
@@ -731,7 +658,7 @@ namespace CryptoExchange.Net.Sockets
|
||||
|
||||
return await socketClient.SubscribeAndWaitAsync(this, socketSubscription.Request!, socketSubscription).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Status of the socket connection
|
||||
/// </summary>
|
||||
@@ -750,6 +677,10 @@ namespace CryptoExchange.Net.Sockets
|
||||
/// </summary>
|
||||
Reconnecting,
|
||||
/// <summary>
|
||||
/// Resubscribing on reconnected socket
|
||||
/// </summary>
|
||||
Resubscribing,
|
||||
/// <summary>
|
||||
/// Closing
|
||||
/// </summary>
|
||||
Closing,
|
||||
|
||||
Reference in New Issue
Block a user