views:

263

answers:

3

I am writing acceptance tests to validate a system and it's data access is all done through web service calls.

Unfortunately, because I make so many calls, so quickly, I run into the following error

System.Net.Sockets.SocketException: Only one usage of each socket address (protocol/network address/port) is normally permitted

I have locally cached as many of the calls as possible, but that's not enough. I can't fake or mock these calls. They have to be real for this level of tests to be valid.

Is there a way to get repeated frequent web service calls to pool?

Or to check if a connection can be made to the service prior to attempting the call, and wait until an available socket exists?

In production the calls are never made at this speed, so the issue won't arise...but it would be nice to have some code in place to allow for the tests to work, and throttle or share the connections.

Thanks

+1  A: 

The MSDN Article Avoiding TCP/IP Port Exchastion recommends the following approaches

  • Increase the number of ephemeral ports available via the registry (that is, the ports allocated on the client to call the webservice
  • Reduce the TCP/IP timeout to free ports more quickly (again via the registry).

You should also check that you're closing the connections correctly after each call in the teardown of the test.

Alternativly, if you don't want to make any config changes, you could scan the ephemeral port range and if there are no ports free, pause and wait for some to be released. The article 'Detecting the next available, Free TCP port' has some example code that could be adapted for this purpose.

Robert Christie
The web-service call is using a "using" statement, and thus, it's closing and disposing properly. The problem is that Windows doesn't close the socket immediately (the timeout). So even though you close it, it's not really closed, and they become exhausted. I'd rather NOT make system changes as these tests should be able to run anywhere... And changing system settings to increase ephemeral ports, or reducing the timeout just delays the problem, it doesn't solve it.
Chad
@Chad: Added a section suggesting you scan the ephemeral range to check if there are any ports free (and included a link to some example code). That should let you back off the tests without requiring the use of exceptions.
Robert Christie
A: 

A simple solution would be to slow down your tests by making you thread sleep for a short time; faster than would be in production but shorter than what would use up all the ports.

No change in the app or the system.

Jim
@Jim, I've actually implemented that. But in a less than ideal manner. I have a do/while wrapping the call. If a SocketException is thrown I set a flag "hasSocketException" to true and loop until it's false again. Which it's set to prior to each attempted call. I would prefer a solution though, that didn't use exception handling to do flow control.
Chad
This would be cleaner: System.Threading.Thread.Sleep();You could also perform the tests in serial rather than parallel(not sure how you do it).
Jim
@Jim, the tests are Serial. Oh, and there is a Sleep when an exception happens. Forgot to mention that.
Chad
I would sleep the thread after every test. If you encounter an exception, add more sleep time. You would have to do guess work to find a value that is does not cause an exception but lets the test finish in a reasonable amount of time. You are really running into an architectural limit of the system. Pooling will solve this but if your system will never come close to that amount of load in production then it is not necessary. Waiting for a free port also would work.
Jim
+1  A: 

Turns out, a co-worker had a solution already that was used on our e-com site. You just inherit from your web-service, override the GetWebRequest function with some different settings and you're done.

class SafeMyWebService : MyWS.Service
{
    private static int _lastBindPortUsed;

    protected override WebRequest GetWebRequest(Uri uri)
    {
        var webreq = base.GetWebRequest(uri) as HttpWebRequest;

        webreq.KeepAlive = true;
        webreq.ProtocolVersion = HttpVersion.Version10;
        webreq.ServicePoint.MaxIdleTime = 10 * 1000; // milliseconds 
        webreq.ServicePoint.BindIPEndPointDelegate = new BindIPEndPoint(BindIPEndPointCallback);
        webreq.Timeout = 2000;
        webreq.ConnectionGroupName = "MyConnectionGroupName";

        return webreq;
    }

    public static IPEndPoint BindIPEndPointCallback(ServicePoint servicePoint, IPEndPoint remoteEndPoint, int retryCount)
    {
        int port = Interlocked.Increment(ref _lastBindPortUsed);
        Interlocked.CompareExchange(ref _lastBindPortUsed, 5001, 65534);

        return remoteEndPoint.AddressFamily == AddressFamily.InterNetwork 
            ? new IPEndPoint(IPAddress.Any, port) 
            : new IPEndPoint(IPAddress.IPv6Any, port);
    }

}

I'm guessing it uses more resources, but it doesn't fail.

Chad