Toolserver:Daemons

From mediawiki.org

This page was moved from the Toolserver wiki.
Toolserver has been replaced by Toolforge. As such, the instructions here may no longer work, but may still be of historical interest.
Please help by updating examples, links, template links, etc. If a page is still relevant, move it to a normal title and leave a redirect.

In UNIX circles, a program that runs in the background is called a daemon. This page is a guide to running a program on the Toolserver as a daemon.

Setting up a daemon[edit]

The following procedure uses cron to run /sbin/start-stop-daemon at set intervals; start-stop-daemon checks if your daemon is still running, and starts a new instance if it is not.

Step 1. Write the daemon program itself. This is the program that actually does whatever it is you want to do. You have to add a small amount of initialization code so that your program will run as a daemon; see below.

Step 2. Test the command line for the start-stop-daemon program. This program does not run all the time. Its only purpose is to kick-start your program from step 1.

The basic command line is:

/sbin/start-stop-daemon -q --oknodo --start --pidfile /home/cbm/daemon/daemon.pid --name daemon.pl --startas /home/cbm/daemon/daemon.pl

where

  • /home/cbm/daemon/daemon.pid is the name of a file into which your daemon writes its pid when it starts
  • daemon.pl is the filename of the program
  • /home/cbm/daemon/daemon.pl is the command line to start the program.

For testing purposes, you will want to remove the -q option and add -v and sometimes -t as documented at man start-stop-daemon. Make sure your command line for start-stop-daemon will start the daemon. Then make sure it will not start a second instance while the first is running. Then kill the first instance and make sure your command line will start a new instance. Then remove the -v and -t options, and add the -q option.

Step 3. Add the command line from part 2 to your crontab. First run crontab -e to edit your crontab. Then, if COMMANDLINE is the command from step 2, you want to this line to the bottom of the file:

0,30 * * * * COMMANDLINE

This line tells cron that it should run /sbin/start-stop-daemon every 30 minutes. See man -s 5 crontab for details on the format of this line.

Finally, you want to double-check that your daemon is not leaving zombie processes. Run ps aux | grep daemon.pl and look at the output. If you see any "defunct" processes, like the example below, then your program has not fully disassociated itself. In that case, disable the crontab, fix the program, and then repeat step 3.

cbm@nightshade:~/daemon$ ps aux | grep daemon.pl
cbm      28651  0.2  0.0      0     0 ?        Zs   21:10   0:00 [daemon.pl] <defunct>
cbm      28706  0.0  0.0 105816  2064 ?        Ss   21:10   0:00 /usr/bin/perl /home/cbm/daemon/daemon.pl

Writing your daemon program[edit]

To initialize itself as a daemon, your program must follow these general steps:

  • Fork a child, and have the parent exit.
  • The child (now the main process) calls setsid() to become a w:process group leader. Now the original terminal closing will not kill the daemon (for example, when you log out). You can still kill the daemon with the kill command.
  • Chdir to the root directory /.
  • Close and/or reopen the standard I/O streams STDOUT, STDERR, and STDIN so that they are no longer attached to the original terminal. Failure to do this can result in zombies. You will want to make sure your own I/O only uses streams that were opened after the program became a daemon.
  • Set the umask to something appropriate.
  • For the second time, fork a child, and have the parent exit. Now the main process is the grandchild of the original process. This second fork is needed to prevent zombies in some circumstances.

Make sure your program can be run from the command line; use chmod to set its permissions so it is executable.

There are a few things you want to keep in mind with a long-running process:

  • Since you cannot check on the program from the console, some sort of logging is needed to diagnose runtime errors.
  • Don't consume excessive CPU, memory, or I/O.
  • Open files when you need them and close them as soon as you are done. Don't hog or leak filehandles.
  • If you program has an expensive state, save it regularly so that nothing is lost in a system crash.
  • Make your code robust against temporary errors such as network outages. Expect that your process may be unable to connect to external hosts for minutes at a time; retry all network requests after appropriate timeouts.
  • Use full paths to files that you open and programs that you execute.
  • Make sure the permissions are correct on files you create.

Example code[edit]

Perl[edit]

#!/usr/bin/perl

use POSIX 'setsid';

# The first step is to start a new "session" by running setsid(). 
# You must fork first.
if ( fork() ) {
  exit;
}

