A simple unix/linux daemon in Python

by Sander Marechal

I've written a simple Python class for creating daemons on unix/linux systems. It was pieced together for various other examples, mostly corrections to various Python Cookbook articles and a couple of examples posted to the Python mailing lists. It has support for a pidfile to keep track of the process. I hope it's useful to someone.

Below is the Daemon class. To use it, simply subclass it and implement the run() method. Download this file.

Update 2009-05-31: An anonymous contributor has written a version of the Daemon class suitable for Python 3.x. Download the Python 3.x version here. The code below is for Python 2.x

  1. #!/usr/bin/env python
  2.  
  3. import sys, os, time, atexit
  4. from signal import SIGTERM
  5.  
  6. class Daemon:
  7.         """
  8.         A generic daemon class.
  9.        
  10.         Usage: subclass the Daemon class and override the run() method
  11.         """
  12.         def __init__(self, pidfile, stdin='/dev/null', stdout='/dev/null', stderr='/dev/null'):
  13.                 self.stdin = stdin
  14.                 self.stdout = stdout
  15.                 self.stderr = stderr
  16.                 self.pidfile = pidfile
  17.        
  18.         def daemonize(self):
  19.                 """
  20.                 do the UNIX double-fork magic, see Stevens' "Advanced
  21.                 Programming in the UNIX Environment" for details (ISBN 0201563177)
  22.                 http://www.erlenstar.demon.co.uk/unix/faq_2.html#SEC16
  23.                 """
  24.                 try:
  25.                         pid = os.fork()
  26.                         if pid > 0:
  27.                                 # exit first parent
  28.                                 sys.exit(0)
  29.                 except OSError, e:
  30.                         sys.stderr.write("fork #1 failed: %d (%s)\n" % (e.errno, e.strerror))
  31.                         sys.exit(1)
  32.        
  33.                 # decouple from parent environment
  34.                 os.chdir("/")
  35.                 os.setsid()
  36.                 os.umask(0)
  37.        
  38.                 # do second fork
  39.                 try:
  40.                         pid = os.fork()
  41.                         if pid > 0:
  42.                                 # exit from second parent
  43.                                 sys.exit(0)
  44.                 except OSError, e:
  45.                         sys.stderr.write("fork #2 failed: %d (%s)\n" % (e.errno, e.strerror))
  46.                         sys.exit(1)
  47.        
  48.                 # redirect standard file descriptors
  49.                 sys.stdout.flush()
  50.                 sys.stderr.flush()
  51.                 si = file(self.stdin, 'r')
  52.                 so = file(self.stdout, 'a+')
  53.                 se = file(self.stderr, 'a+', 0)
  54.                 os.dup2(si.fileno(), sys.stdin.fileno())
  55.                 os.dup2(so.fileno(), sys.stdout.fileno())
  56.                 os.dup2(se.fileno(), sys.stderr.fileno())
  57.        
  58.                 # write pidfile
  59.                 atexit.register(self.delpid)
  60.                 pid = str(os.getpid())
  61.                 file(self.pidfile,'w+').write("%s\n" % pid)
  62.        
  63.         def delpid(self):
  64.                 os.remove(self.pidfile)
  65.  
  66.         def start(self):
  67.                 """
  68.                 Start the daemon
  69.                 """
  70.                 # Check for a pidfile to see if the daemon already runs
  71.                 try:
  72.                         pf = file(self.pidfile,'r')
  73.                         pid = int(pf.read().strip())
  74.                         pf.close()
  75.                 except IOError:
  76.                         pid = None
  77.        
  78.                 if pid:
  79.                         message = "pidfile %s already exist. Daemon already running?\n"
  80.                         sys.stderr.write(message % self.pidfile)
  81.                         sys.exit(1)
  82.                
  83.                 # Start the daemon
  84.                 self.daemonize()
  85.                 self.run()
  86.  
  87.         def stop(self):
  88.                 """
  89.                 Stop the daemon
  90.                 """
  91.                 # Get the pid from the pidfile
  92.                 try:
  93.                         pf = file(self.pidfile,'r')
  94.                         pid = int(pf.read().strip())
  95.                         pf.close()
  96.                 except IOError:
  97.                         pid = None
  98.        
  99.                 if not pid:
  100.                         message = "pidfile %s does not exist. Daemon not running?\n"
  101.                         sys.stderr.write(message % self.pidfile)
  102.                         return # not an error in a restart
  103.  
  104.                 # Try killing the daemon process       
  105.                 try:
  106.                         while 1:
  107.                                 os.kill(pid, SIGTERM)
  108.                                 time.sleep(0.1)
  109.                 except OSError, err:
  110.                         err = str(err)
  111.                         if err.find("No such process") > 0:
  112.                                 if os.path.exists(self.pidfile:
  113.                                         os.remove(self.pidfile)
  114.                         else:
  115.                                 print str(err)
  116.                                 sys.exit(1)
  117.  
  118.         def restart(self):
  119.                 """
  120.                 Restart the daemon
  121.                 """
  122.                 self.stop()
  123.                 self.start()
  124.  
  125.         def run(self):
  126.                 """
  127.                 You should override this method when you subclass Daemon. It will be called after the process has been
  128.                 daemonized by start() or restart().
  129.                 """

And here is an example implementation. It implements the daemon as well as it's controlling client. Simply invoke this script with start, stop or restart as it's first argument. Download this file.

  1. #!/usr/bin/env python
  2.  
  3. import sys, time
  4. from daemon import Daemon
  5.  
  6. class MyDaemon(Daemon):
  7.         def run(self):
  8.                 while True:
  9.                         time.sleep(1)
  10.  
  11. if __name__ == "__main__":
  12.         daemon = MyDaemon('/tmp/daemon-example.pid')
  13.         if len(sys.argv) == 2:
  14.                 if 'start' == sys.argv[1]:
  15.                         daemon.start()
  16.                 elif 'stop' == sys.argv[1]:
  17.                         daemon.stop()
  18.                 elif 'restart' == sys.argv[1]:
  19.                         daemon.restart()
  20.                 else:
  21.                         print "Unknown command"
  22.                         sys.exit(2)
  23.                 sys.exit(0)
  24.         else:
  25.                 print "usage: %s start|stop|restart" % sys.argv[0]
  26.                 sys.exit(2)

That's it! I hope this is of some use to someone. Happy coding!

Creative Commons Attribution-ShareAlike

Comments

#1 Guido van Steen

Wonderful python code! Thanks very much!

#2 coolluck (http://tech.coolluck.org/)

Thank you for your daemon code
I think it'll be better add checking pidfile existance
because atexit handler may delete pidfile before execute os.remove(self.pidfile) in exception Handler "except OSError, err"

...
except OSError, err:
err = str(err)
if err.find("No such process") > 0:
if os.path.exists(self.pidfile):
os.remove(self.pidfile)
...

#3 Sander Marechal (http://www.jejik.com)

Thank you for your suggestion. I have updated the daemon code accordingly.

#4 narthollis

Thanks very much - highly useful bit of code!

And far more elegant than my previous solution.

Many Thanks :)

