views:

1980

answers:

10

I'd like a daemonizer that can turn an arbitrary, generic script or command into a daemon.

There are two common cases I'd like to deal with:

  1. I have a script that should run forever. If it ever dies (or on reboot), restart it. Don't let there ever be two copies running at once (detect if a copy is already running and don't launch it in that case).

  2. I have a simple script or command line command that I'd like to keep executing repeatedly forever (with a short pause between runs). Again, don't allow two copies of the script to ever be running at once.

Of course it's trivial to write a "while(true)" loop around the script in case 2 and then apply a solution for case 1, but a more general solution will just solve case 2 directly since that applies to the script in case 1 as well (you may just want a shorter or no pause if the script is not intended to ever die (of course if the script really does never die then the pause doesn't actually matter)).

Note that the solution should not involve, say, adding file-locking code or PID recording to the existing scripts.

More specifically, I'd like a program "daemonize" that I can run like

% daemonize myscript arg1 arg2

or, for example,

% daemonize 'echo `date` >> /tmp/times.txt'

which would keep a growing list of dates appended to times.txt. (Note that if the argument(s) to daemonize is a script that runs forever as in case 1 above, then daemonize will still do the right thing, restarting it when necessary.) I could then put a command like above in my .login and/or cron it hourly or minutely (depending on how worried I was about it dying unexpectedly).

NB: The daemonize script will need to remember the command string it is daemonizing so that if the same command string is daemonized again it does not launch a second copy.

Also, the solution should ideally work on both OS X and linux but solutions for one or the other are welcome.

EDIT: It's fine if you have to invoke it with sudo daemonize myscript myargs.

(If I'm thinking of this all wrong or there are quick-and-dirty partial solutions, I'd love to hear that too.)


PS: In case it's useful, here's a similar question specific to python:

http://stackoverflow.com/questions/473620/how-do-you-create-a-daemon-in-python

And this answer to a similar question has what appears to be a useful idiom for a quick-and-dirty daemonizing of an arbitrary script:

http://stackoverflow.com/questions/38596/whats-the-best-way-to-keep-a-php-script-running-as-a-daemon/39609#39609

+5  A: 

There is a very similar question on the Perl Monks site:

http://www.perlmonks.org/index.pl?node_id=478839

Colin
If someone could distill that answer, make it self-contained here, and make it conform to the specs in the question, I think that would be worth the bounty.
dreeves
+2  A: 

If you're using OS X specifically, I suggest you take a look at how launchd works. It will automatically check to ensure your script is running and relaunch it if necessary. It also includes all sorts of scheduling features, etc. It should satisfy both requirement 1 and 2.

As for ensuring only one copy of your script can run, you need to use a PID file. Generally I write a file to /var/run/.pid that contains a PID of the current running instance. if the file exists when the program runs, it checks if the PID in the file is actually running (the program may have crashed or otherwise forgotten to delete the PID file). If it is, abort. If not, start running and overwrite the PID file.

Kamil Kisiel
+3  A: 

First get createDaemon() from http://code.activestate.com/recipes/278731/

Then the main code:

import subprocess
import time

createDaemon()

while True:
    subprocess.call(" ".join(sys.argv[1:]),shell=True)
    time.sleep(10)
Douglas Leeder
Ooh, thanks! Want to make it slightly more general so you can do "daemonize foo arg1 arg2" as well as "daemonize 'foo arg1 arg2'"?
dreeves
Ok, it'll join arguments now - however you will have to change it if you ever want to have spaces inside your arguments.
Douglas Leeder
Thanks Douglas! There's a big flaw though: running "daemonize foo" twice starts two copies of foo running.
dreeves
Yes - don't do that...
Douglas Leeder
You could add some PID recording code, but it might be best to only run the script once...
Douglas Leeder
I'm kind of thinking of that as fundamental to the whole "daemonize" wrapper concept. (Eg, I could then cron it hourly or minutely to make sure it was always running.) Am I thinking of this wrong? Does createDaemon already guarantee that somehow? What about after reboot?
dreeves
+4  A: 

I think you may want to try start-stop-daemon(8). Check out scripts in /etc/init.d in any Linux distro for examples. It can find started processes by command line invoked or PID file, so it matches all your requirements except being a watchdog for your script. But you can always start another daemon watchdog script that just restarts your script if necessary.

Alex B
A: 

