3

Closed

Sftpclient.Disconnect takes very long time

description

The following code takes over two minutes to execute. It gets stuck on Disconnect(). I am trying to connect to TITAN commercial FTP Server.
       SftpClient client = new SftpClient(sAddress, iPort, sUserID, password);
       client.Connect();
       client.Disconnect();
I can connect to other SFTP sites just fine.

Any idea what would be wrong?

-Jayesh
Closed Sep 17, 2016 at 7:34 AM by drieseng
This should improve considerably in the next version of SSH.NET.

If you're still seeing this issue in v2016.1.0 (beta1 or higher), then please submit an issue at our new home @GitHub.

comments

chazjn wrote Nov 2, 2015 at 6:49 AM

I am also seeing this issue on WingFTP Server.

wrote Nov 2, 2015 at 6:49 AM

wrote Nov 5, 2015 at 7:09 PM

mahowling wrote Nov 16, 2015 at 3:35 PM

I was also getting an issue where the disconnect from a WingFTP server wasn't responding. Whilst comparing the SshNet logging to another SFTP site I noticed this appearing after the Receive message for ChannelCloseMessage:

A first chance exception of type 'System.Net.Sockets.SocketException' occurred in System.dll

I tracked that back to the SocketRead method in Session.Net.cs. I can only assume that the FTP is disconnecting so quickly it's causing an issue with reading the result (or there just isn't a result).

The catch block in this message attempts to Disconnect if there is a ConnectionAbort, but we are already in the disconnect process. I think this causes an issue with the subsequent closing of the connection.

I think I've managed to solve it (at least for me). I modified the SocketException Catch in Session.Net.cs SocketRead to perform a check on the _isDisconnecting flag.
catch (SocketException exp)
{
    if (exp.SocketErrorCode == SocketError.ConnectionAborted)
    {
       buffer = new byte[length];
       if (!_isDisconnecting) Disconnect();
       return;
    }

   if (exp.SocketErrorCode == SocketError.WouldBlock ||
       exp.SocketErrorCode == SocketError.IOPending ||
       exp.SocketErrorCode == SocketError.NoBufferSpaceAvailable)
       {
            // socket buffer is probably empty, wait and try again
            Thread.Sleep(30);
        }
        else
            throw;  // any serious error occurred
}

kroq wrote Apr 13, 2016 at 9:25 PM

Similar issue - getting up to a two minute hang on calling disconnect. Curiously, it seemed to happen to me mostly when I was moving small amounts of data (on the order to 10KB). That might have been an effect of my test scenario, however. Most of my tests were with small chunks of data.

I tried the solution by mahowling but it did not work in my case. I dug a little deeper and discovered the hang was happening in the SocketDisconnect method of Session.NET.cs. Specifically, the call to _socket.Disconnect(true) was causing the hang. That true parameter says allow the socket to be re-used.

A call to (sftpclient).Disconnect actually calls the DisconnectAndDispose function in Session.cs - so this socket is not going to be reused anyway. My solution was to create an overload method for SocketDisconnect that passed a {false} value for the reuse parameter. That seemed to cure it.

Here's what the overload method looks like:
        /// <summary>
        /// Closes the socket and optionally flags the socket for reuse
        /// </summary>
        /// <param name="reuse">{true} to allow the socket to be reusable, {false} to discard the socket</param>
        /// <exception cref="SocketException">An error occurred when trying to access the socket.</exception>
        partial void SocketDisconnect(bool reuse)
        {
            _socket.Shutdown(SocketShutdown.Both);
            _socket.Disconnect(false);
        }
Since this is a partial, you have to put the stub declaration at the top of Session.cs thusly:
        /// <summary>
        /// Closes the socket and optionally flags the socket for reuse. Added kr 4/13/2016 to prevent hanging when closing a reusable socket
        /// </summary>
        /// <param name="reuse">{true} to allow the socket to be reusable, {false} to discard the socket</param>
        /// <exception cref="SocketException">An error occurred when trying to access the socket.</exception>
        partial void SocketDisconnect(bool reuse);
Finally - changed the call in SocketDisconnectAndDispose in Session.cs to call my overload method with the reuse parameter set to false:
        private void SocketDisconnectAndDispose()
        {
            if (_socket != null)
            {
                lock (_socketLock)
                {
                    if (_socket != null)
                    {
                        if (_socket.Connected)
                         SocketDisconnect(false);
                        _socket.Dispose();
                        _socket = null;
                    }
                }
            }
        }

wrote Sep 17, 2016 at 7:34 AM