Skip to content

Instantly share code, notes, and snippets.

@jmhobbs
Created April 13, 2011 05:09
Show Gist options
  • Save jmhobbs/916994 to your computer and use it in GitHub Desktop.
Save jmhobbs/916994 to your computer and use it in GitHub Desktop.
Cooked version of Python daemon base script.
#!/usr/bin/env python
# Origin: http://www.jejik.com/articles/2007/02/a_simple_unix_linux_daemon_in_python/
# Cooked up a bit by @jmhobbs
import sys, os, time, errno, atexit
from signal import SIGTERM
class Daemon:
"""
A generic daemon class.
Usage: subclass the Daemon class and override the run() method
"""
def __init__(self, pidfile, stdin='/dev/null', stdout='/dev/null', stderr='/dev/null'):
self.stdin = stdin
self.stdout = stdout
self.stderr = stderr
self.pidfile = pidfile
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
"""
try:
pid = os.fork()
if pid > 0:
# exit first parent
sys.exit(0)
except OSError, e:
sys.stderr.write("fork #1 failed: %d (%s)\n" % (e.errno, e.strerror))
sys.exit(1)
# decouple from parent environment
os.chdir("/")
os.setsid()
os.umask(0)
# do second fork
try:
pid = os.fork()
if pid > 0:
# exit from second parent
sys.exit(0)
except OSError, e:
sys.stderr.write("fork #2 failed: %d (%s)\n" % (e.errno, e.strerror))
sys.exit(1)
# redirect standard file descriptors
sys.stdout.flush()
sys.stderr.flush()
si = file(self.stdin, 'r')
so = file(self.stdout, 'a+')
se = file(self.stderr, 'a+', 0)
os.dup2(si.fileno(), sys.stdin.fileno())
os.dup2(so.fileno(), sys.stdout.fileno())
os.dup2(se.fileno(), sys.stderr.fileno())
# write pidfile
atexit.register(self.delpid)
pid = str(os.getpid())
file(self.pidfile,'w+').write("%s\n" % pid)
def delpid(self):
os.remove(self.pidfile)
def start(self):
"""
Start the daemon
"""
if self._status():
sys.stderr.write("Daemon already running?\n")
sys.exit(1)
print "Daemon starting."
# Start the daemon
self.daemonize()
self.run()
def stop(self):
"""
Stop the daemon
"""
pid = None
try:
pid = self._status()
if not pid:
sys.stderr.write("Daemon not running?\n")
return # not an error in a restart
except Exception, e:
print "Error:", e
sys.exit(1)
# Try killing the daemon process
try:
while 1:
os.kill(pid, SIGTERM)
time.sleep(0.1)
except OSError, err:
err = str(err)
if err.find("No such process") > 0:
print "Daemon stopped."
if os.path.exists(self.pidfile):
os.remove(self.pidfile)
else:
print str(err)
sys.exit(1)
def status(self):
if not self._status():
status = "not running"
else:
status = "running"
print "The daemon is %s." % status
def _status(self):
"""
Check if the daemon is running
"""
# Get the pid from the pidfile
try:
pf = file(self.pidfile,'r')
pid = int(pf.read().strip())
pf.close()
except IOError:
pid = None
if not pid:
return None
# See if it is alive
try:
os.kill(pid, 0)
except OSError, err:
if err.errno == errno.ESRCH:
return None
elif err.errno == errno.EPERM:
raise Exception( "Could not signal process with pid %d" % pid )
else:
raise Exception( "Unknown error" )
else:
return pid
def restart(self):
"""
Restart the daemon
"""
self.stop()
self.start()
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().
"""
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment