0

I have a computer running Windows Server 2012 that I use to run a lot of my applications. Most of them are processing many asynchronous web requests in parallel (using HttpClient in .NET 4.5).

When I check NETSTAT, I see almost all available (assuming 66,536 is the maximum) ports are in use. The piped output from the basic NETSTAT call is a 4mb text file with 64,583 lines. The majority of these are in TIME_WAIT, lots are not attributed to any particular process but labelled as "System Process".

I understand TIME_WAIT is not necessarily a bad thing and my applications do not seem to be throwing exceptions explicitly stating they are unable to complete a request due to no available ports. However, this concerns me and I'm worried it might be causing time outs.

Is this to be expected? Should I reduce the TcpTimedWaitDelay value in the registry? I believe connections are kept in TIME_WAIT for 4 minutes by default. Also is it possible I'm doing something wrong in my code that leaves connections open like this? I reuse the HttpClient object where possible and always dispose of it when finished. Should I be looking out for connection: close headers in responses?

EDIT: Adding code, as requested. Here's the function that makes the request (part of a custom class wrapper for HttpClient);

  Public Async Function WebRequestAsync(Url As String, Optional RequestMethod As RequestMethod = RequestMethod.GET, Optional Content As Object = Nothing, _
                                              Optional ContentType As ContentType = ContentType.Default, Optional Accept As String = DefaultAcceptString, _
                                              Optional AdditionalHeaders As NameValueCollection = Nothing, Optional Referer As String = Nothing, _
                                              Optional NoCache As Boolean = False, Optional CustomCookieHandler As Boolean = False, _
                                              Optional Attempts As Integer = 2, Optional CanBeCancelled As Boolean = True) _
                                   As Tasks.Task(Of HttpResponseMessage)

    If Attempts < 1 Then Attempts = 1

    Dim Method As HttpMethod = Nothing
    Select Case RequestMethod
      Case Variables.RequestMethod.DELETE : Method = HttpMethod.Delete
      Case Variables.RequestMethod.GET : Method = HttpMethod.Get
      Case Variables.RequestMethod.OPTIONS : Method = HttpMethod.Options
      Case Variables.RequestMethod.POST : Method = HttpMethod.Post
      Case Variables.RequestMethod.PUT : Method = HttpMethod.Put
    End Select

    'prepare message
    Dim Message As New HttpRequestMessage(Method, Url)
    Message.Headers.ExpectContinue = False
    Message.Headers.TryAddWithoutValidation("Accept", Accept)
    If Referer IsNot Nothing Then Message.Headers.Add("Referer", Referer)
    If NoCache Then
      Message.Headers.Add("Pragma", "no-cache")
      Message.Headers.Add("Cache-Control", "no-cache")
    End If
    If AdditionalHeaders IsNot Nothing Then
      For Each Key In AdditionalHeaders.AllKeys
        Message.Headers.TryAddWithoutValidation(Key, AdditionalHeaders(Key))
      Next
    End If

    'set content
    If Content IsNot Nothing Then
      Dim ContentTypeString As String = GetEnumDescription(ContentType)

      Dim ContentBytes As Byte() = Nothing
      If TypeOf Content Is String Then
        ContentBytes = Encoding.UTF8.GetBytes(CType(Content, String))
      ElseIf TypeOf Content Is Byte() Then
        ContentBytes = CType(Content, Byte())
      ElseIf TypeOf Content Is MultiPartPostData Then
        Dim MultiPartPostData As MultiPartPostData = CType(Content, MultiPartPostData)
        ContentBytes = MultiPartPostData.Bytes
        ContentTypeString += "; boundary=" & MultiPartPostData.Boundary
      End If

      Dim ByteArrayContent As New ByteArrayContent(ContentBytes)
      ByteArrayContent.Headers.Add("Content-Type", ContentTypeString)
      Message.Content = ByteArrayContent
    End If

    'get response
    Output(RequestMethod.ToString & " " & Url, OutputType.Debug)

    'Set cancellation token
    Dim CToken As New CancellationToken
    If CancellationToken IsNot Nothing AndAlso CancellationToken.HasValue AndAlso CanBeCancelled Then CToken = CancellationToken.Value

    Dim Response As HttpResponseMessage = Nothing
    For Attempt = 1 To Attempts
      Try
        Response = Await Client.SendAsync(Message, HttpCompletionOption.ResponseHeadersRead, CToken).ConfigureAwait(False)
      Catch ex As Tasks.TaskCanceledException
        If DebugMode Then Output(Method.ToString & " " & Url & " Timed out", OutputType.Error)
      Catch ex As HttpRequestException
        If ex.InnerException IsNot Nothing Then
          If DebugMode Then Output(Method.ToString & " " & Url & " " & ex.InnerException.Message, OutputType.Error)
          If ex.InnerException.Message = "Timed out" Then Continue For
        Else
          If DebugMode Then Output(Method.ToString & " " & Url & " " & ex.Message, OutputType.Error)
        End If
      Catch ex As Exception
        If DebugMode Then Output(Method.ToString & " " & Url & " " & ex.Message, OutputType.Error)
      End Try

      Exit For
    Next

    If Response IsNot Nothing Then
      Output(Method.ToString & " " & Url & " " & Response.StatusCode & " " & Response.StatusCode.ToString, OutputType.Debug)
      If CustomCookieHandler AndAlso Cookies IsNot Nothing Then
        Dim Values As IEnumerable(Of String) = Nothing
        If Response.Headers.TryGetValues("Set-Cookie", Values) Then ManuallyExtractCookies(Values, New Uri(Url).GetLeftPart(UriPartial.Authority))
      End If
    End If

    Return Response
  End Function

