X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=blobdiff_plain;f=lib%2FCatalyst%2FRestarter.pm;h=c08517a038ac3bd77523562383a898354db10f11;hb=1a50c493813f72e440cf1975d41f4080f411a547;hp=93dbfa7097332a48c1d16d03c585614acd9d55ff;hpb=a13b99da9aeb2204721a7708dfdf5c01d1ae802b;p=catagits%2FCatalyst-Devel.git diff --git a/lib/Catalyst/Restarter.pm b/lib/Catalyst/Restarter.pm index 93dbfa7..c08517a 100644 --- a/lib/Catalyst/Restarter.pm +++ b/lib/Catalyst/Restarter.pm @@ -2,32 +2,81 @@ package Catalyst::Restarter; use Moose; +use Cwd qw( abs_path ); use File::ChangeNotify; +use File::Spec; +use FindBin; use namespace::clean -except => 'meta'; -has restart_sub => ( +has start_sub => ( is => 'ro', isa => 'CodeRef', required => 1, ); +has argv => ( + is => 'ro', + isa => 'ArrayRef', + required => 1, +); + has _watcher => ( is => 'rw', isa => 'File::ChangeNotify::Watcher', ); +has _filter => ( + is => 'rw', + isa => 'RegexpRef', +); + has _child => ( is => 'rw', isa => 'Int', ); +sub pick_subclass { + my $class = shift; + + my $subclass; + $subclass = + defined $ENV{CATALYST_RESTARTER} + ? $ENV{CATALYST_RESTARTER} + : $^O eq 'MSWin32' + ? 'Win32' + : 'Forking'; + + $subclass = 'Catalyst::Restarter::' . $subclass; + + eval "use $subclass"; + die $@ if $@; + + return $subclass; +} + sub BUILD { my $self = shift; my $p = shift; - delete $p->{restart_sub}; + delete $p->{start_sub}; + + $p->{filter} ||= qr/(?:\/|^)(?![.#_]).+(?:\.yml$|\.yaml$|\.conf|\.pm)$/; + + my $app_root = abs_path( File::Spec->catdir( $FindBin::Bin, '..' ) ); + + # Monitor application root dir + $p->{directories} ||= $app_root; + + # exclude t/, root/ and hidden dirs + $p->{exclude} ||= [ + File::Spec->catdir($app_root, 't'), + File::Spec->catdir($app_root, 'root'), + qr(/\.[^/]*/?$), # match hidden dirs + ]; - $p->{filter} ||= qr/(?:\/|^)(?!\.\#).+(?:\.yml$|\.yaml$|\.conf|\.pm)$/; + # keep filter regexp to make sure we don't restart on deleted + # files or directories where we can't check -d + $self->_filter( $p->{filter} ); # We could make this lazily, but this lets us check that we # received valid arguments for the watcher up front. @@ -44,61 +93,52 @@ sub run_and_watch { $self->_restart_on_changes; } -sub _fork_and_start { - my $self = shift; - - if ( my $pid = fork ) { - $self->_child($pid); - } - else { - $self->restart_sub->(); - } -} - sub _restart_on_changes { my $self = shift; - my @events = $self->_watcher->wait_for_events(); - $self->_handle_events(@events); + # We use this loop in order to avoid having _handle_events() call back + # into this method. We used to do that, and the end result was that stack + # traces became longer and longer with every restart. Using this loop, the + # portion of the stack trace that covers this code does not grow. + while (1) { + my @events = $self->_watcher->wait_for_events(); + $self->_handle_events(@events); + } } sub _handle_events { my $self = shift; my @events = @_; - print STDERR "\n"; - print STDERR "Saw changes to the following files:\n"; - + my @files; + # Filter out any events which are the creation / deletion of directories + # so that creating an empty directory won't cause a restart for my $event (@events) { my $path = $event->path(); my $type = $event->type(); - - print STDERR " - $path ($type)\n"; + if ( ( $type ne 'delete' && -f $path ) + || ( $type eq 'delete' && $path =~ $self->_filter ) ) + { + push @files, { path => $path, type => $type }; + } } - print STDERR "\n"; - print STDERR "Attempting to restart the server\n\n"; + if (@files) { + print STDERR "\n"; + print STDERR "Saw changes to the following files:\n"; - $self->_kill_child; + for my $f (@files) { + my $path = $f->{path}; + my $type = $f->{type}; + print STDERR " - $path ($type)\n"; + } - $self->_fork_and_start; + print STDERR "\n"; + print STDERR "Attempting to restart the server\n\n"; - $self->_restart_on_changes; -} - -sub _kill_child { - my $self = shift; + $self->_kill_child; - return unless $self->_child; - - return unless kill 0, $self->_child; - - local $SIG{CHLD} = 'IGNORE'; - unless ( kill 'INT', $self->_child ) { - # The kill 0 thing does not work on Windows, but the restarter - # seems to work fine on Windows with this hack. - return if $^O eq 'MSWin32'; - die "Cannot send INT signal to ", $self->_child, ": $!"; + $self->_fork_and_start; } } @@ -116,42 +156,53 @@ __END__ =head1 NAME -Catalyst::Restarter - Uses Catalyst::Watcher to check for changed files and restart the server +Catalyst::Restarter - Uses File::ChangeNotify to check for changed files and restart the server =head1 SYNOPSIS - my $watcher = Catalyst::Watcher->new( - directory => '/path/to/MyApp', - regex => '\.yml$|\.yaml$|\.conf|\.pm$', - interval => 3, + my $class = Catalyst::Restarter->pick_subclass; + + my $restarter = $class->new( + directories => '/path/to/MyApp', + regex => '\.yml$|\.yaml$|\.conf|\.pm$', + start_sub => sub { ... } ); - while (1) { - my @changed_files = $watcher->watch(); - } + $restarter->run_and_watch; =head1 DESCRIPTION -This class monitors a directory of files for changes made to any file -matching a regular expression. It correctly handles new files added to the -application as well as files that are deleted. +This is the base class for all restarters, and it also provide +functionality for picking an appropriate restarter subclass for a +given platform. + +This class uses L to watch one or more directories +of files and restart the Catalyst server when any of those files +changes. =head1 METHODS -=head2 new ( directory => $path [, regex => $regex, delay => $delay ] ) +=head2 pick_subclass + +Returns the name of an appropriate subclass for the given platform. + +=head2 new ( start_sub => sub { ... }, ... ) + +This method creates a new restarter object, but should be called on a +subclass, not this class. -Creates a new Watcher object. +The "start_sub" argument is required. This is a subroutine reference +that can be used to start the Catalyst server. -=head2 find_changed_files +=head2 run_and_watch -Returns a list of files that have been added, deleted, or changed -since the last time watch was called. Each element returned is a hash -reference with two keys. The C key contains the filename, and -the C key contains one of "modified", "added", or "deleted". +This method forks, starts the server in a child process, and then +watched for changed files in the parent. When files change, it kills +the child, forks again, and starts a new server. =head1 SEE ALSO -L, L, +L, L =head1 AUTHORS