Explorar o código

Add autorestart feature.

George Baugh hai 5 meses
pai
achega
4e234bb8e5
Modificáronse 2 ficheiros con 56 adicións e 16 borrados
  1. 1 0
      bin/tpsgi
  2. 55 16
      lib/TPSGI.pm

+ 1 - 0
bin/tpsgi

@@ -31,6 +31,7 @@ tpsgi obeys a config file, ~/.tpsgi.ini which is in Config::Simple (key=value) f
     basedir     # Directory you want to chdir into on startup. Relative to TPSGI install dir.  Default '.'
     user        # Who to run as
     http_user   # Who's proxying this application (used for statics)
+    autoreload  # Whether or not to automatically reload the application when library files change. Default 0.
 
 The idea is that this application is going to be run in a chroot with the user's homedir being the application dir.
 This is why the various paths asked for are relative to the tPSGI install dir.

+ 55 - 16
lib/TPSGI.pm

@@ -9,7 +9,6 @@ use feature qw{state};
 
 use FindBin;
 
-use Cwd();
 use Carp::Always;
 
 use UUID();
@@ -39,6 +38,7 @@ use File::Basename qw{dirname basename};
 use Log::Dispatch;
 use Log::Dispatch::Screen;
 use Log::Dispatch::FileRotate;
+use Linux::Perl::inotify;
 
 use Config::Simple;
 
@@ -224,6 +224,9 @@ sub new {
     my @routes;
     my %aliases;
 
+    # Setup inotify watchers to know if we need to reload the application after this request.
+    $self->_watch_for_changes("$options{tpsgi_dir}/lib");
+
     # XXX TODO make these routes able to be fully qualified namespaces (::)!
     no strict 'refs';
     foreach my $route ( @{ $self->{routers} } ) {
@@ -239,6 +242,8 @@ sub new {
         my $libdir = dirname("$options{tpsgi_dir}/$route");
         push( @INC, $libdir );
 
+        $self->_watch_for_changes($libdir);
+
         local $@;
         $self->DEBUG("require $options{tpsgi_dir}/$route");
         my $success = eval { require "$options{tpsgi_dir}/$route"; 1; };
@@ -275,6 +280,7 @@ sub new {
     $self->{routes}    = \@routes;
     $self->{aliases}   = \%aliases;
     $self->{callbacks} = [];
+
     return $self;
 }
 
@@ -811,6 +817,9 @@ sub route {
     # Setup the CGI vars they expect IF requested
     local %ENV = ( %ENV, CGI::Emulate::PSGI->emulate_environment($env) ) if $route->{env};
 
+    # Make this a truly 'dynamic' application if requested.
+    $self->restart_if_changes() if $self->{autoreload};
+
     {
         my $output = $callback->( $self, $query );
 
@@ -1100,6 +1109,7 @@ sub get_config {
         basedir    => '.',
         http_user  => '',
 		tpsgi_dir  => Cwd::getcwd(),
+        autoreload => 0,
     );
     my $config_file = "$ENV{HOME}/.tpsgi.ini";
     if ( -f $config_file ) {
@@ -1169,21 +1179,9 @@ sub signal_restart_parent {
     $self->add_post_close_callback( \&_restart_parent );
 }
 
-# Instruct the parent to restart.  Normally this is HUP, but nginx-unit decides to be special.
-# Don't do anything if running NOHUP=1, which is useful when doing bulk operations
-sub _restart_parent {
-    my $parent = shift;
-    return if $ENV{NOHUP};
-
-    if ( $ENV{PSGI_ENGINE} && $ENV{PSGI_ENGINE} eq 'nginx-unit' ) {
-        my $conf         = Trog::Config->get();
-        my $nginx_socket = $conf->param('nginx-unit.socket');
-        my $client       = HTTP::Tiny::UNIX->new();
-        my $res          = $client->request( 'GET', "http:$nginx_socket//control/applications/tcms/restart" );
-        WARN("could not reload application (got $res->{status} from nginx-unit)!") unless $res->{status} == 200;
-        return 1;
-    }
-    $parent //= getppid;
+# Instruct tPSGI to reload.
+sub _restart_parent  {
+    my $parent = getppid;
     kill 'HUP', $parent;
 }
 
@@ -1262,4 +1260,45 @@ sub invalidate_render {
     unlink "$file";
 }
 
+sub _watch_for_changes {
+    my ($self, @dirs) = @_;
+
+    $self->{wds} //= [];
+
+    $self->{inf} //= Linux::Perl::inotify->new(flags => [qw{NONBLOCK}]);
+    foreach my $directory (@dirs) {
+        # Recursive scan for directories and setting up inotifies
+        push(@dirs, _readdir( $directory ));
+        $self->INFO( "Watching $directory for changes\n" );
+        push(@{$self->{wds}}, $self->{inf}->add( path => $directory, events => [qw{CREATE MODIFY}] ));
+    }
+}
+
+sub restart_if_changes {
+    my $self = shift;
+
+    my @result = $self->{inf}->read();
+    my $had_changes = 0;
+
+    foreach my $res (@result) {
+        if ($res->{name} =~ m/\.pm$/) {
+            $had_changes++;
+            last;
+        }
+    }
+
+    $self->INFO("Relevant Change in libdirs detected, reloading\n");
+    foreach my $wd (@{$self->{wds}}) { $self->{inf}->remove($wd) }
+    $self->signal_restart_parent();
+    return 0;
+}
+
+sub _readdir {
+    my $dir = shift;
+    opendir(my $dh, $dir);
+    my @dirs = map { "$dir/$_" } grep { -d "$dir/$_" && !m/^\.+$/ } readdir($dh);
+    closedir($dh);
+    return @dirs;
+}
+
 1;