cleaning up stuff
[gitmo/MooseX-Daemonize.git] / lib / MooseX / Daemonize.pm
CommitLineData
a4952679 1package MooseX::Daemonize;
a392fa53 2use strict; # because Kwalitee is pedantic
a4952679 3use Moose::Role;
8ac4733f 4use MooseX::Types::Path::Class;
a392fa53 5
d9e417f4 6our $VERSION = 0.05;
a4952679 7
4327fe98 8with 'MooseX::Daemonize::WithPidFile',
9 'MooseX::Getopt';
92cf56b7 10
11use constant OK => 0;
12use constant ERROR => 1;
a4952679 13
14has progname => (
5b9ebe08 15 metaclass => 'Getopt',
4327fe98 16 isa => 'Str',
17 is => 'ro',
18 lazy => 1,
19 required => 1,
20 default => sub {
a392fa53 21 ( my $name = lc $_[0]->meta->name ) =~ s/::/_/g;
22 return $name;
23 },
a4952679 24);
25
d9e417f4 26has pidbase => (
5b9ebe08 27 metaclass => 'Getopt',
4327fe98 28 isa => 'Path::Class::Dir',
29 is => 'ro',
30 coerce => 1,
5b9ebe08 31 required => 1,
4327fe98 32 lazy => 1,
33 default => sub { Path::Class::Dir->new('var', 'run') },
24a6a660 34);
35
d9e417f4 36has basedir => (
5b9ebe08 37 metaclass => 'Getopt',
4327fe98 38 isa => 'Path::Class::Dir',
39 is => 'ro',
40 coerce => 1,
41 required => 1,
42 lazy => 1,
43 default => sub { Path::Class::Dir->new('/') },
a4952679 44);
45
46has foreground => (
2eced271 47 metaclass => 'Getopt',
cbff8e52 48 cmd_aliases => 'f',
a4952679 49 isa => 'Bool',
50 is => 'ro',
51 default => sub { 0 },
52);
53
b916501e 54has stop_timeout => (
5b9ebe08 55 metaclass => 'Getopt',
4327fe98 56 isa => 'Int',
57 is => 'rw',
58 default => sub { 2 }
b916501e 59);
60
5b9ebe08 61# internal book-keeping
62
63has status_message => (
64 metaclass => 'NoGetopt',
65 isa => 'Str',
66 is => 'rw',
67 clearer => 'clear_status_message',
68);
69
70has exit_code => (
71 metaclass => 'NoGetopt',
72 isa => 'Int',
73 is => 'rw',
74 clearer => 'clear_exit_code',
75);
76
4327fe98 77# methods ...
78
79## PID file related stuff ...
80
d9e417f4 81sub init_pidfile {
82 my $self = shift;
83 my $file = $self->pidbase . '/' . $self->progname . '.pid';
84 confess "Cannot write to $file" unless (-e $file ? -w $file : -w $self->pidbase);
d8985b7d 85 MooseX::Daemonize::Pid::File->new( file => $file );
d9e417f4 86}
87
5b9ebe08 88# backwards compat,
4327fe98 89sub check { (shift)->pidfile->is_running }
90sub save_pid { (shift)->pidfile->write }
91sub remove_pid { (shift)->pidfile->remove }
92sub get_pid { (shift)->pidfile->pid }
93
94## signal handling ...
95
96sub setup_signals {
97 my $self = shift;
4a24225a 98 $SIG{'INT'} = sub { $self->shutdown };
99# I can't think of a sane default here really ...
100# $SIG{'HUP'} = sub { $self->handle_sighup };
4327fe98 101}
102
4a24225a 103sub shutdown {
104 my $self = shift;
105 $self->pidfile->remove if $self->pidfile->pid == $$;
106 exit(0);
107}
4327fe98 108
109## daemon control methods ...
110
a4952679 111sub start {
5b9ebe08 112 my $self = shift;
113
114 $self->clear_status_message;
115 $self->clear_exit_code;
116
117 if ($self->pidfile->is_running) {
92cf56b7 118 $self->exit_code(OK);
119 $self->status_message('Daemon is already running with pid (' . $self->pidfile->pid . ')');
5b9ebe08 120 return !($self->exit_code);
121 }
8ac4733f 122
5b9ebe08 123 if ($self->foreground) {
124 $self->is_daemon(1);
125 }
126 else {
127 eval { $self->daemonize };
128 if ($@) {
92cf56b7 129 $self->exit_code(ERROR);
5b9ebe08 130 $self->status_message('Start failed : ' . $@);
131 return !($self->exit_code);
132 }
133 }
cbff8e52 134
5b9ebe08 135 unless ($self->is_daemon) {
92cf56b7 136 $self->exit_code(OK);
5b9ebe08 137 $self->status_message('Start succeeded');
138 return !($self->exit_code);
139 }
140
141 $self->pidfile->pid($$);
cbff8e52 142
24a6a660 143 # Change to basedir
144 chdir $self->basedir;
fa2b72a4 145
ff5cee29 146 $self->pidfile->write;
3543c999 147 $self->setup_signals;
148 return $$;
149}
150
5b9ebe08 151sub status {
152 my $self = shift;
153
154 $self->clear_status_message;
155 $self->clear_exit_code;
156
157 if ($self->pidfile->is_running) {
92cf56b7 158 $self->exit_code(OK);
5b9ebe08 159 $self->status_message('Daemon is running with pid (' . $self->pidfile->pid . ')');
160 }
161 else {
92cf56b7 162 $self->exit_code(ERROR);
5b9ebe08 163 $self->status_message('Daemon is not running with pid (' . $self->pidfile->pid . ')');
164 }
165
166 return !($self->exit_code);
167}
168
4327fe98 169sub restart {
5b9ebe08 170 my $self = shift;
171
172 $self->clear_status_message;
173 $self->clear_exit_code;
174
175 unless ($self->stop) {
92cf56b7 176 $self->exit_code(ERROR);
5b9ebe08 177 $self->status_message('Restart (Stop) failed : ' . $@);
178 }
179
180 unless ($self->start) {
92cf56b7 181 $self->exit_code(ERROR);
5b9ebe08 182 $self->status_message('Restart (Start) failed : ' . $@);
183 }
184
92cf56b7 185 if ($self->exit_code == OK) {
186 $self->exit_code(OK);
187 $self->status_message("Restart successful");
188 }
5b9ebe08 189
190 return !($self->exit_code);
4327fe98 191}
192
b916501e 193# Make _kill *really* private
194my $_kill;
195
3543c999 196sub stop {
5b9ebe08 197 my $self = shift;
198
199 $self->clear_status_message;
200 $self->clear_exit_code;
201
202 # if the pid is not running
203 # then we dont need to stop
204 # anything ...
205 if ($self->pidfile->is_running) {
206
207 # if we are foreground, then
208 # no need to try and kill
209 # ourselves
210 unless ($self->foreground) {
211
212 # kill the process ...
213 eval { $self->$_kill($self->pidfile->pid) };
214 # and complain if we can't ...
215 if ($@) {
92cf56b7 216 $self->exit_code(ERROR);
5b9ebe08 217 $self->status_message('Stop failed : ' . $@);
218 }
219 # or gloat if we succeed ..
220 else {
92cf56b7 221 $self->exit_code(OK);
5b9ebe08 222 $self->status_message('Stop succeeded');
223 }
224
225 }
226
227 # clean up ...
228 eval { $self->pidfile->remove };
229 if ($@) {
230 warn "Could not remove pidfile ("
231 . $self->pidfile->file
232 . ") because : $!";
233 }
234
235 }
236 else {
237 # this just returns the OK
238 # exit code for now, but
239 # we should make this overridable
92cf56b7 240 $self->exit_code(OK);
5b9ebe08 241 $self->status_message("Not running");
242 }
243
244 # if we are returning to our script
245 # then we actually need the opposite
246 # of what the system/OS expects
247 return !($self->exit_code);
3543c999 248}
249
b916501e 250$_kill = sub {
a4952679 251 my ( $self, $pid ) = @_;
2361a590 252 return unless $pid;
3543c999 253 unless ( CORE::kill 0 => $pid ) {
3543c999 254 # warn "$pid already appears dead.";
255 return;
256 }
257
258 if ( $pid eq $$ ) {
4a24225a 259 die "$pid is us! Can't commit suicide.";
a4952679 260 }
261
b916501e 262 my $timeout = $self->stop_timeout;
a4952679 263
b916501e 264 # kill 0 => $pid returns 0 if the process is dead
265 # $!{EPERM} could also be true if we cant kill it (permission error)
a4952679 266
b916501e 267 # Try SIGINT ... 2s ... SIGTERM ... 2s ... SIGKILL ... 3s ... UNDEAD!
268 for ( [ 2, $timeout ], [15, $timeout], [9, $timeout * 1.5] ) {
ea9485d8 269 my ($signal, $timeout) = @$_;
270 $timeout = int $timeout;
5b9ebe08 271
ea9485d8 272 CORE::kill($signal, $pid);
5b9ebe08 273
b916501e 274 last unless CORE::kill 0 => $pid or $!{EPERM};
5b9ebe08 275
ea9485d8 276 while ($timeout) {
277 sleep(1);
278 last unless CORE::kill 0 => $pid or $!{EPERM};
279 $timeout--;
280 }
a4952679 281 }
282
b916501e 283 return unless ( CORE::kill 0 => $pid or $!{EPERM} );
284
285 # IF it is still running
d9e417f4 286 Carp::carp "$pid doesn't seem to want to die."; # AHH EVIL DEAD!
b916501e 287};
a4952679 288
2891;
290__END__
291
8ac4733f 292=pod
293
a4952679 294=head1 NAME
295
892a51e6 296MooseX::Daemonize - Role for daemonizing your Moose based application
a4952679 297
298=head1 VERSION
299
4327fe98 300This document describes MooseX::Daemonize version 0.05
a4952679 301
302=head1 SYNOPSIS
303
4327fe98 304 package My::Daemon;
a4952679 305 use Moose;
5b9ebe08 306
a4952679 307 with qw(MooseX::Daemonize);
5b9ebe08 308
4327fe98 309 # ... define your class ....
5b9ebe08 310
311 after start => sub {
4327fe98 312 my $self = shift;
313 return unless $self->is_daemon;
314 # your daemon code here ...
315 };
a4952679 316
5b9ebe08 317 # then in your script ...
318
4327fe98 319 my $daemon = My::Daemon->new_with_options();
5b9ebe08 320
4327fe98 321 my ($command) = @{$daemon->extra_argv}
322 defined $command || die "No command specified";
5b9ebe08 323
324 $daemon->start if $command eq 'start';
325 $daemon->status if $command eq 'status';
326 $daemon->restart if $command eq 'restart';
327 $daemon->stop if $command eq 'stop';
328
92cf56b7 329 warn($daemon->status_message);
5b9ebe08 330 exit($daemon->exit_code);
331
a4952679 332=head1 DESCRIPTION
333
b916501e 334Often you want to write a persistant daemon that has a pid file, and responds
5b9ebe08 335appropriately to Signals. This module provides a set of basic roles as an
4327fe98 336infrastructure to do that.
a4952679 337
338=head1 ATTRIBUTES
339
4327fe98 340This list includes attributes brought in from other roles as well
341we include them here for ease of documentation. All of these attributes
5b9ebe08 342are settable though L<MooseX::Getopt>'s command line handling, with the
4327fe98 343exception of C<is_daemon>.
344
a4952679 345=over
346
4327fe98 347=item I<progname Path::Class::Dir | Str>
a4952679 348
4327fe98 349The name of our daemon, defaults to C<$package_name =~ s/::/_/>;
a4952679 350
4327fe98 351=item I<pidbase Path::Class::Dir | Str>
a4952679 352
4327fe98 353The base for our bid, defaults to C</var/run/$progname>
a4952679 354
4327fe98 355=item I<pidfile MooseX::Daemonize::Pid::File | Str>
a4952679 356
4327fe98 357The file we store our PID in, defaults to C</var/run/$progname>
a4952679 358
4327fe98 359=item I<foreground Bool>
a4952679 360
5b9ebe08 361If true, the process won't background. Useful for debugging. This option can
b916501e 362be set via Getopt's -f.
a4952679 363
4327fe98 364=item I<is_daemon Bool>
365
5b9ebe08 366If true, the process is the backgrounded daemon process, if false it is the
367parent process. This is useful for example in an C<after 'start' => sub { }>
368block.
e7a196e7 369
4327fe98 370B<NOTE:> This option is explicitly B<not> available through L<MooseX::Getopt>.
b916501e 371
4327fe98 372=item I<stop_timeout>
b916501e 373
374Number of seconds to wait for the process to stop, before trying harder to kill
4327fe98 375it. Defaults to 2 seconds.
e7a196e7 376
a4952679 377=back
378
5b9ebe08 379These are the internal attributes, which are not available through MooseX::Getopt.
380
381=over 4
382
383=item I<exit_code Int>
384
92cf56b7 385=item I<status_message Str>
5b9ebe08 386
387=back
388
389=head1 METHODS
a4952679 390
4327fe98 391=head2 Daemon Control Methods
392
5b9ebe08 393These methods can be used to control the daemon behavior. Every effort
394has been made to have these methods DWIM (Do What I Mean), so that you
395can focus on just writing the code for your daemon.
a4952679 396
5b9ebe08 397Extending these methods is best done with the L<Moose> method modifiers,
4327fe98 398such as C<before>, C<after> and C<around>.
399
400=over 4
401
402=item B<start>
a4952679 403
404Setup a pidfile, fork, then setup the signal handlers.
405
4327fe98 406=item B<stop>
a4952679 407
408Stop the process matching the pidfile, and unlinks the pidfile.
409
4327fe98 410=item B<restart>
a4952679 411
4327fe98 412Literally this is:
a4952679 413
414 $self->stop();
415 $self->start();
416
5b9ebe08 417=item B<status>
418
92cf56b7 419=item B<shutdown>
420
4327fe98 421=back
422
5b9ebe08 423
4327fe98 424=head2 Pidfile Handling Methods
425
426=over 4
427
428=item B<init_pidfile>
429
430This method will create a L<MooseX::Daemonize::Pid::File> object and tell
431it to store the PID in the file C<$pidbase/$progname.pid>.
432
433=item B<check>
434
5b9ebe08 435This checks to see if the daemon process is currently running by checking
4327fe98 436the pidfile.
a4952679 437
4327fe98 438=item B<get_pid>
a4952679 439
4327fe98 440Returns the PID of the daemon process.
a4952679 441
4327fe98 442=item B<save_pid>
a4952679 443
4327fe98 444Write the pidfile.
445
446=item B<remove_pid>
447
448Removes the pidfile.
449
450=back
451
452=head2 Signal Handling Methods
453
454=over 4
455
456=item B<setup_signals>
457
5b9ebe08 458Setup the signal handlers, by default it only sets up handlers for SIGINT and
4327fe98 459SIGHUP. If you wish to add more signals just use the C<after> method modifier
460and add them.
461
462=item B<handle_sigint>
a4952679 463
cecbee2d 464Handle a INT signal, by default calls C<$self->stop()>
a4952679 465
4327fe98 466=item B<handle_sighup>
a4952679 467
cecbee2d 468Handle a HUP signal. By default calls C<$self->restart()>
a4952679 469
4327fe98 470=back
471
472=head2 Introspection
473
474=over 4
475
a4952679 476=item meta()
477
cecbee2d 478The C<meta()> method from L<Class::MOP::Class>
a4952679 479
480=back
481
482=head1 DEPENDENCIES
483
4327fe98 484L<Moose>, L<MooseX::Getopt>, L<MooseX::Types::Path::Class> and L<POSIX>
a4952679 485
486=head1 INCOMPATIBILITIES
487
4327fe98 488None reported. Although obviously this will not work on Windows.
a4952679 489
490=head1 BUGS AND LIMITATIONS
491
a4952679 492No bugs have been reported.
493
494Please report any bugs or feature requests to
495C<bug-acme-dahut-call@rt.cpan.org>, or through the web interface at
496L<http://rt.cpan.org>.
497
498=head1 SEE ALSO
499
4327fe98 500L<Proc::Daemon>, L<Daemon::Generic>
a4952679 501
92cf56b7 502=head1 AUTHORS
a4952679 503
504Chris Prather C<< <perigrin@cpan.org> >>
505
92cf56b7 506Stevan Little C<< <stevan.little@iinteractive.com> >>
507
7ada91b8 508=head1 THANKS
509
5b9ebe08 510Mike Boyko, Matt S. Trout, Stevan Little, Brandon Black, Ash Berlin and the
b916501e 511#moose denzians
a4952679 512
637573c4 513Some bug fixes sponsored by Takkle Inc.
514
a4952679 515=head1 LICENCE AND COPYRIGHT
516
5b9ebe08 517Copyright (c) 2007, Chris Prather C<< <perigrin@cpan.org> >>. All rights
b916501e 518reserved.
a4952679 519
520This module is free software; you can redistribute it and/or
521modify it under the same terms as Perl itself. See L<perlartistic>.
522
a4952679 523=head1 DISCLAIMER OF WARRANTY
524
525BECAUSE THIS SOFTWARE IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
526FOR THE SOFTWARE, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
527OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
528PROVIDE THE SOFTWARE "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER
529EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
530WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE
531ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE SOFTWARE IS WITH
532YOU. SHOULD THE SOFTWARE PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL
533NECESSARY SERVICING, REPAIR, OR CORRECTION.
534
535IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
536WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
537REDISTRIBUTE THE SOFTWARE AS PERMITTED BY THE ABOVE LICENCE, BE
538LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL,
539OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE
540THE SOFTWARE (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
541RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
542FAILURE OF THE SOFTWARE TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
543SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
544SUCH DAMAGES.
8ac4733f 545
546=cut