1
0
mirror of https://github.com/JKorf/CryptoExchange.Net.git synced 2026-10-13 22:51:39 +00:00

WebSocket covered subscriptions

Updated WebSocket duplicate subscription handling
Fixed incorrect status keeping of WebSocket subscriptions which don't expect a response
This commit is contained in:
Jan Korf
2026-10-12 13:19:44 +02:00
committed by GitHub
parent 550afd41c3
commit a0f3dcaa4c
3 changed files with 64 additions and 13 deletions
@@ -23,6 +23,10 @@ namespace CryptoExchange.Net.SharedApis
/// Get user funding history capability.
/// </summary>
public static SharedRestCapability<IGetUserFundingHistory, IGetUserFundingHistoryRest> GetUserFundingHistory { get; } = new();
/// <summary>
/// Subscribe to funding info updates capability.
/// </summary>
public static SharedCapabilityReference<ISubscribeFundingInfoSocket> SubscribeFundingInfo { get; } = new();
}
}
}
@@ -702,7 +702,7 @@ namespace CryptoExchange.Net.Sockets.Default
if (subscription.CancellationTokenRegistration.HasValue)
subscription.CancellationTokenRegistration.Value.Dispose();
bool anyDuplicateSubscription = _listeners.OfType<Subscription>().Any(x => x != subscription && x.MessageRouter.Routes.All(l => subscription.MessageRouter.ContainsCheck(l)));
bool anyDuplicateSubscription = SubscriptionIsCovered(subscription);
bool shouldCloseConnection = _listeners.OfType<Subscription>().All(r => !r.UserSubscription || r.Status == SubscriptionStatus.Closing || r.Status == SubscriptionStatus.Closed) && !DedicatedRequestConnection.IsDedicatedRequestConnection;
if (!anyDuplicateSubscription)
@@ -1069,6 +1069,32 @@ namespace CryptoExchange.Net.Sockets.Default
return CallResult.Ok();
}
/// <summary>
/// Whether there is currently a subscription covering all the routes the subscription lists
/// </summary>
protected bool SubscriptionIsCovered(Subscription subscription)
=> _listeners.OfType<Subscription>().Any(
x => x != subscription
&& x.Active
&& x.MessageRouter.Routes.All(l => subscription.MessageRouter.ContainsCheck(l)));
/// <summary>
/// Whether there is a subscription earlier in the list covering all the routes the subscription lists
/// </summary>
protected bool IsCoveredByEarlierSubscription(Subscription subscription)
{
foreach (var other in _listeners.OfType<Subscription>())
{
if (other == subscription)
return false;
if (other.Active && other.MessageRouter.Routes.All(x => subscription.MessageRouter.ContainsCheck(x)))
return true;
}
return false;
}
/// <summary>
/// Try to subscribe a new subscription by sending the subscribe query and wait for the result as needed
/// </summary>
@@ -1103,11 +1129,11 @@ namespace CryptoExchange.Net.Sockets.Default
return WebSocketResult.Ok(ApiClient.Exchange, SocketId, default, 0, ConnectionUriString);
}
var subCompleteHandler = () =>
var subCompleteHandler = (bool success) =>
{
subscription.Status = subQuery.Success ? SubscriptionStatus.Subscribed : SubscriptionStatus.Pending;
subscription.Status = success ? SubscriptionStatus.Subscribed : SubscriptionStatus.Pending;
subscription.HandleSubQueryResponse(this, subQuery.Response);
if (newSubscription && subQuery.Success)
if (newSubscription && success)
{
subscription.TokenExpired += HandleTokenExpired;
if (subCancelToken != default)
@@ -1122,6 +1148,14 @@ namespace CryptoExchange.Net.Sockets.Default
};
subQuery.OnComplete = subCompleteHandler;
// For duplicate subscription don't send another subscribe request but just handle it internally
// Unsubscribe also doesn't unsubscribe when there is still a duplicate subscription running
if (IsCoveredByEarlierSubscription(subscription))
{
subQuery.Complete();
return WebSocketResult.Ok(ApiClient.Exchange, SocketId, default, subQuery.Id, ConnectionUriString);
}
var subQueryResult = await SendAndWaitQueryAsync(subQuery).ConfigureAwait(false);
if (!subQueryResult.Success)
{
@@ -1132,9 +1166,6 @@ namespace CryptoExchange.Net.Sockets.Default
return WebSocketResult.Fail(ApiClient.Exchange, subQueryResult.Error!);
}
if (!subQuery.ExpectsResponse)
subCompleteHandler();
return subQueryResult;
}
+22 -6
View File
@@ -124,7 +124,7 @@ namespace CryptoExchange.Net.Sockets
/// <summary>
/// On complete callback
/// </summary>
public Action? OnComplete { get; set; }
public Action<bool>? OnComplete { get; set; }
/// <inheritdoc />
public event Action? OnMessageRouterUpdated;
@@ -157,8 +157,7 @@ namespace CryptoExchange.Net.Sockets
}
else
{
Completed = true;
_event.Set();
Complete();
}
}
@@ -175,6 +174,11 @@ namespace CryptoExchange.Net.Sockets
/// </summary>
public abstract void Timeout();
/// <summary>
/// Mark request as successfully completed
/// </summary>
public abstract void Complete();
/// <summary>
/// Mark request as failed
/// </summary>
@@ -248,7 +252,7 @@ namespace CryptoExchange.Net.Sockets
{
Completed = true;
_event.Set();
OnComplete?.Invoke();
OnComplete?.Invoke(Success);
}
return handled;
@@ -267,7 +271,19 @@ namespace CryptoExchange.Net.Sockets
Completed = true;
_event.Set();
OnComplete?.Invoke();
OnComplete?.Invoke(Success);
}
/// <inheritdoc />
public override void Complete()
{
if (Completed)
return;
Result = CallResult<THandlerResponse>.Ok(default!);
Completed = true;
_event.Set();
OnComplete?.Invoke(Success);
}
/// <inheritdoc />
@@ -280,7 +296,7 @@ namespace CryptoExchange.Net.Sockets
Completed = true;
_event.Set();
OnComplete?.Invoke();
OnComplete?.Invoke(Success);
}
}
}