3

Question

How to configure the TCP timeout for a single WebRequest?

Context

According to the docs, WebRequest.Timeout:

The length of time, in milliseconds, until the request times out, or the value Timeout.Infinite to indicate that the request does not time out. The default value is defined by the descendant class.

Requesting web resource from an non-existent endpoint (IIS without service bindings on the requested port) fails with a different and constant timeout of about 21 seconds.

According to this serverfault answer this appears to be a connect TCP timeout.

Sample Code

 public static async Task<string> WebRequestToString(string requestUri, int timeoutMilliseconds)
 {
     var request = WebRequest.Create(requestUri) as HttpWebRequest;
     request.KeepAlive = false;
     request.Timeout = timeoutMilliseconds;
     request.ReadWriteTimeout = timeoutMilliseconds;
     using (var response = await request.GetResponseAsync() as HttpWebResponse)
     {
         // Get the response stream
         using (var reader = new StreamReader(response.GetResponseStream()))
         {
             var responseBody = await reader.ReadToEndAsync();
             return responseBody;
         }
     }
 }

 static void Main(string[] args)
 {
     string uri = "http://10.15.1.24:8081/thiservicedoesnotexist/";
     int timeoutMilliseconds = 10;

     Stopwatch sw = new Stopwatch();
     sw.Start();
     try
     {
         WebRequestToString(uri, timeoutMilliseconds).Wait();
     }
     catch (AggregateException ex)
     {
         Console.WriteLine(ex.ToString());
     }
     sw.Stop();

     Console.WriteLine("Elaped {0}ms", sw.ElapsedMilliseconds);
     Console.ReadKey();
 }

Example Output

  System.AggregateException: One or more errors occurred. ---> System.Net.WebExcep
  tion: Unable to connect to the remote server ---> System.Net.Sockets.SocketExcep
  tion: A connection attempt failed because the connected party did not properly r
  espond after a period of time, or established connection failed because connecte
  d host has failed to respond 10.15.1.24:8081
     at System.Net.Sockets.Socket.EndConnect(IAsyncResult asyncResult)
     at System.Net.ServicePoint.ConnectSocketInternal(Boolean connectFailure, Sock
  et s4, Socket s6, Socket& socket, IPAddress& address, ConnectSocketState state,
  IAsyncResult asyncResult, Exception& exception)
     --- End of inner exception stack trace ---
     at System.Net.HttpWebRequest.EndGetResponse(IAsyncResult asyncResult)
     at System.Threading.Tasks.TaskFactory`1.FromAsyncCoreLogic(IAsyncResult iar,
  Func`2 endFunction, Action`1 endAction, Task`1 promise, Boolean requiresSynchron
  ization)
  --- End of stack trace from previous location where exception was thrown ---
     at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
     at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNot
  ification(Task task)
     at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
     at TimeoutTests.Program.<WebRequestToString>d__0.MoveNext() in \\vmware-host\
  shared folders\Documents\Visual Studio 2012\Projects\TimeoutTests\TimeoutTests\P
  rogram.cs:line 20
     --- End of inner exception stack trace ---
     at System.Threading.Tasks.Task.ThrowIfExceptional(Boolean includeTaskCanceled
  Exceptions)
     at System.Threading.Tasks.Task.Wait(Int32 millisecondsTimeout, CancellationTo
  ken cancellationToken)
     at System.Threading.Tasks.Task.Wait()
     at TimeoutTests.Program.Main(String[] args) in \\vmware-host\shared folders\D
  ocuments\Visual Studio 2012\Projects\TimeoutTests\TimeoutTests\Program.cs:line 4
  0
  ---> (Inner Exception #0) System.Net.WebException: Unable to connect to the remo
  te server ---> System.Net.Sockets.SocketException: A connection attempt failed b
  ecause the connected party did not properly respond after a period of time, or e
  stablished connection failed because connected host has failed to respond 10.15.
  1.24:8081
     at System.Net.Sockets.Socket.EndConnect(IAsyncResult asyncResult)
     at System.Net.ServicePoint.ConnectSocketInternal(Boolean connectFailure, Sock
  et s4, Socket s6, Socket& socket, IPAddress& address, ConnectSocketState state,
  IAsyncResult asyncResult, Exception& exception)
     --- End of inner exception stack trace ---
     at System.Net.HttpWebRequest.EndGetResponse(IAsyncResult asyncResult)
     at System.Threading.Tasks.TaskFactory`1.FromAsyncCoreLogic(IAsyncResult iar,
  Func`2 endFunction, Action`1 endAction, Task`1 promise, Boolean requiresSynchron
  ization)
  --- End of stack trace from previous location where exception was thrown ---
     at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
     at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNot
  ification(Task task)
     at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
     at TimeoutTests.Program.<WebRequestToString>d__0.MoveNext() in \\vmware-host\
  shared folders\Documents\Visual Studio 2012\Projects\TimeoutTests\TimeoutTests\P
  rogram.cs:line 20<---

  Elaped 21169ms
Community
  • 1
  • 1
tozevv
  • 764
  • 7
  • 17

2 Answers2

2

I am very doubtful that this timeout is actually the same as the TCP connection timeout. The documentation of the webrequest timeout is as follows :

The length of time, in milliseconds, until the request times out, or the value Timeout.Infinite to indicate that the request does not time out. The default value is defined by the descendant class.

That is a timeout on the level of your request, ie HTTP. It might be that a connection to the server is establised but that creating the response takes a long time. Also that time can time out.

How the webrequest handles this and maps it on TCP is implementation dependent. A TCP timeout for connection establishment cannot be infinite.

I also read the following : The Timeout property indicates the length of time, in milliseconds, until the request times out and throws a WebException. The Timeout property affects only synchronous requests made with the GetResponse method. To time out asynchronous requests, use the Abort method.

You are asynchronous!

Philip Stuyck
  • 7,344
  • 3
  • 28
  • 39
  • I'm also not sure, I just suspect it is. But the point is, why WebRequest does not honour the timeout globally, ignores the 10ms timeout and waits for 20 seconds? – tozevv Mar 28 '15 at 12:42
  • Updated my answer. The timeout is for synchronous operation. – Philip Stuyck Mar 28 '15 at 13:13
  • 10ms is very low, too low actually for any response. – Philip Stuyck Mar 28 '15 at 13:14
  • It's irrelevant, 10 ms is just an exaggeration to make my point. It happens with 1000 ms, 2000ms, whatever. It only fails after 21 seconds. I can confirm this only happens to async calls (poor API design to apply such property to only the sync versions...) – tozevv Mar 28 '15 at 14:40
1

I ended up fixing this by creating a GetResponseAsync method extension based on approach and seems to work fine:

public static class WebRequestExtensions
{
    public static async Task<WebResponse> GetResponseAsyncWithTimeout(this WebRequest request)
    {
        var timeoutCancellationTokenSource = new CancellationTokenSource();

        var responseTask = request.GetResponseAsync();

        var completedTask = await Task.WhenAny(responseTask, Task.Delay(request.Timeout, timeoutCancellationTokenSource.Token));
        if (completedTask == responseTask)
        {
            timeoutCancellationTokenSource.Cancel();
            return await responseTask;
        }
        else
        {
            request.Abort();
            throw new TimeoutException("The operation has timed out.");
        }
    }
}
Community
  • 1
  • 1
tozevv
  • 764
  • 7
  • 17