about summary refs log tree commit homepage
diff options
context:
space:
mode:
authorEric Wong <BOFH@YHBT.net>2023-06-05 10:12:36 +0000
committerEric Wong <bofh@yhbt.net>2023-06-05 10:38:42 +0000
commit8e04d66f0b9a5fcd9999d41a1df7b2a9d8083fbe (patch)
treeed212a1e4ac79b006b353fed90ece25b29a3502b
parented886ecbc7da21ba7814ddd10b672dd287e701f6 (diff)
downloadunicorn-8e04d66f0b9a5fcd9999d41a1df7b2a9d8083fbe.tar.gz
Another socat dependency down...  I've also started turning
FD_CLOEXEC off on a pipe as a mechanism to detect daemonized
process death in tests.
-rw-r--r--t/active-unix-socket.t117
-rw-r--r--t/integration.ru1
-rwxr-xr-xt/t0011-active-unix-socket.sh79
3 files changed, 118 insertions, 79 deletions
diff --git a/t/active-unix-socket.t b/t/active-unix-socket.t
new file mode 100644
index 0000000..6b5c218
--- /dev/null
+++ b/t/active-unix-socket.t
@@ -0,0 +1,117 @@
+#!perl -w
+# Copyright (C) unicorn hackers <unicorn-public@yhbt.net>
+# License: GPL-3.0+ <https://www.gnu.org/licenses/gpl-3.0.txt>
+
+use v5.14; BEGIN { require './t/lib.perl' };
+use IO::Socket::UNIX;
+my %to_kill;
+END { kill('TERM', values(%to_kill)) if keys %to_kill }
+my $u1 = "$tmpdir/u1.sock";
+my $u2 = "$tmpdir/u2.sock";
+my $unix_req = sub {
+        my $s = IO::Socket::UNIX->new(Peer => shift, Type => SOCK_STREAM);
+        print $s @_, "\r\n\r\n" or die $!;
+        $s;
+};
+{
+        use autodie;
+        open my $fh, '>', "$tmpdir/u1.conf.rb";
+        print $fh <<EOM;
+pid "$tmpdir/u.pid"
+listen "$u1"
+stderr_path "$tmpdir/err1.log"
+EOM
+        close $fh;
+
+        open $fh, '>', "$tmpdir/u2.conf.rb";
+        print $fh <<EOM;
+pid "$tmpdir/u.pid"
+listen "$u2"
+stderr_path "$tmpdir/err2.log"
+EOM
+        close $fh;
+
+        open $fh, '>', "$tmpdir/u3.conf.rb";
+        print $fh <<EOM;
+pid "$tmpdir/u3.pid"
+listen "$u1"
+stderr_path "$tmpdir/err3.log"
+EOM
+        close $fh;
+}
+
+my @uarg = qw(-D -E none t/integration.ru);
+
+# this pipe will be used to notify us when all daemons die:
+pipe(my ($p0, $p1)) or die "pipe: $!";
+fcntl($p1, POSIX::F_SETFD, 0) or die "fcntl: $!"; # clear FD_CLOEXEC
+
+# start the first instance
+unicorn('-c', "$tmpdir/u1.conf.rb", @uarg)->join;
+is($?, 0, 'daemonized 1st process');
+chomp($to_kill{u1} = slurp("$tmpdir/u.pid"));
+like($to_kill{u1}, qr/\A\d+\z/s, 'read pid file');
+
+chomp(my $worker_pid = readline($unix_req->($u1, 'GET /pid')));
+like($worker_pid, qr/\A\d+\z/s, 'captured worker pid');
+ok(kill(0, $worker_pid), 'worker is kill-able');
+
+
+# 2nd process conflicts on PID
+unicorn('-c', "$tmpdir/u2.conf.rb", @uarg)->join;
+isnt($?, 0, 'conflicting PID file fails to start');
+
+chomp(my $pidf = slurp("$tmpdir/u.pid"));
+is($pidf, $to_kill{u1}, 'pid file contents unchanged after start failure');
+
+chomp(my $pid2 = readline($unix_req->($u1, 'GET /pid')));
+is($worker_pid, $pid2, 'worker PID unchanged');
+
+
+# 3rd process conflicts on socket
+unicorn('-c', "$tmpdir/u3.conf.rb", @uarg)->join;
+isnt($?, 0, 'conflicting UNIX socket fails to start');
+
+chomp($pid2 = readline($unix_req->($u1, 'GET /pid')));
+is($worker_pid, $pid2, 'worker PID still unchanged');
+
+chomp($pidf = slurp("$tmpdir/u.pid"));
+is($pidf, $to_kill{u1}, 'pid file contents unchanged after 2nd start failure');
+
+{ # teardown initial process via SIGKILL
+        ok(kill('KILL', delete $to_kill{u1}), 'SIGKILL initial daemon');
+        close $p1;
+        vec(my $rvec = '', fileno($p0), 1) = 1;
+        is(select($rvec, undef, undef, 5), 1, 'timeout for pipe HUP');
+        is(my $undef = <$p0>, undef, 'process closed pipe writer at exit');
+        ok(-f "$tmpdir/u.pid", 'pid file stayed after SIGKILL');
+        ok(-S $u1, 'socket stayed after SIGKILL');
+        is(IO::Socket::UNIX->new(Peer => $u1, Type => SOCK_STREAM), undef,
+                'fail to connect to u1');
+        ok(!kill(0, $worker_pid), 'worker gone after parent dies');
+}
+
+# restart the first instance
+{
+        pipe(($p0, $p1)) or die "pipe: $!";
+        fcntl($p1, POSIX::F_SETFD, 0) or die "fcntl: $!"; # clear FD_CLOEXEC
+        unicorn('-c', "$tmpdir/u1.conf.rb", @uarg)->join;
+        is($?, 0, 'daemonized 1st process');
+        chomp($to_kill{u1} = slurp("$tmpdir/u.pid"));
+        like($to_kill{u1}, qr/\A\d+\z/s, 'read pid file');
+
+        chomp($pid2 = readline($unix_req->($u1, 'GET /pid')));
+        like($pid2, qr/\A\d+\z/, 'worker running');
+
+        ok(kill('TERM', delete $to_kill{u1}), 'SIGTERM restarted daemon');
+        close $p1;
+        vec(my $rvec = '', fileno($p0), 1) = 1;
+        is(select($rvec, undef, undef, 5), 1, 'timeout for pipe HUP');
+        is(my $undef = <$p0>, undef, 'process closed pipe writer at exit');
+        ok(!-f "$tmpdir/u.pid", 'pid file gone after SIGTERM');
+        ok(-S $u1, 'socket stays after SIGTERM');
+}
+
+my @log = slurp("$tmpdir/err.log");
+diag("@log") if $ENV{V};
+done_testing;
diff --git a/t/integration.ru b/t/integration.ru
index c0bef99..21f5449 100644
--- a/t/integration.ru
+++ b/t/integration.ru
@@ -57,6 +57,7 @@ run(lambda do |env|
     when '/unknown-status-pass-through'; [ '666 I AM THE BEAST', {}, [] ]
     when '/env_dump'; [ 200, {}, [ env_dump(env) ] ]
     when '/write_on_close'; write_on_close
+    when '/pid'; [ 200, {}, [ "#$$\n" ] ]
     end # case PATH_INFO (GET)
   when 'POST'
     case env['PATH_INFO']
diff --git a/t/t0011-active-unix-socket.sh b/t/t0011-active-unix-socket.sh
deleted file mode 100755
index fae0b6c..0000000
--- a/t/t0011-active-unix-socket.sh
+++ /dev/null
@@ -1,79 +0,0 @@
-#!/bin/sh
-. ./test-lib.sh
-t_plan 11 "existing UNIX domain socket check"
-
-read_pid_unix () {
-        x=$(printf 'GET / HTTP/1.0\r\n\r\n' | \
-            socat - UNIX:$unix_socket | \
-            tail -1)
-        test -n "$x"
-        y="$(expr "$x" : '\([0-9][0-9]*\)')"
-        test x"$x" = x"$y"
-        test -n "$y"
-        echo "$y"
-}
-
-t_begin "setup and start" && {
-        rtmpfiles unix_socket unix_config
-        rm -f $unix_socket
-        unicorn_setup
-        grep -v ^listen < $unicorn_config > $unix_config
-        echo "listen '$unix_socket'" >> $unix_config
-        unicorn -D -c $unix_config pid.ru
-        unicorn_wait_start
-        orig_master_pid=$unicorn_pid
-}
-
-t_begin "get pid of worker" && {
-        worker_pid=$(read_pid_unix)
-        t_info "worker_pid=$worker_pid"
-}
-
-t_begin "fails to start with existing pid file" && {
-        rm -f $ok
-        unicorn -D -c $unix_config pid.ru || echo ok > $ok
-        test x"$(cat $ok)" = xok
-}
-
-t_begin "worker pid unchanged" && {
-        test x"$(read_pid_unix)" = x$worker_pid
-        > $r_err
-}
-
-t_begin "fails to start with listening UNIX domain socket bound" && {
-        rm $ok $pid
-        unicorn -D -c $unix_config pid.ru || echo ok > $ok
-        test x"$(cat $ok)" = xok
-        > $r_err
-}
-
-t_begin "worker pid unchanged (again)" && {
-        test x"$(read_pid_unix)" = x$worker_pid
-}
-
-t_begin "nuking the existing Unicorn succeeds" && {
-        kill -9 $unicorn_pid
-        while kill -0 $unicorn_pid
-        do
-                sleep 1
-        done
-        check_stderr
-}
-
-t_begin "succeeds in starting with leftover UNIX domain socket bound" && {
-        test -S $unix_socket
-        unicorn -D -c $unix_config pid.ru
-        unicorn_wait_start
-}
-
-t_begin "worker pid changed" && {
-        test x"$(read_pid_unix)" != x$worker_pid
-}
-
-t_begin "killing succeeds" && {
-        kill $unicorn_pid
-}
-
-t_begin "no errors" && check_stderr
-
-t_done