Nethereum / Nethereum/Nethereum

EthLogsObservableSubscription.StopAsync() never returns

Open
#931 0 comments 0 reactions 0 assignees View on GitHub

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

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.