FluentFTP: GetListing blocking with no timeout

FTP OS: ?? (https://www.drivehq.com/)

FTP Server: ?? (https://www.drivehq.com/)

Computer OS: Ubuntu 16.04 with Docker (Docker image: microsoft/dotnet:2.0-runtime) on Azure VM

My docker service randomly blocks on a GetListing call. There is no timeout, a restart of the service is required. I did not change any default settings. This has happened randomly several times within the last weeks.

Any possibility that some timeouts are not working as expected under .NET Core?

Logs :

Randomly the service blocks after sending the MLSD command and never receives the response.

sync_1           | 2018-01-04T18:27:20.556490347Z 17129314 [1] INFO FtpSyncProcess (null) - Listing CSV files
sync_1           | 2018-01-04T18:27:20.556493147Z
sync_1           | 2018-01-04T18:27:20.556495647Z # GetListing("/Data/Source_1", Auto)
sync_1           | 2018-01-04T18:27:20.556498447Z Command:  TYPE I
sync_1           | 2018-01-04T18:27:20.714983101Z Response: 200 Type set to I
sync_1           | 2018-01-04T18:27:20.715014201Z
sync_1           | 2018-01-04T18:27:20.715017301Z # OpenPassiveDataStream(AutoPassive, "MLSD /Data/Source_1", 0)
sync_1           | 2018-01-04T18:27:20.715020301Z Command:  EPSV
sync_1           | 2018-01-04T18:27:20.873663654Z Response: 501 Syntax error: Command not understood.
sync_1           | 2018-01-04T18:27:20.873687254Z
sync_1           | 2018-01-04T18:27:20.873689854Z # OpenPassiveDataStream(PASV, "MLSD /Data/Source_1", 0)
sync_1           | 2018-01-04T18:27:20.873692754Z Command:  PASV
sync_1           | 2018-01-04T18:27:21.031643009Z Response: 227 Entering Passive Mode (66,220,9,50,35,112).
sync_1           | 2018-01-04T18:27:21.031667409Z Status:   Connecting to 66.220.9.50:9072
sync_1           | 2018-01-04T18:27:21.189184163Z Command:  MLSD /Data/Source_1

Normally a Response is received.

sync_1           | 2018-01-05T10:15:44.040386500Z Response: 227 Entering Passive Mode (66,220,9,50,16,213).
sync_1           | 2018-01-05T10:15:44.040412000Z Status:   Connecting to 66.220.9.50:4309
sync_1           | 2018-01-05T10:15:44.197930265Z Command:  MLSD /Data/Source_1
sync_1           | 2018-01-05T10:15:44.363922522Z Response: 150 Connection accepted

About this issue

  • Original URL
  • State: closed
  • Created 6 years ago
  • Comments: 16 (14 by maintainers)

Most upvoted comments

@robinrodricks Currently for .NET Core the synchronous call is using stream.ReadAsync() on the stream instead of the sync version stream.Read(). Async read calls (.ReadAsync or .BeginRead (which isn’t available in .NET Core afaik)) on a stream will never use the ReadTimeout. So if the server doesn’t send anything back but keeps the connection alive FluentFTP is stuck on this call forever until the socket/stream closes. For the sync version just using .Read instead will use the Timeout and doesn’t need a call to .Result

In general using .Result is not the prefered way to call async code synchronously (which should be prevented cause of potential deadlocks anyway) cause it will for example not Unwrap Exceptions see: https://stackoverflow.com/questions/17284517/is-task-result-the-same-as-getawaiter-getresult

The async part is a bit more complicated cause we should use the Async calls there. There is a general issue with CancellationTokens in FluentFTP. The Async calls should pass a global CancellationToken down the line to the ReadAsync call. Currently for the workaround I use another Task with Task.Delay and check if the timeout task is the first one that finished. If it is, then I force close the stream. That will cause the “real” task to finish with an IO Exception and causes it to get unstuck. I will throw a timeoutexception instead of awaiting the original Task.

I’ve made a first attempt to pass cancellation tokens from every method down to ReadAsync. There are a few cases where this is not possible so I made a OnCancled function that checks for the cancelationtoken being triggered and then closing the stream/socket. https://github.com/WolfspiritM/FluentFTP/commit/393a9202dbc2fbd0db825da693dfdaa4c2f9af15 It’s a big change and I’m not sure if I got everything…but I think it’s the cleanest way cause that’s how ReadAsync is supposed to work. I will take a look at it more later. I did change a few method invocations so the CancelationToken is at the end…so I don’t think it’s a good thing to include in a minor update.

	// GetListing with Timeout of 1000 milliseconds
	var cts = new CancellationTokenSource(1000);
	var test = await cl.GetListingAsync(cts.Token);

	foreach (var item in test)
	{
		Console.WriteLine(item.FullName);
	}

The question is if we should combine that token with another one that is using ReadTimeout within the ReadAsync method so we are using the global ReadTimeout variable?!