#5 Anonymous Coward

What is the license on this code? Can I have it under GPL?

#6 Sander Marechal (http://www.jejik.com)

@Anonymous Coward

This is really basic stuff and not very original. It's Public Domain, so do with it as you please.

#7 Jacob Singh (http://pajamadesign.com)

Thanks for the code,

I've got it working, but I have one problem. I'm trying to do some stuff before exiting. So, in my code:

signal.signal(signal.SIGTEM,myhandler)
def myhandler(signum,frame):
#do something
sys.exit()

I assume that my sys.exit is calling itself by raising a SIGTERM somehow which is really annoying. How can I stop this?

Thanks!
Jacob

#8 Sander Marechal (http://www.jejik.com)

@Jacob: Use the atexit function instead. If you look at line 59 you see that I am registering the self.delpid() function to be run when the daemon shuts down. That function will be run by automatically by Python when the program stops.

So, add the stuff you want to do before shutdown to the self.delpid() function.

#9 Anonymous Coward

excellent, thx. i'm running on os x and only got things to work by commenting out this section:
# 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())

any idea?
thx

#10 Sander Marechal (http://www.jejik.com)

Nope, none. The section you commented out should work on MacOSX as well as Linux because both are basically Unix-like POSIX systems. I'm not nearly familiar enough with MacOSX to be able to tell why it doesn't work. Sorry.

#11 Anonymous Coward

k. seems that /dev/null isn't working, at least for my 2.52, os 10.5x config. however, py's os.devnull works like a charm.

#12 Sander Marechal (http://www.jejik.com)

There's no /dev/null on a Mac machine? MacOSX 10.5 claims to be POSIX compliant, and I believe that having /dev/null is required by POSIX.

