Recipe 9.13. Forking a Daemon Process on Unix
Credit: Jürgen Hermann, Andy Gimblett, Josh
Hoyt, Noah Spurrier, Jonathan Bartlett, Greg Stein
Problem
You need to fork a daemon process on a Unix
or Unix-like system, which, in turn, requires a certain precise
sequence of system calls.
Solution
Unix daemon processes must detach from their controlling terminal and
process group. Doing so is not hard, but it does require some care,
so it's worth writing a
daemonize.py module once and for all:
import sys, os
''' Module to fork the current process as a daemon.
NOTE: don't do any of this if your daemon gets started by inetd! inetd
does all you need, including redirecting standard file descriptors;
the chdir( ) and umask( ) steps are the only ones you may still want.
'''
def daemonize (stdin='/dev/null',
stdout='/dev/null', stderr='/dev/null'):
''' Fork the current process as a daemon, redirecting standard file
descriptors (by default, redirects them to /dev/null).
'''
# Perform first fork.
try:
pid = os.fork( )
if pid > 0:
sys.exit(0) # Exit first parent.
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.umask(0)
os.setsid( )
# Perform second fork.
try:
pid = os.fork( )
if pid > 0:
sys.exit(0) # Exit second parent.
except OSError, e:
sys.stderr.write("fork #2 failed: (%d) %s\n" % (e.errno, e.strerror))
sys.exit(1)
# The process is now daemonized, redirect standard file descriptors.
for f in sys.stdout, sys.stderr: f.flush( )
si = file(stdin, 'r')
so = file(stdout, 'a+')
se = file(stderr, 'a+', 0)
os.dup2(si.fileno( ), sys.stdin.fileno( ))
os.dup2(so.fileno( ), sys.stdout.fileno( ))
os.dup2(se.fileno( ), sys.stderr.fileno( ))
def _example_main ( ):
''' Example main function: print a count & timestamp each second '''
import time
sys.stdout.write('Daemon started with pid %d\n' % os.getpid( ) )
sys.stdout.write('Daemon stdout output\n')
sys.stderr.write('Daemon stderr output\n')
c = 0
while True:
sys.stdout.write('%d: %s\n' % (c, time.ctime( )))
sys.stdout.flush( )
c = c + 1
time.sleep(1)
if _ _name_ _ == "_ _main_ _":
daemonize('/dev/null','/tmp/daemon.log','/tmp/daemon.log')
_example_main( )
Discussion
Forking a daemon on Unix requires a certain specific sequence of
system calls, which is explained in W. Richard
Stevens' seminal book, Advanced
Programming in the Unix Environment (Addison-Wesley). We
need to fork twice, terminating each parent
process and letting only the grandchild of the original process run
the daemon's code. This allows us to decouple the
daemon process from the calling terminal, so that the daemon process
can keep running (typically as a server process without further user
interaction, like a web server) even after the calling terminal is
closed. The only visible effect of doing so is that when your script
runs this module's daemonize
function, you get your shell prompt back immediately.For all of the details about how and why this works in Unix and
Unix-like systems, see Stevens' wonderful book.
Another important source of information on both practical and
theoretical issues about "daemon
forking" can be found as part of the Unix
Programming FAQ, at http://www.erlenstar.demon.co.uk/unix/faq_2l#SEC16.To summarize: the first fork lets the shell
return, and also lets you do a setsid (to remove
you from your controlling terminal, so you can't
accidentally be sent a signal). However, setsid
makes this process a "session
leader", which means that if the process ever opens
any terminal, it will become the process'
controlling terminal. We do not want a daemon to have
any controlling terminal, which is why we fork
again. After the second fork, the process is no
longer a "session leader", so it
can open any file (including a terminal) without thereby accidentally
reacquiring a controlling terminal.Both Stevens and the Unix Programming FAQ provide examples in the C
programming language, but since the Python Standard Library exposes a
full POSIX interface, you can also do it all in Python. Typical C
code for a daemon fork translates almost literally to Python; the
only difference you have to care abouta minor detailis
that Python's os.fork does not
return -1 on errors, but rather throws an OSError
exception. Therefore, rather than testing for a less-than-zero return
code from fork, as we would in C, we run the
fork in the try clause of a
TRy/except statement, so that
we can catch the exception, should it happen, and print appropriate
diagnostics to standard
error.
See Also
Documentation of the standard library
module os in the Library
Reference and Python in a
Nutshell; Unix manpages for the fork,
umask, and setsid system calls;
W.Richard Stevens, Advanced Programming in the Unix
Environment (Addison-Wesley); also, the Unix Programming
FAQ on daemon forking, at http://www.erlenstar.demon.co.uk/unix/faq_2l#SEC16.