This looks similar to start-stop-daemon, but for Mac OS X. Or perhaps it will work for any unix; I haven't tried it yet:

http://trac.macports.org/browser/trunk/base/src/programs/daemondo

dreeves
+8  A: 

You can daemonize any executable in Unix by using nohup and the & operator:

nohup yourScript.sh script args&

The nohup command allows you to shut down your shell session without it killing your script, while the & places your script in the background so you get a shell prompt to continue your session. The only minor problem with this is standard out and standard error both get sent to ./nohup.out, so if you start several scripts in this manor their output will be intertwined. A better command would be:

nohup yourScript.sh script args >script.out 2>script.error&

This will send standard out to the file of your choice and standard error to a different file of your choice. If you want to use just one file for both standard out and standard error you can us this:

nohup yourScript.sh script args >script.out 2>&1 &

The 2>&1 tells the shell to redirect standard error (file descriptor 2) to the same file as standard out (file descriptor 1).

To run a command only once and restart it if it dies you can use this script:

#!/bin/bash

if [[ $# < 1 ]]; then
    echo "Name of pid file not given."
    exit
fi

# Get the pid file's name.
PIDFILE=$1
shift

if [[ $# < 1 ]]; then
    echo "No command given."
    exit
fi

echo "Checking pid in file $PIDFILE."

#Check to see if process running.
PID=$(cat $PIDFILE 2>/dev/null)
if [[ $? = 0 ]]; then
    ps -p $PID >/dev/null 2>&1
    if [[ $? = 0 ]]; then
        echo "Command $1 already running."
        exit
    fi
fi

# Write our pid to file.
echo $$ >$PIDFILE

# Get command.
COMMAND=$1
shift

# Run command until we're killed.
while true; do
    $COMMAND "$*"
done

The first argument is the name of the pid file to use. The second argument is the command. And all other arguments are the command's arguments.

If you name this script restart.sh this is how you would call it:

nohup restart.sh pidFileName yourScript.sh script args >script.out 2>&1 &
Robert Menteer
Awesome; thank you. I wonder if it should also have an option for a delay in restarting. Or maybe better to just use it in conjunction with this: http://stackoverflow.com/questions/555116/repeat-a-unix-command-every-x-seconds-forever
dreeves
This handles only SIGHUP, there are other (normally) fatal signals that should be handled.
Tim Post
+2  A: 

You should have a look at daemonize. It allows to detect second copy (but it uses file locking mechanism). Also it works on different UNIX and Linux distributions.

If you need to automatically start your application as daemon, then you need to create appropriate init-script.

You can use the following template:

#!/bin/sh
#
# mydaemon     This shell script takes care of starting and stopping
#               the <mydaemon>
#

# Source function library
. /etc/rc.d/init.d/functions


# Do preliminary checks here, if any
#### START of preliminary checks #########


##### END of preliminary checks #######


# Handle manual control parameters like start, stop, status, restart, etc.

case "$1" in
  start)
    # Start daemons.

    echo -n $"Starting <mydaemon> daemon: "
    echo
    daemon <mydaemon>
    echo
    ;;

  stop)
    # Stop daemons.
    echo -n $"Shutting down <mydaemon>: "
    killproc <mydaemon>
    echo

    # Do clean-up works here like removing pid files from /var/run, etc.
    ;;
  status)
    status <mydaemon>

    ;;
  restart)
    $0 stop
    $0 start
    ;;

  *)
    echo $"Usage: $0 {start|stop|status|restart}"
    exit 1
esac

exit 0
uthark
Looks like a candidate for the correct answer. Especially considering its "single instance checking".
Martin Wickman
This might be the best possible answer -- I'm not sure -- but if you think it is, can you also include an explanation for why the spec I gave in the question is wrong-headed?
dreeves
I don't like the `killproc` in the stopping part: if you had a process that, say, ran `java`, the `killproc` will cause all other Java processes to be killed too.
Chris Jester-Young
+4  A: 

