Browse Source

Add autorestart feature.

George Baugh 5 tháng trước cách đây
mục cha
commit
4e234bb8e5
2 tập tin đã thay đổi với 56 bổ sung và 16 xóa
  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 '.'
     basedir     # Directory you want to chdir into on startup. Relative to TPSGI install dir.  Default '.'
     user        # Who to run as
     user        # Who to run as
     http_user   # Who's proxying this application (used for statics)
     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.
 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.
 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 FindBin;
 
 
-use Cwd();
 use Carp::Always;
 use Carp::Always;
 
 
 use UUID();
 use UUID();
@@ -39,6 +38,7 @@ use File::Basename qw{dirname basename};
 use Log::Dispatch;
 use Log::Dispatch;
 use Log::Dispatch::Screen;
 use Log::Dispatch::Screen;
 use Log::Dispatch::FileRotate;
 use Log::Dispatch::FileRotate;
+use Linux::Perl::inotify;
 
 
 use Config::Simple;
 use Config::Simple;
 
 
@@ -224,6 +224,9 @@ sub new {
     my @routes;
     my @routes;
     my %aliases;
     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 (::)!
     # XXX TODO make these routes able to be fully qualified namespaces (::)!
     no strict 'refs';
     no strict 'refs';
     foreach my $route ( @{ $self->{routers} } ) {
     foreach my $route ( @{ $self->{routers} } ) {
@@ -239,6 +242,8 @@ sub new {
         my $libdir = dirname("$options{tpsgi_dir}/$route");
         my $libdir = dirname("$options{tpsgi_dir}/$route");
         push( @INC, $libdir );
         push( @INC, $libdir );
 
 
+        $self->_watch_for_changes($libdir);
+
         local $@;
         local $@;
         $self->DEBUG("require $options{tpsgi_dir}/$route");
         $self->DEBUG("require $options{tpsgi_dir}/$route");
         my $success = eval { require "$options{tpsgi_dir}/$route"; 1; };
         my $success = eval { require "$options{tpsgi_dir}/$route"; 1; };
@@ -275,6 +280,7 @@ sub new {
     $self->{routes}    = \@routes;
     $self->{routes}    = \@routes;
     $self->{aliases}   = \%aliases;
     $self->{aliases}   = \%aliases;
     $self->{callbacks} = [];
     $self->{callbacks} = [];
+
     return $self;
     return $self;
 }
 }
 
 
@@ -811,6 +817,9 @@ sub route {
     # Setup the CGI vars they expect IF requested
     # Setup the CGI vars they expect IF requested
     local %ENV = ( %ENV, CGI::Emulate::PSGI->emulate_environment($env) ) if $route->{env};
     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 );
         my $output = $callback->( $self, $query );
 
 
@@ -1100,6 +1109,7 @@ sub get_config {
         basedir    => '.',
         basedir    => '.',
         http_user  => '',
         http_user  => '',
 		tpsgi_dir  => Cwd::getcwd(),
 		tpsgi_dir  => Cwd::getcwd(),
+        autoreload => 0,
     );
     );
     my $config_file = "$ENV{HOME}/.tpsgi.ini";
     my $config_file = "$ENV{HOME}/.tpsgi.ini";
     if ( -f $config_file ) {
     if ( -f $config_file ) {
@@ -1169,21 +1179,9 @@ sub signal_restart_parent {
     $self->add_post_close_callback( \&_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;
     kill 'HUP', $parent;
 }
 }
 
 
@@ -1262,4 +1260,45 @@ sub invalidate_render {
     unlink "$file";
     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;
 1;