Can you tell me what os.path.devnull is set to in Python on OSX?

#13 Preston Hunt (http://prestonhunt.com)

A beautiful piece of code, thank you for sharing!

#14 Guilherme Gall

Nice code, very useful. I think it would be useful the creation of a method to show the status of the process/pidfile to allow the identification of a manual kill.

Something like that:

def status(self):
try:
pf = file(self.pidfile, 'r')
pid = int(pf.read().strip())
pf.close()
except IOError:
pid = None

try:
procfile = file("/proc/%d/status" % pid, 'r')
procfile.close()
except IOError:
sys.stdout.write("there is not a process with the PID specified in %s\n" % self.pidfile)
sys.exit(0)
except TypeError:
sys.stdout.write("pidfile %s does not exist\n" % self.pidfile)
sys.exit(0)

sys.stdout.write("the process with the PID %d is running\n" % pid)

#15 Jonathan Manning

For the /dev/null issue, on OS X or Windows, try:

if (hasattr(os, "devnull")):
DEVNULL = os.devnull
else:
DEVNULL = "/dev/null"

Then use DEVNULL instead of the string "/dev/null" in the default arguments.
~J

#16 HosipLan (http://kdyby.org)

Thank you i'm going to use this for my little robot and it helped a lot

#17 Raj (http://rajorshi.net/blog)

Very useful, thanks a lot :)

#18 Anonymous Coward

Hi. Great code, however i have a problem writing to a log file in my run function :

def run(self):
while True:
print "test1\n"
time.sleep(1)

This does not log anything in the log file specified in the constructor of Daemon. If I remove the sleep(1) call, it works. I was thinking of a buffering issue but removing the buffering to stdout in daemon.py did not change anything.

Any idea ?

#19 Sander Marechal (http://www.jejik.com)

You may want to flush stdout before you sleep() using self.stdout.flush().

A far better alternative however is to use the "logging" module instead.

#20 Anonymous Coward

Thanks - sys.stdout.flush() works. self.stdout.flush() does not.
I tried replacing the print statement with logging.info("Test"). This has the same buffer issue as print statement. Can you explain why you redirect in your code stdout if it cannot be used reliably for output ? For example, when the daemon is stopped, there is no flush or close in the stop function and information that was sent to the log file is lost.

#21 Sander Marechal (http://www.jejik.com)

When you use the logging module you should log to a file, not to stdout. A true daemon has no environment. No parent process, no working directory and no stdin, stdout and stderr. That's why I redirect everything to /dev/null and that's why you need to fork() twice.

Using stdout and flush() can sorta work, but you don't have a real daemon anymore. Consider this. Open a terminal, start your program and then close the terminal. What happens to stdout now? It's gone. That's why it needs to be redirected to /dev/null. And that's why all daemons use logfiles or log to the syslog.

#22 Vik

Excellent stuff. Thanks. I have just started learning python & this has helped me a lot.
I have a question, I need to start some scripts and stop them when I want and would also like to know their status, I have attached to this daemon, but then when I am starting the daemon, in ps -aux only the daemon is shown, not that those scripts have started. Or maybe I am going in the wrong way... Can someone help me?

#23 Sander Marechal (http://www.jejik.com)

I m not quite sure what you mean Vik. Is your source code online somewhere? If not, you can post it here (use the pre tags) or post it to a service like pastebin.com.

#24 Roel

I'd like to be able to ask the status of my daemon by issuing 'daemon status'. So I implemented the status method in my subclass of Daemon and added the necessary lines to the starter script. However, I do not get the desired result as the starter script creates a new instance and then call the .status() from that instance instead of querying the status from the running instance. How would you solve this issue? Thanks for your help and the code!

#25 Sander Marechal (http://www.jejik.com)

What status do you want to show? Just running or not running? Just try to read the pidfile and see if that process still exists. For anything above and beyond that you need a way to communicate between the base script and the running instance. So I suggest you take a look at the python library under the inter-process communication section.

#26 Andr

Thank you it works perfect when the class is in the module I use it from...

When I extract this class to a submodule it does not work. It seems that the wrong process is terminated after the first fork...

Here is my modulcode... Help would really be appriciated...
#!/usr/bin/env python

try:
    import os
    import sys
    import time
    import atexit
    from signal import SIGTERM
except StandardError, e:
    import sys
    print "Error while loading libraries: "
    print e
    sys.exit()

