about summary refs log tree commit homepage
diff options
context:
space:
mode:
authorEric Wong <e@yhbt.net>2020-03-22 08:58:48 +0000
committerEric Wong <e@yhbt.net>2020-03-24 22:00:25 +0000
commitd7fda3f4b9d4c9e6d01c818f09905d6827fa693f (patch)
treeeff9163fcada3fd086e0f74684422a293ccfdf7e
parentc7acdfe78bda5bf36660a699e882e0e3c431a351 (diff)
downloadpublic-inbox-d7fda3f4b9d4c9e6d01c818f09905d6827fa693f.tar.gz
Disabling workers via `-W0' blesses the contents of the
@listeners array, so we need to ensure we call fcntl on
the GLOB ref in ->{sock}.

Add tests to ensure USR2 works regardless of whether workers
are enabled or not.
-rw-r--r--lib/PublicInbox/Daemon.pm3
-rw-r--r--t/httpd-unix.t105
2 files changed, 98 insertions, 10 deletions
diff --git a/lib/PublicInbox/Daemon.pm b/lib/PublicInbox/Daemon.pm
index 43ef2691..3d582e35 100644
--- a/lib/PublicInbox/Daemon.pm
+++ b/lib/PublicInbox/Daemon.pm
@@ -403,6 +403,9 @@ sub upgrade { # $_[0] = signal name or number (unused)
                 $ENV{LISTEN_FDS} = scalar @listeners;
                 $ENV{LISTEN_PID} = $$;
                 foreach my $s (@listeners) {
+                        # @listeners are globs with workers, PI::L w/o workers
+                        $s = $s->{sock} if ref($s) eq 'PublicInbox::Listener';
+
                         my $fl = fcntl($s, F_GETFD, 0);
                         fcntl($s, F_SETFD, $fl &= ~FD_CLOEXEC);
                 }
diff --git a/t/httpd-unix.t b/t/httpd-unix.t
index b321c789..02f5e4a9 100644
--- a/t/httpd-unix.t
+++ b/t/httpd-unix.t
@@ -6,6 +6,8 @@ use warnings;
 use Test::More;
 use PublicInbox::TestCommon;
 use Errno qw(EADDRINUSE);
+use Cwd qw(abs_path);
+use Carp qw(croak);
 require_mods(qw(Plack::Util Plack::Builder HTTP::Date HTTP::Status));
 use IO::Socket::UNIX;
 my ($tmpdir, $for_destroy) = tmpdir();
@@ -79,9 +81,26 @@ check_sock($unix);
         ok(-S $unix, 'unix socket still exists');
 }
 
+sub delay_until {
+        my $cond = shift;
+        for (1..1000) {
+                return if $cond->();
+                select undef, undef, undef, 0.012;
+        }
+        Carp::croak('condition failed');
+}
+
 SKIP: {
         require_mods('Net::Server::Daemonize', 20);
         my $pid_file = "$tmpdir/pid";
+        my $read_pid = sub {
+                my $f = shift;
+                open my $fh, '<', $f or die "open $f failed: $!";
+                my $pid = do { local $/; <$fh> };
+                chomp $pid;
+                $pid || 0;
+        };
+
         for my $w (qw(-W0 -W1)) {
                 # wait for daemonization
                 $spawn_httpd->("-l$unix", '-D', '-P', $pid_file, $w);
@@ -90,18 +109,84 @@ SKIP: {
                 check_sock($unix);
 
                 ok(-f $pid_file, "$w pid file written");
-                open my $fh, '<', "$tmpdir/pid" or die "open failed: $!";
-                my $rpid = do { local $/; <$fh> };
-                chomp $rpid;
-                like($rpid, qr/\A\d+\z/s, "$w pid file looks like a pid");
-                is(kill('TERM', $rpid), 1, "signaled daemonized $w process");
-                for (1..100) {
-                        kill(0, $rpid) or last;
-                        select undef, undef, undef, 0.02;
-                }
-                is(kill(0, $rpid), 0, "daemonized $w process exited");
+                my $pid = $read_pid->($pid_file);
+                is(kill('TERM', $pid), 1, "signaled daemonized $w process");
+                delay_until(sub { !kill(0, $pid) });
+                is(kill(0, $pid), 0, "daemonized $w process exited");
                 ok(!-e $pid_file, "$w pid file unlinked at exit");
         }
+
+        # try a USR2 upgrade with workers:
+        my $httpd = abs_path('blib/script/public-inbox-httpd');
+        $psgi = abs_path($psgi);
+        my $opt = { run_mode => 0 };
+
+        my @args = ("-l$unix", '-D', '-P', $pid_file, -1, $out, -2, $err);
+        $td = start_script([$httpd, @args, $psgi], undef, $opt);
+        $td->join;
+        is($?, 0, "daemonized process again");
+        check_sock($unix);
+        my $pid = $read_pid->($pid_file);
+
+        # stop worker to ensure check_sock below hits $new_pid
+        kill('TTOU', $pid) or die "TTOU failed: $!";
+
+        kill('USR2', $pid) or die "USR2 failed: $!";
+        delay_until(sub {
+                $pid != (eval { $read_pid->($pid_file) } // $pid)
+        });
+        my $new_pid = $read_pid->($pid_file);
+        isnt($new_pid, $pid, 'new child started');
+        my $old_pid = $read_pid->("$pid_file.oldbin");
+        is($old_pid, $pid, '.oldbin pid file written');
+
+        check_sock($unix); # ensures $new_pid is ready to receive signals
+
+        # first, back out of the upgrade
+        kill('QUIT', $new_pid) or die "kill new PID failed: $!";
+        delay_until(sub {
+                $pid == (eval { $read_pid->($pid_file) } // 0)
+        });
+        is($read_pid->($pid_file), $pid, 'old PID file restored');
+        ok(!-f "$pid_file.oldbin", '.oldbin PID file gone');
+
+        # retry USR2 upgrade
+        kill('USR2', $pid) or die "USR2 failed: $!";
+        delay_until(sub {
+                $pid != (eval { $read_pid->($pid_file) } // $pid)
+        });
+        $new_pid = $read_pid->($pid_file);
+        isnt($new_pid, $pid, 'new child started again');
+        $old_pid = $read_pid->("$pid_file.oldbin");
+        is($old_pid, $pid, '.oldbin pid file written');
+
+        # drop the old parent
+        kill('QUIT', $old_pid) or die "QUIT failed: $!";
+        delay_until(sub { !kill(0, $old_pid) });
+
+        # drop the new child
+        check_sock($unix);
+        kill('QUIT', $new_pid) or die "QUIT failed: $!";
+        delay_until(sub { !kill(0, $new_pid) });
+        ok(!-f $pid_file, 'PID file is gone');
+
+
+        # try USR2 without workers (-W0)
+        $td = start_script([$httpd, @args, '-W0', $psgi], undef, $opt);
+        $td->join;
+        is($?, 0, 'daemonized w/o workers');
+        check_sock($unix);
+        $pid = $read_pid->($pid_file);
+
+        # replace running process
+        kill('USR2', $pid) or die "USR2 failed: $!";
+        delay_until(sub { !kill(0, $pid) });
+
+        check_sock($unix);
+        $pid = $read_pid->($pid_file);
+        kill('QUIT', $pid) or die "USR2 failed: $!";
+        delay_until(sub { !kill(0, $pid) });
+        ok(!-f $pid_file, 'PID file is gone');
 }
 
 done_testing();