Recipe9.13.Forking a Daemon Process on Unix


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_2.html#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_2.html#SEC16.



Python Cookbook
Python Cookbook
ISBN: 0596007973
EAN: 2147483647
Year: 2004
Pages: 420

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net