diff --git a/agkyra/protocol.py b/agkyra/protocol.py index 393959a94a854a0f7331f60e0c16adb7df5abd2c..6c883823a2595a4c95f0abd3552b4f39159aad02 100644 --- a/agkyra/protocol.py +++ b/agkyra/protocol.py @@ -707,6 +707,86 @@ class WebSocketProtocol(WebSocket): self.terminate() +def close_fds(): + import resource + # Default maximum for the number of available file descriptors. + MAXFD = 1024 + + maxfd = resource.getrlimit(resource.RLIMIT_NOFILE)[1] + if (maxfd == resource.RLIM_INFINITY): + maxfd = MAXFD + + # Iterate through and close all file descriptors. + for fd in range(2, maxfd): + try: + os.close(fd) + except OSError: # ERROR, fd wasn't open to begin with (ignored) + pass + + +# Adapted from https://code.activestate.com/recipes/278731/ +def daemonize(command): + # Default daemon parameters. + # File mode creation mask of the daemon. + UMASK = 0 + + # Default working directory for the daemon. + WORKDIR = "/" + + # The standard I/O file descriptors are redirected to /dev/null by default. + if (hasattr(os, "devnull")): + REDIRECT_TO = os.devnull + else: + REDIRECT_TO = "/dev/null" + + pid = os.fork() + if not pid: + # To become the session leader of this new session and the process + # group leader of the new process group, we call os.setsid(). The + # process is also guaranteed not to have a controlling terminal. + os.setsid() + + # Fork a second child and exit immediately to prevent zombies. + pid = os.fork() + if not pid: + # Since the current working directory may be a mounted + # filesystem, we avoid the issue of not being able to unmount + # the filesystem at shutdown time by changing it to the root + # directory. + os.chdir(WORKDIR) + + # We probably don't want the file mode creation mask inherited + # from the parent, so we give the child complete control over + # permissions. + os.umask(0) + + # Close all open file descriptors. This prevents the child from + # keeping open any file descriptors inherited from the parent. + # There is a variety of methods to accomplish this task. + close_fds() + + # Redirect the standard I/O file descriptors to the specified + # file. Since the daemon has no controlling terminal, most + # daemons redirect stdin, stdout, and stderr to /dev/null. This + # is done to prevent side-effects from reads and writes to the + # standard I/O file descriptors. + + # This call to open is guaranteed to return the lowest file + # descriptor, which will be 0 (stdin), since it was closed + # above. + os.open(REDIRECT_TO, os.O_RDWR) # standard input (0) + + # Duplicate standard input to standard output and standard error. + os.dup2(0, 1) # standard output (1) + os.dup2(0, 2) # standard error (2) + + os.execlp(*command) + else: + os._exit(0) + else: + os.wait() + + def launch_server(callback, debug): """Launch the server in a separate process""" LOGGER.info('Start SessionHelper session') @@ -719,12 +799,6 @@ def launch_server(callback, debug): command += opts subprocess.Popen(command, close_fds=True) else: - import daemon - pid = os.fork() - if not pid: - with daemon.DaemonContext(): - command = [callback, callback] - command += opts - os.execlp(*command) - else: - os.wait() + command = [callback, callback] + command += opts + daemonize(command) diff --git a/setup.py b/setup.py index 0bfaa4215e17a3af70cd56ceec022367e81f2eea..c43f30bbc32e3ce44003bcae0a552a2fdc25bda4 100644 --- a/setup.py +++ b/setup.py @@ -43,9 +43,6 @@ INSTALL_REQUIRES = [ 'certifi', ] -if not sys.platform.startswith("win"): - INSTALL_REQUIRES.append("python-daemon") - EXTRAS_REQUIRES = { }