Quellcode durchsuchen

Correctly hup the server AFTER closing stdout

This fixes a longstanding bug where we'd race the server getting hup'd.
George Baugh vor 5 Monaten
Ursprung
Commit
7d9482eba1
1 geänderte Dateien mit 107 neuen und 6 gelöschten Zeilen
  1. 107 6
      lib/TPSGI.pm

+ 107 - 6
lib/TPSGI.pm

@@ -259,6 +259,7 @@ sub new {
 
     $self->{routes}  = \@routes;
     $self->{aliases} = \%aliases;
+	$self->{callbacks} = [];
     return $self;
 }
 
@@ -299,6 +300,7 @@ sub serve {
 
     push( @headers, 'Accept-Ranges' => 'bytes' );
 
+    $self->DEBUG("FETCH $path");
     my $mt         = ( stat($path) )[9];
     my $sz         = ( stat(_) )[7];
     my @gm         = gmtime($mt);
@@ -418,6 +420,30 @@ sub _generic {
     return [$code, [$ct => $content_types{html}], ["$type"]];
 }
 
+=head2 redirect, redirect_permanent, see_also
+
+Redirects to the provided page.
+
+=cut
+
+sub redirect {
+	my ($self, $to) = @_;
+    $self->INFO("redirect: $to");
+    return [ 302, [ "Location" => $to, "Content-Length" => 0 ], [''] ];
+}
+
+sub redirect_permanent {
+	my ($self, $to) = @_;
+    $self->INFO("permanent redirect: $to");
+    return [ 301, [ "Location" => $to, "Content-Length" => 0 ], [''] ];
+}
+
+sub see_also {
+	my ($self, $to) = @_;
+    $self->INFO("see also: $to");
+    return [ 303, [ "Location" => $to, "Content-Length" => 0 ], [''] ];
+}
+
 =head2 notfound, forbidden, badrequest, toolong, error
 
 If you need to return these HTTP errors, return these within a route:
@@ -720,7 +746,8 @@ sub route {
     {
         my $output = $callback->($self, $query);
 
-        # If it's streaming, just hand it off
+        # If it's streaming, just hand it off.
+		# It's up to the caller to handle things like running post-close callbacks in this event.
         return $output if ref $output eq 'CODE';
 
         die "$fullpath returned no or malformed data!" unless ref $output eq 'ARRAY' && @$output == 3;
@@ -733,6 +760,10 @@ sub route {
         # Append server-timing headers if they aren't present
         my $tot = tv_interval($start) * 1000;
         push( @{ $output->[1] }, 'Server-Timing' => "app;dur=$tot" ) unless List::Util::any { $_ eq 'Server-Timing' } @{ $output->[1] };
+
+		# In the event that we have post-close callbacks, go ahead and run them.
+		return $self->stream_raw_psgi($output, $query) if @{$self->{callbacks}};
+
         return $output;
     }
 }
@@ -791,12 +822,42 @@ sub cgi {
     };
 }
 
-sub stream_raw_and_i_mean_it {
-    my ($self, $data, $output, $last_fetch, $to_fork, $callback) = @_;
-    return sub {
-        my $responder = shift;
+# Used primarily when we have post-close callbacks
+sub stream_raw_psgi {
+    my ($self, $response, $data) = @_;
 
-    };
+    my $time_to_here = tv_interval($data->{start});
+    $self->DEBUG("Routing took $time_to_here s");
+    my $post_routing = [gettimeofday];
+
+    my $fh = $self->{filehandle};
+
+    print $fh "HTTP/1.1 $response->[0]\n";
+
+	# Emit the rest of the headers
+	foreach (my $i=0; $i < @{$response->[1]}; $i += 2)  {
+		my $header = $response->[1][$i];
+		my $value  = $response->[1][$i+1];
+		print $fh "$header: $value\n";
+	}
+	print $fh "\n";
+
+	# Emit the body
+	print $fh $response->[2][0];
+
+    close($fh);
+
+    $self->DEBUG("Response written in ".(tv_interval($post_routing))." s");
+	while( my $callback = shift @{$self->{callbacks}}) {
+		if (ref $callback eq 'CODE') {
+			my $post_start = [gettimeofday];
+			local $@;
+			eval { $callback->() };
+			$self->ERROR("Post-close callback encountered exception: $@") if $@;
+			$self->DEBUG("Post-close callback took ".(tv_interval($post_start))." s");
+		}
+	}
+    exit 0;
 }
 
 sub stream_raw_http {
@@ -986,4 +1047,44 @@ sub build_server_timing {
     return join(', ', @st);
 }
 
+=head2 add_post_close_callback()
+
+Add a thing to do after closing stdout; useful for housekeeping that doesn't need to hold up pageload.
+
+=cut
+
+sub add_post_close_callback {
+	my ($self, $cb) = @_;
+	push(@{$self->{callbacks}}, $cb);
+}
+
+=head2 signal_restart_parent()
+
+Instruct tPSGI to reload after closing stdout.
+
+=cut
+
+sub signal_restart_parent {
+	my ($self) = @_;
+	$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;
+    kill 'HUP', $parent;
+}
+
 1;