unicorn-maintained 6.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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 +149 -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 +317 -0
- data/HACKING +112 -0
- data/ISSUES +102 -0
- data/KNOWN_ISSUES +79 -0
- data/LATEST +1 -0
- data/LICENSE +67 -0
- data/Links +58 -0
- data/NEWS +1 -0
- data/PHILOSOPHY +139 -0
- data/README +156 -0
- data/Rakefile +16 -0
- data/SIGNALS +123 -0
- data/Sandbox +104 -0
- data/TODO +3 -0
- data/TUNING +119 -0
- data/archive/.gitignore +3 -0
- data/archive/slrnpull.conf +4 -0
- data/bin/unicorn +128 -0
- data/bin/unicorn_rails +209 -0
- data/examples/big_app_gc.rb +2 -0
- data/examples/echo.ru +26 -0
- data/examples/init.sh +102 -0
- data/examples/logger_mp_safe.rb +25 -0
- data/examples/logrotate.conf +44 -0
- data/examples/nginx.conf +156 -0
- data/examples/unicorn.conf.minimal.rb +13 -0
- data/examples/unicorn.conf.rb +110 -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 +116 -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 +39 -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 +4334 -0
- data/ext/unicorn_http/unicorn_http.rl +1040 -0
- data/ext/unicorn_http/unicorn_http_common.rl +76 -0
- data/lib/unicorn/app/old_rails/static.rb +59 -0
- data/lib/unicorn/app/old_rails.rb +35 -0
- data/lib/unicorn/cgi_wrapper.rb +147 -0
- data/lib/unicorn/configurator.rb +748 -0
- data/lib/unicorn/const.rb +21 -0
- data/lib/unicorn/http_request.rb +201 -0
- data/lib/unicorn/http_response.rb +93 -0
- data/lib/unicorn/http_server.rb +859 -0
- data/lib/unicorn/launcher.rb +62 -0
- data/lib/unicorn/oob_gc.rb +81 -0
- data/lib/unicorn/preread_input.rb +33 -0
- data/lib/unicorn/select_waiter.rb +6 -0
- data/lib/unicorn/socket_helper.rb +185 -0
- data/lib/unicorn/stream_input.rb +151 -0
- data/lib/unicorn/tee_input.rb +131 -0
- data/lib/unicorn/tmpio.rb +33 -0
- data/lib/unicorn/util.rb +90 -0
- data/lib/unicorn/version.rb +1 -0
- data/lib/unicorn/worker.rb +165 -0
- data/lib/unicorn.rb +136 -0
- data/man/man1/unicorn.1 +222 -0
- data/man/man1/unicorn_rails.1 +207 -0
- data/setup.rb +1586 -0
- data/t/.gitignore +4 -0
- data/t/GNUmakefile +5 -0
- data/t/README +49 -0
- data/t/active-unix-socket.t +117 -0
- data/t/bin/unused_listen +40 -0
- data/t/broken-app.ru +12 -0
- data/t/client_body_buffer_size.ru +14 -0
- data/t/client_body_buffer_size.t +80 -0
- data/t/detach.ru +11 -0
- data/t/env.ru +3 -0
- data/t/fails-rack-lint.ru +5 -0
- data/t/heartbeat-timeout.ru +12 -0
- data/t/heartbeat-timeout.t +62 -0
- data/t/integration.ru +115 -0
- data/t/integration.t +356 -0
- data/t/lib.perl +258 -0
- data/t/listener_names.ru +4 -0
- data/t/my-tap-lib.sh +201 -0
- data/t/oob_gc.ru +17 -0
- data/t/oob_gc_path.ru +17 -0
- data/t/pid.ru +3 -0
- data/t/preread_input.ru +22 -0
- data/t/reload-bad-config.t +54 -0
- data/t/reopen-logs.ru +13 -0
- data/t/reopen-logs.t +39 -0
- data/t/t0008-back_out_of_upgrade.sh +110 -0
- data/t/t0009-broken-app.sh +56 -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 +12 -0
- data/t/t0014-rewindable-input-true.sh +24 -0
- data/t/t0014.ru +12 -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 +13 -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 +67 -0
- data/t/working_directory.t +94 -0
- data/test/aggregate.rb +15 -0
- data/test/benchmark/README +60 -0
- data/test/benchmark/dd.ru +18 -0
- data/test/benchmark/ddstream.ru +50 -0
- data/test/benchmark/readinput.ru +40 -0
- data/test/benchmark/stack.ru +8 -0
- data/test/benchmark/uconnect.perl +66 -0
- data/test/exec/README +5 -0
- data/test/exec/test_exec.rb +1029 -0
- data/test/test_helper.rb +306 -0
- data/test/unit/test_ccc.rb +91 -0
- data/test/unit/test_configurator.rb +175 -0
- data/test/unit/test_droplet.rb +28 -0
- data/test/unit/test_http_parser.rb +884 -0
- data/test/unit/test_http_parser_ng.rb +714 -0
- data/test/unit/test_request.rb +169 -0
- data/test/unit/test_server.rb +244 -0
- data/test/unit/test_signals.rb +188 -0
- data/test/unit/test_socket_helper.rb +159 -0
- data/test/unit/test_stream_input.rb +210 -0
- data/test/unit/test_tee_input.rb +303 -0
- data/test/unit/test_util.rb +131 -0
- data/test/unit/test_waiter.rb +34 -0
- data/unicorn.gemspec +48 -0
- metadata +275 -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,117 @@
|
|
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
|
+
open my $fh, '>', "$tmpdir/u1.conf.rb";
|
15
|
+
print $fh <<EOM;
|
16
|
+
pid "$tmpdir/u.pid"
|
17
|
+
listen "$u1"
|
18
|
+
stderr_path "$err_log"
|
19
|
+
EOM
|
20
|
+
close $fh;
|
21
|
+
|
22
|
+
open $fh, '>', "$tmpdir/u2.conf.rb";
|
23
|
+
print $fh <<EOM;
|
24
|
+
pid "$tmpdir/u.pid"
|
25
|
+
listen "$u2"
|
26
|
+
stderr_path "$tmpdir/err2.log"
|
27
|
+
EOM
|
28
|
+
close $fh;
|
29
|
+
|
30
|
+
open $fh, '>', "$tmpdir/u3.conf.rb";
|
31
|
+
print $fh <<EOM;
|
32
|
+
pid "$tmpdir/u3.pid"
|
33
|
+
listen "$u1"
|
34
|
+
stderr_path "$tmpdir/err3.log"
|
35
|
+
EOM
|
36
|
+
close $fh;
|
37
|
+
}
|
38
|
+
|
39
|
+
my @uarg = qw(-D -E none t/integration.ru);
|
40
|
+
|
41
|
+
# this pipe will be used to notify us when all daemons die:
|
42
|
+
pipe(my $p0, my $p1);
|
43
|
+
fcntl($p1, POSIX::F_SETFD, 0);
|
44
|
+
|
45
|
+
# start the first instance
|
46
|
+
unicorn('-c', "$tmpdir/u1.conf.rb", @uarg)->join;
|
47
|
+
is($?, 0, 'daemonized 1st process');
|
48
|
+
chomp($to_kill{u1} = slurp("$tmpdir/u.pid"));
|
49
|
+
like($to_kill{u1}, qr/\A\d+\z/s, 'read pid file');
|
50
|
+
|
51
|
+
chomp(my $worker_pid = readline(unix_start($u1, 'GET /pid')));
|
52
|
+
like($worker_pid, qr/\A\d+\z/s, 'captured worker pid');
|
53
|
+
ok(kill(0, $worker_pid), 'worker is kill-able');
|
54
|
+
|
55
|
+
|
56
|
+
# 2nd process conflicts on PID
|
57
|
+
unicorn('-c', "$tmpdir/u2.conf.rb", @uarg)->join;
|
58
|
+
isnt($?, 0, 'conflicting PID file fails to start');
|
59
|
+
|
60
|
+
chomp(my $pidf = slurp("$tmpdir/u.pid"));
|
61
|
+
is($pidf, $to_kill{u1}, 'pid file contents unchanged after start failure');
|
62
|
+
|
63
|
+
chomp(my $pid2 = readline(unix_start($u1, 'GET /pid')));
|
64
|
+
is($worker_pid, $pid2, 'worker PID unchanged');
|
65
|
+
|
66
|
+
|
67
|
+
# 3rd process conflicts on socket
|
68
|
+
unicorn('-c', "$tmpdir/u3.conf.rb", @uarg)->join;
|
69
|
+
isnt($?, 0, 'conflicting UNIX socket fails to start');
|
70
|
+
|
71
|
+
chomp($pid2 = readline(unix_start($u1, 'GET /pid')));
|
72
|
+
is($worker_pid, $pid2, 'worker PID still unchanged');
|
73
|
+
|
74
|
+
chomp($pidf = slurp("$tmpdir/u.pid"));
|
75
|
+
is($pidf, $to_kill{u1}, 'pid file contents unchanged after 2nd start failure');
|
76
|
+
|
77
|
+
{ # teardown initial process via SIGKILL
|
78
|
+
ok(kill('KILL', delete $to_kill{u1}), 'SIGKILL initial daemon');
|
79
|
+
close $p1;
|
80
|
+
vec(my $rvec = '', fileno($p0), 1) = 1;
|
81
|
+
is(select($rvec, undef, undef, 5), 1, 'timeout for pipe HUP');
|
82
|
+
is(my $undef = <$p0>, undef, 'process closed pipe writer at exit');
|
83
|
+
ok(-f "$tmpdir/u.pid", 'pid file stayed after SIGKILL');
|
84
|
+
ok(-S $u1, 'socket stayed after SIGKILL');
|
85
|
+
is(IO::Socket::UNIX->new(Peer => $u1, Type => SOCK_STREAM), undef,
|
86
|
+
'fail to connect to u1');
|
87
|
+
for (1..50) { # wait for init process to reap worker
|
88
|
+
kill(0, $worker_pid) or last;
|
89
|
+
sleep 0.011;
|
90
|
+
}
|
91
|
+
ok(!kill(0, $worker_pid), 'worker gone after parent dies');
|
92
|
+
}
|
93
|
+
|
94
|
+
# restart the first instance
|
95
|
+
{
|
96
|
+
pipe($p0, $p1);
|
97
|
+
fcntl($p1, POSIX::F_SETFD, 0);
|
98
|
+
unicorn('-c', "$tmpdir/u1.conf.rb", @uarg)->join;
|
99
|
+
is($?, 0, 'daemonized 1st process');
|
100
|
+
chomp($to_kill{u1} = slurp("$tmpdir/u.pid"));
|
101
|
+
like($to_kill{u1}, qr/\A\d+\z/s, 'read pid file');
|
102
|
+
|
103
|
+
chomp($pid2 = readline(unix_start($u1, 'GET /pid')));
|
104
|
+
like($pid2, qr/\A\d+\z/, 'worker running');
|
105
|
+
|
106
|
+
ok(kill('TERM', delete $to_kill{u1}), 'SIGTERM restarted daemon');
|
107
|
+
close $p1;
|
108
|
+
vec(my $rvec = '', fileno($p0), 1) = 1;
|
109
|
+
is(select($rvec, undef, undef, 5), 1, 'timeout for pipe HUP');
|
110
|
+
is(my $undef = <$p0>, undef, 'process closed pipe writer at exit');
|
111
|
+
ok(!-f "$tmpdir/u.pid", 'pid file gone after SIGTERM');
|
112
|
+
ok(-S $u1, 'socket stays after SIGTERM');
|
113
|
+
}
|
114
|
+
|
115
|
+
check_stderr;
|
116
|
+
undef $tmpdir;
|
117
|
+
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}")
|
data/t/broken-app.ru
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
# we do not want Rack::Lint or anything to protect us
|
2
|
+
use Rack::ContentLength
|
3
|
+
use Rack::ContentType, "text/plain"
|
4
|
+
map "/" do
|
5
|
+
run lambda { |env| [ 200, {}, [ "OK\n" ] ] }
|
6
|
+
end
|
7
|
+
map "/raise" do
|
8
|
+
run lambda { |env| raise "BAD" }
|
9
|
+
end
|
10
|
+
map "/nil" do
|
11
|
+
run lambda { |env| nil }
|
12
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
#\ -E none
|
2
|
+
app = lambda do |env|
|
3
|
+
input = env['rack.input']
|
4
|
+
case env["PATH_INFO"]
|
5
|
+
when "/tmp_class"
|
6
|
+
body = input.instance_variable_get(:@tmp).class.name
|
7
|
+
when "/input_class"
|
8
|
+
body = input.class.name
|
9
|
+
else
|
10
|
+
return [ 500, {}, [] ]
|
11
|
+
end
|
12
|
+
[ 200, {}, [ body ] ]
|
13
|
+
end
|
14
|
+
run app
|
@@ -0,0 +1,80 @@
|
|
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
|
+
open my $conf_fh, '>', $u_conf;
|
8
|
+
$conf_fh->autoflush(1);
|
9
|
+
print $conf_fh <<EOM;
|
10
|
+
client_body_buffer_size 0
|
11
|
+
EOM
|
12
|
+
my $srv = tcp_server();
|
13
|
+
my $host_port = tcp_host_port($srv);
|
14
|
+
my @uarg = (qw(-E none t/client_body_buffer_size.ru -c), $u_conf);
|
15
|
+
my $ar = unicorn(@uarg, { 3 => $srv });
|
16
|
+
my ($c, $status, $hdr);
|
17
|
+
my $mem_class = 'StringIO';
|
18
|
+
my $fs_class = 'Unicorn::TmpIO';
|
19
|
+
|
20
|
+
$c = tcp_start($srv, "PUT /input_class HTTP/1.0\r\nContent-Length: 0");
|
21
|
+
($status, $hdr) = slurp_hdr($c);
|
22
|
+
like($status, qr!\AHTTP/1\.[01] 200\b!, 'status line valid');
|
23
|
+
is(readline($c), $mem_class, 'zero-byte file is StringIO');
|
24
|
+
|
25
|
+
$c = tcp_start($srv, "PUT /tmp_class HTTP/1.0\r\nContent-Length: 1");
|
26
|
+
print $c '.';
|
27
|
+
($status, $hdr) = slurp_hdr($c);
|
28
|
+
like($status, qr!\AHTTP/1\.[01] 200\b!, 'status line valid');
|
29
|
+
is(readline($c), $fs_class, '1 byte file is filesystem-backed');
|
30
|
+
|
31
|
+
|
32
|
+
my $fifo = "$tmpdir/fifo";
|
33
|
+
POSIX::mkfifo($fifo, 0600) or die "mkfifo: $!";
|
34
|
+
seek($conf_fh, 0, SEEK_SET);
|
35
|
+
truncate($conf_fh, 0);
|
36
|
+
print $conf_fh <<EOM;
|
37
|
+
after_fork { |_,_| File.open('$fifo', 'w') { |fp| fp.write "pid=#\$\$" } }
|
38
|
+
EOM
|
39
|
+
$ar->do_kill('HUP');
|
40
|
+
open my $fifo_fh, '<', $fifo;
|
41
|
+
like(my $wpid = readline($fifo_fh), qr/\Apid=\d+\z/a ,
|
42
|
+
'reloaded w/ default client_body_buffer_size');
|
43
|
+
|
44
|
+
|
45
|
+
$c = tcp_start($srv, "PUT /tmp_class HTTP/1.0\r\nContent-Length: 1");
|
46
|
+
($status, $hdr) = slurp_hdr($c);
|
47
|
+
like($status, qr!\AHTTP/1\.[01] 200\b!, 'status line valid');
|
48
|
+
is(readline($c), $mem_class, 'class for a 1 byte file is memory-backed');
|
49
|
+
|
50
|
+
|
51
|
+
my $one_meg = 1024 ** 2;
|
52
|
+
$c = tcp_start($srv, "PUT /tmp_class HTTP/1.0\r\nContent-Length: $one_meg");
|
53
|
+
($status, $hdr) = slurp_hdr($c);
|
54
|
+
like($status, qr!\AHTTP/1\.[01] 200\b!, 'status line valid');
|
55
|
+
is(readline($c), $fs_class, '1 megabyte file is FS-backed');
|
56
|
+
|
57
|
+
# reload with bigger client_body_buffer_size
|
58
|
+
say $conf_fh "client_body_buffer_size $one_meg";
|
59
|
+
$ar->do_kill('HUP');
|
60
|
+
open $fifo_fh, '<', $fifo;
|
61
|
+
like($wpid = readline($fifo_fh), qr/\Apid=\d+\z/a ,
|
62
|
+
'reloaded w/ bigger client_body_buffer_size');
|
63
|
+
|
64
|
+
|
65
|
+
$c = tcp_start($srv, "PUT /tmp_class HTTP/1.0\r\nContent-Length: $one_meg");
|
66
|
+
($status, $hdr) = slurp_hdr($c);
|
67
|
+
like($status, qr!\AHTTP/1\.[01] 200\b!, 'status line valid');
|
68
|
+
is(readline($c), $mem_class, '1 megabyte file is now memory-backed');
|
69
|
+
|
70
|
+
my $too_big = $one_meg + 1;
|
71
|
+
$c = tcp_start($srv, "PUT /tmp_class HTTP/1.0\r\nContent-Length: $too_big");
|
72
|
+
($status, $hdr) = slurp_hdr($c);
|
73
|
+
like($status, qr!\AHTTP/1\.[01] 200\b!, 'status line valid');
|
74
|
+
is(readline($c), $fs_class, '1 megabyte + 1 byte file is FS-backed');
|
75
|
+
|
76
|
+
|
77
|
+
undef $ar;
|
78
|
+
check_stderr;
|
79
|
+
undef $tmpdir;
|
80
|
+
done_testing;
|
data/t/detach.ru
ADDED
data/t/env.ru
ADDED
@@ -0,0 +1,5 @@
|
|
1
|
+
# This rack app returns an invalid status code, which will cause
|
2
|
+
# Rack::Lint to throw an exception if it is present. This
|
3
|
+
# is used to check whether Rack::Lint is in the stack or not.
|
4
|
+
|
5
|
+
run lambda {|env| return [42, {}, ["Rack::Lint wasn't there if you see this"]]}
|
@@ -0,0 +1,12 @@
|
|
1
|
+
use Rack::ContentLength
|
2
|
+
headers = { 'content-type' => 'text/plain' }
|
3
|
+
run lambda { |env|
|
4
|
+
case env['PATH_INFO']
|
5
|
+
when "/block-forever"
|
6
|
+
Process.kill(:STOP, $$)
|
7
|
+
sleep # in case STOP signal is not received in time
|
8
|
+
[ 500, headers, [ "Should never get here\n" ] ]
|
9
|
+
else
|
10
|
+
[ 200, headers, [ "#$$" ] ]
|
11
|
+
end
|
12
|
+
}
|
@@ -0,0 +1,62 @@
|
|
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
|
+
open my $fh, '>', $u_conf;
|
10
|
+
print $fh <<EOM;
|
11
|
+
pid "$tmpdir/pid"
|
12
|
+
preload_app true
|
13
|
+
stderr_path "$err_log"
|
14
|
+
timeout 3 # WORST FEATURE EVER
|
15
|
+
EOM
|
16
|
+
close $fh;
|
17
|
+
|
18
|
+
my $ar = unicorn(qw(-E none t/heartbeat-timeout.ru -c), $u_conf, { 3 => $srv });
|
19
|
+
|
20
|
+
my ($status, $hdr, $wpid) = do_req($srv, 'GET /pid HTTP/1.0');
|
21
|
+
like($status, qr!\AHTTP/1\.[01] 200\b!, 'PID request succeeds');
|
22
|
+
like($wpid, qr/\A[0-9]+\z/, 'worker is running');
|
23
|
+
|
24
|
+
my $t0 = clock_gettime(CLOCK_MONOTONIC);
|
25
|
+
my $c = tcp_start($srv, 'GET /block-forever HTTP/1.0');
|
26
|
+
vec(my $rvec = '', fileno($c), 1) = 1;
|
27
|
+
is(select($rvec, undef, undef, 6), 1, 'got readiness');
|
28
|
+
$c->blocking(0);
|
29
|
+
is(sysread($c, my $buf, 128), 0, 'got EOF response');
|
30
|
+
my $elapsed = clock_gettime(CLOCK_MONOTONIC) - $t0;
|
31
|
+
ok($elapsed > 3, 'timeout took >3s');
|
32
|
+
|
33
|
+
my @timeout_err = slurp($err_log);
|
34
|
+
truncate($err_log, 0);
|
35
|
+
is(grep(/timeout \(\d+s > 3s\), killing/, @timeout_err), 1,
|
36
|
+
'noted timeout error') or diag explain(\@timeout_err);
|
37
|
+
|
38
|
+
# did it respawn?
|
39
|
+
($status, $hdr, my $new_pid) = do_req($srv, 'GET /pid HTTP/1.0');
|
40
|
+
like($status, qr!\AHTTP/1\.[01] 200\b!, 'PID request succeeds');
|
41
|
+
isnt($new_pid, $wpid, 'spawned new worker');
|
42
|
+
|
43
|
+
diag 'SIGSTOP for 4 seconds...';
|
44
|
+
$ar->do_kill('STOP');
|
45
|
+
sleep 4;
|
46
|
+
$ar->do_kill('CONT');
|
47
|
+
for my $i (1..2) {
|
48
|
+
($status, $hdr, my $spid) = do_req($srv, 'GET /pid HTTP/1.0');
|
49
|
+
like($status, qr!\AHTTP/1\.[01] 200\b!,
|
50
|
+
"PID request succeeds #$i after STOP+CONT");
|
51
|
+
is($new_pid, $spid, "worker pid unchanged after STOP+CONT #$i");
|
52
|
+
if ($i == 1) {
|
53
|
+
diag 'sleeping 2s to ensure timeout is not delayed';
|
54
|
+
sleep 2;
|
55
|
+
}
|
56
|
+
}
|
57
|
+
|
58
|
+
$ar->join('TERM');
|
59
|
+
check_stderr;
|
60
|
+
undef $tmpdir;
|
61
|
+
|
62
|
+
done_testing;
|
data/t/integration.ru
ADDED
@@ -0,0 +1,115 @@
|
|
1
|
+
#!ruby
|
2
|
+
# Copyright (C) unicorn hackers <unicorn-public@80x24.org>
|
3
|
+
# License: GPL-3.0+ <https://www.gnu.org/licenses/gpl-3.0.txt>
|
4
|
+
|
5
|
+
# this goes for t/integration.t We'll try to put as many tests
|
6
|
+
# in here as possible to avoid startup overhead of Ruby.
|
7
|
+
|
8
|
+
def early_hints(env, val)
|
9
|
+
env['rack.early_hints'].call('link' => val) # val may be ary or string
|
10
|
+
[ 200, {}, [ val.class.to_s ] ]
|
11
|
+
end
|
12
|
+
|
13
|
+
$orig_rack_200 = nil
|
14
|
+
def tweak_status_code
|
15
|
+
$orig_rack_200 = Rack::Utils::HTTP_STATUS_CODES[200]
|
16
|
+
Rack::Utils::HTTP_STATUS_CODES[200] = "HI"
|
17
|
+
[ 200, {}, [] ]
|
18
|
+
end
|
19
|
+
|
20
|
+
def restore_status_code
|
21
|
+
$orig_rack_200 or return [ 500, {}, [] ]
|
22
|
+
Rack::Utils::HTTP_STATUS_CODES[200] = $orig_rack_200
|
23
|
+
[ 200, {}, [] ]
|
24
|
+
end
|
25
|
+
|
26
|
+
class WriteOnClose
|
27
|
+
def each(&block)
|
28
|
+
@callback = block
|
29
|
+
end
|
30
|
+
|
31
|
+
def close
|
32
|
+
@callback.call "7\r\nGoodbye\r\n0\r\n\r\n"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def write_on_close
|
37
|
+
[ 200, { 'transfer-encoding' => 'chunked' }, WriteOnClose.new ]
|
38
|
+
end
|
39
|
+
|
40
|
+
def env_dump(env)
|
41
|
+
require 'json'
|
42
|
+
h = {}
|
43
|
+
env.each do |k,v|
|
44
|
+
case v
|
45
|
+
when String, Integer, true, false; h[k] = v
|
46
|
+
else
|
47
|
+
case k
|
48
|
+
when 'rack.version', 'rack.after_reply'; h[k] = v
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
h.to_json
|
53
|
+
end
|
54
|
+
|
55
|
+
def rack_input_tests(env)
|
56
|
+
return [ 100, {}, [] ] if /\A100-continue\z/i =~ env['HTTP_EXPECT']
|
57
|
+
cap = 16384
|
58
|
+
require 'digest/md5'
|
59
|
+
dig = Digest::MD5.new
|
60
|
+
input = env['rack.input']
|
61
|
+
case env['PATH_INFO']
|
62
|
+
when '/rack_input/size_first'; input.size
|
63
|
+
when '/rack_input/rewind_first'; input.rewind
|
64
|
+
when '/rack_input'; # OK
|
65
|
+
else
|
66
|
+
abort "bad path: #{env['PATH_INFO']}"
|
67
|
+
end
|
68
|
+
if buf = input.read(rand(cap))
|
69
|
+
begin
|
70
|
+
raise "#{buf.size} > #{cap}" if buf.size > cap
|
71
|
+
dig.update(buf)
|
72
|
+
end while input.read(rand(cap), buf)
|
73
|
+
buf.clear # remove this call if Ruby ever gets escape analysis
|
74
|
+
end
|
75
|
+
h = { 'content-type' => 'text/plain' }
|
76
|
+
if env['HTTP_TRAILER'] =~ /\bContent-MD5\b/i
|
77
|
+
cmd5_b64 = env['HTTP_CONTENT_MD5'] or return [500, {}, ['No Content-MD5']]
|
78
|
+
cmd5_bin = cmd5_b64.unpack('m')[0]
|
79
|
+
if cmd5_bin != dig.digest
|
80
|
+
h['content-length'] = cmd5_b64.size.to_s
|
81
|
+
return [ 500, h, [ cmd5_b64 ] ]
|
82
|
+
end
|
83
|
+
end
|
84
|
+
h['content-length'] = '32'
|
85
|
+
[ 200, h, [ dig.hexdigest ] ]
|
86
|
+
end
|
87
|
+
|
88
|
+
run(lambda do |env|
|
89
|
+
case env['REQUEST_METHOD']
|
90
|
+
when 'GET'
|
91
|
+
case env['PATH_INFO']
|
92
|
+
when '/rack-2-newline-headers'; [ 200, { 'X-R2' => "a\nb\nc" }, [] ]
|
93
|
+
when '/rack-3-array-headers'; [ 200, { 'x-r3' => %w(a b c) }, [] ]
|
94
|
+
when '/nil-header-value'; [ 200, { 'X-Nil' => nil }, [] ]
|
95
|
+
when '/unknown-status-pass-through'; [ '666 I AM THE BEAST', {}, [] ]
|
96
|
+
when '/env_dump'; [ 200, {}, [ env_dump(env) ] ]
|
97
|
+
when '/write_on_close'; write_on_close
|
98
|
+
when '/pid'; [ 200, {}, [ "#$$\n" ] ]
|
99
|
+
when '/early_hints_rack2'; early_hints(env, "r\n2")
|
100
|
+
when '/early_hints_rack3'; early_hints(env, %w(r 3))
|
101
|
+
when '/broken_app'; raise RuntimeError, 'hello'
|
102
|
+
else '/'; [ 200, {}, [ env_dump(env) ] ]
|
103
|
+
end # case PATH_INFO (GET)
|
104
|
+
when 'POST'
|
105
|
+
case env['PATH_INFO']
|
106
|
+
when '/tweak-status-code'; tweak_status_code
|
107
|
+
when '/restore-status-code'; restore_status_code
|
108
|
+
end # case PATH_INFO (POST)
|
109
|
+
# ...
|
110
|
+
when 'PUT'
|
111
|
+
case env['PATH_INFO']
|
112
|
+
when %r{\A/rack_input}; rack_input_tests(env)
|
113
|
+
end
|
114
|
+
end # case REQUEST_METHOD
|
115
|
+
end) # run
|