class HcpDaemon:
    """
    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):
        try:
            pid = os.fork()
            if pid > 0:
                # exit first parent
                sys.exit(0)
        except OSError, e:
            print >>sys.stderr, "fork #1 failed: %d (%s)" % (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, print eventual PID before
                print "Daemon PID %d" % pid
                sys.exit(0)
        except OSError, e:
            print >>sys.stderr, "fork #2 failed: %d (%s)" % (e.errno, e.strerror)
            sys.exit(1)

        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())

        pid = str(os.getpid())
        file(self.pidfile,'w+').write("%s\n" % pid)

        atexit.register(self.delpid)

    def delpid(self):
        os.remove(self.pidfile)

    def stop(self):
        try:
            pf = file(self.pidfile,'r')
            pid = int(pf.read().strip())
            pf.close()
        except IOError:
            pid = None

        if not pid:
            message = "pidfile %s does not exist. Daemon not running?\n"
            sys.stderr.write(message % self.pidfile)
            return # not an error in a restart

        try:
            while 1:
                os.kill(pid, SIGTERM)
                time.sleep(0.1)
        except OSError, err:
            err = str(err)
            if err.find("No such process") > 0:
                if os.path.exists(self.pidfile):
                    os.remove(self.pidfile)
            else:
                print str(err)
                sys.exit(1)

    def restart(self):
        self.stop()
        self.start()

    def start(self):
        try:
            pf = file(self.pidfile,'r')
            pid = int(pf.read().strip())
            pf.close()
        except IOError:
            pid = None

        if pid:
            message = "pidfile %s already exist. Daemon already running?\n"
            sys.stderr.write(message % self.pidfile)
            sys.exit(1)
        self.daemonize()
        self.run()

    def run(self):
        """Override while subclassing"""

#27 Andr

Nevermind

just stupid me forget to import a module required by the overriding run method...

#28 Sander Marechal (http://www.jejik.com)

I wrote a little workaround to help me debug issues like that. I added another option besides start, stop and restart called "foreground". When I use that, it calls daemon.run() instead of daemon.start(). That means the application will not fork to the background but run in the foreground instead. That means you can easily spot errors like this because exceptions get printed to the commandline instead of to a logfile or pipe. It's really useful for debugging :-)

#29 Andre

Thank you, sounds like a good idea ;-)

#30 Brian

Shouldn't your example MyDaemon provide an __init__ routine and call the parent class's __init__?

#31 Robert

Thank you for this code! I'm using it for a python based weather station logger.

#32 Sander Marechal (http://www.jejik.com)

@Brian: No. Because MyDaemon does not implement __init__, the parent's __init__ will be called automatically.

@Robert: That sounds like a great project. I always like hearing where my code ends up :-)

#33 Brian

Ah, yes. I'm still new to Python.

Thanks for the very useful code! I'm using it to implement an IRC bot. Works great.

#34 Vik

I am using a similar daemon and that daemon for me does call quite a lot of functions for me from various scripts. I also have a log file for errors and warnings. After the daemon is up for certain amount of time, I am getting the following error:

close failed: [Errno 10] No child processes

I am not sure as where I am going wrong, when I looked up on google to find out that this might be a bug in python:

http://mail.python.org/pipermail/python-bugs-list/2007-August/039194.html

I wanna keep my logs and get rid of this error, the problem is the error is continuously writing to a file and that way in a certain period of time my hard drive will be full with just one text file.

Please let me know if anyone has ever faced this error. Any help is appreciated.

Thanks
Vik

#35 Anonymous Coward

Very nice code. Here is an updated version for python 3.x series:


"""Generic linux daemon base class for python 3.x."""

import sys, os, time, atexit, signal

class daemon:
"""A generic daemon class.

