''' *** Modified generic daemon class *** Author: http://www.jejik.com/articles/2007/02/ a_simple_unix_linux_daemon_in_python/www.boxedice.com License: http://creativecommons.org/licenses/by-sa/3.0/ Changes: 23rd Jan 2009 (David Mytton ) - Replaced hard coded '/dev/null in __init__ with os.devnull - Added OS check to conditionally remove code that doesn't work on OS X - Added output to console on completion - Tidied up formatting 11th Mar 2009 (David Mytton ) - Fixed problem with daemon exiting on Python 2.4 (before SystemExit was part of the Exception base) 13th Aug 2010 (David Mytton - Fixed unhandled exception if PID file is empty ''' # Core modules from __future__ import print_function import atexit import errno import os import sys import time import signal class Daemon(object): """ A generic daemon class. Usage: subclass the Daemon class and override the run() method """ def __init__(self, pidfile, stdin=os.devnull, stdout=os.devnull, stderr=os.devnull, home_dir='.', umask=0o22, verbose=1, use_gevent=False, use_eventlet=False): self.stdin = stdin self.stdout = stdout self.stderr = stderr self.pidfile = pidfile self.home_dir = home_dir self.verbose = verbose self.umask = umask self.daemon_alive = True self.use_gevent = use_gevent self.use_eventlet = use_eventlet def log(self, *args): if self.verbose >= 1: print(*args) def daemonize(self): """ Do the UNIX double-fork magic, see Stevens' "Advanced Programming in the UNIX Environment" for details (ISBN 0201563177) http://www.erlenstar.demon.co.uk/unix/faq_2.html#SEC16 """ if self.use_eventlet: import eventlet.tpool eventlet.tpool.killall() try: pid = os.fork() if pid > 0: # Exit first parent sys.exit(0) except OSError as e: sys.stderr.write( "fork #1 failed: %d (%s)\n" % (e.errno, e.strerror)) sys.exit(1) # Decouple from parent environment os.chdir(self.home_dir) os.setsid() os.umask(self.umask) # Do second fork try: pid = os.fork() if pid > 0: # Exit from second parent sys.exit(0) except OSError as e: sys.stderr.write( "fork #2 failed: %d (%s)\n" % (e.errno, e.strerror)) sys.exit(1) if sys.platform != 'darwin': # This block breaks on OS X # Redirect standard file descriptors sys.stdout.flush() sys.stderr.flush() si = open(self.stdin, 'r') so = open(self.stdout, 'a+') if self.stderr: try: se = open(self.stderr, 'a+', 0) except ValueError: # Python 3 can't have unbuffered text I/O se = open(self.stderr, 'a+', 1) else: se = so os.dup2(si.fileno(), sys.stdin.fileno()) os.dup2(so.fileno(), sys.stdout.fileno()) os.dup2(se.fileno(), sys.stderr.fileno()) def sigtermhandler(signum, frame): self.daemon_alive = False sys.exit() if self.use_gevent: import gevent gevent.reinit() gevent.signal(signal.SIGTERM, sigtermhandler, signal.SIGTERM, None) gevent.signal(signal.SIGINT, sigtermhandler, signal.SIGINT, None) else: signal.signal(signal.SIGTERM, sigtermhandler) signal.signal(signal.SIGINT, sigtermhandler) self.log("Started") # Write pidfile atexit.register( self.delpid) # Make sure pid file is removed if we quit pid = str(os.getpid()) open(self.pidfile, 'w+').write("%s\n" % pid) def delpid(self): try: # the process may fork itself again pid = int(open(self.pidfile, 'r').read().strip()) if pid == os.getpid(): os.remove(self.pidfile) except OSError as e: if e.errno == errno.ENOENT: pass else: raise def start(self, *args, **kwargs): """ Start the daemon """ self.log("Starting...") # Check for a pidfile to see if the daemon already runs try: pf = open(self.pidfile, 'r') pid = int(pf.read().strip()) pf.close() except IOError: pid = None except SystemExit: pid = None if pid: message = "pidfile %s already exists. Is it already running?\n" sys.stderr.write(message % self.pidfile) sys.exit(1) # Start the daemon self.daemonize() self.run(*args, **kwargs) def stop(self): """ Stop the daemon """ if self.verbose >= 1: self.log("Stopping...") # Get the pid from the pidfile pid = self.get_pid() if not pid: message = "pidfile %s does not exist. Not running?\n" sys.stderr.write(message % self.pidfile) # Just to be sure. A ValueError might occur if the PID file is # empty but does actually exist if os.path.exists(self.pidfile): os.remove(self.pidfile) sys.exit(0) # Not an error in a restart # Try killing the daemon process try: i = 0 while 1: os.kill(pid, signal.SIGTERM) time.sleep(0.1) i = i + 1 if i % 10 == 0: os.kill(pid, signal.SIGHUP) except OSError as err: if err.errno == errno.ESRCH: if os.path.exists(self.pidfile): os.remove(self.pidfile) else: print(str(err)) sys.exit(1) self.log("Stopped") def restart(self): """ Restart the daemon """ self.stop() self.start() def get_pid(self): try: pf = open(self.pidfile, 'r') pid = int(pf.read().strip()) pf.close() except IOError: pid = None except SystemExit: pid = None return pid def is_running(self): pid = self.get_pid() if pid is None: self.log('Process is stopped') return False elif os.path.exists('/proc/%d' % pid): self.log('Process (pid %d) is running...' % pid) return True else: self.log('Process (pid %d) is killed' % pid) return False def run(self): """ You should override this method when you subclass Daemon. It will be called after the process has been daemonized by start() or restart(). """ raise NotImplementedError