giampaolo / pyftpdlib

Extremely fast and scalable Python FTP server library
MIT License
1.65k stars 267 forks source link

Daemonizing the server throws Bad File Descriptor Error #526

Closed damey2011 closed 4 years ago

damey2011 commented 4 years ago

Running the server as a daemon as illustrated in the demo here: https://github.com/giampaolo/pyftpdlib/blob/master/demo/unix_daemon.py

def daemonize():
    """A wrapper around python-daemonize context manager."""

    def _daemonize():
        pid = os.fork()
        if pid > 0:
            # exit first parent
            sys.exit(0)

        # decouple from parent environment
        os.chdir(WORKDIR)
        os.setsid()
        os.umask(0)

        # do second fork
        pid = os.fork()
        if pid > 0:
            # exit from second parent
            sys.exit(0)

        # redirect standard file descriptors
        sys.stdout.flush()
        sys.stderr.flush()
        # We only want to log error
        # with open(LOG_FILE, 'a+') as se:
        #     os.dup2(se.fileno(), sys.stderr.fileno())
        # write pidfile
        pid = str(os.getpid())
        with open(PID_FILE, 'w') as f:
            f.write("%s\n" % pid)
        atexit.register(lambda: os.remove(PID_FILE))

    pid = get_pid()
    if pid and pid_exists(pid):
        sys.exit('daemon already running (pid %s)' % pid)
    # instance FTPd before daemonizing, so that in case of problems we
    # get an exception here and exit immediately
    server = get_server()
    _daemonize()
    server.serve_forever()

This throws OSError: [Errno 9] Bad file descriptor.

Platform: OSX

damey2011 commented 4 years ago

Just in case or anyone who encounters such issue also, the issue is that creating ioloop before forking is problematic on OSX, and it's a known issue here: https://groups.google.com/forum/#!topic/python-tornado/DkXjSNPCzsI. The solution is to swap the lines server = get_server() with the _daemonize(), so we get to create the fork before initializing the server, which will end up in a code like:


def daemonize():
    """A wrapper around python-daemonize context manager."""

    def _daemonize():
        pid = os.fork()
        ....
        ....

    _daemonize()
    server = get_server()
    server.serve_forever()