Daemontools ( http://cr.yp.to/daemontools.html ) is a set of pretty hard-core utilities used to do this, written by dj bernstein. I have used this with some success. The annoying part about it is that none of the scripts return any visible results when you run them - just invisible return codes. But once it's running it's bulletproof.

fastmultiplication
Yes, I was going to write an entry that uses daemontools too. I'll write my own post, because I'm hoping to be much more comprehensive with my answer, and hope to get the bounty that way. We'll see. :-)
Chris Jester-Young
+1  A: 

Okay, I'll bite, for a chance at the bounty:

This is a working version complete with an example which you can copy into an empty directory and try out (after installing the CPAN dependencies, which are Getopt::Long, File::Spec, File::Pid, and IPC::System::Simple -- all pretty standard and are highly recommended for any hacker: you can install them all at once with cpan <modulename> <modulename> ...).


keepAlive.pl:

#!/usr/bin/perl

# Usage:
# 1. put this in your crontab, to run every minute:
#     keepAlive.pl --pidfile=<pidfile> --command=<executable> <arguments>
# 2. put this code somewhere near the beginning of your script,
#    where $pidfile is the same value as used in the cron job above:
#     use File::Pid;
#     File::Pid->new({file => $pidfile})->write;

# if you want to stop your program from restarting, you must first disable the
# cron job, then manually stop your script. There is no need to clean up the
# pidfile; it will be cleaned up automatically when you next call
# keepAlive.pl.

use strict;
use warnings;

use Getopt::Long;
use File::Spec;
use File::Pid;
use IPC::System::Simple qw(system);

my ($pid_file, $command);
GetOptions("pidfile=s"   => \$pid_file,
           "command=s"   => \$command)
    or print "Usage: $0 --pidfile=<pidfile> --command=<executable> <arguments>\n", exit;

my @arguments = @ARGV;

# check if process is still running
my $pid_obj = File::Pid->new({file => $pid_file});

if ($pid_obj->running())
{
    # process is still running; nothing to do!
    exit 0;
}

# no? restart it
print "Pid " . $pid_obj->pid . " no longer running; restarting $command @arguments\n";

system($command, @arguments);

example.pl:

#!/usr/bin/perl

use strict;
use warnings;

use File::Pid;
File::Pid->new({file => "pidfile"})->write;

print "$0 got arguments: @ARGV\n";

Now you can invoke the example above with: ./keepAlive.pl --pidfile=pidfile --command=./example.pl 1 2 3 and the file pidfile will be created, and you will see the output:

Pid <random number here> no longer running; restarting ./example.pl 1 2 3
./example.pl got arguments: 1 2 3
Ether
I believe this is not quite to spec, if I understand correctly. In your solution (thanks, btw!) the program you want to daemonize has to be modified to write its PID to the the PID file. I'm hoping for a utility that can daemonize an arbitrary script.
dreeves
@dreeves: yes, but there are two ways around that: 1. the script invoked by keepAlive.pl (e.g. example.pl) could simply be a wrapper to execute the real program, or 2. keepAlive.pl could parse the table of active system processes (with CPAN's Proc::ProcessTable) to attempt to find the relevant process and its pid).
Ether
+2  A: 

I apologise for the long answer (please see comments about how my answer nails the spec). I'm trying to be comprehensive, so you have as good of a leg up as possible. :-)

If you are able to install programs (have root access), and are willing to do one-time legwork to set up your script for daemon execution (i.e., more involved than simply specifying the command-line arguments to run on the command line, but only needing to be done once per service), I have a way that's more robust.

It involves using daemontools. The rest of the post describes how to set up services using daemontools.

Initial setup

  1. Follow the instructions in How to install daemontools. Some distributions (e.g., Debian, Ubuntu) already have packages for it, so just use that.
  2. Make a directory called /service. The installer should have already done this, but just verify, or if installing manually. If you dislike this location, you can change it in your svscanboot script, although most daemontools users are used to using /service and will get confused if you don't use it.
  3. If you're using Ubuntu or another distro that doesn't use standard init (i.e., doesn't use /etc/inittab), you will need to use the pre-installed inittab as a base for arranging svscanboot to be called by init. It's not hard, but you need to know how to configure the init that your OS uses. svscanboot is a script that calls svscan, which does the main work of looking for services; it's called from init so init will arrange to restart it if it dies for any reason.

Per-service setup

  1. Each service needs a service directory, which stores housekeeping information about the service. You can also make a location to house these service directories so they're all in one place; usually I use /var/lib/svscan, but any new location will be fine.
  2. I usually use a script to set up the service directory, to save lots of manual repetitive work. e.g.,

    sudo mkservice -d /var/lib/svscan/some-service-name -l -u user -L loguser "command line here"
    

    where some-service-name is the name you want to give your service, user is the user to run that service as, and loguser is the user to run the logger as. (Logging is explained in just a little bit.)

  3. Your service has to run in the foreground. If your program backgrounds by default, but has an option to disable that, then do so. If your program backgrounds without a way to disable it, read up on fghack, although this comes at a trade-off: you can no longer control the program using svc.
  4. Edit the run script to ensure it's doing what you want it to. You may need to place a sleep call at the top, if you expect your service to exit frequently.
  5. When everything is set up right, create a symlink in /service pointing to your service directory. (Don't put service directories directly within /service; it makes it harder to remove the service from svscan's watch.)

Logging

  1. The daemontools way of logging is to have the service write log messages to standard output (or standard error, if you're using scripts generated with mkservice); svscan takes care of sending log messages to the logging service.
  2. The logging service takes the log messages from standard input. The logging service script generated by mkservice will create auto-rotated, timestamped log files in the log/main directory. The current log file is called current.
  3. The logging service can be started and stopped independently of the main service.
  4. Piping the log files through tai64nlocal will translate the timestamps into a human-readable format. (TAI64N is a 64-bit atomic timestamp with a nanosecond count.)

Controlling services

  1. Use svstat to get the status of a service. Note that the logging service is independent, and has its own status.
  2. You control your service (start, stop, restart, etc.) using svc. For example, to restart your service, use svc -t /service/some-service-name; -t means "send SIGTERM".
  3. Other signals available include -h (SIGHUP), -a (SIGALRM), -1 (SIGUSR1), -2 (SIGUSR2), and -k (SIGKILL).
  4. To down the service, use -d. You can also prevent a service from automatically starting at bootup by creating a file named down in the service directory.
  5. To start the service, use -u. This is not necessary unless you've downed it previously (or set it up not to auto-start).
  6. To ask the supervisor to exit, use -x; usually used with -d to terminate the service as well. This is the usual way to allow a service to be removed, but you have to unlink the service from /service first, or else svscan will restart the supervisor. Also, if you created your service with a logging service (mkservice -l), remember to also exit the logging supervisor (e.g., svc -dx /var/lib/svscan/some-service-name/log) before removing the service directory.

Summary

Pros:

  1. daemontools provides a bulletproof way to create and manage services. I use it for my servers, and I highly recommend it.
  2. Its logging system is very robust, as is the service auto-restart facility.
  3. Because it starts services with a shell script that you write/tune, you can tailor your service however you like.
  4. Powerful service control tools: you can send most any signal to a service, and can bring services up and down reliably.
  5. Your services are guaranteed a clean execution environment: they will execute with the same environment, process limits, etc., as what init provides.

Cons:

  1. Each service takes a bit of setup. Thankfully, this only needs doing once per service.
  2. Services must be set up to run in the foreground. Also, for best results, they should be set up to log to standard output/standard error, rather than syslog or other files.
  3. Steep learning curve if you're new to the daemontools way of doing things. You have to restart services using svc, and cannot run the run scripts directly (since they would then not be under the control of the supervisor).
  4. Lots of housekeeping files, and lots of housekeeping processes. Each service needs its own service directory, and each service uses one supervisor process to auto-restart the service if it dies. (If you have many services, you will see lots of supervise processes in your process table.)

In balance, I think daemontools is an excellent system for your needs. I welcome any questions about how to set it up and maintain it.

Chris Jester-Young
How my answer nails the spec: 1. You have to set up services, so as long as you don't set up duplicates (and as long as your service does not background itself), no duplicates will occur. 2. `supervise`, the supervisor, takes care of restarting any service that exits. It waits one second between restars; if that's not enough time for you, put in a sleep at the top of the service run script.
Chris Jester-Young
2a. `supervise` is itself backed by `svscan`, so if a supervisor dies, it will be restarted. 2b. `svscan` is backed by `init`, which will auto-restart `svscan` as necessary. 2c. If your `init` dies for any reason, you're screwed anyway. :-P
Chris Jester-Young
To answer other questions about housekeeping, the daemontools system doesn't use PID files, since they can get stale. Instead, all process information is kept by the supervisor backing a given service. The supervisor maintains a bunch of files (and FIFOs) in the service directory that tools like `svstat` and `svc` can work with.
Chris Jester-Young