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