diff --git a/CryptoExchange.Net/Trackers/UserData/UserDataTracker.cs b/CryptoExchange.Net/Trackers/UserData/UserDataTracker.cs index ff27446..84131e0 100644 --- a/CryptoExchange.Net/Trackers/UserData/UserDataTracker.cs +++ b/CryptoExchange.Net/Trackers/UserData/UserDataTracker.cs @@ -5,6 +5,7 @@ using Microsoft.Extensions.Logging; using System; using System.Collections.Generic; using System.Linq; +using System.Threading; using System.Threading.Tasks; namespace CryptoExchange.Net.Trackers.UserData @@ -22,6 +23,11 @@ namespace CryptoExchange.Net.Trackers.UserData /// Listen key to use for subscriptions /// protected string? _listenKey; + /// + /// Cts + /// + protected CancellationTokenSource? _cts; + /// /// List of data trackers /// @@ -68,6 +74,8 @@ namespace CryptoExchange.Net.Trackers.UserData /// public async Task StartAsync() { + _cts = new CancellationTokenSource(); + foreach(var tracker in DataTrackers) tracker.OnConnectedChange += (x) => OnConnectedChange?.Invoke(tracker.DataType, x); @@ -100,12 +108,21 @@ namespace CryptoExchange.Net.Trackers.UserData public async Task StopAsync() { _logger.LogDebug("Stopping UserDataTracker"); + _cts?.Cancel(); + var tasks = new List(); foreach (var dataTracker in DataTrackers) tasks.Add(dataTracker.StopAsync()); + await DoStopAsync().ConfigureAwait(false); await Task.WhenAll(tasks).ConfigureAwait(false); _logger.LogDebug("Stopped UserDataTracker"); } + + /// + /// Stop implementation + /// + /// + protected virtual Task DoStopAsync() => Task.CompletedTask; } } diff --git a/CryptoExchange.Net/Trackers/UserData/UserFuturesDataTracker.cs b/CryptoExchange.Net/Trackers/UserData/UserFuturesDataTracker.cs index 2e05306..26d3268 100644 --- a/CryptoExchange.Net/Trackers/UserData/UserFuturesDataTracker.cs +++ b/CryptoExchange.Net/Trackers/UserData/UserFuturesDataTracker.cs @@ -20,6 +20,7 @@ namespace CryptoExchange.Net.Trackers.UserData private readonly IFuturesSymbolRestClient _symbolClient; private readonly IListenKeyRestClient? _listenKeyClient; private readonly ExchangeParameters? _exchangeParameters; + private Task? _lkKeepAliveTask; /// protected override UserDataItemTracker[] DataTrackers { get; } @@ -115,10 +116,38 @@ namespace CryptoExchange.Net.Trackers.UserData return lkResult; } + _lkKeepAliveTask = KeepAliveListenKeyAsync(); + _listenKey = lkResult.Data; } return CallResult.SuccessResult; } + + /// + protected override async Task DoStopAsync() + { + if (_lkKeepAliveTask != null) + await _lkKeepAliveTask.ConfigureAwait(false); + } + + private async Task KeepAliveListenKeyAsync() + { + var interval = TimeSpan.FromMinutes(30); + while (!_cts!.IsCancellationRequested) + { + try { await Task.Delay(interval, _cts.Token).ConfigureAwait(false); } catch (Exception) + { + break; + } + + var result = await _listenKeyClient!.KeepAliveListenKeyAsync(new KeepAliveListenKeyRequest(_listenKey!, TradingMode.Spot)).ConfigureAwait(false); + if (!result) + _logger.LogWarning("Listen key keep alive failed: " + result.Error); + + // If failed shorten the delay to allow a couple more retries + interval = result ? TimeSpan.FromMinutes(30) : TimeSpan.FromMinutes(5); + } + } } } diff --git a/CryptoExchange.Net/Trackers/UserData/UserSpotDataTracker.cs b/CryptoExchange.Net/Trackers/UserData/UserSpotDataTracker.cs index 22f78dd..6856bca 100644 --- a/CryptoExchange.Net/Trackers/UserData/UserSpotDataTracker.cs +++ b/CryptoExchange.Net/Trackers/UserData/UserSpotDataTracker.cs @@ -7,6 +7,7 @@ using System.Linq; using CryptoExchange.Net.Trackers.UserData.Interfaces; using CryptoExchange.Net.Trackers.UserData.Objects; using CryptoExchange.Net.Trackers.UserData.ItemTrackers; +using System; namespace CryptoExchange.Net.Trackers.UserData { @@ -18,6 +19,7 @@ namespace CryptoExchange.Net.Trackers.UserData private readonly ISpotSymbolRestClient _symbolClient; private readonly IListenKeyRestClient? _listenKeyClient; private readonly ExchangeParameters? _exchangeParameters; + private Task? _lkKeepAliveTask; /// protected override UserDataItemTracker[] DataTrackers { get; } @@ -91,10 +93,39 @@ namespace CryptoExchange.Net.Trackers.UserData return lkResult; } + _lkKeepAliveTask = KeepAliveListenKeyAsync(); + _listenKey = lkResult.Data; } return CallResult.SuccessResult; } + + /// + protected override async Task DoStopAsync() + { + if (_lkKeepAliveTask != null) + await _lkKeepAliveTask.ConfigureAwait(false); + } + + private async Task KeepAliveListenKeyAsync() + { + var interval = TimeSpan.FromMinutes(30); + while (!_cts!.IsCancellationRequested) + { + try { await Task.Delay(interval, _cts.Token).ConfigureAwait(false); } + catch (Exception) + { + break; + } + + var result = await _listenKeyClient!.KeepAliveListenKeyAsync(new KeepAliveListenKeyRequest(_listenKey!, TradingMode.Spot)).ConfigureAwait(false); + if (!result) + _logger.LogWarning("Listen key keep alive failed: " + result.Error); + + // If failed shorten the delay to allow a couple more retries + interval = result ? TimeSpan.FromMinutes(30) : TimeSpan.FromMinutes(5); + } + } } }