Usage: subclass the daemon class and override the run() method."""

def __init__(self, pidfile): self.pidfile = pidfile

def daemonize(self):
"""Deamonize class. UNIX double fork mechanism."""

try:
pid = os.fork()
if pid > 0:
# exit first parent
sys.exit(0)
except OSError as err:
sys.stderr.write('fork #1 failed: {0}\n'.format(err))
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 as err:
sys.stderr.write('fork #2 failed: {0}\n'.format(err))
sys.exit(1)

# redirect standard file descriptors
sys.stdout.flush()
sys.stderr.flush()
si = open(os.devnull, 'r')
so = open(os.devnull, 'a+')
se = open(os.devnull, 'a+')
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())
with open(self.pidfile,'w+') as f:
f.write(pid + '\n')

def delpid(self):
os.remove(self.pidfile)

def start(self):
"""Start the daemon."""

# Check for a pidfile to see if the daemon already runs
try:
with open(self.pidfile,'r') as pf:
pid = int(pf.read().strip())
except IOError:
pid = None

if pid:
message = "pidfile {0} already exist. " + \
"Daemon already running?\n"
sys.stderr.write(message.format(self.pidfile))
sys.exit(1)

# Start the daemon
self.daemonize()
self.run()

def stop(self):
"""Stop the daemon."""

# Get the pid from the pidfile
try:
with open(self.pidfile,'r') as pf:
pid = int(pf.read().strip())
except IOError:
pid = None

if not pid:
message = "pidfile {0} does not exist. " + \
"Daemon not running?\n"
sys.stderr.write(message.format(self.pidfile))
return # not an error in a restart

# Try killing the daemon process
try:
while 1:
os.kill(pid, signal.SIGTERM)
time.sleep(0.1)
except OSError as err:
e = str(err.args)
if e.find("No such process") > 0:
if os.path.exists(self.pidfile):
os.remove(self.pidfile)
else:
print (str(err.args))
sys.exit(1)

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()."""

#36 Sander Marechal (http://www.jejik.com)

Vik, I don't think that error has anything to do with the daemon code. According to the post you linked, this is about threading and subprocesses.

I suggest you look at the backtraces for your error and see where they originate. Then simply catch the OSError and act appropriately (i.e. assume the thread has died, clean up and restart it for example).

#37 Sander Marechal (http://www.jejik.com)

@Anonymous: Thanks for the Python 3.x code. I have updated the main article with a link you your implementation.

#38 Vik

hey Sander, Thanks for your help. I shall try that. Might need your help again in case.

#39 Sander Marechal (http://www.jejik.com)

Oops! There were two comments posted here that I accidentally removed as spam (the spam/ham buttons are perhaps a tad too close together on my admin panel). Anyway, I will try to answer them from memory.

The first poster had a problem implementing his own daemon. He was implementing his code after the run() function. The answer is that your code should replace the run() function. That is what overriding a function means. You create your own function that replaces the original. You should make sure that the run() function never exits. That is why the example code has an infinite loop in it. If the run() function ever exits then the daemon stops.

The second poster had a question about the pidfile. IIRC he had a problem under Python 2.4 that the PID of the first fork was written to the pidfile and not the PID from the second fork, the one that keeps running. If I get this wrong, please re-post and I promise I will press the "ham" button this time :-)

Anyway, I have no idea what would cause this. I have tested this daemon code in 2.4, 2.5 and 2.6 and it works fine for me. The code that reads the pid and created the pidfile happens after the second fork. The original process and the first fork should already be dead by the time the pidfile is written.

It may be possible that there is a bug in the 2.4 version of os.getpid() but that is pure speculation. I know of no such bug, but one hypothetical scenario is that the second fork reaches os.getpid() before the first fork has exited completely (remember, forks run in parrallel). You could try a simple time.sleep(1) after the second fork but before calling the os.getpid() function. That sleep will make sure that the first fork is dead by the time the pidfile is created.

#40 Justin

Second poster here.

The issue I was wondering about is that the pid of the daemon is the same as the second parent's pid--that's what's actually running, and that's what's in pidfile. To see what I'm seeing, add 'print pid' after each occurrence of 'if pid > 0:' in daemonize(), just before 'sys.exit()'. If I'm understanding correctly, those should be the pids of the first and second parents, while the second child's pid is obtained further on with a call to 'os.getpid()'. In my environment, python 2.4 on CentOS 5.3, the second child's pid is the same as the second parent's pid.

It's not really a problem, but I'm obviously not getting something here. I thought the process went like this: first parent->fork->first child/second parent->fork->second child. So there should be three pids in all.

The purpose of the second fork is to make sure that the daemon isn't the process group leader of the new session, but if the second fork isn't taking a new pid, then it's still the process group leader, yes?

#41 Sander Marechal (http://www.jejik.com)

Ah, I see now. Your assumptions about pid are not correct.

The fork() command returns 0 in the child and returns the child's pid in the parent. So, the value of pid that you are printing just after 'if pid > 0:' is the pid of the child, not the pid of the parent. You are printing the pid of the first child and second child, not of the parent and first child.

