Solving Port Exhaustion Issue In .NET

Bayo ABayo A
4 min read

When developing applications that connect to an external resource such as databases, queues, or storage systems, we use classes that make these connections by abstracting away the underlying implementation.

In this article, we will look at how to properly use such classes to avoid port exhaustion.

Let's get started!

Port Exhaustion Issue

When we access external resources over TCP or UDP, we do so over a port. And while there are 65,535 ports available, our system can quickly run out of ports to use if we improperly instantiate classes that use these ports.

A good example of such a class in .NET is the HttpClient class. Other examples include classes in the Azure SDK such as:

  • ServiceBus.Messaging.QueueClient for Azure Service Bus

  • Azure.Documents.Client.DocumentClient for Azure Cosmos DB

  • StackExchange.Redis.ConnectionMultiplexer for Redis Cache

  • Azure.Storage.Blobs for Azure Storage Blob

These classes ought to be instantiated once and used for the lifetime of the application.

When we use HttpClient to request a URL:

public class HttpService
{
    public HttpService() { }

    public async Task<string> GetDataAsync()
    {
        string url = "https://www.google.com";

        using var _client = new HttpClient();

        var response = await _client.GetStringAsync(url);

        Console.WriteLine(response);
        return response!;
    }
}

Here, we can see that we instantiate a new HttpClient each time we want to make a request to the URL. While this may work fine for an application that is run once, this could lead to socket exceptions when we have an application that processes a lot of requests e.g. a web application.

When this happens, our application throws a SocketException error:

"An operation on a socket could not be performed because the system lacked sufficient buffer space or because a queue was full"

Diagnosing Port Exhaustion Issues

Port exhaustion issues can lead to SocketException errors and increased resource usage which will make our applications unresponsive.

One quick way of diagnosing the problem is to run the netstat command:

> netstat

This would produce a list of running processes accessing the ports and we can quickly see which process has opened a lot of ports:

image showing result of running netstat

Properly Instantiating Classes

To solve this, we must instantiate one instance of the class if the class is thread-safe. This can be achieved by creating a shared singleton instance or a pool of instances.

We can refactor our HttpService to use a single instance of HttpClient:

public class HttpService
{
    private static HttpClient? _client;

    public HttpService()
    {
        _client = new HttpClient();
    }

    public async Task<string> GetDataAsync()
    {
        string url = "https://www.google.com";

        var response = await _client!.GetStringAsync(url);

        Console.WriteLine(response);
        return response!;
    }
}

Here, we can see that we declare a static instance of the HttpClient and initialize it once. This ensures that only one instance of the HttpClient is used throughout the lifetime of the application.

If our application is using Dependency Injection, we can inject the HttpClient by registering it during startup:

public void ConfigureServices(IServiceCollection services)
{
    services.AddHttpClient();
}

We can then access the HttpClient in our code:

public class HttpService
{
    private readonly IHttpClientFactory _clientFactory;

    public HttpService(IHttpClientFactory clientFactory)
    {
        _clientFactory = clientFactory;
    }

    public async Task<string> GetDataAsync()
    {
        string url = "https://www.google.com";

        HttpClient _client = _clientFactory.CreateClient();
        var response = await _client!.GetStringAsync(url);

        Console.WriteLine(response);
        return response!;
    }
}

Properly Instantiating Azure Services

Let's look at how to instantiate some Azure services in a way that doesn't lead to part exhaustion.

If our application uses Dependency Injection, we could easily use the Azure extension method to register our services and let the Inversion of Control library manage the pool of resources:

public void ConfigureServices(IServiceCollection services)
{
    services.AddAzureClients(builder =>
    {
        builder.AddBlobServiceClient("ConnectionString");

        builder.AddServiceBusClient("ConnectionString");
    });
}

Here, we use the Azure SDK extension to register our Azure Blob and Azure Service Bus services.

We can then use the services in our code:

public class ServiceClass 
{
    private readonly ServiceBusClient _client;
    private readonly BlobServiceClient _blobServiceClient;

    public ServiceClass(ServiceBusClient client, BlobServiceClient _blobServiceClient)
    {
        _client = client;
        _blobServiceClient = blobServiceClient;
    }
}

For Azure CosmosDb, we create a singleton that can be reused across our application. We do this because the CosmosClient is thread-safe and one instance can be shared:

public void ConfigureServices(IServiceCollection services)
{
    services.AddSingleton<CosmosClient>(provider => 
    {
        CosmosClientOptions cosmosClientOptions = new CosmosClientOptions()
        {
            ConnectionMode = ConnectionMode.Direct
        };

        var client = new CosmosClient("Connection string", "Authentication Key", cosmosClientOptions);

        return client;

    }
}

Conclusion

Properly instantiating classes that use the network helps us avoid issues within our application. In this article, we learned how to avoid instantiating these classes in a way that will not lead to port exhaustion and other issues in our application.

  1. https://learn.microsoft.com/en-us/azure/architecture/antipatterns/improper-instantiation/

  2. https://learn.microsoft.com/en-us/dotnet/azure/sdk/dependency-injection?tabs=web-app-builder

0
Subscribe to my newsletter

Read articles from Bayo A directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Bayo A
Bayo A

Software engineer with a passion for learning and trying out new tech.