# Become the process group leader of a new group
setsid(); 

# Now get rid of all ties to the initiating process.
chdir('/');
open STDIN, "<", "/dev/null";
open STDOUT, ">", "/dev/null";
open STDERR, ">", "/dev/null";
umask(077); 

# Fork one more time; this is not "necessary" for most toolserver 
# daemons but it is a best practice as there are situations where
# forking twice is needed to avoid zombies. A second fork also
# prevents the daemon from ever re-acquiring a terminal, by making 
# the main daemon process not be the process group leader
if ( fork() ) {
  exit;
}

# Write the program's PID to a file AFTER you have forked
open OUT, ">", "/home/cbm/daemon/daemon.pid";
print OUT "$$\n";
close OUT;  # remember to close the file!

# Now go back to your regular business. 
exec "/home/cbm/daemon/the.real.daemon.pl";

PHP[edit]

<?php

#start-stop-daemon requires signal handlers to be set, and declaring signal handlers requires ticks to be set.
declare(ticks=1);

$pidnum = pcntl_fork();

if ($pidnum == -1) {
	#Well, we can't daemonize for some reason.
	die("Problem - Could not fork child!\n"); 
} else if ($pidnum) {
	#The child process pid - returned by pcntl_fork() - has to be written to the file defined as pidfile for start-stop-daemon
	file_put_contents('/path/to/file/daemon.pid',$pidnum);
	#We'll only be running a child process, so, this process need not continue.
	die("Detaching from terminal.\n");
} else {
	#We're the child. Continuing on below as the child.
}

if (posix_setsid() == -1) {
	#If we can't detach, we're not daemonized. No reason to go on.
	die("Problem - I could not detach!\n");
}

#Set up signal handlers -- Linked to the functions below.
pcntl_signal(SIGHUP, "SIGHUP");
pcntl_signal(SIGTERM, "SIGTERM");

function SIGHUP() {
	#Do what you want it to do upon receiving a SIGHUP
}

function SIGTERM() {
	#Do what you want it to do upon receiving a SIGTERM. In this case, die.
	die("Received SIGTERM\n");
}

#Initialize your daemon here (i.e. make the IRC connection, DB connection, set constants and variables, include files, whatever)

$variable = "Value";
while (0 == 0) {
	#Here's where the meat of your daemon goes.
	sleep(rand(1,5)); #sleep for 1-5s
	echo $variable . "\n"; #spit out $variable
	if($variable != "Value") { #If $variable doesn't equal "Value" do something.
		echo "Oh no! Something went HORRIDLY wrong!\n"; 
		break; #Exit while() loop
	}
}

#Clean up your connections, finish file writes here, or whatever you want to do as the daemon shuts down.

?>

Python[edit]

This example code isn't best practice. You should probably use the daemon module instead. (This is installed on both nightshade and wolfsbane.)

import sys, os

__version__ = '$Id: $'

is_daemon = False

def daemonize(close_fd = True, chdir = True, write_pid = False, redirect_std = None):
    """ Daemonize the current process. Only works on POSIX compatible operating
        systems. The process will fork to the background and return control to
        the terminal.
        
        Arguments:
            - close_fd: Close the standard streams and replace them by /dev/null
            - chdir: Change the current working directory to /
            - write_pid: Write the pid to sys.argv[0] + '.pid'
            - redirect_std: Filename to redirect stdout and stdin to
    """
    
    # Fork away
    if not os.fork():
        # Become session leader        
        os.setsid()
        # Fork again to prevent the process from acquiring a 
        # controlling terminal
        pid = os.fork()
        if not pid:
            global is_daemon
            is_daemon = True
            
            if close_fd:
                os.close(0)
                os.close(1)
                os.close(2)
                os.open('/dev/null', os.O_RDWR)
                if redirect_std:
                    os.open(redirect_std, os.O_WRONLY | os.O_APPEND)
                else:
                    os.dup2(0, 1)
                os.dup2(1, 2)
            if chdir:
                os.chdir('/')
            return
        else:
            # Write out the pid
            f = open(sys.argv[0] + '.pid', 'w')
            f.write(str(pid))
            f.close()
            os._exit(0)
    else:
        # Exit to return control to the terminal
        # os._exit to prevent the cleanup to run
        os._exit(0)

External links[edit]

Category:Commands