Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

UploadBytes incorrectly reports failure after long delay when upload succeeded #1593

Closed
kiddailey opened this issue Jul 13, 2024 · 8 comments

Comments

@kiddailey
Copy link

FTP Server OS: Windows

FTP Server Type: Windows Server IIS

Client Computer OS: Android

FluentFTP Version: 50.1.0

Framework: Xamarin

After updating from 50.0.1 to 50.1.0, UploadBytes is oddly failing when the client attempts to upload an empty or small file (< 10 bytes). The upload starts, the file is uploaded completely and after a period of about 1-2 minutes, the upload call returns the upload as a failure even though it wasn't.

This issue does not occur in the previous version 50.0.1 where it works as expected.

Example code:

var uploadResult = await ftpClient.UploadBytes(new byte[] { 0 }, "file.txt", FtpRemoteExists.Overwrite);

Logs :


# UploadBytes("/timezonetest.dat", Overwrite, False)
# FileExists("/timezonetest.dat")
Command:  SIZE /timezonetest.dat
Status:   Waiting for response to: SIZE /timezonetest.dat
Response: 550 The system cannot find the file specified.  [56ms]
# OpenWrite("/timezonetest.dat", Binary, -1, False)
Command:  TYPE I
Status:   Waiting for response to: TYPE I
Response: 200 Type set to I. [2ms]
# OpenDataStreamAsync("STOR /timezonetest.dat", 0)
# OpenPassiveDataStreamAsync(AutoPassive, "STOR /timezonetest.dat", 0)
Command:  EPSV
Status:   Waiting for response to: EPSV
Response: 229 Entering Extended Passive Mode (|||55404|) [3ms]
Status:   Connecting(async) AsyncFtpClient.FtpSocketStream(data) IP #1 = ***:55404
Command:  STOR /timezonetest.dat
Status:   Waiting for response to: STOR /timezonetest.dat
Response: 125 Data connection already open; Transfer starting. [5ms]
Status:   FTPS authentication successful, lib = .NET SslStream, cipher suite = Tls12 (None, None, 0) [25ms]
Status:   Uploaded 1 bytes (2ms, 0 bytes/s)
Status:   Disposing(async) AsyncFtpClient.FtpSocketStream(data)
Status:   Waiting for response to: STOR /timezonetest.dat

... Short Delay ...

Status:   Disposing(async) AsyncFtpClient.FtpSocketStream(control)

... Long Delay ...

There are no further log entries after the socket stream is disposed

For comparison, here is what the same thing looks like in 50.0.1 where it works correctly:

# UploadBytes("/timezonetest.dat", Overwrite, False)
# FileExists("/timezonetest.dat")
Command:  SIZE /timezonetest.dat
Status:   Waiting for response to: SIZE /timezonetest.dat
Response: 550 The system cannot find the file specified.  [1ms]
# OpenWrite("/timezonetest.dat", Binary, -1, False)
Command:  TYPE I
Status:   Waiting for response to: TYPE I
Response: 200 Type set to I. [33ms]
# OpenDataStreamAsync("STOR /timezonetest.dat", 0)
# OpenPassiveDataStreamAsync(AutoPassive, "STOR /timezonetest.dat", 0)
Command:  EPSV
Status:   Waiting for response to: EPSV
Response: 229 Entering Extended Passive Mode (|||55334|) [57ms]
Status:   Connecting to IP #1= ***:55334
Command:  STOR /timezonetest.dat
Status:   Waiting for response to: STOR /timezonetest.dat
Response: 125 Data connection already open; Transfer starting. [3ms]
Status:   FTPS authentication successful, lib = .NET SslStream, cipher suite = Tls12 (None, None, 0) [15ms]
Status:   Uploaded 1 bytes
Status:   Disposing(async) FtpSocketStream(data connection of AsyncFtpClient)
Status:   Waiting for response to: STOR /timezonetest.dat
Response: 226 Transfer complete. [24ms]
# GetModifiedTime("/timezonetest.dat")
Command:  MDTM /timezonetest.dat
Status:   Waiting for response to: MDTM /timezonetest.dat
Response: 213 20240713185604 [6ms]
# DeleteFile("/timezonetest.dat")
Command:  DELE /timezonetest.dat
Status:   Waiting for response to: DELE /timezonetest.dat
Response: 250 DELE command successful. [4ms]
# Disconnect()
Command:  QUIT
Status:   Waiting for response to: QUIT
Response: 221 Goodbye. [2ms]

Notes:

  • I have also tried uploading a set of bytes that isn't empty, and I get the same results
  • I haven't tested UploadFile() yet, so this may not be specific to UploadBytes()
  • Other calls (creating directories, checking if files exist, etc) seem to work
@FanDjango
Copy link
Collaborator

I would think these are at fault:

image

Trouble is, those changes are needed.

Now it seems to be a case of deciding, in which case - needed or not needed, server specific, size and timing specific.

I will try to find out what is best.

@kiddailey
Copy link
Author

Interesting. I hadn't seen those commits.

Not sure if' it's helpful, but looking back at the logs above, the "226 transfer complete" response isn't logged at all even though it says it uploaded. Almost as if it wasn't received/processed or the buffer wasn't flushed.

@FanDjango
Copy link
Collaborator

FanDjango commented Jul 14, 2024

After updating from 50.0.1 to 50.1.0

I really need a new set of glasses. Please disregard my initial post, those commits were 50.0.0->50.0.1, so previous to your working version.

Running your code right now against a ProFTPD server and it is working fine.

Checking the 50.0.1+ commits now...

@FanDjango
Copy link
Collaborator

Ok, I have checked and I would think that

FtpSocketStream.cs

is the culprit when going from 50.0.1 to 50.1.0

Since I cannot reproduce the problem here and we are looking at Android / Xamarin (Yikes!) there is now work for you to do (pretty please!).

Compare the changes in FtpSocketStream.cs between 50.0.1 and 50.1.0 - you will find some that involve overrides for Read / Write : These are enabled for certain .NET levels and I have no idea whether or not these kick in under Xamarin and if they then work. Another set of changes is right at the bottom of the source, where SYNC and ASYNC dispose logic has been changed. Any other changes are improvements to logging texts which are actually very helpful to further analyse this issue.

First: Recode your mini-test-program to use SYNC. Does that change anything? Works?
Second: Get the "older" FtpSocketStream.cs and use that. Does that .....?
Third: Use the "newer" FtpSocketStream.cs and remove the Read / Write overloads that were added.
Fourth: Use the newer one, but pick up the SYNC/ASYNC disposal logic from the older one.

That's what I would do myself in order to bisect this problem further, but I can't do it. Only you can help here right now.

@kiddailey
Copy link
Author

Sure thing - I will take a detailed look at this in the next week or so. And yes Android/Xamarin (yikes) :) Updating to MAUI is in progress, but still have to support old app in the meantime. You may be right about .NET levels.

The code in question is a separate FTPS "provider" plugin for a larger app and it does have unit testing, so it shouldn't be too difficult to put together a sync version and give it a thorough run through with the modifications you suggested.

Appreciate your help, thanks!

@FanDjango
Copy link
Collaborator

@kiddailey You will want to investigate the newest Nuget - V51.0.0

@kiddailey
Copy link
Author

Just wanted to let you know that yes, v51 appears to have solved the issue on Android and success continues with v51.1.0. I will be testing shortly on iOS as well and will close this issue is successful. Thank you so much and apologies for the slow response - been a busy month.

@kiddailey
Copy link
Author

Confirmed, also works perfectly on iOS. Thanks again.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants