robinrodricks / FluentFTP

An FTP and FTPS client for .NET & .NET Standard, optimized for speed. Provides extensive FTP commands, File uploads/downloads, SSL/TLS connections, Automatic directory listing parsing, File hashing/checksums, File permissions/CHMOD, FTP proxies, FXP support, UTF-8 support, Async/await support, Powershell support and more. Written entirely in C#.
MIT License
3.08k stars 650 forks source link

Getting Random "No connection to the server exists" Error during DownloadStream #1343

Closed mikegit75 closed 10 months ago

mikegit75 commented 1 year ago

FTP Server OS: Unix / Windows / Embedded

FTP Server Type: Vsftpd / glFTPd / BFTPd / ProFTPD / Pure-FTPd / IBM CS FTP (z/OS, OS/400) / Windows CE / Windows Server IIS / Vax / VMS / OpenVMS / Tandem / HP NonStop

Client Computer OS: Windows

FluentFTP Version: ? 47.1.0

Framework: .NET 6

<write details about your bug report / feature request here>

Logs :

FluentFTP.Exceptions.FtpException: Error while downloading the file from the server. See InnerException for more info. ---> System.InvalidOperationException: No connection to the server exists. at FluentFTP.AsyncFtpClient.GetReplyAsyncInternal(CancellationToken token, String command, Boolean exhaustNoop, Int32 timeOut) at FluentFTP.AsyncFtpClient.GetReplyAsyncInternal(CancellationToken token, String command) at FluentFTP.AsyncFtpClient.Execute(String command, CancellationToken token) at FluentFTP.AsyncFtpClient.OpenPassiveDataStreamAsync(FtpDataConnectionType type, String command, Int64 restart, CancellationToken token) at FluentFTP.AsyncFtpClient.OpenDataStreamAsync(String command, Int64 restart, CancellationToken token) at FluentFTP.AsyncFtpClient.OpenRead(String path, FtpDataType type, Int64 restart, Int64 fileLen, CancellationToken token) at FluentFTP.AsyncFtpClient.DownloadFileInternalAsync(String localPath, String remotePath, Stream outStream, Int64 restartPosition, IProgress`1 progress, CancellationToken token, FtpProgress metaProgress, Int64 knownFileSize, Boolean isAppend, Int64 stopPosition) --- End of inner exception stack trace --- at FluentFTP.AsyncFtpClient.DownloadFileInternalAsync(String localPath, String remotePath, Stream outStream, Int64 restartPosition, IProgress`1 progress, CancellationToken token, FtpProgress metaProgress, Int64 knownFileSize, Boolean isAppend, Int64 stopPosition) at FluentFTP.AsyncFtpClient.DownloadStream(Stream outStream, String remotePath, Int64 restartPosition, IProgress`1 progress, CancellationToken token, Int64 stopPosition) at Helpers.FTP.GetFTPFile(String _ftp_UrlAndFilename, String _ftp_username, String _ftp_password) in D:\Users\Mike\Documents\Visual Studio 2019\Codes\PST\Helpers\FTP.cs:line 231 -->

Sorry my first time to report a problem in Github. This error we got is intermittent. Screenshot below shows where the codes where the error happens when this code is called: await client.DownloadStream(stream, trev[1]);

image

Is there a way to check and retry? Another question is why is AutoConnect() not helping with this issue?

Any help would be appreciated. Thanks! :)

FanDjango commented 1 year ago

Sorry my first time to report a problem in Github.

No problem. Learn the formatting for this, there is not much.

This error we got is intermittent.

So the code is failing with the above exception for certain inputs of _ftp_UrlAndFilename, right? Which ones?

If you would produce a verbose log of the connections, one would see why it is failing.

Is there a way to check and retry?

Of course, you could check client.IsConnected and then try again, perhaps incrementing a retry counter to limit retries to 5 or so?

Another question is why is AutoConnect() not helping with this issue?

It will try to connect successfully, using a number of different tries - but if it cannot, it will fail. You are not testing the result of AutoConnect before proceeding with the DownloadStream. That is bad practice. Also, a log (especially a verbose log) would tell you exactly what part of the very convoluted FTP connect/log-in process has failed and why.

mikegit75 commented 11 months ago

So the code is failing with the above exception for certain inputs of _ftp_UrlAndFilename, right? Which ones? The code is failing randomly, not all the time... when it's calling DownloadStream The only error I got is: System.InvalidOperationException: No connection to the server exists

If you would produce a verbose log of the connections, one would see why it is failing. I couldn't specifically trigger the error during debug mode, as it's not occurring frequently and just happens intermittently.

Is there a way to check and retry? Of course, you could check client.IsConnected and then try again, perhaps incrementing a retry counter to limit retries to 5 or so?

Where do I place IsConnected right after DownloadStream? But the Exception error already happens during DownloadStream...

Another question is why is AutoConnect() not helping with this issue?

It will try to connect successfully, using a number of different tries - but if it cannot, it will fail. You are not testing the result of AutoConnect before proceeding with the DownloadStream. That is bad practice. Also, a log (especially a verbose log) would tell you exactly what part of the very convoluted FTP connect/log-in process has failed and why.

So I should place a call of client.IsConnected right before DownloadStream right? Something like... Would this code work below?

            if (client.IsConnected)
            {
                await client.DownloadStream(stream, trev[1]);
            } else
            {
                return await GetFTPFile(_ftp_UrlAndFilename, _ftp_username, _ftp_password);
            }
FanDjango commented 11 months ago

Something like this, maybe?

retryloop with counter:

  client.AutoConnect();
  if (!client.IsConnected()) {
    counter++;
    if (counter < 5) continue;
    else break ( or throw an exception );

  try {
    Your normal processing goes here... catch the exception in download stream
  }
  catch {
    if exception occurs, continue the loop to re-connect.
  }

loop_end.
mikegit75 commented 9 months ago

@FanDjango Sorry for the long wait... I was busy with other projects. image My problem is that the error happens during the AutoConnect() call. So I think I wouldn't be able to check the IsConnected() if AutoConnect() failed...

Below is the updated code we're using right now and we're still getting random errors during AutoConnect()...

            try
            {
                // remove ftp:// and %2F/
                _ftp_UrlAndFilename = _ftp_UrlAndFilename.Replace("ftp://", "").Replace("%2F/", "");
                string[] trev = _ftp_UrlAndFilename.Split(new[] { '/' }, 2);

                AsyncRetryPolicy _retryPolicy = Policy.Handle<Exception>()
                    .WaitAndRetryAsync(4, retryAttempt =>
                    TimeSpan.FromMilliseconds(retryAttempt * 1500));

                var client = new AsyncFtpClient(trev[0], _ftp_username, _ftp_password);
                Stream stream = new MemoryStream();

                await client.AutoConnect();
                await _retryPolicy.ExecuteAsync(async () =>
                {
                    await client.DownloadStream(stream, trev[1]);
                });
                await client.Disconnect();

                return stream;
            }
            catch (Exception ex)
            {
                throw new Exception($"There was a problem downloading {_ftp_UrlAndFilename}", ex);
            }

Should also include the await client.AutoConnect() inside the retryPolicy? Or create a different retry policy for AutoConnect()?

FanDjango commented 9 months ago
          try {
                await client.AutoConnect();
         }
        catch etc. etc.
                await _retryPolicy.ExecuteAsync(async () =>
                {
                    await client.DownloadStream(stream, trev[1]);
                });

Put a try-catch around the AutoConnect?

mikegit75 commented 9 months ago
          try {
                await client.AutoConnect();
         }
        catch etc. etc.
                await _retryPolicy.ExecuteAsync(async () =>
                {
                    await client.DownloadStream(stream, trev[1]);
                });

Put a try-catch around the AutoConnect?

But the AutoConnect is already in a try-catch... The only problem I see is that AutoConnect wouldn't try to retry if a problem occurs or would it?