]> Sergey Matveev's repositories - public-inbox.git/blobdiff - lib/PublicInbox/Qspawn.pm
qspawn: fix EINTR with generic PSGI servers
[public-inbox.git] / lib / PublicInbox / Qspawn.pm
index da7cd74fe8cb1628f6884b36d29b61c9c230ea7e..f2beb4b21febc96f8a22862f835a3ffc1244e405 100644 (file)
@@ -40,7 +40,7 @@ my $def_limiter;
 # $cmd_env is the environ for the child process (not PSGI env)
 # $opt can include redirects and perhaps other process spawning options
 # {qsp_err} is an optional error buffer callers may access themselves
-sub new ($$$;) {
+sub new {
        my ($class, $cmd, $cmd_env, $opt) = @_;
        bless { args => [ $cmd, $cmd_env, $opt ] }, $class;
 }
@@ -106,10 +106,12 @@ sub finalize ($$) {
                return unless $@;
                warn "E: $@"; # hope qspawn.wcb can handle it
        }
+       return if $self->{passed}; # another command chained it
        if (my $wcb = delete $env->{'qspawn.wcb'}) {
                # have we started writing, yet?
+               my $code = delete $env->{'qspawn.fallback'} // 500;
                require PublicInbox::WwwStatic;
-               $wcb->(PublicInbox::WwwStatic::r(500));
+               $wcb->(PublicInbox::WwwStatic::r($code));
        }
 }
 
@@ -132,7 +134,7 @@ sub start ($$$) {
        }
 }
 
-sub psgi_qx_init_cb {
+sub psgi_qx_init_cb { # this may be PublicInbox::HTTPD::Async {cb}
        my ($self) = @_;
        my $async = delete $self->{async}; # PublicInbox::HTTPD::Async
        my ($r, $buf);
@@ -187,7 +189,7 @@ sub event_step {
        my ($self, $err) = @_; # $err: $!
        warn "psgi_{return,qx} $err" if defined($err);
        finish($self);
-       my ($fh, $qx_fh) = delete(@$self{qw(fh qx_fh)});
+       my ($fh, $qx_fh) = delete(@$self{qw(qfh qx_fh)});
        $fh->close if $fh; # async-only (psgi_return)
 }
 
@@ -199,7 +201,7 @@ sub rd_hdr ($) {
        my $total_rd = 0;
        my $hdr_buf = $self->{hdr_buf};
        my ($ph_cb, $ph_arg) = @{$self->{parse_hdr}};
-       do {
+       until (defined($ret)) {
                my $r = sysread($self->{rpipe}, $$hdr_buf, 4096,
                                length($$hdr_buf));
                if (defined($r)) {
@@ -208,6 +210,13 @@ sub rd_hdr ($) {
                        if ($@) {
                                warn "parse_hdr: $@";
                                $ret = [ 500, [], [ "Internal error\n" ] ];
+                       } elsif (!defined($ret) && !$r) {
+                               my $cmd = $self->{cmd} // [ '(?)' ];
+                               my $env = $self->{psgi_env};
+                               warn <<EOM;
+EOF parsing headers from @$cmd ($self->{psgi_env}->{REQUEST_URI})
+EOM
+                               $ret = [ 500, [], [ "Internal error\n" ] ];
                        }
                } else {
                        # caller should notify us when it's ready:
@@ -216,16 +225,21 @@ sub rd_hdr ($) {
                        warn "error reading header: $!";
                        $ret = [ 500, [], [ "Internal error\n" ] ];
                }
-       } until (defined $ret);
+       }
        delete $self->{parse_hdr}; # done parsing headers
        $ret;
 }
 
-sub psgi_return_init_cb {
+sub psgi_return_init_cb { # this may be PublicInbox::HTTPD::Async {cb}
        my ($self) = @_;
        my $r = rd_hdr($self) or return;
        my $env = $self->{psgi_env};
-       my $filter = delete($env->{'qspawn.filter'}) // (ref($r) eq 'ARRAY' ?
+       my $filter;
+       if (ref($r) eq 'ARRAY' && Scalar::Util::blessed($r->[2]) &&
+                       $r->[2]->can('attach')) {
+               $filter = pop @$r;
+       }
+       $filter //= delete($env->{'qspawn.filter'}) // (ref($r) eq 'ARRAY' ?
                PublicInbox::GzipFilter::qsp_maybe($r->[1], $env) : undef);
 
        my $wcb = delete $env->{'qspawn.wcb'};
@@ -233,16 +247,23 @@ sub psgi_return_init_cb {
        if (ref($r) ne 'ARRAY' || scalar(@$r) == 3) { # error
                if ($async) { # calls rpipe->close && ->event_step
                        $async->close; # PublicInbox::HTTPD::Async::close
-               } else {
-                       $self->{rpipe}->close;
+               } else { # generic PSGI:
+                       delete($self->{rpipe})->close;
                        event_step($self);
+                       waitpid_err($self);
+               }
+               if (ref($r) eq 'ARRAY') { # error
+                       $wcb->($r)
+               } elsif (ref($r) eq 'CODE') { # chain another command
+                       $r->($wcb);
+                       $self->{passed} = 1;
                }
-               $wcb->($r) if ref($r) eq 'ARRAY';
+               # else do nothing
        } elsif ($async) {
                # done reading headers, handoff to read body
                my $fh = $wcb->($r); # scalar @$r == 2
                $fh = $filter->attach($fh) if $filter;
-               $self->{fh} = $fh;
+               $self->{qfh} = $fh;
                $async->async_pass($env->{'psgix.io'}, $fh,
                                        delete($self->{hdr_buf}));
        } else { # for synchronous PSGI servers