And an example of making a request;

Using HttpClient As New HttpClientWrapper(User, Task)
  Dim Response As String = Await HttpClient.WebRequestStringAsync("http://www.google.com")
  If Response Is Nothing Then Return Nothing
End Using
DavidG
  • 113,891
  • 12
  • 217
  • 223
iguanaman
  • 930
  • 3
  • 13
  • 25
  • Is this a c# programming question, or a general computer networking question? – gunr2171 Sep 24 '14 at 13:05
  • @gunr2171 Specific to HttpClient in .NET, coding in C#. I want to know if I should be closing connections (and if so, how). These are connections my applications are making so, it's a coding question I believe. – iguanaman Sep 24 '14 at 13:07
  • Then you should post some code for us to look at! :) – DavidG Sep 24 '14 at 13:09
  • 2
    Maybe post some connection code? Very difficult to diagnose when guessing. As a side note, you should ALWAYS close connections when you're finished with them. – laminatefish Sep 24 '14 at 13:09
  • As I recall, closing an outgoing TCP socket in WCF does not immediately release it - the OS will keep it open in anticipation of a subsequent request. Apps generating high numbers of outgoing WCF calls need to do their own TCP connection pooling to prevent port exhaustion scenarios. If you google ".net TCP Port Exhaustion" you will find some material covering this. Its possibly related to what you are seeing - TCP ports dont get closed just because your code closes them... – PhillipH Sep 24 '14 at 13:16
  • "Also is it possible I'm doing something wrong in my code that leaves connections open like this?" Yes. Show it :) Also, if you fire up 60.000 requests in parallel, I would expect 60.000 open ports simultaneously. So don't do that. – Martijn Sep 24 '14 at 13:16
  • @Davidg Added code, as requested. – iguanaman Sep 24 '14 at 13:18
  • @Martijn Code is there now. I'm not making that many in parallel, at most 5k. But the issue is not ports in ESTABLISHED or FIN_WAIT but TIME_WAIT. Which leads me to believe it's requests that have finished and I want the port available to be re-used. – iguanaman Sep 24 '14 at 13:19
  • @PhillipH I can't restrict the requests, I need to have them going through in parallel. I have been reading about the topic and it seems to be the solution is to increase the number of ephemeral ports and reduce the TcpTimedWaitDelay by editing the registry. I don't really know if this is correct though. – iguanaman Sep 24 '14 at 13:22
  • The code wasn't c# as you suggested, so I've added [tag:vb.net] tag. – DavidG Sep 24 '14 at 13:24
  • @LokiSinclair By closing the connection do I have to do something other than disposing the HttpClient object? Send a connection: close header? – iguanaman Sep 24 '14 at 13:26
  • @DavidG Thanks, I got confused there. – iguanaman Sep 24 '14 at 13:27

1 Answers1

0

In HTTP 1.1, all connections are considered persistent unless declared otherwise.

So if you don't close the connection the server will keep waiting for a new request (at least until certain timeout).

The solution would be to add a Connection: close header or explicitly close the connection after receiving the response: Response.Dispose().

pmoleri
  • 4,238
  • 1
  • 15
  • 25