You can see that by leaving those print statements in and adding a 'print os.getpid()' all the way at the beginning of the daemonize() method, before the first fork. Now you should see three different pid's printed. The original parent, the first child and the second child.

#42 Seth

Refined on the Python 3 version a bit. The original version has the attitude of creating a new daemon instance on every call (on stop calls too), which might have some unwanted side effects. I separated the daemon and control logic in different classes:

daemon.py:
"""\
Daemon base and control class.

This file contains the daemon base class for a UNIX daemon and the control
class for it. See test logic at the end of file and test_daemon.py to
understand how to use it.\
"""

import sys, os, time, atexit, signal

# -- generic daemon base class ------------------------------------------ #

class daemon_base:
	"""A generic daemon base class.
	
	Usage: subclass this class and override the run() method.
	"""
	def __init__(self, pidfile, workpath='/'):
		"""Constructor.

		We need the pidfile for the atexit cleanup method.
		The workpath is the path the daemon will operate
		in. Normally this is the root directory, but can be some
		data directory too, just make sure it exists.
		"""
		self.pidfile = pidfile
		self.workpath = workpath
	
	def perror(self, msg, err):
		"""Print error message and exit. (helper method)
		"""
		msg = msg + '\n'
		sys.stderr.write(msg.format(err))
		sys.exit(1)

	def daemonize(self):
		"""Deamonize calss process. (UNIX double fork mechanism).
		"""
		if not os.path.isdir(self.workpath):
			self.perror('workpath does not exist!', '')

		try: # exit first parent process
			pid = os.fork() 
			if pid > 0: sys.exit(0) 
		except OSError as err:
			self.perror('fork #1 failed: {0}', err)
	
		# decouple from parent environment
		try: os.chdir(self.workpath)
		except OSError as err:
			self.perror('path change failed: {0}', err)

		os.setsid() 
		os.umask(0) 
	
		try: # exit from second parent
			pid = os.fork() 
			if pid > 0: sys.exit(0) 
		except OSError as err:
			self.perror('fork #2 failed: {0}', err)
	
		# redirect standard file descriptors
		sys.stdout.flush()
		sys.stderr.flush()
		si = open(os.devnull, 'r')
		so = open(os.devnull, 'a+')
		se = open(os.devnull, 'a+')
		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(os.remove, self.pidfile)
		pid = str(os.getpid())
		with open(self.pidfile,'w+') as f:
			f.write(pid + '\n')
		self.run()
	
	def run(self):
		"""Worker method.
		
		It will be called after the process has been daemonized
		by start() or restart(). You'll have to overwrite this
		method with the daemon program logic.
		"""
		while True:
			time.sleep(1)

# -- daemon control class ----------------------------------------------- #

class daemon_ctl:
	"""Control class for a daemon.

	Usage:
	>>>	dc = daemon_ctl(daemon_base, '/tmp/foo.pid')
	>>>	dc.start()

	This class is the control wrapper for the above (daemon_base)
	class. It adds start/stop/restart functionality for it withouth
	creating a new daemon every time.
	"""
	def __init__(self, daemon, pidfile, workdir='/'):
		"""Constructor.

		@param daemon: daemon class (not instance)
		@param pidfile: daemon pid file
		@param workdir: daemon working directory
		"""
		self.daemon = daemon
		self.pidfile = pidfile
		self.workdir = workdir
	
	def start(self):
		"""Start the daemon.
		"""
		try: # check for pidfile to see if the daemon already runs
			with open(self.pidfile, 'r') as pf:
				pid = int(pf.read().strip())
		except IOError: pid = None
	
		if pid:
			message = "pidfile {0} already exist. " + \
					"Daemon already running?\n"
			sys.stderr.write(message.format(self.pidfile))
			sys.exit(1)
		
		# Start the daemon
		d = self.daemon(self.pidfile, self.workdir)
		d.daemonize()

	def stop(self):
		"""Stop the daemon.

		This is purely based on the pidfile / process control
		and does not reference the daemon class directly.
		"""
		try: # get the pid from the pidfile
			with open(self.pidfile,'r') as pf:
				pid = int(pf.read().strip())
		except IOError: pid = None
	
		if not pid:
			message = "pidfile {0} does not exist. " + \
					"Daemon not running?\n"
			sys.stderr.write(message.format(self.pidfile))
			return # not an error in a restart

		try: # try killing the daemon process	
			while 1:
				os.kill(pid, signal.SIGTERM)
				time.sleep(0.1)
		except OSError as err:
			e = str(err.args)
			if e.find("No such process") > 0:
				if os.path.exists(self.pidfile):
					os.remove(self.pidfile)
			else:
				print (str(err.args))
				sys.exit(1)

	def restart(self):
		"""Restart the daemon.
		"""
		self.stop()
		self.start()

