Why is my blazor app leaving so many ports open

Viewed 64

I created a .net 6 app using server side Blazor and SignalR. The app was basically a single page with 10 different components. Each component was a client that looked something like this:

@code {
private HubConnection? hubConnection;
private ExampleViewModel data { get; set; } = new ExampleViewModel();

protected override async Task OnInitializedAsync()
{
    hubConnection = new HubConnectionBuilder()
        .WithUrl(NavigationManager.ToAbsoluteUri("/mainhub"))
        .Build();

    hubConnection.On<ExampleViewModel>("example", (Data) =>
    {
        data = Data;
        StateHasChanged();
    });

    await hubConnection.StartAsync();
}

public async ValueTask DisposeAsync()
{
    if (hubConnection is not null)
    {
        await hubConnection.DisposeAsync();
    }
}
}

Each component has a "broadcaster" that runs on a timer and makes a call to the database using Mediator and Dapper. Example:

 public class ExampleBroadcaster : IDataBroadcaster
    {
        private readonly IMediator _mediator;

        private readonly ILogger<ExampleBroadcaster> _logger;

        private readonly IHubContext<MainHub> _mainHub;

        private readonly IMemoryCache _cache;

        private const string Something  = "example";

        private Timer _timer;

        public ExampleBroadcaster(IHubContext<MainHub> mainHub, 
            IMediator mediator, ILogger<ExampleBroadcaster> logger,
            IMemoryCache cache)
        {
            _mainHub = mainHub;
            _mediator = mediator;
            _logger = logger;
            _cache = cache;
        }

        public void Start()
        {
            _timer = new Timer(BroadcastData, null, 0, 30000);
        }

        private async void BroadcastData(object? state)
        {
            ExampleViewModel viewModel;

            try
            {
                if (_cache.TryGetValue(Something, out ExampleViewModel data))
                {
                    viewModel = data;
                }
                else
                {
                    viewModel = _mediator.Send(new GetExampleData()).Result;
                    _cache.Set(Something, viewModel, TimeSpan.FromMinutes(10));
                }

                await _mainHub.Clients.All.SendAsync("example", viewModel);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, ex.Message);
            }
        }
    }

The mediator handler simply uses Dapper to get data from the database:

 public class GetExampleData : IRequest<ExampleViewModel>
    {
    }

    public class GetExampleDataHandler : IRequestHandler<GetExampleData, ExampleViewModel>
    {
        private readonly IDbConnectionFactory _connectionFactory;

        private string _storedProcedure = "some sproc name";

        public GetExampleDataHandler(IDbConnectionFactory connectionFactory)
        {
            _connectionFactory = connectionFactory;
        }

        public async Task<ExampleViewModel> Handle(GetExampleData request, CancellationToken cancellationToken)
        {
            using (var connection = _connectionFactory.GetReadOnlyConnection())
            {
                return await connection.QueryFirstAsync<ExampleViewModel>(_storedProcedure, CommandType.StoredProcedure);
            }
        }
    }

This is the main razor page that houses all the individual components:

@code {
    private HubConnection? hubConnection;

    protected override async Task OnInitializedAsync()
    {
        try
        {
            hubConnection = new HubConnectionBuilder()
            .WithUrl(NavigationManager.ToAbsoluteUri("/mainhub"))
            .Build();

            await hubConnection.StartAsync();

            await hubConnection.SendAsync("Init");
        }
        catch(Exception exception)
        {
            Logger.LogError(exception, exception.Message);
        }
    }

    public async ValueTask DisposeAsync()
    {
        if (hubConnection is not null)
        {
            await hubConnection.DisposeAsync();
        }
    } 
}

Finally, the MainHub.cs code:

public class MainHub : Hub
    {
        IEnumerable<IDataBroadcaster> _broadcasters;

        private static bool _started;

        public MainHub(IEnumerable<IDataBroadcaster> broadcasters)
        {
            _broadcasters = broadcasters;
        }

        public void Init()
        {
            if (!_started)
            {
                StartBroadcasting();
                _started = true;
            }
        }

        private void StartBroadcasting()
        {
            foreach (var broadcaster in _broadcasters)
            {
                broadcaster.Start();
            }
        }
    }

This all worked fine locally, in our dev environment, and our test environment. In production, we found that the app was crashing after a number of hours. According to the server admins, the app is opening 100s or 1000s of ports and leaving them open until the number of allotted ports was hit, causing the app to crash.

What is the issue here? The broadcasters are registered as singletons. This app only runs on one web server.

0 Answers
Related