How do I keep a FTP connection alive?

Question:

I used ftputil to download a batch of files from a FTP server. It raised the error ftputil.error.FTPIOError: [Errno 60] Operation timed out.

As described in Documentation – ftputil,

keep_alive() attempts to keep the connection to the remote server active in order to prevent timeouts from happening. This method is primarily intended to keep the underlying FTP connection of an FTPHost object alive while a file is uploaded or downloaded. This will require either an extra thread while the upload or download is in progress or calling keep_alive from a callback function.

I called keep_alive from a callback function with,

ftp_host.download(source, target, callback=ftp_host.keep_alive) 

but it raised ERROR __main__ keep_alive() takes 1 positional argument but 2 were given.

How do I keep a FTP connection alive?

Asked By: SparkAndShine

||

Answers:

This isn’t directly an answer to your question, but it may help finding an answer for your particular problem yourself. Also, a ticket on the ftputil website is better for help with debugging a problem. That said, I think it’s fine to ask on StackOverflow first since you don’t know in advance if the problem is a simple one or not. 🙂

Since FTP is a stateful protocol, client and server can’t send arbitrary commands at a given time. The allowed commands and possibly replies are determined by the state the connection is in. See also the state diagrams in RFC 959.

To work around this limitation, ftputil creates a new FTP connection behind the scenes for each remote file object [1]. With this approach, you can still send commands like chdir or start a download while another is still in progress. However, this means that from the perspective of the server, all these FTP connections that come from a single FTPHost object are independent connections, so each of these connections can have their timeout at different times, depending on the usage pattern of the respective connection.

For example, there was ftputil ticket 141, where presumably the main connection initiated by the FTPHost object timed out while a connection used for downloading was still usable.

In your case, it might be helpful to find out which of the underlying connections is timing out (the initial connection or a connection for a remote file). You can use ftputil.session.session_factory to create factories that have FTP debugging enabled (see the documentation).

Unfortunately, a timeout of 60 seconds is quite short, so there are relatively many chances for timeouts.

Especially given the possibility of timeouts in FTP connections, my advice is to write software for FTP transfers in a way so that you can restart the operation (ideally with a new FTPHost object for robustness) where it was interrupted by the timeout. So far I haven’t been able to come up with a way to universally work around timeouts. In simple cases you may actually be better off using ftplib directly, although ftputil has robustness and latency improvements that ftplib doesn’t have. Using ftplib doesn’t save you from timeouts, but at least you don’t have any "hidden" connections that may make debugging more difficult.


[1] That said, if you close a remote file in ftputil, the underlying FTP connection can be reused unless it’s not timed out. The library checks for a timeout before it reuses the connection.

The picture regarding timeouts is even more complicated by ftputil caching a lot of information from the server to reduce latency. For example, if you call FTPHost.getcwd(), the current directory is retrieved from a cached attribute, not by sending a PWD command to the server and thereby resetting the timeout. Stat information from directory listings is also usually cached.

Answered By: sschwarzer

After couple hours looking for solutions I got it running without ‘421 Timeout’ errors calling keepalive from separate thread. However your I/O Timeout error probably was caused by connection problems.

import ftputil
from threading import Thread
from time import sleep

fhandle = ftputil.FTPHost('host', 'user', 'pwd')

quitThread = 0

def _thread_keep_alive():
    while quitThread == 0:
        print("KEEPALIVE!")
        fhandle.keep_alive()
        sleep(25)

thread = Thread(target = _thread_keep_alive)
thread.start()

# some downloading...

quitThread = 1

fhandle.close()
Answered By: Rolandas Dundulis
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.