0

I am trying to move to using System.Net.FtpClient, but things are not working as expected.

Running the code below, it seems like even though IsConnected returns true, a directly following call to FileExists() calls Connect() (which means the connection is lost exactly between the calls?). However, as Connect() can fail every now and then, this also results in a failing FileExists() (where failing means it throws Connection refused).

Is there anything wrong with my code? Is this something to be expected, i.e. should I be prepared to retry everything I do with an FtpClient instance? Is there any flag to set to retry automatically?

string myPath = ..;
string myTempPath = myPath+".tmp";

_client = GetClient(_ioc, false);
var _stream = _client.OpenWrite(myTempPath);

//write to stream

_stream.Close();
Android.Util.Log.Debug("NETFTP", "connected: " + _client.IsConnected.ToString()); //always outputs true

if (_client.FileExists(myPath) //sporadically throws, see below
    _client.DeleteFile(myPath);

where GetClient() is implemented as using my custom "retry-loop" to hande sporadic connecting failures.

private static T DoInRetryLoop<T>(Func<T> func)
{
    double timeout = 30.0;
    double timePerRequest = 1.0;
    var startTime = DateTime.Now;
    while (true)
    {
        var attemptStartTime = DateTime.Now;
        try
        {
            return func();
        }
        catch (System.Net.Sockets.SocketException e)
        {
            if ((e.ErrorCode != 10061) || (DateTime.Now > startTime.AddSeconds(timeout)))
            {
                throw;
            }
            double secondsSinceAttemptStart = (DateTime.Now - attemptStartTime).TotalSeconds;
            if (secondsSinceAttemptStart < timePerRequest)
            {
                Thread.Sleep(TimeSpan.FromSeconds(timePerRequest - secondsSinceAttemptStart));
            }
        }
    }       
}

internal FtpClient GetClient(IOConnectionInfo ioc)
{
    FtpClient client = new FtpClient();
    if ((ioc.UserName.Length > 0) || (ioc.Password.Length > 0))
        client.Credentials = new NetworkCredential(ioc.UserName, ioc.Password);
    else
        client.Credentials = new NetworkCredential("anonymous", ""); 

    Uri uri = IocPathToUri(ioc.Path);
    client.Host = uri.Host;
    if (!uri.IsDefaultPort)
        client.Port = uri.Port;
    client.EnableThreadSafeDataConnections = false;

    client.EncryptionMode = ConnectionSettings.FromIoc(ioc).EncryptionMode;

    Func<FtpClient> connect = () =>
    {
        client.Connect();
        return client;
    };
    return DoInRetryLoop(connect);

}

This is the exception which appears sporadically:

System.Net.Sockets.SocketException : Connection refused
10-24 13:08:07.487 I/mono-stdout(24073):          at System.Net.Sockets.SocketAsyncResult.CheckIfThrowDelayedException () [0x00017] in /Users/builder/data/lanes/3540/1cf254db/source/mono/mcs/class/System/System.Net.Sockets/SocketAsyncResult.cs:127 
          at System.Net.Sockets.SocketAsyncResult.CheckIfThrowDelayedException () [0x00017] in /Users/builder/data/lanes/3540/1cf254db/source/mono/mcs/class/System/System.Net.Sockets/SocketAsyncResult.cs:127 
10-24 13:08:07.487 I/mono-stdout(24073):          at System.Net.Sockets.Socket.EndConnect (IAsyncResult result) [0x0002f] in /Users/builder/data/lanes/3540/1cf254db/source/mono/mcs/class/System/System.Net.Sockets/Socket.cs:1593 
          at System.Net.Sockets.Socket.EndConnect (IAsyncResult result) [0x0002f] in /Users/builder/data/lanes/3540/1cf254db/source/mono/mcs/class/System/System.Net.Sockets/Socket.cs:1593 
          at System.Net.FtpClient.FtpSocketStream.Connect (System.String host, Int32 port, FtpIpVersion ipVersions) [0x0011a] in [my source folder]src
etftpandroid\System.Net.FtpClient\FtpSocketStream.cs:611 
10-24 13:08:07.487 I/mono-stdout(24073):          at System.Net.FtpClient.FtpSocketStream.Connect (System.String host, Int32 port, FtpIpVersion ipVersions) [0x0011a] in [my source folder]src
etftpandroid\System.Net.FtpClient\FtpSocketStream.cs:611 
10-24 13:08:07.487 I/mono-stdout(24073):          at (wrapper remoting-invoke-with-check) System.Net.FtpClient.FtpSocketStream:Connect (string,int,System.Net.FtpClient.FtpIpVersion)
          at (wrapper remoting-invoke-with-check) System.Net.FtpClient.FtpSocketStream:Connect (string,int,System.Net.FtpClient.FtpIpVersion)
10-24 13:08:07.487 I/mono-stdout(24073):          at System.Net.FtpClient.FtpClient.Connect () [0x000ce] in [my source folder]src
etftpandroid\System.Net.FtpClient\FtpClient.cs:807 
          at System.Net.FtpClient.FtpClient.Connect () [0x000ce] in [my source folder]src
etftpandroid\System.Net.FtpClient\FtpClient.cs:807 
          at System.Net.FtpClient.FtpClient.Execute (System.String command) [0x00136] in [my source folder]src
etftpandroid\System.Net.FtpClient\FtpClient.cs:735 
10-24 13:08:07.487 I/mono-stdout(24073):          at System.Net.FtpClient.FtpClient.Execute (System.String command) [0x00136] in [my source folder]src
etftpandroid\System.Net.FtpClient\FtpClient.cs:735 
10-24 13:08:07.487 I/mono-stdout(24073):          at System.Net.FtpClient.FtpClient.Execute (System.String command, System.Object[] args) [0x00001] in [my source folder]src
etftpandroid\System.Net.FtpClient\FtpClient.cs:694 
          at System.Net.FtpClient.FtpClient.Execute (System.String command, System.Object[] args) [0x00001] in [my source folder]src
etftpandroid\System.Net.FtpClient\FtpClient.cs:694 
10-24 13:08:07.487 I/mono-stdout(24073):          at System.Net.FtpClient.FtpClient.DirectoryExists (System.String path) [0x0005d] in [my source folder]src
etftpandroid\System.Net.FtpClient\FtpClient.cs:2679 
          at System.Net.FtpClient.FtpClient.DirectoryExists (System.String path) [0x0005d] in [my source folder]src
etftpandroid\System.Net.FtpClient\FtpClient.cs:2679 
10-24 13:08:07.487 I/mono-stdout(24073):          at System.Net.FtpClient.FtpClient.FileExists (System.String path, FtpListOption options) [0x0001c] in [my source folder]src
etftpandroid\System.Net.FtpClient\FtpClient.cs:2751 
10-24 13:08:07.487 I/mono-stdout(24073):          at System.Net.FtpClient.FtpClient.FileExists (System.String path) [0x00001] in [my source folder]src
etftpandroid\System.Net.FtpClient\FtpClient.cs:2733 
          at System.Net.FtpClient.FtpClient.FileExists (System.String path, FtpListOption options) [0x0001c] in [my source folder]src
etftpandroid\System.Net.FtpClient\FtpClient.cs:2751 
          at System.Net.FtpClient.FtpClient.FileExists (System.String path) [0x00001] in [my source folder]src
etftpandroid\System.Net.FtpClient\FtpClient.cs:2733 
Philipp
  • 11,549
  • 8
  • 66
  • 126

1 Answers1

1

It turned out that the FtpClient was reconnecting due to some unexpected response from the client which triggered the reconnect because of "stale data". My solution was to derive my own class from FtpClient which overrides the Connect() method using the DoInRetryLoop as posted in the question.

Unfortunately, this only works with either EnableThreadSafeDataConnections=false or with overriding the "CloneConnection" method as well. The latter required me to make it virtual.

Philipp
  • 11,549
  • 8
  • 66
  • 126