From acb8f6af597333eeabcd08e961598f2968039be0 Mon Sep 17 00:00:00 2001 From: Jan Korf Date: Fri, 28 Dec 2018 19:56:51 +0100 Subject: [PATCH 1/4] Socket reconnecting fix --- CryptoExchange.Net/CryptoExchange.Net.csproj | 2 +- CryptoExchange.Net/Interfaces/IWebsocket.cs | 1 + CryptoExchange.Net/SocketClient.cs | 6 +++++ CryptoExchange.Net/Sockets/BaseSocket.cs | 27 +++++++++++++------- 4 files changed, 26 insertions(+), 10 deletions(-) diff --git a/CryptoExchange.Net/CryptoExchange.Net.csproj b/CryptoExchange.Net/CryptoExchange.Net.csproj index 8dd848a..ae1620c 100644 --- a/CryptoExchange.Net/CryptoExchange.Net.csproj +++ b/CryptoExchange.Net/CryptoExchange.Net.csproj @@ -7,7 +7,7 @@ CryptoExchange.Net JKorf - 2.0.3 + 2.0.5 false https://github.com/JKorf/CryptoExchange.Net https://github.com/JKorf/CryptoExchange.Net/blob/master/LICENSE diff --git a/CryptoExchange.Net/Interfaces/IWebsocket.cs b/CryptoExchange.Net/Interfaces/IWebsocket.cs index 3c6829e..a208d66 100644 --- a/CryptoExchange.Net/Interfaces/IWebsocket.cs +++ b/CryptoExchange.Net/Interfaces/IWebsocket.cs @@ -14,6 +14,7 @@ namespace CryptoExchange.Net.Interfaces int Id { get; } bool ShouldReconnect { get; set; } + bool Reconnecting { get; set; } Func DataInterpreter { get; set; } DateTime? DisconnectTime { get; set; } string Url { get; } diff --git a/CryptoExchange.Net/SocketClient.cs b/CryptoExchange.Net/SocketClient.cs index 4e34cb5..e3e379e 100644 --- a/CryptoExchange.Net/SocketClient.cs +++ b/CryptoExchange.Net/SocketClient.cs @@ -169,6 +169,11 @@ namespace CryptoExchange.Net { if (socket.ShouldReconnect) { + if (socket.Reconnecting) + return; // Already reconnecting + + socket.Reconnecting = true; + log.Write(LogVerbosity.Info, $"Socket {socket.Id} Connection lost, will try to reconnect"); Task.Run(() => { @@ -191,6 +196,7 @@ namespace CryptoExchange.Net lock (sockets) subscription = sockets.Single(s => s.Socket == socket); + socket.Reconnecting = false; if (!SocketReconnect(subscription, DateTime.UtcNow - time.Value)) socket.Close().Wait(); // Close so we end up reconnecting again else diff --git a/CryptoExchange.Net/Sockets/BaseSocket.cs b/CryptoExchange.Net/Sockets/BaseSocket.cs index e152e7c..f3da7d0 100644 --- a/CryptoExchange.Net/Sockets/BaseSocket.cs +++ b/CryptoExchange.Net/Sockets/BaseSocket.cs @@ -32,7 +32,10 @@ namespace CryptoExchange.Net.Sockets public int Id { get; } public DateTime? DisconnectTime { get; set; } + public bool ShouldReconnect { get; set; } + public bool Reconnecting { get; set; } + public string Url { get; } public bool IsClosed => socket.State == WebSocketState.Closed; public bool IsOpen => socket.State == WebSocketState.Open; @@ -115,14 +118,17 @@ namespace CryptoExchange.Net.Sockets { while (true) { - if (socket == null || socket.State != WebSocketState.Open) - return; - - if (DateTime.UtcNow - LastActionTime > Timeout) + lock (socketLock) { - log.Write(LogVerbosity.Warning, $"No data received for {Timeout}, reconnecting socket"); - Close().Wait(); - return; + if (socket == null || socket.State != WebSocketState.Open) + return; + + if (DateTime.UtcNow - LastActionTime > Timeout) + { + log.Write(LogVerbosity.Warning, $"No data received for {Timeout}, reconnecting socket"); + Close().ConfigureAwait(false); + return; + } } Thread.Sleep(500); @@ -165,8 +171,11 @@ namespace CryptoExchange.Net.Sockets public virtual void Reset() { - socket.Dispose(); - socket = null; + lock (socketLock) + { + socket?.Dispose(); + socket = null; + } } public virtual void Send(string data) From 36169d08a3134fb7aaf6629a6d064031af723f9a Mon Sep 17 00:00:00 2001 From: Jan Korf Date: Fri, 28 Dec 2018 20:26:55 +0100 Subject: [PATCH 2/4] fixed unit test --- CryptoExchange.Net.UnitTests/TestImplementations/TestSocket.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/CryptoExchange.Net.UnitTests/TestImplementations/TestSocket.cs b/CryptoExchange.Net.UnitTests/TestImplementations/TestSocket.cs index 7157419..94d73a9 100644 --- a/CryptoExchange.Net.UnitTests/TestImplementations/TestSocket.cs +++ b/CryptoExchange.Net.UnitTests/TestImplementations/TestSocket.cs @@ -32,6 +32,7 @@ namespace CryptoExchange.Net.UnitTests.TestImplementations public SslProtocols SSLProtocols { get; set; } public int ConnectCalls { get; private set; } + public bool Reconnecting { get; set; } public static int lastId = 0; public static object lastIdLock = new object(); From b423d4b6c7e5845c5f36b72b6e0237842cb6e934 Mon Sep 17 00:00:00 2001 From: Jan Korf Date: Fri, 28 Dec 2018 21:44:38 +0100 Subject: [PATCH 3/4] added lock for events, fixed some forgotten configureawait(false) calls --- .../Sockets/SocketSubscription.cs | 77 ++++++++++++------- .../Sockets/UpdateSubscription.cs | 2 +- 2 files changed, 50 insertions(+), 29 deletions(-) diff --git a/CryptoExchange.Net/Sockets/SocketSubscription.cs b/CryptoExchange.Net/Sockets/SocketSubscription.cs index 6406c1f..a5911c0 100644 --- a/CryptoExchange.Net/Sockets/SocketSubscription.cs +++ b/CryptoExchange.Net/Sockets/SocketSubscription.cs @@ -26,6 +26,7 @@ namespace CryptoExchange.Net.Sockets private bool lostTriggered; private readonly List waitingForEvents; + private object eventLock = new object(); public SocketSubscription(IWebsocket socket) @@ -44,8 +45,11 @@ namespace CryptoExchange.Net.Sockets Socket.DisconnectTime = DateTime.UtcNow; lostTriggered = true; - foreach (var events in Events) - events.Reset(); + lock (eventLock) + { + foreach (var events in Events) + events.Reset(); + } if (Socket.ShouldReconnect) ConnectionLost?.Invoke(); @@ -62,72 +66,89 @@ namespace CryptoExchange.Net.Sockets public void AddEvent(string name) { - Events.Add(new SocketEvent(name)); + lock (eventLock) + Events.Add(new SocketEvent(name)); } public void SetEventByName(string name, bool success, Error error) { - var waitingEvent = waitingForEvents.SingleOrDefault(e => e.Name == name); - if (waitingEvent != null) + lock (eventLock) { - waitingEvent.Set(success, error); - waitingForEvents.Remove(waitingEvent); + var waitingEvent = waitingForEvents.SingleOrDefault(e => e.Name == name); + if (waitingEvent != null) + { + waitingEvent.Set(success, error); + waitingForEvents.Remove(waitingEvent); + } } } public void SetEventById(string id, bool success, Error error) { - var waitingEvent = waitingForEvents.SingleOrDefault(e => e.WaitingId == id); - if (waitingEvent != null) + lock (eventLock) { - waitingEvent.Set(success, error); - waitingForEvents.Remove(waitingEvent); + var waitingEvent = waitingForEvents.SingleOrDefault(e => e.WaitingId == id); + if (waitingEvent != null) + { + waitingEvent.Set(success, error); + waitingForEvents.Remove(waitingEvent); + } } } public SocketEvent GetWaitingEvent(string name) { - return waitingForEvents.SingleOrDefault(w => w.Name == name); + lock (eventLock) + return waitingForEvents.SingleOrDefault(w => w.Name == name); } - - - + public Task> WaitForEvent(string name, TimeSpan timeout) { - return WaitForEvent(name, (int)Math.Round(timeout.TotalMilliseconds, 0)); + lock (eventLock) + return WaitForEvent(name, (int)Math.Round(timeout.TotalMilliseconds, 0)); } public Task> WaitForEvent(string name, int timeout) { - var evnt = Events.Single(e => e.Name == name); - waitingForEvents.Add(evnt); - return Task.Run(() => evnt.Wait(timeout)); + lock (eventLock) + { + var evnt = Events.Single(e => e.Name == name); + waitingForEvents.Add(evnt); + return Task.Run(() => evnt.Wait(timeout)); + } } public Task> WaitForEvent(string name, string id, TimeSpan timeout) { - return WaitForEvent(name, id, (int)Math.Round(timeout.TotalMilliseconds, 0)); + lock (eventLock) + return WaitForEvent(name, id, (int)Math.Round(timeout.TotalMilliseconds, 0)); } public Task> WaitForEvent(string name, string id, int timeout) { - var evnt = Events.Single(e => e.Name == name); - evnt.WaitingId = id; - waitingForEvents.Add(evnt); - return Task.Run(() => evnt.Wait(timeout)); + lock (eventLock) + { + var evnt = Events.Single(e => e.Name == name); + evnt.WaitingId = id; + waitingForEvents.Add(evnt); + return Task.Run(() => evnt.Wait(timeout)); + } } public void ResetEvents() { - foreach (var waiting in new List(waitingForEvents)) - waiting.Set(false, new UnknownError("Connection reset")); - waitingForEvents.Clear(); + lock (eventLock) + { + foreach (var waiting in waitingForEvents) + waiting.Set(false, new UnknownError("Connection reset")); + waitingForEvents.Clear(); + } } public async Task Close() { Socket.ShouldReconnect = false; - await Socket.Close(); + await Socket.Close().ConfigureAwait(false); Socket.Dispose(); } } diff --git a/CryptoExchange.Net/Sockets/UpdateSubscription.cs b/CryptoExchange.Net/Sockets/UpdateSubscription.cs index 16be6ed..7188679 100644 --- a/CryptoExchange.Net/Sockets/UpdateSubscription.cs +++ b/CryptoExchange.Net/Sockets/UpdateSubscription.cs @@ -41,7 +41,7 @@ namespace CryptoExchange.Net.Sockets /// public async Task Close() { - await subscription.Close(); + await subscription.Close().ConfigureAwait(false); } } } From 9f5deac9fc58332ae86698e1dd8d7fe2b61d522a Mon Sep 17 00:00:00 2001 From: Jan Korf Date: Sat, 29 Dec 2018 11:38:55 +0100 Subject: [PATCH 4/4] Updated version --- CryptoExchange.Net/CryptoExchange.Net.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CryptoExchange.Net/CryptoExchange.Net.csproj b/CryptoExchange.Net/CryptoExchange.Net.csproj index ae1620c..00e620a 100644 --- a/CryptoExchange.Net/CryptoExchange.Net.csproj +++ b/CryptoExchange.Net/CryptoExchange.Net.csproj @@ -7,7 +7,7 @@ CryptoExchange.Net JKorf - 2.0.5 + 2.0.6 false https://github.com/JKorf/CryptoExchange.Net https://github.com/JKorf/CryptoExchange.Net/blob/master/LICENSE