'MooseX::Getopt';
has progname => (
- metaclass => 'Getopt',
+ metaclass => 'Getopt',
isa => 'Str',
is => 'ro',
lazy => 1,
);
has pidbase => (
- metaclass => 'Getopt',
+ metaclass => 'Getopt',
isa => 'Path::Class::Dir',
is => 'ro',
coerce => 1,
- required => 1,
+ required => 1,
lazy => 1,
default => sub { Path::Class::Dir->new('var', 'run') },
);
has basedir => (
- metaclass => 'Getopt',
+ metaclass => 'Getopt',
isa => 'Path::Class::Dir',
is => 'ro',
coerce => 1,
);
has stop_timeout => (
- metaclass => 'Getopt',
+ metaclass => 'Getopt',
isa => 'Int',
is => 'rw',
default => sub { 2 }
);
+# internal book-keeping
+
+has status_message => (
+ metaclass => 'NoGetopt',
+ isa => 'Str',
+ is => 'rw',
+ clearer => 'clear_status_message',
+);
+
+has exit_code => (
+ metaclass => 'NoGetopt',
+ isa => 'Int',
+ is => 'rw',
+ clearer => 'clear_exit_code',
+);
+
# methods ...
## PID file related stuff ...
MooseX::Daemonize::Pid::File->new( file => $file );
}
-# backwards compat,
+# backwards compat,
sub check { (shift)->pidfile->is_running }
sub save_pid { (shift)->pidfile->write }
sub remove_pid { (shift)->pidfile->remove }
sub setup_signals {
my $self = shift;
$SIG{'INT'} = sub { $self->handle_sigint };
- $SIG{'HUP'} = sub { $self->handle_sighup };
+ $SIG{'HUP'} = sub { $self->handle_sighup };
}
-sub handle_sigint { $_[0]->stop; }
-sub handle_sighup { $_[0]->restart; }
+sub handle_sigint { $_[0]->stop }
+sub handle_sighup { $_[0]->restart }
## daemon control methods ...
sub start {
- my ($self) = @_;
-
- confess "instance already running" if $self->pidfile->is_running;
-
- $self->daemonize unless $self->foreground;
+ my $self = shift;
+
+ $self->clear_status_message;
+ $self->clear_exit_code;
+
+ if ($self->pidfile->is_running) {
+ $self->status_message('Daemon is already running with pid (' . $self->pidfile->pid . ')');
+ return !($self->exit_code);
+ }
- return unless $self->is_daemon;
+ if ($self->foreground) {
+ $self->is_daemon(1);
+ }
+ else {
+ eval { $self->daemonize };
+ if ($@) {
+ $self->exit_code(1);
+ $self->status_message('Start failed : ' . $@);
+ return !($self->exit_code);
+ }
+ }
- $self->pidfile->pid($$);
+ unless ($self->is_daemon) {
+ $self->status_message('Start succeeded');
+ return !($self->exit_code);
+ }
+
+ $self->pidfile->pid($$);
# Change to basedir
chdir $self->basedir;
return $$;
}
+sub status {
+ my $self = shift;
+
+ $self->clear_status_message;
+ $self->clear_exit_code;
+
+ if ($self->pidfile->is_running) {
+ $self->status_message('Daemon is running with pid (' . $self->pidfile->pid . ')');
+ }
+ else {
+ $self->exit_code(1);
+ $self->status_message('Daemon is not running with pid (' . $self->pidfile->pid . ')');
+ }
+
+ return !($self->exit_code);
+}
+
sub restart {
- my ($self) = @_;
- $self->stop( no_exit => 1 );
- $self->start();
+ my $self = shift;
+
+ $self->clear_status_message;
+ $self->clear_exit_code;
+
+ unless ($self->stop) {
+ $self->exit_code(1);
+ $self->status_message('Restart (Stop) failed : ' . $@);
+ }
+
+ unless ($self->start) {
+ $self->exit_code(1);
+ $self->status_message('Restart (Start) failed : ' . $@);
+ }
+
+ $self->status_message("Restart successful")
+ if !$self->exit_code;
+
+ return !($self->exit_code);
}
# Make _kill *really* private
my $_kill;
sub stop {
- my ( $self, %args ) = @_;
- my $pid = $self->pidfile->pid;
- $self->$_kill($pid) unless $self->foreground();
- $self->pidfile->remove;
- return 1 if $args{no_exit};
- exit;
+ my $self = shift;
+
+ $self->clear_status_message;
+ $self->clear_exit_code;
+
+ # if the pid is not running
+ # then we dont need to stop
+ # anything ...
+ if ($self->pidfile->is_running) {
+
+ # if we are foreground, then
+ # no need to try and kill
+ # ourselves
+ unless ($self->foreground) {
+
+ # kill the process ...
+ eval { $self->$_kill($self->pidfile->pid) };
+ # and complain if we can't ...
+ if ($@) {
+ $self->exit_code(1);
+ $self->status_message('Stop failed : ' . $@);
+ }
+ # or gloat if we succeed ..
+ else {
+ $self->status_message('Stop succeeded');
+ }
+
+ }
+
+ # clean up ...
+ eval { $self->pidfile->remove };
+ if ($@) {
+ warn "Could not remove pidfile ("
+ . $self->pidfile->file
+ . ") because : $!";
+ }
+
+ }
+ else {
+ # this just returns the OK
+ # exit code for now, but
+ # we should make this overridable
+ $self->status_message("Not running");
+ }
+
+ # if we are returning to our script
+ # then we actually need the opposite
+ # of what the system/OS expects
+ return !($self->exit_code);
}
$_kill = sub {
# kill 0 => $pid returns 0 if the process is dead
# $!{EPERM} could also be true if we cant kill it (permission error)
+ # if this is being called
+ # inside the daemon then
+ # we don't want sig-INT to
+ # fall into a loop here
+ # so we reset it.
+ if ($self->is_daemon) {
+ $SIG{INT} = 'DEFAULT';
+ }
+
# Try SIGINT ... 2s ... SIGTERM ... 2s ... SIGKILL ... 3s ... UNDEAD!
for ( [ 2, $timeout ], [15, $timeout], [9, $timeout * 1.5] ) {
my ($signal, $timeout) = @$_;
$timeout = int $timeout;
-
+
CORE::kill($signal, $pid);
-
+
last unless CORE::kill 0 => $pid or $!{EPERM};
-
+
while ($timeout) {
sleep(1);
last unless CORE::kill 0 => $pid or $!{EPERM};
=head1 NAME
-MooseX::Daemonize - provides a Role that daemonizes your Moose based
+MooseX::Daemonize - provides a Role that daemonizes your Moose based
application.
=head1 VERSION
package My::Daemon;
use Moose;
-
+
with qw(MooseX::Daemonize);
-
+
# ... define your class ....
-
- after start => sub {
+
+ after start => sub {
my $self = shift;
return unless $self->is_daemon;
# your daemon code here ...
};
- # then in your script ...
-
+ # then in your script ...
+
my $daemon = My::Daemon->new_with_options();
-
+
my ($command) = @{$daemon->extra_argv}
defined $command || die "No command specified";
-
- $daemon->start() if $command eq 'start';
- $daemon->stop() if $command eq 'stop';
-
+
+ $daemon->start if $command eq 'start';
+ $daemon->status if $command eq 'status';
+ $daemon->restart if $command eq 'restart';
+ $daemon->stop if $command eq 'stop';
+
+ warn($daemon->status);
+ exit($daemon->exit_code);
+
=head1 DESCRIPTION
Often you want to write a persistant daemon that has a pid file, and responds
-appropriately to Signals. This module provides a set of basic roles as an
+appropriately to Signals. This module provides a set of basic roles as an
infrastructure to do that.
=head1 ATTRIBUTES
This list includes attributes brought in from other roles as well
we include them here for ease of documentation. All of these attributes
-are settable though L<MooseX::Getopt>'s command line handling, with the
+are settable though L<MooseX::Getopt>'s command line handling, with the
exception of C<is_daemon>.
=over
=item I<foreground Bool>
-If true, the process won't background. Useful for debugging. This option can
+If true, the process won't background. Useful for debugging. This option can
be set via Getopt's -f.
=item I<is_daemon Bool>
-If true, the process is the backgrounded daemon process, if false it is the
-parent process. This is useful for example in an C<after 'start' => sub { }>
-block.
+If true, the process is the backgrounded daemon process, if false it is the
+parent process. This is useful for example in an C<after 'start' => sub { }>
+block.
B<NOTE:> This option is explicitly B<not> available through L<MooseX::Getopt>.
=back
-=head1 METHODS
+These are the internal attributes, which are not available through MooseX::Getopt.
+
+=over 4
+
+=item I<exit_code Int>
+
+=item I<status Str>
+
+=back
+
+=head1 METHODS
=head2 Daemon Control Methods
-These methods can be used to control the daemon behavior. Every effort
-has been made to have these methods DWIM (Do What I Mean), so that you
-can focus on just writing the code for your daemon.
+These methods can be used to control the daemon behavior. Every effort
+has been made to have these methods DWIM (Do What I Mean), so that you
+can focus on just writing the code for your daemon.
-Extending these methods is best done with the L<Moose> method modifiers,
+Extending these methods is best done with the L<Moose> method modifiers,
such as C<before>, C<after> and C<around>.
=over 4
$self->stop();
$self->start();
+=item B<status>
+
=back
+
=head2 Pidfile Handling Methods
=over 4
=item B<check>
-This checks to see if the daemon process is currently running by checking
+This checks to see if the daemon process is currently running by checking
the pidfile.
=item B<get_pid>
=item B<setup_signals>
-Setup the signal handlers, by default it only sets up handlers for SIGINT and
+Setup the signal handlers, by default it only sets up handlers for SIGINT and
SIGHUP. If you wish to add more signals just use the C<after> method modifier
and add them.
=head1 THANKS
-Mike Boyko, Matt S. Trout, Stevan Little, Brandon Black, Ash Berlin and the
+Mike Boyko, Matt S. Trout, Stevan Little, Brandon Black, Ash Berlin and the
#moose denzians
Some bug fixes sponsored by Takkle Inc.
=head1 LICENCE AND COPYRIGHT
-Copyright (c) 2007, Chris Prather C<< <perigrin@cpan.org> >>. All rights
+Copyright (c) 2007, Chris Prather C<< <perigrin@cpan.org> >>. All rights
reserved.
This module is free software; you can redistribute it and/or
-use Test::More tests => 4;
-use Test::MooseX::Daemonize;
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+use Cwd;
+use File::Spec::Functions;
+
+use Test::More tests => 9;
use MooseX::Daemonize;
-## Since a daemon will not be able to print terminal output, we
-## have a test daemon create a file, and another process test for
-## its existence.
+use constant DEBUG => 0;
+
+my $CWD = Cwd::cwd;
+my $FILENAME = "$CWD/im_alive";
+$ENV{MX_DAEMON_STDOUT} = catfile($CWD, 'Out.txt');
+$ENV{MX_DAEMON_STDERR} = catfile($CWD, 'Err.txt');
{
after start => sub {
my $self = shift;
- $self->create_file( $self->filename )
- if $self->is_daemon;
+ if ($self->is_daemon) {
+ $self->create_file( $self->filename );
+ }
};
sub create_file {
open( my $FILE, ">$file" ) || die $!;
close($FILE);
}
-
- no Moose;
}
-package main;
-use strict;
-use warnings;
-use Cwd;
-
-## Try to make sure we are in the test directory
-chdir 't' if ( Cwd::cwd() !~ m|/t$| );
-my $cwd = Cwd::cwd();
-
my $app = FileMaker->new(
- pidbase => $cwd,
- filename => "$cwd/im_alive",
+ pidbase => $CWD,
+ filename => $FILENAME,
);
-daemonize_ok( $app, 'child forked okay' );
+
+ok(!$app->status, '... the daemon is running');
+
+diag $$ if DEBUG;
+
+ok($app->start, '... daemon started');
+sleep(1); # give it a second ...
+
+ok($app->status, '... the daemon is running');
+
+my $pid = $app->pidfile->pid;
+isnt($pid, $$, '... the pid in our pidfile is correct (and not us)');
+
+if (DEBUG) {
+ diag `ps $pid`;
+ diag "Status is: " . $app->status_message;
+}
+
ok( -e $app->filename, "file exists" );
-ok( $app->stop( no_exit => 1 ), 'app stopped' );
-ok( not(-e $app->pidfile) , 'pidfile gone' );
-unlink( $app->filename );
+ok($app->status, '... the daemon is still running');
+
+if (DEBUG) {
+ diag `ps $pid`;
+ diag "Status is: " . $app->status_message;
+}
+
+ok( $app->stop, 'app stopped' );
+ok(!$app->status, '... the daemon is no longer running');
+
+if (DEBUG) {
+ diag `ps $pid`;
+ diag "Status is: " . $app->status_message;
+}
+
+ok( not(-e $app->pidfile->file) , 'pidfile gone' );
+
+unlink $FILENAME;
+unlink $ENV{MX_DAEMON_STDOUT};
+unlink $ENV{MX_DAEMON_STDERR};