Introduction

As a developer, testing network conditions and connection persistence can be a challenge. Sometimes, network transportation isn’t always reliable, and it’s not easy to simulate a poor network connection during testing. Fortunately, I recently discovered clumsy, a tool that made it easy for me to test my SignalR connection persistence in a C# project. In this article, I will explain how clumsy was helpful for me and how it can be helpful for other developers.

Hub client

First let me show you the hub client I’m using in my project:

using System.Net;
using System.Security;
using Polly;
using Serilog;
using Microsoft.AspNet.SignalR.Client;

namespace Test.SignalR;

/// <summary>
/// SignalR Hub client implementation for ASP.NET
/// </summary>
public class TestHubClient : IAsyncDisposable
{
    private HubConnection _connection;
    private IHubProxy _hubProxy;

    public event Func<Task>? Disconnected;

    public string HubUrl { get; }
    public ILogger Logger { get; }

    public ConnectionState State => _connection.State;

    private bool _tryingToReconnect;

    public TestHubClient(string hubUrl, SecureString key, ILogger? logger = null)
    {
        HubUrl = hubUrl;
        Logger = logger ?? Log.Logger;

        _connection = new HubConnection(HubUrl, false);
        _hubProxy = _connection.CreateHubProxy("testHub");

        // Setup events
        _connection.Reconnecting  += OnReconnecting;
        _connection.Reconnected += OnReconnected;
        _connection.Closed += OnClosed;
        _connection.Error += OnError;
        _connection.ConnectionSlow += OnConnectionSlow;
    }

    /// <summary>
    /// When invoked, client will automatically try to reconnect when connection is closed
    /// </summary>
    public void WithAutomaticReconnect()
    {
        Disconnected += async () =>
        {
            if (!_tryingToReconnect)
            {
                Logger.Debug("Reconnecting after 5 seconds");

                await Task.Delay(TimeSpan.FromSeconds(5));
                await ConnectAsync(CancellationToken.None);
            }
        };
    }

    private void OnClosed()
    {
        Logger.Warning("Disconnected from hub: {HubUrl}", HubUrl);

        Disconnected?.Invoke();
    }

    private void OnReconnected()
    {
        Logger.Information("Reconnected to hub: {HubUrl}", HubUrl);
    }

    private void OnReconnecting()
    {
        Logger.Debug("Reconnecting to hub: {HubUrl}", HubUrl);
    }

    private void OnError(Exception exception)
    {
        Logger.Warning(exception, "Error occured in hub: {HubUrl}", HubUrl);
    }

    private void OnConnectionSlow()
    {
        Logger.Warning("Slow connection to hub: {HubUrl}", HubUrl);
    }

    public async Task ConnectAsync(CancellationToken ct)
    {
        try
        {
            _tryingToReconnect = true;

            // Here we setup Polly policy to retry connection forever 
            // with wait time between each attempt increasing to maximum of 1 minute
            await Policy
                .Handle<Exception>()
                .WaitAndRetryForeverAsync(
                    retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)).Clamp(TimeSpan.FromMinutes(1)),
                    (exception, timespan) =>
                    {
                        Logger.Warning(exception, "Error occured while trying to connect to hub {HubUrl}. Another attempt after {TimeSpan}", HubUrl, timespan);    
                    })
                .ExecuteAsync(async () =>
                {
                    ct.ThrowIfCancellationRequested();

                    Logger.Debug("Connecting to {HubUrl}", HubUrl);

                    await _connection.Start();

                    Logger.Information("Connected to hub: {HubUrl}", HubUrl);
                });
        }
        finally
        {
            _tryingToReconnect = false;
        }
    }

    public Task DisconnectAsync()
    {
        _connection.Stop();

        return Task.CompletedTask;
    }

    public ValueTask DisposeAsync()
    {
        _connection.Dispose();

        return ValueTask.CompletedTask;
    }
}

Solution

I used clumsy to intercept and manipulate network packets.

file

Here is one scenario that I used. I started my C# application that is using SignalR(old ASP.Net 🙄), letting it successfully connect to the hub. I then started clumsy, setting the filter to ip.DstAddr == 123.45.67.89 where the ip address 123.45.67.89 being the address of my SignalR hub. I then set Lag – delay to 1000ms and Drop chance to 90%. After hitting the Start button in clumsy inteface and little bit of a delay, I could see messages in console showing how my app was disconnected and is trying to reconnect. Note that I’m using Polly and it’s retry policies.

[13:07:34 WRN] Disconnected from hub: https://example.com/signalr/
[13:07:38 DBG] Connecting to https://example.com/signalr/
[13:13:09 WRN] Error occured while trying to connect to hub https://example.com/signalr/. Another attempt after 00:00:32

After hitting Stop button in clumsy, my application was able to successfully establish the SignalR connection again.

[13:13:41 DBG] Connecting to https://example.com/signalr/
[13:13:41 INF] Connected to hub: https://example.com/signalr/

Using clumsy, I was able to test and debug my SignalR connection persistence quickly and efficiently. The tool allowed me to simulate various network conditions and observe how my application would handle them. I did not need to change any code in my application or install any additional software, making it a great tool for quick testing.

Conclusion

In conclusion, clumsy is an excellent tool for developers who want to test their application’s behavior under poor network conditions. It’s user-friendly GUI(It also has CLI! 👍), simple setup process, and the ability to intercept and manipulate network packets make it an essential tool in any developer’s toolbox. I was able to use clumsy to test my SignalR connection persistence quickly and efficiently, without needing to modify my application’s code. I highly recommend it to any developer who wants to ensure that their application can handle a wide range of network conditions.

Last modified: April 11, 2023
Subscribe
Notify of
guest
0 Comments
Inline Feedbacks
View all comments