I wrote this module I called Demonize.pm. I think it's pretty useful, but I was wondering what the Perl Monks think of it. It's a general class to create Perl daemons by subclassing Demonize and overriding the run method.

I know there are other modules out there that do something similar to this, is this module just something that I find useful?

#!/usr/bin/perl -w # This is a general-purpose daemon class module. # Extend this class and override the run method with your own. # # You can run this class alone and it will create a /var/log/Demonize. +log file and write # a message to it. # # In your sub-class you should should set error_recipient to an email +address that will be notified # if the daemon dies (you could do numerous email addresses like this +alerts@yourdomain.com,help@yourdomain.com # # You should also call method email and set the "From" for the error m +essages. # # To set the number of seconds daemon should sleep, call sleep method. # # To set logdir call logdir. # # To set logfile all logfile. # # Here's an example from a constructor I have in use that uses YAML to + store the configuration data: # # $self->logfile(sprintf('%s/%s.log', $self->logdir, $self->name)); # # $self->logdir($config->{logdir}); # # $self->error_recipient($config->{error_recipient}); # $self->email($config->{email}); # $self->seconds($config->{sleepfor}); # # Created By: Keith Vance <next@take88.com> # Created On: 11/28/2008 # package Demonize; use strict; use warnings FATAL => 'all', NONFATAL => 'redefine'; use DBI; use Proc::PID::File(); use POSIX; use MIME::Lite(); use LWP::Simple; our %Demon = (LOG_DIR => '', ERROR_RECIPIENT => '', EMAIL => '', SECONDS => '', LOG_FILE => ''); our $_Demon; sub _classobject { my $self = shift; my $class = ref($self) || $self; no strict "refs"; return \%$class; } sub new { my $class = shift; my $classobj = $class->_classobject(); bless my $self = { LOG_DIR => '/var/log', ERROR_RECIPIENT => 'youremail@yourdomain.com', EMAIL => 'youremail@yourdomain.com', SECONDS => 1} => (ref $class || $class); $self->logfile(sprintf('%s/%s.log', $self->logdir, $self->name)); $_Demon = $self; return $self; } sub start { my $self = shift; if (my $pid = fork()) { exit 0; } if (Proc::PID::File->running({name=>$self->name})) { die "Couldn't start: " . $self->name . " already running."; } $self->init_daemon; } sub logfile { my $self = shift; $self->{LOG_FILE} = shift if @_; return $self->{LOG_FILE}; } sub name { return __PACKAGE__ } sub logdir { my $self = shift; $self->{LOG_DIR} = shift if @_; return $self->{LOG_DIR}; } sub error_recipient { my $self = shift; $self->{ERROR_RECIPIENT} = shift if @_; return $self->{ERROR_RECIPIENT}; } sub email { my $self = shift; $self->{EMAIL} = shift if @_; return $self->{EMAIL}; } sub seconds { my $self = shift; $self->{SECONDS} = shift if @_; return $self->{SECONDS}; } sub init_daemon { my $self = shift; printf "Starting: %s\n", $self->name; *CORE::GLOBAL::warn = \&warn_to_log; $SIG{__DIE__} = \&die_to_log; $SIG{__WARN__} = \&warn_to_log; eval { chdir '/' or die $!; open STDIN, '/dev/null' or die $!; open(STDOUT, '>>' . $self->logfile); open(STDERR, '+>&STDOUT'); logmsg(sprintf('Starting %s', $self->name)); POSIX::setsid or die $!; logmsg('Successful'); }; if ($@) { die "Couldn't start child '" . $self->name . "': $@"; } local $SIG{CHLD} = 'IGNORE'; $self->run; } sub stop { my $self = shift; my $pid; unless ($pid = Proc::PID::File->running({name=>$self->name})) { printf "%s not running\n", $self->name; return; } $| = 1; printf "Shutting down %s", $self->name; kill -3 => $pid; my $i = 0; while (kill (-0 => $pid) && $i++ < 30) { print '.'; sleep 1; } unless (kill -0 => $pid) { print "\nShut down complete\n"; return 0; } print "\nNot responding - sending kill signal\n"; kill -9 => $pid; return; } sub status { my $self = shift; if (Proc::PID::File->running({name=>$self->name})) { printf "%s is running\n", $self->name; } else { printf "%s is not running\n", $self->name; } exit 0; } sub run { my $self = shift; while (1) { logmsg("I'm not doing anything, I'm just sleeping for " . $self->s +econds . " seconds. Please override the run method in your subclass t +o do something interesting instead of writing this log message."); sleep($self->seconds); } } sub logmsg { print format_msg($_[0]); } sub format_msg { return (timestamp(),' [',(caller(1))[0]," $$] : ",$_[0]."\n"); } sub timestamp { my ($sec,$min,$hour,$day,$mon,$year) = localtime; return sprintf ("%4d-%02d-%02d %02d:%02d:%02d", $year+1900,++$mon,$day,$hour,$min,$sec); } sub warn_to_log { print format_msg('**** ' . $_[0]); } sub die_to_log { return if $^S; my $error = $_[0]; my $self = $_Demon; eval { my @to = ($self->error_recipient); my $msg = MIME::Lite->new( From => $self->email, To => @to, Subject => 'Error running ' . $self->name, Data => $self->name . ".pm DIED\n" . $error, Encoding => 'quoted-printable' ); $msg->attr('content-type', => 'text/plain; charset=utf-8; format=f +lowed'); $msg->send(); }; if ($@) { $error .= ' Additionally, an error occurred sending the alert emai +l: ' . $@; } die format_msg('**** DIE!!! **** ' . $error); } 1
Here's a script to run it. It just writes a message to /var/log/Demonize.log

