~sschwarzer/ftputil

6e26f9dc6ad39561f10cb3858530717ffadf33d8 — Stefan Schwarzer 12 years ago 5c73295
Don't use child sessions which have timed out. Fixes #44.
2 files changed, 38 insertions(+), 8 deletions(-)

M ftputil.py
M test/test_real_ftp.py
M ftputil.py => ftputil.py +18 -7
@@ 152,16 152,27 @@ class FTPHost(object):

    def _available_child(self):
        """
        Return an available (i. e. one whose `_file` object is closed)
        child (`FTPHost` object) from the pool of children or `None`
        if there aren't any.
        Return an available (i. e. one whose `_file` object is closed
        and doesn't have a timed-out server connection) child
        (`FTPHost` object) from the pool of children or `None` if
        there aren't any.
        """
        #FIXME Don't reuse child sessions that have timed out.
        #  (Maybe this should go into `_make_session` or `file`.)
        #  Write a unit test before attempting a fix.
        for host in self._children:
            # Test for timeouts only after testing for a closed file:
            # - If a file isn't closed, save time; don't bother to access
            #   the remote server.
            # - If a file transfer on the child is in progress, requesting
            #   the directory is an invalid operation because of the way
            #   the FTP state machine works (see RFC 959).
            if host._file.closed:
                return host
                try:
                    host._session.pwd()
                # Timed-out sessions raise `error_temp`.
                except ftplib.error_temp:
                    continue
                else:
                    # Everything's ok; use this `FTPHost` instance.
                    return host
        # Be explicit.
        return None


M test/test_real_ftp.py => test/test_real_ftp.py +20 -1
@@ 3,6 3,7 @@

# Execute a test on a real FTP server (other tests use a mock server)

import ftplib
import getpass
import operator
import os


@@ 549,6 550,24 @@ class TestFTPFiles(RealFTPTest):
        self.assertEqual(len(host._children), 2)
        self.assert_(file_obj._host is host._children[1])

    def test_no_timed_out_children(self):
        REMOTE_FILENAME = "debian-keyring.tar.gz"
        host = self.host
        file_obj1 = host.open(REMOTE_FILENAME, 'rb')
        file_obj1.close()
        # Monkey-patch file to simulate an FTP server timeout below.
        def timed_out_pwd():
            raise ftplib.error_temp("simulated timeout")
        file_obj1._host._session.pwd = timed_out_pwd
        # Try to get a file - which shouldn't be the timed-out file.
        file_obj2 = host.open(REMOTE_FILENAME, 'rb')
        self.assert_(file_obj1 is not file_obj2)
        # Re-use closed and not timed-out child session.
        file_obj2.close()
        file_obj3 = host.open(REMOTE_FILENAME, 'rb')
        file_obj3.close()
        self.assert_(file_obj2 is file_obj3)


class TestChmod(RealFTPTest):



@@ 682,5 701,5 @@ minutes because it has to wait to test the timezone calculation.
    unittest.main()
    import __main__
    #unittest.main(__main__,
    #              "TestFTPFiles.test_only_closed_children")
    #              "TestFTPFiles.test_no_timed_out_children")