Author |
|
vachooho Newbie
Joined: 17 November 2009 Location: United States
Online Status: Offline Posts: 18
|
Posted: 17 November 2009 at 3:08pm | IP Logged
|
|
|
I have very simple code:
Imap imap = new Imap();
if (imap.Connect(...))
{
if (imap.Login(...))
{
...
}
}
if(imap.IsConnected)
imap.Disconnect();
imap.Dispose();
the problem is that it leaves the underlying socket in TIME_WAIT state
(sometimes it is closed but mostly in TIME_WAIT) netstat will show it
How can I force the client class to close socket immediately? what;s wrong in the code?
|
Back to Top |
|
|
vachooho Newbie
Joined: 17 November 2009 Location: United States
Online Status: Offline Posts: 18
|
Posted: 17 November 2009 at 3:09pm | IP Logged
|
|
|
and yes, the code does call Disconnect() and return value is true
|
Back to Top |
|
|
Igor AfterLogic Support
Joined: 24 June 2008 Location: United States
Online Status: Offline Posts: 6104
|
Posted: 18 November 2009 at 5:39am | IP Logged
|
|
|
This is native behavior for sockets buffer, it's not connected with .NET framework or our component. Instead of immediate socket termination, operating system keeps it in a buffer for some time in case if the socket is requested again. This happens in most applications which handle multiple connections.
--
Regards,
Igor, AfterLogic Support
|
Back to Top |
|
|
vachooho Newbie
Joined: 17 November 2009 Location: United States
Online Status: Offline Posts: 18
|
Posted: 18 November 2009 at 10:06am | IP Logged
|
|
|
This happens if socket is not disconnected from the server before closing it.
for imap servers, if you issue "logout" (this will close socket on server side) before closing client socket: there will be no TIME_WAIT on client side.
|
Back to Top |
|
|
Alex AfterLogic Support
Joined: 19 November 2003
Online Status: Offline Posts: 2206
|
Posted: 18 November 2009 at 10:18am | IP Logged
|
|
|
That's not correct. The socket will remain in TIME_WAIT regardless whether the client issued LOGOUT prior to disconnecting or not.
You can manually connect to an IMAP server using telnet, then issue "some_tag LOGOUT" to make the server close the connection, and then "netstat /a" to view recently closed connection in TIME_WAIT state.
You can enable logging using Imap.Log class to see if the LOGOUT command is successfully issued by the client in MailBee case. Anyway, if you do not get any exception when calling Disconnect, this means the logout process goes fine.
Regards,
Alex
|
Back to Top |
|
|
vachooho Newbie
Joined: 17 November 2009 Location: United States
Online Status: Offline Posts: 18
|
Posted: 18 November 2009 at 11:52am | IP Logged
|
|
|
I see both fastmail.fm and gmail behave the way I describe.
When you issue logout, connection is closed from server side, socket goes into CLOSE_WAIT state. When I close socket, it disappear.
telnet mail.messagingengine.com 143
* OK IMAP4 ready
TCP ******-dc7800:3367 &nb sp; mail.messagingengine.com:imap ESTABLISHED
c1 login *********@fastmail.fm *********
c1 OK User logged in SESSIONID=<slots13b2p2-31983-1258573047-1>
c2 logout
* BYE LOGOUT received
c2 OK Completed
Connection to host lost.
netstat finds no socket at this point
Enter incoming IMAP server name [imap.gmail.com]:
and port (143|[993]):
[0003]|1118.114512.898 imapConnection:_ReadThread
logout
--- Thread switch ---
[0004]|1118.114525.773 imapConnection.SendCommand
[0004]|1118.114525.773 . IMAP00000001 LOGOUT
[0004]|1118.114525.773 [e] imapConnection.SendCommand
--- Thread switch ---
[0003]|1118.114525.805 . IMAP00000001 OK Quoth the raven, nevermore... 1if2451165pxi.71
[0003]|1118.114525.805 . Connection closed...
[0003]|1118.114525.805 [e] imapConnection:_ReadThread
close
C:\Documents and Settings\***>netstat /a | find "993"
TCP ***-dc7800:3376 px-in-f109.1e100.net:993 ESTABLISHED
C:\Documents and Settings\***>netstat /a | find "993"
TCP ***-dc7800:3376 px-in-f109.1e100.net:993 CLOSE_WAIT
C:\Documents and Settings\***>netstat /a | find "993"
C:\Documents and Settings\***>
|
Back to Top |
|
|
Igor AfterLogic Support
Joined: 24 June 2008 Location: United States
Online Status: Offline Posts: 6104
|
Posted: 19 November 2009 at 6:12am | IP Logged
|
|
|
We've run the following sample code:
Code:
Imap.LicenseKey = "...";
Imap imap = new Imap();
imap.Log.Enabled = true;
imap.Log.Filename = @"C:\log.txt";
if (imap.Connect("mail.messagingengine.com", 143))
{
Console.WriteLine("Connected");
}
Console.Write("Press ENTER to continue");
Console.ReadLine();
if (imap.IsConnected)
{
imap.Disconnect();
Console.WriteLine("Disconnected");
}
Console.Write("Press any key for exit");
Console.ReadLine(); |
|
|
We've run netstat /a right after each pause starts, here's the output:
Code:
C:\Documents and Settings\igor>netstat /a | find "imap"
TCP workstation26:imap workstation26.dom2.local:0 LISTENING
TCP workstation26:3814 mail.messagingengine.com:imap ESTABLISHED
C:\Documents and Settings\igor>netstat /a | find "imap"
TCP workstation26:imap workstation26.dom2.local:0 LISTENING |
|
|
As you can see, TIME_WAIT is not there. We've tried other servers, including imap.gmail.com and afterlogic.com, behavior is the same. Maybe there's a delay of few seconds, but there's nothing to worry about. In fact, this behavior highly depends on multiple environment configuration settings and its current state, but not on the mailer itself.
--
Regards,
Igor, AfterLogic Support
|
Back to Top |
|
|
Igor AfterLogic Support
Joined: 24 June 2008 Location: United States
Online Status: Offline Posts: 6104
|
Posted: 19 November 2009 at 6:19am | IP Logged
|
|
|
In addition to the above: be sure to check the log file to confirm that LOGOUT command is issued successfully.
--
Regards,
Igor, AfterLogic Support
|
Back to Top |
|
|
vachooho Newbie
Joined: 17 November 2009 Location: United States
Online Status: Offline Posts: 18
|
Posted: 19 November 2009 at 9:13am | IP Logged
|
|
|
LOGOUT is in the log
[11/19/2009 09:02:55.22] [SEND] [0007] [IMAP-00................] MBN00000003 LOGOUT\r\n
[11/19/2009 09:02:55.30] [RECV] [0007] [IMAP-00................] * BYE LOGOUT Requested\r\n [Total 24 bytes received.]
[11/19/2009 09:02:55.31] [RECV] [0007] [IMAP-00................] MBN00000003 OK 73 good day (Success)\r\n [Total 38 bytes received.]
[11/19/2009 09:02:55.31] [INFO] [0007] [IMAP-00................] Will disconnect from host "imap.gmail.com".
[11/19/2009 09:02:55.31] [INFO] [0007] [IMAP-00................] Disconnected from host "imap.gmail.com".
However I still claim socket is closed a little early thus leaving it in TIME_WAIT state. You can see it from the log (although these may be just matters related to logging) that diconnect happens immediately after receiving OK from LOGOUT (at 09:02:55.31). I think if you wait after LOGOUT for few milliseconds for server to disconnect first before client closes the socket - there will be no TIME_WAIT.
Also note, that this happens in 7-8 tries out of 10.In other cases it is closed without TIME_WAIT, proving timing issues (to me at least).
|
Back to Top |
|
|
vachooho Newbie
Joined: 17 November 2009 Location: United States
Online Status: Offline Posts: 18
|
Posted: 19 November 2009 at 11:18am | IP Logged
|
|
|
Here is the little more complex code based on your sample.
static void StartCmd()
{
Thread.Sleep(1000);
Process p = new Process();
ProcessStartInfo psI = new ProcessStartInfo("cmd.exe");
psI.Arguments = "/C netstat /a | find \"imap\"";
psI.UseShellExecute = false;
psI.RedirectStandardOutput = true;
psI.RedirectStandardError = true;
psI.CreateNoWindow = true;
p.StartInfo = psI;
if (p.Start())
{
p.WaitForExit();
Console.WriteLine(p.StandardOutput.ReadToEnd());
}
}
static void imap_Connected(object sender, MailBee.ConnectedEventArgs e)
{
Console.WriteLine("Connect");
StartCmd();
}
static void imap_Disconnected(object sender, MailBee.DisconnectedEventArgs e)
{
Console.WriteLine("Disconnect");
StartCmd();
}
static void Main(string[] args)
{
new Thread(ProcessIt).Start();
}
static void ProcessIt()
{
Imap.LicenseKey = "...";
Imap imap = new Imap();
imap.ThrowExceptions = false;
imap.Connected += imap_Connected;
imap.Disconnected += imap_Disconnected;
while (true)
{
if (imap.Connect("mail.messagingengine.com", 143))
{
imap.Login("...@fastmail.fm", "...");
imap.Disconnect();
}
}
}
}
and here is the output after couple dozen of iterations
Disconnect
TCP ***-dc7800:1343 mail.messagingengine.com:imap TIME_WAIT
TCP ***-dc7800:1344 mail.messagingengine.com:imap TIME_WAIT
TCP ***-dc7800:1347 mail.messagingengine.com:imap TIME_WAIT
TCP ***-dc7800:1351 mail.messagingengine.com:imap TIME_WAIT
TCP ***-dc7800:1360 mail.messagingengine.com:imap TIME_WAIT
TCP ***-dc7800:1362 mail.messagingengine.com:imap TIME_WAIT
TCP ***-dc7800:1363 mail.messagingengine.com:imap TIME_WAIT
TCP ***-dc7800:1370 mail.messagingengine.com:imap TIME_WAIT
Based on the local port numbers you can see which iterations are leaving socket in wait mode (1343, 1344, 1347, 51, 62). Assuming ports are assigned sequentially, its 2 failing, 2 succeed, 1 failing, 3 succeed, 1 failing, 11 succeed etc
If I try to enumerate folders or download messages, the frequency of TIME_WAIT will increase.
|
Back to Top |
|
|
Alex AfterLogic Support
Joined: 19 November 2003
Online Status: Offline Posts: 2206
|
Posted: 20 November 2009 at 7:43am | IP Logged
|
|
|
Quote:
I think if you wait after LOGOUT for few milliseconds for server to disconnect first before client closes the socket - there will be no TIME_WAIT.
|
|
|
Your suggestion would even make things worse.
If you used such approach, you would have a problem on slower connection when it's not enough to wait for a few milliseconds for the server to respond. You should wait for the response from the server telling you the server is closing the connection.
If you carefully examine the log, you'll find that the server replies "MBN00000003 OK 73 good day (Success)" telling you it actually closed the connection on its end. Then, it sends a packet of zero length which means "the socket is now closed". Then we call socket.Shutdown and socket.Close.
So, MailBee actually does what you suggested, but in more reliable way as defined by IMAP RFC.
After the socket is shut down and closed, the OS is free to do anything with it during a few minutes (the exact timespan value depends on registry settings and other reasons). If the socket transmitted a lot of data through it when it was open, it may take time for OS to free it internally. It's like garbage collection in .NET, which may occur at any spare moment.
Regards,
Alex
|
Back to Top |
|
|
vachooho Newbie
Joined: 17 November 2009 Location: United States
Online Status: Offline Posts: 18
|
Posted: 20 November 2009 at 10:09am | IP Logged
|
|
|
quote:
Your suggestion would even make things worse.
:o) I didn't suggest put a sleep in the code.
quote:
If you carefully examine the log, you'll find that the server replies "MBN00000003 OK 73 good day (Success)" telling you it actually closed the connection on its end.
This only means the server IS GOING to close the connection (if it were closed, closing socket on the client side will not leave it in TIME_WAIT state, also no write to the closed socket is possible).
So if the client closes the connection BEFORE server closes it, TIME_WAIT will happen. If accidentaly client manages to close the connection AFTER server closes it, it is closed immediately. Depending on the socket usage and server implementation both scenarios could happen.
We do this in other our component: LOGOUT, peek socket until it is closed by the server (yeah! usually it takes few millis), close socket.
quote:
If the socket transmitted a lot of data through it when it was open, it may take time for OS to free it internally. It's like garbage collection in .NET, which may occur at any spare moment.
TIME_WAIT is not like .NET garbage collection.
It is showing that more communication with the other side may happen. For IMAP client there should be no TIME_WAIT socket since protocol is designed to allow a clean disconnect.
|
Back to Top |
|
|
vachooho Newbie
Joined: 17 November 2009 Location: United States
Online Status: Offline Posts: 18
|
Posted: 20 November 2009 at 10:50am | IP Logged
|
|
|
this sequence does not leave socket in TIME_WAIT mode
imap.Connect()
imap.Login()
imap.ExecuteCustomCommand("LOGOUT", ...)
try { while(imap.Noop()) { } }
catch {}
imap.Disconnect()
imap.Dispose()
|
Back to Top |
|
|
Alex AfterLogic Support
Joined: 19 November 2003
Online Status: Offline Posts: 2206
|
Posted: 20 November 2009 at 11:25am | IP Logged
|
|
|
Quote:
f you carefully examine the log, you'll find that the server replies "MBN00000003 OK 73 good day (Success)" telling you it actually closed the connection on its end.
This only means the server IS GOING to close the connection (if it were closed, closing socket on the client side will not leave it in TIME_WAIT state, also no write to the closed socket is possible). |
|
|
I repeat that the component not just receives "OK" but also receives a zero-length packet which is an indication of that the other peer has actually closed the connection.
Also, we showed you the code tests above (including mail.messagingengine.com) where there is no TIME_WAIT sockets left after the disconnection. I.e. they do not appear in general case.
On other hand, if sockets are closed incorrectly, TIME_WAIT would indeed appear always. And this is not the case.
Quote:
TIME_WAIT is not like .NET garbage collection.
It is showing that more communication with the other side may happen. For IMAP client there should be no TIME_WAIT socket since protocol is designed to allow a clean disconnect. |
|
|
In theory. However, in practice it's different and heavily depends on implementation.
For sure, I also tested adding 1 second delay after receipt of zero-packet and before calling Shutdown and Close. This changed nothing. In a series of identical runs, TIME_WAIT appears a few times, but in most cases it doesn't.
Anyway. If I use just telnet (connecting and typing LOGOUT manually), I got the same results. Sometimes, there is a timewait, sometimes, there isn't. I don't think telnet developers wrote bad code.
Regards,
Alex
|
Back to Top |
|
|
vachooho Newbie
Joined: 17 November 2009 Location: United States
Online Status: Offline Posts: 18
|
Posted: 20 November 2009 at 2:29pm | IP Logged
|
|
|
"I don't think telnet developers wrote bad code. "
I don't think either. And it never leaves socket in the scenarios I have tested.
"I also tested adding 1 second delay after receipt of zero-packet and before calling Shutdown and Close."
??! you need to pick a socket to figure out it is closed.
Anyway, I found a sequence which does not leave socket in TIME_WAIT state (see my previous post) and going to stick to that.
Thanks for your time
Enjoy your other threads
|
Back to Top |
|
|
Alex AfterLogic Support
Joined: 19 November 2003
Online Status: Offline Posts: 2206
|
Posted: 20 November 2009 at 4:34pm | IP Logged
|
|
|
OK, if you know what you're doing and it works as you want (at least in your particular case).
However, it would be better to avoid exceptions anyway (throwing and catching exceptions should not occur under normal circumstances). If you need to work with the socket directly, try using Imap.GetSocket().
Regards,
Alex
|
Back to Top |
|
|
vachooho Newbie
Joined: 17 November 2009 Location: United States
Online Status: Offline Posts: 18
|
Posted: 23 November 2009 at 10:32am | IP Logged
|
|
|
aha, GetSocket() is what I actually need
I missed it in the documentation?
I can do
imap.ExecuteCustomCommand("LOGOUT", ...)
imap.GetSocket().Poll()
before
imap.Disconnect()
imap.Dispose()
though I still think it should be transparently done inside Disconnect()
anyway I have the way to get what I want
thanks
|
Back to Top |
|
|
Alex AfterLogic Support
Joined: 19 November 2003
Online Status: Offline Posts: 2206
|
Posted: 23 November 2009 at 11:12am | IP Logged
|
|
|
OK, thanks. I'll give your approach a try if I succeed to get time_wait with our current implementation (currently, I can get time_wait only when connecting to a local server on the same computer and this time_wait belongs to the server's listening socket). With remote servers, neither telnet nor MailBee can't make time_wait appear with my current tests (regardless whether I receive e-mails/folders in the code or just connect/disconnect).
Regards,
Alex
|
Back to Top |
|
|