12

I know that TIME_WAIT is an integral part of TCP/IP, but there's many questions on SO (and other places) where multiple sockets are being created per second and the server ends up running out of ephemeral ports.

What I found out is that when using a TCPClient (or Socket for that matter), if I call either the Close() or Dispose() methods the socket's TCP state changes to TIME_WAIT and will respect the timeout period before fully closing.

However, if It just set the variable to null the socket will be fully closed on the next GC run, which can of course be forced, without ever going through a TIME_WAIT state.

This doesn't make a lot of sense for me, since this is an IDisposable object shouldn't the GC also invoke the Dispose() method of the object?

Here's some PowerShell code that demonstrates that (no VS installed on this machine). I used TCPView from Sysinternals to check the sockets state in real time:

$sockets = @()
0..100 | % {
    $sockets += New-Object System.Net.Sockets.TcpClient
    $sockets[$_].Connect('localhost', 80)
}

Start-Sleep -Seconds 10

$sockets = $null

[GC]::Collect()

Using this method, the sockets never go into a TIME_WAIT state. Same if I just close the app before manually invoking Close() or Dispose()

Can someone shed some light and explain whether this would be a good practice (which I imagine people are going to say it's not).

EDIT

GC's stake in the matter has already been answered, but I am still interested in finding out why this would have any impact on the socket state as this should be controlled by the OS, not .NET.

Also interested in finding out whether it would be good practice to use this method to prevent TIME_WAIT states and ultimately whether this is a bug somewhere (i.e., should all sockets go through a TIME_WAIT state?)

cogumel0
  • 2,430
  • 5
  • 29
  • 45
  • 7
    "shouldn't the GC also invoke the `Dispose()` method of the object?" The GC *never* invokes `Dispose`. It invokes only the finalizer of the class, if it has one. Normally, `Dispose()` is called from the finalizer, but this bears repeating: `Dispose` is intended only for `using`. The GC doesn't care about it at all. – Jeroen Mostert Nov 24 '16 at 11:59
  • That's a great explanation on the GC's involvement on all of this, thx! – cogumel0 Nov 24 '16 at 12:16
  • 1
    Note that your code doesn't give the GC time to actually collect the sockets, since they have finalizers. You need to do `GC.Collect(); GC.WaitForPendingFinalizers(); GC.Collect();` to force that. Your `GC.Collect();` call just put the sockets in the finalizer queue (if that) - and socket finalizers can take a long time (often killing your process, in fact). The sockets aren't in TIME_WAIT most likely because they haven't even been closed *yet*. The same is true when you kill the process - it's actually non-trivial to shutdown a TCP socket, even for the OS. List all ports, not just TIME_WAIT. – Luaan Nov 24 '16 at 13:47
  • 1
    I'm not filtering on TIME_WAIT sockets, I'm looking at *all* sockets regardless of state. Before I tell the GC to run I can see them all as ESTABLISHED. About 2 seconds after I tell the GC to run, they all disappear completely without first going to TIME_WAIT. If I `Close()` or `Dispose()` of them first, then they go from ESTABLISHED to TIME_WAIT. So, while you might be right that my code is missing something, it is definitely not the case that they don't go to TIME_WAIT because they haven't been closed yet. – cogumel0 Nov 24 '16 at 15:51
  • Time wait occurs because there is a timing issue when sockets are closed simultaneously at client and server. Solution is to only close socket at client. TCP is reliable and every datagram has an acknowledge. When both client and server close simultaneously one of the ACKs does not occur leaving socket in the Time_Wait while it wait for an ACK that never occurs. The issue is how do you exit the app at the server. Most systems you wait until you get a close event from client. But Net library does have the event. – jdweng Jan 13 '23 at 17:42

6 Answers6

10

This doesn't make a lot of sense for me, since this is an IDisposable object shouldn't the GC also invoke the Dispose() method of the object?

The Dispose pattern, also known as IDisposable, provides two ways for an unmanaged object to be cleaned up. The Dispose method provides a direct and fast way to clean up the resource. The finalize method, which is called by the garbage collector, is a fail-safe way to make sure that the unmanaged resource is cleaned up in case another developer using the code forgets to call the Dispose method. This is somewhat similar to C++ developers forgetting to call Delete on heap allocated memory - which results in memory leaks.

According to the referenced link:

"Although finalizers are effective in some cleanup scenarios, they have two significant drawbacks:

  1. The finalizer is called when the GC detects that an object is eligible for collection. This happens at some undetermined period of time after the resource is not needed anymore. The delay between when the developer could or would like to release the resource and the time when the resource is actually released by the finalizer might be unacceptable in programs that acquire many scarce resources (resources that can be easily exhausted) or in cases in which resources are costly to keep in use (e.g., large unmanaged memory buffers).

  2. When the CLR needs to call a finalizer, it must postpone collection of the object’s memory until the next round of garbage collection (the finalizers run between collections). This means that the object’s memory (and all objects it refers to) will not be released for a longer period of time."

Using this method, the sockets never go into a TIME_WAIT state. Same if I just close the app before manually invoking Close() or Dispose()

Can someone shed some light and explain whether this would be a good practice (which I imagine people are going to say it's not).

The reason why it is taking a while for it shut down is because the code lingers by default to give the app some time to handle any queued messages. According to the TcpClient.Close method doc on MSDN:

"The Close method marks the instance as disposed and requests that the associated Socket close the TCP connection. Based on the LingerState property, the TCP connection may stay open for some time after the Close method is called when data remains to be sent. There is no notification provided when the underlying connection has completed closing.

Calling this method will eventually result in the close of the associated Socket and will also close the associated NetworkStream that is used to send and receive data if one was created."

This timeout value can be reduced or completely eliminated by the following code:

// Allow 1 second to process queued msgs before closing the socket.
LingerOption lingerOption = new LingerOption (true, 1);
tcpClient.LingerState = lingerOption;
tcpClient.Close();

// Close the socket right away without lingering.
LingerOption lingerOption = new LingerOption (true, 0);
tcpClient.LingerState = lingerOption;
tcpClient.Close();

Also interested in finding out whether it would be good practice to use this method to prevent TIME_WAIT states and ultimately whether this is a bug somewhere (i.e., should all sockets go through a TIME_WAIT state?)

As for setting the reference to the TcpClient object to null, the recommended approach is to call the Close method. When the reference is set to null, the GC ends up calling the finalize method. The finalize method eventually calls the Dispose method in order to consolidate the code for cleaning up the unmanaged resource. So, it will work to close the socket - its just not recommended.

In my opinion, it depends on the app whether or not some linger time should be allowed to give the app time to handle queued messages. If I was certain my client app had processed all the necessary messages, then I would probably either give it a linger time of 0 seconds or perhaps 1 second if I thought that might change in the future.

For a very busy client and / or weak hardware - then I might give it more time. For a server, I would have to benchmark different values under load.

Other useful references:

What is the proper way of closing and cleaning up a Socket connection?

Are there any cases when TcpClient.Close or Socket.Close(0) could block my code?

Community
  • 1
  • 1
Bob Bryan
  • 3,687
  • 1
  • 32
  • 45
  • Good answer, but I still don't understand why the code on the `Close()` method should differ from that of the `Dispose()`. I mean, *why* does the socket not go into `TIME_WAIT` if it's disposed of without closing? – cogumel0 Mar 27 '17 at 12:15
  • @cogumel0 You said in you question: "What I found out is that when using a TCPClient (or Socket for that matter), if I call either the Close() or Dispose() methods the socket's TCP state changes to TIME_WAIT and will respect the timeout period before fully closing." So, it is when the object is set to null that it does not go into a TIME_WAIT state. I suspect that is because the finalizer realizes there is no longer a valid object to process any queued messages. So, there would be no point to it lingering. It just closes the socket at that point. – Bob Bryan Mar 27 '17 at 14:31
  • @cogumel0 Have you tried using LingerOption to force the Close method to not linger (or go into a TIME_WAIT state)? If that does not work for some reason, then you might be left with no other option than to set the object to null. – Bob Bryan Mar 27 '17 at 14:38
  • Sorry, it's been a long time since I wrote this, forgot what my actual findings were, thx for pointing it out to me. What I meant was Close() vs the finalizer. I'll try the LingerOption now – cogumel0 Mar 27 '17 at 14:49
  • 1
    Tried `LingerOption`, makes no difference. Tested on Windows 10, it always goes into `TIME_WAIT` first and regardless of what `LingerOption` I use it will always stay in `TIME_WAIT` for 30 seconds. Even when setting the first parameter of the `LingerOption` constructor to `false`. – cogumel0 Mar 27 '17 at 15:02
  • Did you try using true, 0 for the linger options? If that option does not work, then you should probably stick with setting the object to null. – Bob Bryan Mar 27 '17 at 15:09
  • What version of C# are you using and what is the .NET Framework version? – Bob Bryan Mar 27 '17 at 15:11
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/139164/discussion-between-cogumel0-and-bob-bryan). – cogumel0 Mar 27 '17 at 15:20
2

@Bob Bryan posted quite good answer while I was preparing mine. It shows why to avoid finalizers and how to abortively close the connection to avoid TIME_WAITs issue on the server.

I want to refer to a great answer https://stackoverflow.com/a/13088864/2138959 about SO_LINGER to question TCP option SO_LINGER (zero) - when it's required, which might clarify things even more to you and so that you can make you decision in each particular case which approach for closing the socket to use.

To summarize, you should design your client-server communication protocol the way that the client closes the connection to avoid TIME_WAITs on the server.

Community
  • 1
  • 1
Andrii Litvinov
  • 12,402
  • 3
  • 52
  • 59
1

The Socket class has a rather lengthy method protected virtual void Dispose(bool disposing) that is called with true as the parameter from .Dispose() and false as a parameter from the destructor that is called by the garbage collector.

Chances are, your answer to any differences in handling the socket's disposal will be found in this method. Matter of fact, it does not do anything on false from the destructor, so there you have your explanation.

nvoigt
  • 75,013
  • 26
  • 93
  • 142
  • That does explain the GC side of things, but bearing in mind that whether a socket goes into TIME_WAIT or not is controlled by the OS itself (not by .NET), why would calling the destructor vs `.Dispose()` have any influence on what the OS does to the socket? My understanding of TIME_WAIT is that *all sockets* should pass through this state to detect retransmission and such. Still don't understand why there's a way to bypass that (and whether it would be "good practice" to use it). – cogumel0 Nov 24 '16 at 12:19
1

I wound up looking up a bunch of these links, and finally sorted my issues out. This was really helpful.

On the server side, I basically do nothing. receive, send the response, and exit the handler. I did add a LingerState of 1, but I don't think it does anything.

On the client side, I use the same LingerState, but after I receive (I know the data is all there, since I'm receiving based on a UInt32 length at the beginning of the packet), I Close() the client socket, then set the Socket object to NULL.

Running both the client and server really aggressively on the same machine, it's cleaning up all of the sockets immediately; I was leaving thousands in TIME_WAIT before.

IgnusFast
  • 79
  • 7
0

Use

tcpClient.Close(0);

It's enough to specify a 0 seconds time out.

GibbOne
  • 629
  • 6
  • 10
0

I was around same TimeWait issue (well, timewait is not an issue in theory) in NET6 when instead I wanted to immediatly abort-close the socket (tcp RST). The problem is that TcpClient.Close() is not TcpClient.Client.Close(). When an instance of TcpClient gets disposed, the sequence of disposing will be:

tcpclient.Dispose() calls networkstream.Dispose() which calls socket.Dispose().

networkstream.Dispose(), prior to dispose the socket, it calls Shutdown(both), which (if i've understood it well) triggers gracefully termination prior to close socket, regardless the linger 0time option we set, and the TimeWait state is entered here. Since (as for actual NET6/7) only socket.Dispose() is able to send a RST, i ended up in disposing them in inverse order, like this:

// socket.Dispose(bool) is the place where a RST can be sent, if the timeout is set to 0
tcpcl.Client.Close(0);

// if ownssocket(true in this case) will call socket.internalshutdown(both)....then socket.close(timeout), but the shutdown already placed the tcp into TimeWait
// but if we already closed the underlying socket, no problem
netstream.Close(0);

// if networkstream not null (if we called GetStream()), it calls networkstream.dispose and it hopes it will close the socket for us
// same problem of before, but if we previously closed the networkstream, it will then try to close socket itself, but still with the socket.internalshutdown(both)
// so again, if we already closed the socket, no problem
tcpcl.Close();