# -- test logic --------------------------------------------------------- #

if __name__ == '__main__':
	"""Daemon test logic.

	This logic must be called as seperate executable (i.e. python3
	daemon.py start/stop/restart).	See test_daemon.py for
	implementation.
	"""
	usage = 'Missing parameter, usage of test logic:\n' + \
			' % python3 daemon.py start|restart|stop\n'
	if len(sys.argv) < 2:
		sys.stderr.write(usage)
		sys.exit(2)

	pidfile = '/tmp/test_daemon.pid'
	dc = daemon_ctl(daemon_base, pidfile)

	if sys.argv[1] == 'start':
		dc.start()
	elif sys.argv[1] == 'stop':
		dc.stop()
	elif sys.argv[1] == 'restart':
		dc.restart()


I also wrote an improvised test file for this file (still ugly, but does the job), test_daemon.py:
"""\
Base daemon test file.

This file tests the base daemon (runs it's test logic). The daemon is
started, then stopped, then started then restarted then stopped again.

The output of this file should look like this (with different pids):

starting daemon
listing processes
15554 ?        S      0:00 python3 daemon.py start
-rw-rw-rw- 1 seth seth 6 2009-06-13 14:55 /tmp/test_daemon.pid
stopping daemon
listing processes
ls: cannot access /tmp/test_daemon.pid: No such file or directory
starting daemon
listing processes
15572 ?        S      0:00 python3 daemon.py start
-rw-rw-rw- 1 seth seth 6 2009-06-13 14:55 /tmp/test_daemon.pid
restarting daemon
listing processes
15582 ?        S      0:00 python3 daemon.py restart
-rw-rw-rw- 1 seth seth 6 2009-06-13 14:55 /tmp/test_daemon.pid
stopping daemon
listing processes
ls: cannot access /tmp/test_daemon.pid: No such file or directory
"""

import os, time

if __name__ == '__main__':
	start_cmd = 'python3 daemon.py start'
	stop_cmd = 'python3 daemon.py stop'
	restart_cmd = 'python3 daemon.py restart'

	os.chdir(os.path.abspath(os.path.dirname(__file__)))
	
	print('starting daemon')
	os.system(start_cmd)
	time.sleep(0.2)
	print('listing processes')
	os.system('ps x | grep "python3 daemon.py" | grep -v grep')
	os.system('ls -l /tmp/test_daemon.pid')

	print('stopping daemon')
	os.system(stop_cmd)
	time.sleep(0.2)
	print('listing processes')
	os.system('ps x | grep "python3 daemon.py" | grep -v grep')
	os.system('ls -l /tmp/test_daemon.pid')
	
	print('starting daemon')
	os.system(start_cmd)
	time.sleep(0.2)
	print('listing processes')
	os.system('ps x | grep "python3 daemon.py" | grep -v grep')
	os.system('ls -l /tmp/test_daemon.pid')
	print('restarting daemon')
	os.system(restart_cmd)
	time.sleep(0.2)
	print('listing processes')
	os.system('ps x | grep "python3 daemon.py" | grep -v grep')
	os.system('ls -l /tmp/test_daemon.pid')
	print('stopping daemon')
	os.system(stop_cmd)
	time.sleep(0.2)
	print('listing processes')
	os.system('ps x | grep "python3 daemon.py" | grep -v grep')
	os.system('ls -l /tmp/test_daemon.pid')
Post a new comment

Registration is not required to post comments, but it is encouraged if you are a regular visitor. One of the advantages of registration is that you can edit your comments later on (editing not yet implemented). You can register or login here.




Your e-mail address will not be published, but your website URL will. All links that you post will tagged rel="nofollow" to throw off spammers. You are allowed to use the following XHTML tags in your comment: <em> <strong> <u> <b> <i> <strike> <blockquote> <big> <small> <ul> <ol> <li> <a href=""> <pre> <code>. Please allow up to 60 second processing time after you post a comment. Our spam filters may take some time.