MockHttpServer icon indicating copy to clipboard operation
MockHttpServer copied to clipboard

Race condition with GetRandomUnusedPort()

Open kenlyon opened this issue 5 years ago • 1 comments

Every so often our tests fail due to trying to start a listener for a port that is in use.

It's that time between the first listener stopping and the second one starting. Another thread might call GetRandomUnusedPort() in between and get the same port number.

kenlyon avatar Oct 29 '20 17:10 kenlyon

This project hasn't been updated in a long time, so I'm not expecting a response here. However, in case anyone else discovers this I thought I'd share my workaround.

I'm using a variation on this answer from stack overflow. The gist of it is to just attempt to start a new HttpListener in a try/catch and use a different port each time until it works. This avoids a race condition since we're not relying on checking the ports currently in use, which can change at any time.

The example in the link goes through all ports in ascending order. In my own implementation, I choose a random port up to a specified maxAttempts.

private static bool TryBindListenerOnFreePort(int maxAttempts, out HttpListener httpListener, out int port)
{
    const int minValue = 49152; // IANA suggested minimum value for dynamic or private ports.
    const int maxValue = 65536; // IANA suggested maximum value for dynamic or private ports _plus one_. (Upper limit is _exclusive_.)
    var attemptCount = 0;
    var rand = new Random();

    while (attemptCount < maxAttempts)
    {
        port = rand.Next(minValue, maxValue);
        httpListener = new HttpListener();
        httpListener.Prefixes.Add($"http://localhost:{port}/");
        try
        {
            httpListener.Start();
            return true;
        }
        catch
        {
            // nothing to do here -- the listener disposes itself when Start throws
        }

        attemptCount++;
    }

    port = 0;
    httpListener = null;
    return false;
}

kenlyon avatar Oct 30 '20 15:10 kenlyon