unicorn-fork 6.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.CHANGELOG.old +25 -0
- data/.document +28 -0
- data/.gitattributes +5 -0
- data/.gitignore +25 -0
- data/.mailmap +26 -0
- data/.manifest +144 -0
- data/.olddoc.yml +25 -0
- data/Application_Timeouts +77 -0
- data/CONTRIBUTORS +39 -0
- data/COPYING +674 -0
- data/DESIGN +99 -0
- data/Documentation/.gitignore +3 -0
- data/Documentation/unicorn.1 +222 -0
- data/Documentation/unicorn_rails.1 +207 -0
- data/FAQ +70 -0
- data/GIT-VERSION-FILE +1 -0
- data/GIT-VERSION-GEN +39 -0
- data/GNUmakefile +318 -0
- data/HACKING +117 -0
- data/ISSUES +102 -0
- data/KNOWN_ISSUES +79 -0
- data/LICENSE +67 -0
- data/Links +58 -0
- data/PHILOSOPHY +139 -0
- data/README +165 -0
- data/Rakefile +17 -0
- data/SIGNALS +123 -0
- data/Sandbox +104 -0
- data/TODO +1 -0
- data/TUNING +119 -0
- data/archive/.gitignore +3 -0
- data/archive/slrnpull.conf +4 -0
- data/bin/unicorn +129 -0
- data/bin/unicorn_rails +210 -0
- data/examples/big_app_gc.rb +3 -0
- data/examples/echo.ru +27 -0
- data/examples/init.sh +102 -0
- data/examples/logger_mp_safe.rb +26 -0
- data/examples/logrotate.conf +44 -0
- data/examples/nginx.conf +156 -0
- data/examples/unicorn.conf.minimal.rb +14 -0
- data/examples/unicorn.conf.rb +111 -0
- data/examples/unicorn.socket +11 -0
- data/examples/unicorn@.service +40 -0
- data/ext/unicorn_http/CFLAGS +13 -0
- data/ext/unicorn_http/c_util.h +115 -0
- data/ext/unicorn_http/common_field_optimization.h +128 -0
- data/ext/unicorn_http/epollexclusive.h +128 -0
- data/ext/unicorn_http/ext_help.h +38 -0
- data/ext/unicorn_http/extconf.rb +40 -0
- data/ext/unicorn_http/global_variables.h +97 -0
- data/ext/unicorn_http/httpdate.c +91 -0
- data/ext/unicorn_http/unicorn_http.c +4348 -0
- data/ext/unicorn_http/unicorn_http.rl +1054 -0
- data/ext/unicorn_http/unicorn_http_common.rl +76 -0
- data/lib/unicorn/app/old_rails/static.rb +60 -0
- data/lib/unicorn/app/old_rails.rb +36 -0
- data/lib/unicorn/cgi_wrapper.rb +148 -0
- data/lib/unicorn/configurator.rb +749 -0
- data/lib/unicorn/const.rb +22 -0
- data/lib/unicorn/http_request.rb +180 -0
- data/lib/unicorn/http_response.rb +95 -0
- data/lib/unicorn/http_server.rb +860 -0
- data/lib/unicorn/launcher.rb +63 -0
- data/lib/unicorn/oob_gc.rb +82 -0
- data/lib/unicorn/preread_input.rb +34 -0
- data/lib/unicorn/select_waiter.rb +7 -0
- data/lib/unicorn/socket_helper.rb +186 -0
- data/lib/unicorn/stream_input.rb +152 -0
- data/lib/unicorn/tee_input.rb +132 -0
- data/lib/unicorn/tmpio.rb +34 -0
- data/lib/unicorn/util.rb +91 -0
- data/lib/unicorn/version.rb +1 -0
- data/lib/unicorn/worker.rb +166 -0
- data/lib/unicorn.rb +137 -0
- data/man/man1/unicorn.1 +222 -0
- data/man/man1/unicorn_rails.1 +207 -0
- data/setup.rb +1587 -0
- data/t/.gitignore +4 -0
- data/t/GNUmakefile +5 -0
- data/t/README +49 -0
- data/t/active-unix-socket.t +110 -0
- data/t/back-out-of-upgrade.t +44 -0
- data/t/bin/unused_listen +40 -0
- data/t/client_body_buffer_size.ru +15 -0
- data/t/client_body_buffer_size.t +79 -0
- data/t/detach.ru +12 -0
- data/t/env.ru +4 -0
- data/t/fails-rack-lint.ru +6 -0
- data/t/heartbeat-timeout.ru +13 -0
- data/t/heartbeat-timeout.t +60 -0
- data/t/integration.ru +129 -0
- data/t/integration.t +509 -0
- data/t/lib.perl +309 -0
- data/t/listener_names.ru +5 -0
- data/t/my-tap-lib.sh +201 -0
- data/t/oob_gc.ru +18 -0
- data/t/oob_gc_path.ru +18 -0
- data/t/pid.ru +4 -0
- data/t/preread_input.ru +23 -0
- data/t/reload-bad-config.t +49 -0
- data/t/reopen-logs.ru +14 -0
- data/t/reopen-logs.t +36 -0
- data/t/t0010-reap-logging.sh +55 -0
- data/t/t0012-reload-empty-config.sh +86 -0
- data/t/t0013-rewindable-input-false.sh +24 -0
- data/t/t0013.ru +13 -0
- data/t/t0014-rewindable-input-true.sh +24 -0
- data/t/t0014.ru +13 -0
- data/t/t0015-configurator-internals.sh +25 -0
- data/t/t0020-at_exit-handler.sh +49 -0
- data/t/t0021-process_detach.sh +29 -0
- data/t/t0022-listener_names-preload_app.sh +32 -0
- data/t/t0300-no-default-middleware.sh +20 -0
- data/t/t0301-no-default-middleware-ignored-in-config.sh +25 -0
- data/t/t0301.ru +14 -0
- data/t/t9001-oob_gc.sh +47 -0
- data/t/t9002-oob_gc-path.sh +75 -0
- data/t/test-lib.sh +125 -0
- data/t/winch_ttin.t +64 -0
- data/t/working_directory.t +86 -0
- data/test/aggregate.rb +16 -0
- data/test/benchmark/README +60 -0
- data/test/benchmark/dd.ru +19 -0
- data/test/benchmark/ddstream.ru +51 -0
- data/test/benchmark/readinput.ru +41 -0
- data/test/benchmark/stack.ru +9 -0
- data/test/benchmark/uconnect.perl +66 -0
- data/test/exec/README +5 -0
- data/test/exec/test_exec.rb +1030 -0
- data/test/test_helper.rb +307 -0
- data/test/unit/test_configurator.rb +176 -0
- data/test/unit/test_droplet.rb +29 -0
- data/test/unit/test_http_parser.rb +885 -0
- data/test/unit/test_http_parser_ng.rb +715 -0
- data/test/unit/test_server.rb +245 -0
- data/test/unit/test_signals.rb +189 -0
- data/test/unit/test_socket_helper.rb +160 -0
- data/test/unit/test_stream_input.rb +211 -0
- data/test/unit/test_tee_input.rb +304 -0
- data/test/unit/test_util.rb +132 -0
- data/test/unit/test_waiter.rb +35 -0
- data/unicorn.gemspec +49 -0
- metadata +266 -0
data/t/.gitignore
ADDED
data/t/GNUmakefile
ADDED
data/t/README
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
= Unicorn integration test suite
|
2
|
+
|
3
|
+
These are all integration tests that start the server on random, unused
|
4
|
+
TCP ports or Unix domain sockets. They're all designed to run
|
5
|
+
concurrently with other tests to minimize test time, but tests may be
|
6
|
+
run independently as well.
|
7
|
+
|
8
|
+
New tests are written in Perl 5 because we need a stable language
|
9
|
+
to test real-world behavior and Ruby introduces incompatibilities
|
10
|
+
at a far faster rate than Perl 5. Perl is Ruby's older cousin, so
|
11
|
+
it should be easy-to-learn for Rubyists.
|
12
|
+
|
13
|
+
Old tests are in Bourne shell and slowly being ported to Perl 5.
|
14
|
+
|
15
|
+
== Requirements
|
16
|
+
|
17
|
+
* {Ruby 2.5.0+}[https://www.ruby-lang.org/en/]
|
18
|
+
* {Perl 5.14+}[https://www.perl.org/] # your distro should have it
|
19
|
+
* {GNU make}[https://www.gnu.org/software/make/]
|
20
|
+
* {curl}[https://curl.haxx.se/]
|
21
|
+
|
22
|
+
We do not use bashisms or any non-portable, non-POSIX constructs
|
23
|
+
in our shell code. We use the "pipefail" option if available and
|
24
|
+
mainly test with {ksh}[http://kornshell.com/], but occasionally
|
25
|
+
with {dash}[http://gondor.apana.org.au/~herbert/dash/] and
|
26
|
+
{bash}[https://www.gnu.org/software/bash/], too.
|
27
|
+
|
28
|
+
== Running Tests
|
29
|
+
|
30
|
+
To run the entire test suite with 8 tests running at once:
|
31
|
+
|
32
|
+
make -j8 && prove -vw
|
33
|
+
|
34
|
+
To run one individual test (Perl5):
|
35
|
+
|
36
|
+
prove -vw t/integration.t
|
37
|
+
|
38
|
+
To run one individual test (shell):
|
39
|
+
|
40
|
+
make t0000-simple-http.sh
|
41
|
+
|
42
|
+
You may also increase verbosity by setting the "V" variable for
|
43
|
+
GNU make. To disable trapping of stdout/stderr:
|
44
|
+
|
45
|
+
make V=1
|
46
|
+
|
47
|
+
To enable the "set -x" option in shell scripts to trace execution
|
48
|
+
|
49
|
+
make V=2
|
@@ -0,0 +1,110 @@
|
|
1
|
+
#!perl -w
|
2
|
+
# Copyright (C) unicorn hackers <unicorn-public@yhbt.net>
|
3
|
+
# License: GPL-3.0+ <https://www.gnu.org/licenses/gpl-3.0.txt>
|
4
|
+
|
5
|
+
use v5.14; BEGIN { require './t/lib.perl' };
|
6
|
+
use IO::Socket::UNIX;
|
7
|
+
use autodie;
|
8
|
+
no autodie 'kill';
|
9
|
+
my %to_kill;
|
10
|
+
END { kill('TERM', values(%to_kill)) if keys %to_kill }
|
11
|
+
my $u1 = "$tmpdir/u1.sock";
|
12
|
+
my $u2 = "$tmpdir/u2.sock";
|
13
|
+
{
|
14
|
+
write_file '>', "$tmpdir/u1.conf.rb", <<EOM;
|
15
|
+
pid "$tmpdir/u.pid"
|
16
|
+
listen "$u1"
|
17
|
+
stderr_path "$err_log"
|
18
|
+
EOM
|
19
|
+
write_file '>', "$tmpdir/u2.conf.rb", <<EOM;
|
20
|
+
pid "$tmpdir/u.pid"
|
21
|
+
listen "$u2"
|
22
|
+
stderr_path "$tmpdir/err2.log"
|
23
|
+
EOM
|
24
|
+
|
25
|
+
write_file '>', "$tmpdir/u3.conf.rb", <<EOM;
|
26
|
+
pid "$tmpdir/u3.pid"
|
27
|
+
listen "$u1"
|
28
|
+
stderr_path "$tmpdir/err3.log"
|
29
|
+
EOM
|
30
|
+
}
|
31
|
+
|
32
|
+
my @uarg = qw(-D -E none t/integration.ru);
|
33
|
+
|
34
|
+
# this pipe will be used to notify us when all daemons die:
|
35
|
+
pipe(my $p0, my $p1);
|
36
|
+
fcntl($p1, POSIX::F_SETFD, 0);
|
37
|
+
|
38
|
+
# start the first instance
|
39
|
+
unicorn('-c', "$tmpdir/u1.conf.rb", @uarg)->join;
|
40
|
+
is($?, 0, 'daemonized 1st process');
|
41
|
+
chomp($to_kill{u1} = slurp("$tmpdir/u.pid"));
|
42
|
+
like($to_kill{u1}, qr/\A\d+\z/s, 'read pid file');
|
43
|
+
|
44
|
+
chomp(my $worker_pid = readline(unix_start($u1, 'GET /pid')));
|
45
|
+
like($worker_pid, qr/\A\d+\z/s, 'captured worker pid');
|
46
|
+
ok(kill(0, $worker_pid), 'worker is kill-able');
|
47
|
+
|
48
|
+
|
49
|
+
# 2nd process conflicts on PID
|
50
|
+
unicorn('-c', "$tmpdir/u2.conf.rb", @uarg)->join;
|
51
|
+
isnt($?, 0, 'conflicting PID file fails to start');
|
52
|
+
|
53
|
+
chomp(my $pidf = slurp("$tmpdir/u.pid"));
|
54
|
+
is($pidf, $to_kill{u1}, 'pid file contents unchanged after start failure');
|
55
|
+
|
56
|
+
chomp(my $pid2 = readline(unix_start($u1, 'GET /pid')));
|
57
|
+
is($worker_pid, $pid2, 'worker PID unchanged');
|
58
|
+
|
59
|
+
|
60
|
+
# 3rd process conflicts on socket
|
61
|
+
unicorn('-c', "$tmpdir/u3.conf.rb", @uarg)->join;
|
62
|
+
isnt($?, 0, 'conflicting UNIX socket fails to start');
|
63
|
+
|
64
|
+
chomp($pid2 = readline(unix_start($u1, 'GET /pid')));
|
65
|
+
is($worker_pid, $pid2, 'worker PID still unchanged');
|
66
|
+
|
67
|
+
chomp($pidf = slurp("$tmpdir/u.pid"));
|
68
|
+
is($pidf, $to_kill{u1}, 'pid file contents unchanged after 2nd start failure');
|
69
|
+
|
70
|
+
{ # teardown initial process via SIGKILL
|
71
|
+
ok(kill('KILL', delete $to_kill{u1}), 'SIGKILL initial daemon');
|
72
|
+
close $p1;
|
73
|
+
vec(my $rvec = '', fileno($p0), 1) = 1;
|
74
|
+
is(select($rvec, undef, undef, 5), 1, 'timeout for pipe HUP');
|
75
|
+
is(my $undef = <$p0>, undef, 'process closed pipe writer at exit');
|
76
|
+
ok(-f "$tmpdir/u.pid", 'pid file stayed after SIGKILL');
|
77
|
+
ok(-S $u1, 'socket stayed after SIGKILL');
|
78
|
+
is(IO::Socket::UNIX->new(Peer => $u1, Type => SOCK_STREAM), undef,
|
79
|
+
'fail to connect to u1');
|
80
|
+
for (1..50) { # wait for init process to reap worker
|
81
|
+
kill(0, $worker_pid) or last;
|
82
|
+
sleep 0.011;
|
83
|
+
}
|
84
|
+
ok(!kill(0, $worker_pid), 'worker gone after parent dies');
|
85
|
+
}
|
86
|
+
|
87
|
+
# restart the first instance
|
88
|
+
{
|
89
|
+
pipe($p0, $p1);
|
90
|
+
fcntl($p1, POSIX::F_SETFD, 0);
|
91
|
+
unicorn('-c', "$tmpdir/u1.conf.rb", @uarg)->join;
|
92
|
+
is($?, 0, 'daemonized 1st process');
|
93
|
+
chomp($to_kill{u1} = slurp("$tmpdir/u.pid"));
|
94
|
+
like($to_kill{u1}, qr/\A\d+\z/s, 'read pid file');
|
95
|
+
|
96
|
+
chomp($pid2 = readline(unix_start($u1, 'GET /pid')));
|
97
|
+
like($pid2, qr/\A\d+\z/, 'worker running');
|
98
|
+
|
99
|
+
ok(kill('TERM', delete $to_kill{u1}), 'SIGTERM restarted daemon');
|
100
|
+
close $p1;
|
101
|
+
vec(my $rvec = '', fileno($p0), 1) = 1;
|
102
|
+
is(select($rvec, undef, undef, 5), 1, 'timeout for pipe HUP');
|
103
|
+
is(my $undef = <$p0>, undef, 'process closed pipe writer at exit');
|
104
|
+
ok(!-f "$tmpdir/u.pid", 'pid file gone after SIGTERM');
|
105
|
+
ok(-S $u1, 'socket stays after SIGTERM');
|
106
|
+
}
|
107
|
+
|
108
|
+
check_stderr;
|
109
|
+
undef $tmpdir;
|
110
|
+
done_testing;
|
@@ -0,0 +1,44 @@
|
|
1
|
+
#!perl -w
|
2
|
+
# Copyright (C) unicorn hackers <unicorn-public@yhbt.net>
|
3
|
+
# License: GPL-3.0+ <https://www.gnu.org/licenses/gpl-3.0.txt>
|
4
|
+
# test backing out of USR2 upgrade
|
5
|
+
use v5.14; BEGIN { require './t/lib.perl' };
|
6
|
+
use autodie;
|
7
|
+
my $srv = tcp_server();
|
8
|
+
mkfifo_die $fifo;
|
9
|
+
write_file '>', $u_conf, <<EOM;
|
10
|
+
preload_app true
|
11
|
+
stderr_path "$err_log"
|
12
|
+
pid "$pid_file"
|
13
|
+
after_fork { |s,w| File.open('$fifo', 'w') { |fp| fp.write "pid=#\$\$" } }
|
14
|
+
EOM
|
15
|
+
my $ar = unicorn(qw(-E none t/pid.ru -c), $u_conf, { 3 => $srv });
|
16
|
+
|
17
|
+
like(my $wpid_orig_1 = slurp($fifo), qr/\Apid=\d+\z/a, 'got worker pid');
|
18
|
+
|
19
|
+
ok $ar->do_kill('USR2'), 'USR2 to start upgrade';
|
20
|
+
ok $ar->do_kill('WINCH'), 'drop old worker';
|
21
|
+
|
22
|
+
like(my $wpid_new = slurp($fifo), qr/\Apid=\d+\z/a, 'got pid from new master');
|
23
|
+
chomp(my $new_pid = slurp($pid_file));
|
24
|
+
isnt $new_pid, $ar->{pid}, 'PID file changed';
|
25
|
+
chomp(my $pid_oldbin = slurp("$pid_file.oldbin"));
|
26
|
+
is $pid_oldbin, $ar->{pid}, '.oldbin PID valid';
|
27
|
+
|
28
|
+
ok $ar->do_kill('HUP'), 'HUP old master';
|
29
|
+
like(my $wpid_orig_2 = slurp($fifo), qr/\Apid=\d+\z/a, 'got worker new pid');
|
30
|
+
ok kill('QUIT', $new_pid), 'abort old master';
|
31
|
+
kill_until_dead $new_pid;
|
32
|
+
|
33
|
+
my ($st, $hdr, $req_pid) = do_req $srv, 'GET /';
|
34
|
+
chomp $req_pid;
|
35
|
+
is $wpid_orig_2, "pid=$req_pid", 'new worker on old worker serves';
|
36
|
+
|
37
|
+
ok !-f "$pid_file.oldbin", '.oldbin PID file gone';
|
38
|
+
chomp(my $old_pid = slurp($pid_file));
|
39
|
+
is $old_pid, $ar->{pid}, 'PID file restored';
|
40
|
+
|
41
|
+
my @log = grep !/ERROR -- : reaped .*? exec\(\)-ed/, slurp($err_log);
|
42
|
+
check_stderr @log;
|
43
|
+
undef $tmpdir;
|
44
|
+
done_testing;
|
data/t/bin/unused_listen
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# -*- encoding: binary -*-
|
3
|
+
# this is to remain compatible with the unused_port function in the
|
4
|
+
# Unicorn test/test_helper.rb file
|
5
|
+
require 'socket'
|
6
|
+
require 'tmpdir'
|
7
|
+
|
8
|
+
default_port = 8080
|
9
|
+
addr = ENV['UNICORN_TEST_ADDR'] || '127.0.0.1'
|
10
|
+
retries = 100
|
11
|
+
base = 5000
|
12
|
+
port = sock = lock_path = nil
|
13
|
+
|
14
|
+
begin
|
15
|
+
begin
|
16
|
+
port = base + rand(32768 - base)
|
17
|
+
while port == default_port
|
18
|
+
port = base + rand(32768 - base)
|
19
|
+
end
|
20
|
+
|
21
|
+
sock = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM, 0)
|
22
|
+
sock.bind(Socket.pack_sockaddr_in(port, addr))
|
23
|
+
sock.listen(5)
|
24
|
+
rescue Errno::EADDRINUSE, Errno::EACCES
|
25
|
+
sock.close rescue nil
|
26
|
+
retry if (retries -= 1) >= 0
|
27
|
+
end
|
28
|
+
|
29
|
+
# since we'll end up closing the random port we just got, there's a race
|
30
|
+
# condition could allow the random port we just chose to reselect itself
|
31
|
+
# when running tests in parallel with gmake. Create a lock file while
|
32
|
+
# we have the port here to ensure that does not happen.
|
33
|
+
lock_path = "#{Dir::tmpdir}/unicorn_test.#{addr}:#{port}.lock"
|
34
|
+
lock = File.open(lock_path, File::WRONLY|File::CREAT|File::EXCL, 0600)
|
35
|
+
rescue Errno::EEXIST
|
36
|
+
sock.close rescue nil
|
37
|
+
retry
|
38
|
+
end
|
39
|
+
sock.close rescue nil
|
40
|
+
puts %Q(listen=#{addr}:#{port} T_RM_LIST="$T_RM_LIST #{lock_path}")
|
@@ -0,0 +1,15 @@
|
|
1
|
+
#\ -E none
|
2
|
+
# frozen_string_literal: false
|
3
|
+
app = lambda do |env|
|
4
|
+
input = env['rack.input']
|
5
|
+
case env["PATH_INFO"]
|
6
|
+
when "/tmp_class"
|
7
|
+
body = input.instance_variable_get(:@tmp).class.name
|
8
|
+
when "/input_class"
|
9
|
+
body = input.class.name
|
10
|
+
else
|
11
|
+
return [ 500, {}, [] ]
|
12
|
+
end
|
13
|
+
[ 200, {}, [ body ] ]
|
14
|
+
end
|
15
|
+
run app
|
@@ -0,0 +1,79 @@
|
|
1
|
+
#!perl -w
|
2
|
+
# Copyright (C) unicorn hackers <unicorn-public@yhbt.net>
|
3
|
+
# License: GPL-3.0+ <https://www.gnu.org/licenses/gpl-3.0.txt>
|
4
|
+
|
5
|
+
use v5.14; BEGIN { require './t/lib.perl' };
|
6
|
+
use autodie;
|
7
|
+
my $conf_fh = write_file '>', $u_conf, <<EOM;
|
8
|
+
client_body_buffer_size 0
|
9
|
+
EOM
|
10
|
+
$conf_fh->autoflush(1);
|
11
|
+
my $srv = tcp_server();
|
12
|
+
my $host_port = tcp_host_port($srv);
|
13
|
+
my @uarg = (qw(-E none t/client_body_buffer_size.ru -c), $u_conf);
|
14
|
+
my $ar = unicorn(@uarg, { 3 => $srv });
|
15
|
+
my ($c, $status, $hdr);
|
16
|
+
my $mem_class = 'StringIO';
|
17
|
+
my $fs_class = 'Unicorn::TmpIO';
|
18
|
+
|
19
|
+
$c = tcp_start($srv, "PUT /input_class HTTP/1.0\r\nContent-Length: 0");
|
20
|
+
($status, $hdr) = slurp_hdr($c);
|
21
|
+
like($status, qr!\AHTTP/1\.[01] 200\b!, 'status line valid');
|
22
|
+
is(readline($c), $mem_class, 'zero-byte file is StringIO');
|
23
|
+
|
24
|
+
$c = tcp_start($srv, "PUT /tmp_class HTTP/1.0\r\nContent-Length: 1");
|
25
|
+
print $c '.';
|
26
|
+
($status, $hdr) = slurp_hdr($c);
|
27
|
+
like($status, qr!\AHTTP/1\.[01] 200\b!, 'status line valid');
|
28
|
+
is(readline($c), $fs_class, '1 byte file is filesystem-backed');
|
29
|
+
|
30
|
+
|
31
|
+
my $fifo = "$tmpdir/fifo";
|
32
|
+
POSIX::mkfifo($fifo, 0600) or die "mkfifo: $!";
|
33
|
+
seek($conf_fh, 0, SEEK_SET);
|
34
|
+
truncate($conf_fh, 0);
|
35
|
+
print $conf_fh <<EOM;
|
36
|
+
after_fork { |_,_| File.open('$fifo', 'w') { |fp| fp.write "pid=#\$\$" } }
|
37
|
+
EOM
|
38
|
+
$ar->do_kill('HUP');
|
39
|
+
open my $fifo_fh, '<', $fifo;
|
40
|
+
like(my $wpid = readline($fifo_fh), qr/\Apid=\d+\z/a ,
|
41
|
+
'reloaded w/ default client_body_buffer_size');
|
42
|
+
|
43
|
+
|
44
|
+
$c = tcp_start($srv, "PUT /tmp_class HTTP/1.0\r\nContent-Length: 1");
|
45
|
+
($status, $hdr) = slurp_hdr($c);
|
46
|
+
like($status, qr!\AHTTP/1\.[01] 200\b!, 'status line valid');
|
47
|
+
is(readline($c), $mem_class, 'class for a 1 byte file is memory-backed');
|
48
|
+
|
49
|
+
|
50
|
+
my $one_meg = 1024 ** 2;
|
51
|
+
$c = tcp_start($srv, "PUT /tmp_class HTTP/1.0\r\nContent-Length: $one_meg");
|
52
|
+
($status, $hdr) = slurp_hdr($c);
|
53
|
+
like($status, qr!\AHTTP/1\.[01] 200\b!, 'status line valid');
|
54
|
+
is(readline($c), $fs_class, '1 megabyte file is FS-backed');
|
55
|
+
|
56
|
+
# reload with bigger client_body_buffer_size
|
57
|
+
say $conf_fh "client_body_buffer_size $one_meg";
|
58
|
+
$ar->do_kill('HUP');
|
59
|
+
open $fifo_fh, '<', $fifo;
|
60
|
+
like($wpid = readline($fifo_fh), qr/\Apid=\d+\z/a ,
|
61
|
+
'reloaded w/ bigger client_body_buffer_size');
|
62
|
+
|
63
|
+
|
64
|
+
$c = tcp_start($srv, "PUT /tmp_class HTTP/1.0\r\nContent-Length: $one_meg");
|
65
|
+
($status, $hdr) = slurp_hdr($c);
|
66
|
+
like($status, qr!\AHTTP/1\.[01] 200\b!, 'status line valid');
|
67
|
+
is(readline($c), $mem_class, '1 megabyte file is now memory-backed');
|
68
|
+
|
69
|
+
my $too_big = $one_meg + 1;
|
70
|
+
$c = tcp_start($srv, "PUT /tmp_class HTTP/1.0\r\nContent-Length: $too_big");
|
71
|
+
($status, $hdr) = slurp_hdr($c);
|
72
|
+
like($status, qr!\AHTTP/1\.[01] 200\b!, 'status line valid');
|
73
|
+
is(readline($c), $fs_class, '1 megabyte + 1 byte file is FS-backed');
|
74
|
+
|
75
|
+
|
76
|
+
undef $ar;
|
77
|
+
check_stderr;
|
78
|
+
undef $tmpdir;
|
79
|
+
done_testing;
|
data/t/detach.ru
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
# frozen_string_literal: false
|
2
|
+
use Rack::ContentType, "text/plain"
|
3
|
+
fifo_path = ENV["TEST_FIFO"] or abort "TEST_FIFO not set"
|
4
|
+
run lambda { |env|
|
5
|
+
pid = fork do
|
6
|
+
File.open(fifo_path, "wb") do |fp|
|
7
|
+
fp.write "HIHI"
|
8
|
+
end
|
9
|
+
end
|
10
|
+
Process.detach(pid)
|
11
|
+
[ 200, {}, [ pid.to_s ] ]
|
12
|
+
}
|
data/t/env.ru
ADDED
@@ -0,0 +1,6 @@
|
|
1
|
+
# frozen_string_literal: false
|
2
|
+
# This rack app returns an invalid status code, which will cause
|
3
|
+
# Rack::Lint to throw an exception if it is present. This
|
4
|
+
# is used to check whether Rack::Lint is in the stack or not.
|
5
|
+
|
6
|
+
run lambda {|env| return [42, {}, ["Rack::Lint wasn't there if you see this"]]}
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: false
|
2
|
+
use Rack::ContentLength
|
3
|
+
headers = { 'content-type' => 'text/plain' }
|
4
|
+
run lambda { |env|
|
5
|
+
case env['PATH_INFO']
|
6
|
+
when "/block-forever"
|
7
|
+
Process.kill(:STOP, $$)
|
8
|
+
sleep # in case STOP signal is not received in time
|
9
|
+
[ 500, headers, [ "Should never get here\n" ] ]
|
10
|
+
else
|
11
|
+
[ 200, headers, [ "#$$" ] ]
|
12
|
+
end
|
13
|
+
}
|
@@ -0,0 +1,60 @@
|
|
1
|
+
#!perl -w
|
2
|
+
# Copyright (C) unicorn hackers <unicorn-public@yhbt.net>
|
3
|
+
# License: GPL-3.0+ <https://www.gnu.org/licenses/gpl-3.0.txt>
|
4
|
+
use v5.14; BEGIN { require './t/lib.perl' };
|
5
|
+
use autodie;
|
6
|
+
use Time::HiRes qw(clock_gettime CLOCK_MONOTONIC);
|
7
|
+
mkdir "$tmpdir/alt";
|
8
|
+
my $srv = tcp_server();
|
9
|
+
write_file '>', $u_conf, <<EOM;
|
10
|
+
pid "$tmpdir/pid"
|
11
|
+
preload_app true
|
12
|
+
stderr_path "$err_log"
|
13
|
+
timeout 3 # WORST FEATURE EVER
|
14
|
+
EOM
|
15
|
+
|
16
|
+
my $ar = unicorn(qw(-E none t/heartbeat-timeout.ru -c), $u_conf, { 3 => $srv });
|
17
|
+
|
18
|
+
my ($status, $hdr, $wpid) = do_req($srv, 'GET /pid HTTP/1.0');
|
19
|
+
like($status, qr!\AHTTP/1\.[01] 200\b!, 'PID request succeeds');
|
20
|
+
like($wpid, qr/\A[0-9]+\z/, 'worker is running');
|
21
|
+
|
22
|
+
my $t0 = clock_gettime(CLOCK_MONOTONIC);
|
23
|
+
my $c = tcp_start($srv, 'GET /block-forever HTTP/1.0');
|
24
|
+
vec(my $rvec = '', fileno($c), 1) = 1;
|
25
|
+
is(select($rvec, undef, undef, 6), 1, 'got readiness');
|
26
|
+
$c->blocking(0);
|
27
|
+
is(sysread($c, my $buf, 128), 0, 'got EOF response');
|
28
|
+
my $elapsed = clock_gettime(CLOCK_MONOTONIC) - $t0;
|
29
|
+
ok($elapsed > 3, 'timeout took >3s');
|
30
|
+
|
31
|
+
my @timeout_err = slurp($err_log);
|
32
|
+
truncate($err_log, 0);
|
33
|
+
is(grep(/timeout \(\d+s > 3s\), killing/, @timeout_err), 1,
|
34
|
+
'noted timeout error') or diag explain(\@timeout_err);
|
35
|
+
|
36
|
+
# did it respawn?
|
37
|
+
($status, $hdr, my $new_pid) = do_req($srv, 'GET /pid HTTP/1.0');
|
38
|
+
like($status, qr!\AHTTP/1\.[01] 200\b!, 'PID request succeeds');
|
39
|
+
isnt($new_pid, $wpid, 'spawned new worker');
|
40
|
+
|
41
|
+
diag 'SIGSTOP for 4 seconds...';
|
42
|
+
$ar->do_kill('STOP');
|
43
|
+
sleep 4;
|
44
|
+
$ar->do_kill('CONT');
|
45
|
+
for my $i (1..2) {
|
46
|
+
($status, $hdr, my $spid) = do_req($srv, 'GET /pid HTTP/1.0');
|
47
|
+
like($status, qr!\AHTTP/1\.[01] 200\b!,
|
48
|
+
"PID request succeeds #$i after STOP+CONT");
|
49
|
+
is($new_pid, $spid, "worker pid unchanged after STOP+CONT #$i");
|
50
|
+
if ($i == 1) {
|
51
|
+
diag 'sleeping 2s to ensure timeout is not delayed';
|
52
|
+
sleep 2;
|
53
|
+
}
|
54
|
+
}
|
55
|
+
|
56
|
+
$ar->join('TERM');
|
57
|
+
check_stderr;
|
58
|
+
undef $tmpdir;
|
59
|
+
|
60
|
+
done_testing;
|
data/t/integration.ru
ADDED
@@ -0,0 +1,129 @@
|
|
1
|
+
#!ruby
|
2
|
+
# frozen_string_literal: false
|
3
|
+
# Copyright (C) unicorn hackers <unicorn-public@80x24.org>
|
4
|
+
# License: GPL-3.0+ <https://www.gnu.org/licenses/gpl-3.0.txt>
|
5
|
+
|
6
|
+
# this goes for t/integration.t We'll try to put as many tests
|
7
|
+
# in here as possible to avoid startup overhead of Ruby.
|
8
|
+
|
9
|
+
def early_hints(env, val)
|
10
|
+
env['rack.early_hints'].call('link' => val) # val may be ary or string
|
11
|
+
[ 200, {}, [ val.class.to_s ] ]
|
12
|
+
end
|
13
|
+
|
14
|
+
$orig_rack_200 = nil
|
15
|
+
def tweak_status_code
|
16
|
+
$orig_rack_200 = Rack::Utils::HTTP_STATUS_CODES[200]
|
17
|
+
Rack::Utils::HTTP_STATUS_CODES[200] = "HI"
|
18
|
+
[ 200, {}, [] ]
|
19
|
+
end
|
20
|
+
|
21
|
+
def restore_status_code
|
22
|
+
$orig_rack_200 or return [ 500, {}, [] ]
|
23
|
+
Rack::Utils::HTTP_STATUS_CODES[200] = $orig_rack_200
|
24
|
+
[ 200, {}, [] ]
|
25
|
+
end
|
26
|
+
|
27
|
+
class WriteOnClose
|
28
|
+
def each(&block)
|
29
|
+
@callback = block
|
30
|
+
end
|
31
|
+
|
32
|
+
def close
|
33
|
+
@callback.call "7\r\nGoodbye\r\n0\r\n\r\n"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def write_on_close
|
38
|
+
[ 200, { 'transfer-encoding' => 'chunked' }, WriteOnClose.new ]
|
39
|
+
end
|
40
|
+
|
41
|
+
def env_dump(env, dump_body = false)
|
42
|
+
require 'json'
|
43
|
+
h = {}
|
44
|
+
env.each do |k,v|
|
45
|
+
case v
|
46
|
+
when String, Integer, true, false; h[k] = v
|
47
|
+
else
|
48
|
+
case k
|
49
|
+
when 'rack.version', 'rack.after_reply'; h[k] = v
|
50
|
+
when 'rack.input'; h[k] = v.class.to_s
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
h['unicorn_test.body'] = env['rack.input'].read if dump_body
|
55
|
+
h.to_json
|
56
|
+
end
|
57
|
+
|
58
|
+
def rack_input_tests(env)
|
59
|
+
return [ 100, {}, [] ] if /\A100-continue\z/i =~ env['HTTP_EXPECT']
|
60
|
+
cap = 16384
|
61
|
+
require 'digest/md5'
|
62
|
+
dig = Digest::MD5.new
|
63
|
+
input = env['rack.input']
|
64
|
+
case env['PATH_INFO']
|
65
|
+
when '/rack_input/size_first'; input.size
|
66
|
+
when '/rack_input/rewind_first'; input.rewind
|
67
|
+
when '/rack_input'; # OK
|
68
|
+
else
|
69
|
+
abort "bad path: #{env['PATH_INFO']}"
|
70
|
+
end
|
71
|
+
if buf = input.read(rand(cap))
|
72
|
+
begin
|
73
|
+
raise "#{buf.size} > #{cap}" if buf.size > cap
|
74
|
+
dig.update(buf)
|
75
|
+
end while input.read(rand(cap), buf)
|
76
|
+
buf.clear # remove this call if Ruby ever gets escape analysis
|
77
|
+
end
|
78
|
+
h = { 'content-type' => 'text/plain' }
|
79
|
+
if env['HTTP_TRAILER'] =~ /\bContent-MD5\b/i
|
80
|
+
cmd5_b64 = env['HTTP_CONTENT_MD5'] or return [500, {}, ['No Content-MD5']]
|
81
|
+
cmd5_bin = cmd5_b64.unpack('m')[0]
|
82
|
+
if cmd5_bin != dig.digest
|
83
|
+
h['content-length'] = cmd5_b64.size.to_s
|
84
|
+
return [ 500, h, [ cmd5_b64 ] ]
|
85
|
+
end
|
86
|
+
end
|
87
|
+
h['content-length'] = '32'
|
88
|
+
[ 200, h, [ dig.hexdigest ] ]
|
89
|
+
end
|
90
|
+
|
91
|
+
$nr_aborts = 0
|
92
|
+
run(lambda do |env|
|
93
|
+
case env['REQUEST_METHOD']
|
94
|
+
when 'GET'
|
95
|
+
case env['PATH_INFO']
|
96
|
+
when '/rack-2-newline-headers'; [ 200, { 'X-R2' => "a\nb\nc" }, [] ]
|
97
|
+
when '/rack-3-array-headers'; [ 200, { 'x-r3' => %w(a b c) }, [] ]
|
98
|
+
when '/nil-header-value'; [ 200, { 'X-Nil' => nil }, [] ]
|
99
|
+
when '/unknown-status-pass-through'; [ '666 I AM THE BEAST', {}, [] ]
|
100
|
+
when '/env_dump'; [ 200, {}, [ env_dump(env) ] ]
|
101
|
+
when '/write_on_close'; write_on_close
|
102
|
+
when '/pid'; [ 200, {}, [ "#$$\n" ] ]
|
103
|
+
when '/early_hints_rack2'; early_hints(env, "r\n2")
|
104
|
+
when '/early_hints_rack3'; early_hints(env, %w(r 3))
|
105
|
+
when '/broken_app'; raise RuntimeError, 'hello'
|
106
|
+
when '/aborted'; $nr_aborts += 1; [ 200, {}, [] ]
|
107
|
+
when '/nr_aborts'; [ 200, { 'nr-aborts' => "#$nr_aborts" }, [] ]
|
108
|
+
when '/nil'; nil
|
109
|
+
when '/read_fifo'; [ 200, {}, [ File.read(env['HTTP_READ_FIFO']) ] ]
|
110
|
+
else '/'; [ 200, {}, [ env_dump(env) ] ]
|
111
|
+
end # case PATH_INFO (GET)
|
112
|
+
when 'POST'
|
113
|
+
case env['PATH_INFO']
|
114
|
+
when '/tweak-status-code'; tweak_status_code
|
115
|
+
when '/restore-status-code'; restore_status_code
|
116
|
+
when '/env_dump'; [ 200, {}, [ env_dump(env, true) ] ]
|
117
|
+
end # case PATH_INFO (POST)
|
118
|
+
# ...
|
119
|
+
when 'PUT'
|
120
|
+
case env['PATH_INFO']
|
121
|
+
when %r{\A/rack_input}; rack_input_tests(env)
|
122
|
+
when '/env_dump'; [ 200, {}, [ env_dump(env) ] ]
|
123
|
+
end
|
124
|
+
when 'OPTIONS'
|
125
|
+
case env['REQUEST_URI']
|
126
|
+
when '*'; [ 200, {}, [ env_dump(env) ] ]
|
127
|
+
end
|
128
|
+
end # case REQUEST_METHOD
|
129
|
+
end) # run
|