81295f1da53dfdc7b44b2ea06c6f8d1a1833aa06
[catagits/Catalyst-Devel.git] / lib / Catalyst / Restarter.pm
1 package Catalyst::Restarter;
2
3 use Moose;
4
5 use Cwd qw( abs_path );
6 use File::ChangeNotify;
7 use File::Spec;
8 use FindBin;
9 use Catalyst::Utils;
10 use namespace::clean -except => 'meta';
11
12 has start_sub => (
13     is       => 'ro',
14     isa      => 'CodeRef',
15     required => 1,
16 );
17
18 has argv =>  (
19     is       => 'ro',
20     isa      => 'ArrayRef',
21     required => 1,
22 );
23
24 has _watcher => (
25     is      => 'rw',
26     isa     => 'File::ChangeNotify::Watcher',
27     clearer => '_clear_watcher',
28 );
29
30 has _filter => (
31     is      => 'rw',
32     isa     => 'RegexpRef',
33 );
34
35 has _child => (
36     is  => 'rw',
37     isa => 'Int',
38 );
39
40 sub pick_subclass {
41     my $class = shift;
42
43     my $subclass;
44     $subclass =
45         defined $ENV{CATALYST_RESTARTER}
46             ? $ENV{CATALYST_RESTARTER}
47             :  $^O eq 'MSWin32'
48             ? 'Win32'
49             : 'Forking';
50
51     $subclass = 'Catalyst::Restarter::' . $subclass;
52
53     Catalyst::Utils::ensure_class_loaded($subclass);
54
55     return $subclass;
56 }
57
58 sub BUILD {
59     my $self = shift;
60     my $p    = shift;
61
62     delete $p->{start_sub};
63
64     $p->{filter} ||= qr/(?:\/|^)(?![.#_]).+(?:\.yml$|\.yaml$|\.conf|\.pm)$/;
65
66     my $app_root = abs_path( File::Spec->catdir( $FindBin::Bin, '..' ) );
67
68     # Monitor application root dir
69     $p->{directories} ||= $app_root;
70
71     # exclude t/, root/ and hidden dirs
72     $p->{exclude} ||= [
73         File::Spec->catdir($app_root, 't'),
74         File::Spec->catdir($app_root, 'root'),
75         qr(/\.[^/]*/?$),    # match hidden dirs
76     ];
77
78     # keep filter regexp to make sure we don't restart on deleted
79     # files or directories where we can't check -d
80     $self->_filter( $p->{filter} );
81
82     # We could make this lazily, but this lets us check that we
83     # received valid arguments for the watcher up front.
84     $self->_watcher( File::ChangeNotify->instantiate_watcher( %{$p} ) );
85 }
86
87 sub run_and_watch {
88     my $self = shift;
89
90     $self->_fork_and_start;
91
92     return unless $self->_child;
93
94     $self->_restart_on_changes;
95 }
96
97 sub _restart_on_changes {
98     my $self = shift;
99
100     # We use this loop in order to avoid having _handle_events() call back
101     # into this method. We used to do that, and the end result was that stack
102     # traces became longer and longer with every restart. Using this loop, the
103     # portion of the stack trace that covers this code does not grow.
104     while (1) {
105         my @events = $self->_watcher->wait_for_events();
106         $self->_handle_events(@events);
107     }
108 }
109
110 sub _handle_events {
111     my $self   = shift;
112     my @events = @_;
113
114     my @files;
115     # Filter out any events which are the creation / deletion of directories
116     # so that creating an empty directory won't cause a restart
117     for my $event (@events) {
118         my $path = $event->path();
119         my $type = $event->type();
120         if ( (    ( $type ne 'delete' && -f $path )
121                || ( $type eq 'delete' )
122              )
123              && ( $path =~ $self->_filter )
124         ) {
125             push @files, { path => $path, type => $type };
126         }
127     }
128
129     if (@files) {
130         print STDERR "\n";
131         print STDERR "Saw changes to the following files:\n";
132
133         for my $f (@files) {
134             my $path = $f->{path};
135             my $type = $f->{type};
136             print STDERR " - $path ($type)\n";
137         }
138
139         print STDERR "\n";
140         print STDERR "Attempting to restart the server\n\n";
141
142         $self->_kill_child;
143
144         $self->_fork_and_start;
145     }
146 }
147
148 sub DEMOLISH {
149     my $self = shift;
150
151     $self->_kill_child;
152 }
153
154 __PACKAGE__->meta->make_immutable;
155
156 1;
157
158 __END__
159
160 =head1 NAME
161
162 Catalyst::Restarter - Uses File::ChangeNotify to check for changed files and restart the server
163
164 =head1 SYNOPSIS
165
166     my $class = Catalyst::Restarter->pick_subclass;
167
168     my $restarter = $class->new(
169         directories => '/path/to/MyApp',
170         regex       => '\.yml$|\.yaml$|\.conf|\.pm$',
171         start_sub => sub { ... }
172     );
173
174     $restarter->run_and_watch;
175
176 =head1 DESCRIPTION
177
178 This is the base class for all restarters, and it also provide
179 functionality for picking an appropriate restarter subclass for a
180 given platform.
181
182 This class uses L<File::ChangeNotify> to watch one or more directories
183 of files and restart the Catalyst server when any of those files
184 changes.
185
186 =head1 METHODS
187
188 =head2 pick_subclass
189
190 Returns the name of an appropriate subclass for the given platform.
191
192 =head2 new ( start_sub => sub { ... }, ... )
193
194 This method creates a new restarter object, but should be called on a
195 subclass, not this class.
196
197 The "start_sub" argument is required. This is a subroutine reference
198 that can be used to start the Catalyst server.
199
200 =head2 run_and_watch
201
202 This method forks, starts the server in a child process, and then
203 watched for changed files in the parent. When files change, it kills
204 the child, forks again, and starts a new server.
205
206 =head1 SEE ALSO
207
208 L<Catalyst>, L<File::ChangeNotify>
209
210 =head1 AUTHORS
211
212 Catalyst Contributors, see Catalyst.pm
213
214 =head1 COPYRIGHT
215
216 This program is free software, you can redistribute it and/or modify
217 it under the same terms as Perl itself.
218
219 =cut