#!/usr/bin/perl -w use strict; use Demonize; my $command = shift @ARGV || ''; my $demon = Demonize->new; if ($command eq 'start') { exit $demon->start; } elsif ($command eq 'restart') { $demon->stop; exit $demon->start; } elsif ($command eq 'stop') { $demon->stop; } elsif ($command eq 'status') { $demon->status; } else { die <<USAGE; Usage : $0 stop|start|restart|status USAGE }


To do something interesting, you have to subclass Demonize

Replies are listed 'Best First'.
Re: Demonize Module, What are your thoughts?
by kyle (Abbot) on Dec 16, 2008 at 02:12 UTC

    These lines seem out of place:

    use DBI; use LWP::Simple;

    Documentation should be POD (perlpod).

    Your accessor/mutator pattern is repeated. This is something a nice little framework could help with. See Tiny Frameworks or go all out with Moose.

    It's nice to use signal names rather than numbers with kill.

    In your log messages, it would be useful to include $$ (and use English, so you can say $PID instead of $$).

    Instead of forcing me to subclass, allow me to pass in a code reference for run().

    It'd be nice if I could tell it I don't want it to send email rather than forcing it to die by giving some bogus email address.

    If you're doing logging, you might consider Log::Log4perl.

    I've used Proc::Daemon for this before.

      Ok I've refactored the code quite a bit. I'm totally onboard with Moose. I've implemented YAML config files and cleaned up the code a bit.

        I haven't read through the rewrite.

        What I meant about passing a code reference is using an anonymous sub.

        # create code reference my $anon_sub = sub { print "Hello world!\n" }; # execute the code $anon_sub->();

        For Daemonize, it would look something like...

        my $run = sub { die 'unfinished' }; my $d = Daemonize->new( run => $run ); # later, inside Daemonize $self->{run}->( $self );

        I think it would be a good idea to pass the Daemonize object to the sub itself (as I did in the example) so it can call methods on it (like logging) if it wants to.

        I'm not quite clear about what you're trying to do with $SIG{__DIE__}, but you might want an END block or a DESTROY method instead.

Re: Demonize Module, What are your thoughts?
by JavaFan (Canon) on Dec 16, 2008 at 08:35 UTC
    I only skimmed your code. But a few remarks:
    • I wouldn't use it. My OS already comes with deamonizing functionality and I'd very much prefer for my daemons to all work similar.
    • You'd actually have to write code to use it which means you have to do (possibly a lot) of work to deamonize an existing program. I'd expect a deamonizer to take command line arguments.
    • Configuration requires code hacking. It's not using a config file.
    • Very big no no: it has its own idea where logging should go to. Not making use of the syslog functionality doesn't make me give it any high marks.
    • Why does it need DBI, LWP and MIME?
    • Why does a module start with #!/usr/bin/perl?
Re: Demonize Module, What are your thoughts?
by Jenda (Abbot) on Dec 16, 2008 at 14:46 UTC