Source code for radical.utils.daemon

# pylint: disable=try-except-raise

import io
import os
import sys
import queue
import signal
import multiprocessing as mp

from .misc    import ru_open
from .testing import sys_exit

# from
# http://stackoverflow.com/questions/1417631/python-code-to-daemonize-a-process


# ------------------------------------------------------------------------------
#
[docs]def daemonize(main=None, args=None, stdout=None, stderr=None, stdin=None, timeout=None): ''' Create a daemon process and run the given method in it. For that, do the UNIX double-fork magic, see Stevens' "Advanced Programming in the UNIX Environment" for details (ISBN 0201563177) The method will return the PID of the spawned damon process, or `None` on failure to create it. stdout, stderr, stdin file names are interpreted by the daemon process, and are expected to be path names which can be opened and read / written in their respective capabilities. ''' if main: assert callable(main) pid = None pid_q = mp.Queue() # first fork try: f1_pid = os.fork() if f1_pid > 0: # wait for daemon pid from second parent # TODO: timeout if timeout: try: pid = pid_q.get(timeout=timeout) except queue.Empty as e: raise RuntimeError('daemon startup timed out') from e else: pid = pid_q.get() if not pid: raise RuntimeError('daemon startup failed') # we are done... return pid except OSError as e: raise RuntimeError( 'Fork failed: %d (%s)\n' % (e.errno, e.strerror)) from e except Exception as e: raise RuntimeError( 'Failed to start daemon: %d (%s)\n' % (e.errno, e.strerror)) from e # decoupling from parent process group is disabled # (some launch methods, e.g., APRun, required to run within the same group) # os.setsid() # second fork try: f2_pid = os.fork() if f2_pid > 0: # communicate pid to first parent pid_q.put(f2_pid) # exit from second parent sys_exit(0) except OSError: pid_q.put(None) # unblock parent sys_exit(1) # redirect standard file descriptors if stdin: try: si = ru_open(stdin, 'r') os.dup2(si.fileno(), sys.stdin.fileno()) except io.UnsupportedOperation: sys.stdin = ru_open(stdin, 'r') if stdout: try: sys.stdout.flush() so = ru_open(stdout, 'a+') os.dup2(so.fileno(), sys.stdout.fileno()) except io.UnsupportedOperation: sys.stdout = ru_open(stdout, 'a+') if stderr: try: sys.stderr.flush() se = ru_open(stderr, 'a+') os.dup2(se.fileno(), sys.stderr.fileno()) except io.UnsupportedOperation: sys.stderr = ru_open(stderr, 'a+') if main: # we are successfully daemonized - run the workload and exit if args is None: main() else : main(*args) sys_exit(0) else: # just return - the calling code will now continue daemonized return
# ------------------------------------------------------------------------------ #
[docs]class Daemon(object): ''' A generic daemon class. Usage: subclass the Daemon class and override the run() method ''' # -------------------------------------------------------------------------- # def __init__(self, stdin='/dev/null', stdout='/dev/null', stderr='/dev/null', target=None, args=None): if target: assert callable(target) self.stdin = stdin self.stdout = stdout self.stderr = stderr self.pid = None self.target = target self.args = args # -------------------------------------------------------------------------- #
[docs] def start(self): # start the daemon, and in the demon process, run the workload self.pid = daemonize(main=self.run, args=self.args, stdin=self.stdin, stdout=self.stdout, stderr=self.stderr) return self.pid
# -------------------------------------------------------------------------- #
[docs] def stop(self, pid=None): ''' Stop the daemon. If a pid is passed, then stop the daeon process with that pid. ''' if not pid: pid = self.pid if not pid: raise RuntimeError('no pid - daemon not started, yet?') # Try killing the daemon process os.kill(pid, signal.SIGTERM)
# -------------------------------------------------------------------------- #
[docs] def restart(self, pid=None): ''' Stop the daemon and restart it. If a pid is passed, then stop the daeon process with that pid and replace it with a daemon process represented by this class instance. It is the caller's responsibility to ensure that this makes semantic sense. This method returns the pid of the new daemon process (see `start()`). ''' self.stop(pid=pid) return self.start()
# -------------------------------------------------------------------------- #
[docs] def run(self): ''' You should override this method when you subclass Daemon and do not pass `target` to the class constructor. This method will be called after the daemon process has been created by start() or restart(). ''' self.target(*self.args)
# ------------------------------------------------------------------------------