Nethereum / Nethereum/Nethereum
EthLogsObservableSubscription.StopAsync() never returns
Nobody has claimed this yet.
- Dominant language
- C#
- Stars
- 2.3k
- Forks
- 744
- PR merge metrics
- No merged PRs in 30d
Description
When Subscribing to an Event using the EthLogsObservableSubscription class in the Nethereum.RPC.Reactive NuGet package and unsubscribe 10 Minutes later the unsubscribe call never returns. The task is simply waiting forever.
Nethereum.RPC.Reactive Version: 4.12.0
Expected Behavior
The call should always return on success or at least give the option to return with an exception on a timeout.
Current Behavior
The call never returns and the task got stuck in the awaiting state.
Possible Solution
Solution 1) Refactoring of the UnsubscribeAsync() and StopAsync() method to always return regardless of the state of the connection or the success of the unsubscribe event.
Solution 2) Refactoring of the UnsubscribeAsync() and StopAsync() method to accept an CancelationToken that can be used for timeout checking on the client side.
Steps to Reproduce
Subscribe to the event like
private async Task<EthLogsObservableSubscription[]> SubscribeEventsAsync()
{
var ret = new EthLogsObservableSubscription[2];
ret[0] = new EthLogsObservableSubscription(_webSocketClient);
ret[1] = new EthLogsObservableSubscription(_webSocketClient);
var filterTransfer = Event<PairCreatedEvent>.GetEventABI().CreateFilterInput(_exofiAccessOptions.FactoryContractAddress);
ret[0].GetSubscribeResponseAsObservable().Subscribe(subscriptionId => _logger.LogInformation("Block Header subscription Id: {subscriptionId}", subscriptionId));
ret[0].GetUnsubscribeResponseAsObservable().Subscribe(response => _logger.LogInformation("Block Header unsubscribe result: {response}", response));
ret[0].GetSubscriptionDataResponsesAsObservable().Subscribe(log => ProcessLog(log));
ret[1].GetSubscribeResponseAsObservable().Subscribe(subscriptionId => _logger.LogInformation("Block Header subscription Id: {subscriptionId}", subscriptionId));
ret[1].GetUnsubscribeResponseAsObservable().Subscribe(response => _logger.LogInformation("Block Header unsubscribe result: {response}", response));
ret[1].GetSubscriptionDataResponsesAsObservable().Subscribe(log => ProcessCommonLog(log));
await _webSocketClient.StartAsync().ConfigureAwait(false);
if(_webSocketClient.WebSocketState != System.Net.WebSockets.WebSocketState.Open)
{
throw new Exception("Websocket connection failed!");
}
await ret[0].SubscribeAsync(filterTransfer).ConfigureAwait(false);
await ret[1].SubscribeAsync().ConfigureAwait(false);
while(ret[0].SubscriptionState == Nethereum.JsonRpc.Client.Streaming.SubscriptionState.Subscribing)
{
await Task.Delay(TimeSpan.FromMilliseconds(100)).ConfigureAwait(false);
}
while(ret[1].SubscriptionState == Nethereum.JsonRpc.Client.Streaming.SubscriptionState.Subscribing)
{
await Task.Delay(TimeSpan.FromMilliseconds(100)).ConfigureAwait(false);
}
if(ret[0].SubscriptionState != Nethereum.JsonRpc.Client.Streaming.SubscriptionState.Subscribed)
{
throw new Exception("CreatePair Subscription failed!");
}
if(ret[1].SubscriptionState != Nethereum.JsonRpc.Client.Streaming.SubscriptionState.Subscribed)
{
throw new Exception("CheckPair Subscription failed!");
}
return ret;
}
then wait for 10 Minutes but keep the connection alive with pings. I used a Task.Delay call to wait for 10 Minutes. While another task is pinging the connection every minute to keep the connection alive.
Unsubscribe from the Events
public async Task StopAsync()
{
for(int i = 0; i < _subscriptions.Length; ++i)
{
await _subscriptions[i].UnsubscribeAsync().ConfigureAwait(false);
}
await _webSocketClient.StopAsync().ConfigureAwait(false);
}
The await _subscriptions[i].UnsubscribeAsync().ConfigureAwait(false); will never finish depending on the connection state. There is sometimes an exception if the connection state is invalid in all other cases the call never returns. On some occations the processing stops at the StopAsync call. I suspect there is a race condition, maybe if the connection state changes during the calls.
Context (Environment)
The ultimate goal was to ensure that the subscription is still valid and receiving data. Since Nethereum does give me any option to verify the state of the subscription I renew the subscription every 10 Minutes. Therefore I unsubscribe and subscribe again. I do this with multiple Subscriptions so the problem was not visible for me at first. One of my test runs did create this behavior on all subscriptions out of pure Luck. At first it looked like my client was stuck and some locking did happen since I got no more event notifications into my client a detail inspection revealed that there was no locking involved and all my tasks are awaiting the custom StopAsync Method posted above.
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with EthLogsObservableSubscription.UnsubscribeAsync() and the custom StopAsync() sequence shown in the issue, then reproduce it with multiple WebSocket subscriptions while the connection is kept alive by pings. Trace the connection-state and awaiting paths during unsubscribe and shutdown; done means these calls no longer hang when connection state changes and their failure or timeout behavior is defined and verified.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- csharp
- Domain
- api, backend, networking
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 38/100