unicorn-maintained 6.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (151) hide show
  1. checksums.yaml +7 -0
  2. data/.CHANGELOG.old +25 -0
  3. data/.document +28 -0
  4. data/.gitattributes +5 -0
  5. data/.gitignore +25 -0
  6. data/.mailmap +26 -0
  7. data/.manifest +149 -0
  8. data/.olddoc.yml +25 -0
  9. data/Application_Timeouts +77 -0
  10. data/CONTRIBUTORS +39 -0
  11. data/COPYING +674 -0
  12. data/DESIGN +99 -0
  13. data/Documentation/.gitignore +3 -0
  14. data/Documentation/unicorn.1 +222 -0
  15. data/Documentation/unicorn_rails.1 +207 -0
  16. data/FAQ +70 -0
  17. data/GIT-VERSION-FILE +1 -0
  18. data/GIT-VERSION-GEN +39 -0
  19. data/GNUmakefile +317 -0
  20. data/HACKING +112 -0
  21. data/ISSUES +102 -0
  22. data/KNOWN_ISSUES +79 -0
  23. data/LATEST +1 -0
  24. data/LICENSE +67 -0
  25. data/Links +58 -0
  26. data/NEWS +1 -0
  27. data/PHILOSOPHY +139 -0
  28. data/README +156 -0
  29. data/Rakefile +16 -0
  30. data/SIGNALS +123 -0
  31. data/Sandbox +104 -0
  32. data/TODO +3 -0
  33. data/TUNING +119 -0
  34. data/archive/.gitignore +3 -0
  35. data/archive/slrnpull.conf +4 -0
  36. data/bin/unicorn +128 -0
  37. data/bin/unicorn_rails +209 -0
  38. data/examples/big_app_gc.rb +2 -0
  39. data/examples/echo.ru +26 -0
  40. data/examples/init.sh +102 -0
  41. data/examples/logger_mp_safe.rb +25 -0
  42. data/examples/logrotate.conf +44 -0
  43. data/examples/nginx.conf +156 -0
  44. data/examples/unicorn.conf.minimal.rb +13 -0
  45. data/examples/unicorn.conf.rb +110 -0
  46. data/examples/unicorn.socket +11 -0
  47. data/examples/unicorn@.service +40 -0
  48. data/ext/unicorn_http/CFLAGS +13 -0
  49. data/ext/unicorn_http/c_util.h +116 -0
  50. data/ext/unicorn_http/common_field_optimization.h +128 -0
  51. data/ext/unicorn_http/epollexclusive.h +128 -0
  52. data/ext/unicorn_http/ext_help.h +38 -0
  53. data/ext/unicorn_http/extconf.rb +39 -0
  54. data/ext/unicorn_http/global_variables.h +97 -0
  55. data/ext/unicorn_http/httpdate.c +91 -0
  56. data/ext/unicorn_http/unicorn_http.c +4334 -0
  57. data/ext/unicorn_http/unicorn_http.rl +1040 -0
  58. data/ext/unicorn_http/unicorn_http_common.rl +76 -0
  59. data/lib/unicorn/app/old_rails/static.rb +59 -0
  60. data/lib/unicorn/app/old_rails.rb +35 -0
  61. data/lib/unicorn/cgi_wrapper.rb +147 -0
  62. data/lib/unicorn/configurator.rb +748 -0
  63. data/lib/unicorn/const.rb +21 -0
  64. data/lib/unicorn/http_request.rb +201 -0
  65. data/lib/unicorn/http_response.rb +93 -0
  66. data/lib/unicorn/http_server.rb +859 -0
  67. data/lib/unicorn/launcher.rb +62 -0
  68. data/lib/unicorn/oob_gc.rb +81 -0
  69. data/lib/unicorn/preread_input.rb +33 -0
  70. data/lib/unicorn/select_waiter.rb +6 -0
  71. data/lib/unicorn/socket_helper.rb +185 -0
  72. data/lib/unicorn/stream_input.rb +151 -0
  73. data/lib/unicorn/tee_input.rb +131 -0
  74. data/lib/unicorn/tmpio.rb +33 -0
  75. data/lib/unicorn/util.rb +90 -0
  76. data/lib/unicorn/version.rb +1 -0
  77. data/lib/unicorn/worker.rb +165 -0
  78. data/lib/unicorn.rb +136 -0
  79. data/man/man1/unicorn.1 +222 -0
  80. data/man/man1/unicorn_rails.1 +207 -0
  81. data/setup.rb +1586 -0
  82. data/t/.gitignore +4 -0
  83. data/t/GNUmakefile +5 -0
  84. data/t/README +49 -0
  85. data/t/active-unix-socket.t +117 -0
  86. data/t/bin/unused_listen +40 -0
  87. data/t/broken-app.ru +12 -0
  88. data/t/client_body_buffer_size.ru +14 -0
  89. data/t/client_body_buffer_size.t +80 -0
  90. data/t/detach.ru +11 -0
  91. data/t/env.ru +3 -0
  92. data/t/fails-rack-lint.ru +5 -0
  93. data/t/heartbeat-timeout.ru +12 -0
  94. data/t/heartbeat-timeout.t +62 -0
  95. data/t/integration.ru +115 -0
  96. data/t/integration.t +356 -0
  97. data/t/lib.perl +258 -0
  98. data/t/listener_names.ru +4 -0
  99. data/t/my-tap-lib.sh +201 -0
  100. data/t/oob_gc.ru +17 -0
  101. data/t/oob_gc_path.ru +17 -0
  102. data/t/pid.ru +3 -0
  103. data/t/preread_input.ru +22 -0
  104. data/t/reload-bad-config.t +54 -0
  105. data/t/reopen-logs.ru +13 -0
  106. data/t/reopen-logs.t +39 -0
  107. data/t/t0008-back_out_of_upgrade.sh +110 -0
  108. data/t/t0009-broken-app.sh +56 -0
  109. data/t/t0010-reap-logging.sh +55 -0
  110. data/t/t0012-reload-empty-config.sh +86 -0
  111. data/t/t0013-rewindable-input-false.sh +24 -0
  112. data/t/t0013.ru +12 -0
  113. data/t/t0014-rewindable-input-true.sh +24 -0
  114. data/t/t0014.ru +12 -0
  115. data/t/t0015-configurator-internals.sh +25 -0
  116. data/t/t0020-at_exit-handler.sh +49 -0
  117. data/t/t0021-process_detach.sh +29 -0
  118. data/t/t0022-listener_names-preload_app.sh +32 -0
  119. data/t/t0300-no-default-middleware.sh +20 -0
  120. data/t/t0301-no-default-middleware-ignored-in-config.sh +25 -0
  121. data/t/t0301.ru +13 -0
  122. data/t/t9001-oob_gc.sh +47 -0
  123. data/t/t9002-oob_gc-path.sh +75 -0
  124. data/t/test-lib.sh +125 -0
  125. data/t/winch_ttin.t +67 -0
  126. data/t/working_directory.t +94 -0
  127. data/test/aggregate.rb +15 -0
  128. data/test/benchmark/README +60 -0
  129. data/test/benchmark/dd.ru +18 -0
  130. data/test/benchmark/ddstream.ru +50 -0
  131. data/test/benchmark/readinput.ru +40 -0
  132. data/test/benchmark/stack.ru +8 -0
  133. data/test/benchmark/uconnect.perl +66 -0
  134. data/test/exec/README +5 -0
  135. data/test/exec/test_exec.rb +1029 -0
  136. data/test/test_helper.rb +306 -0
  137. data/test/unit/test_ccc.rb +91 -0
  138. data/test/unit/test_configurator.rb +175 -0
  139. data/test/unit/test_droplet.rb +28 -0
  140. data/test/unit/test_http_parser.rb +884 -0
  141. data/test/unit/test_http_parser_ng.rb +714 -0
  142. data/test/unit/test_request.rb +169 -0
  143. data/test/unit/test_server.rb +244 -0
  144. data/test/unit/test_signals.rb +188 -0
  145. data/test/unit/test_socket_helper.rb +159 -0
  146. data/test/unit/test_stream_input.rb +210 -0
  147. data/test/unit/test_tee_input.rb +303 -0
  148. data/test/unit/test_util.rb +131 -0
  149. data/test/unit/test_waiter.rb +34 -0
  150. data/unicorn.gemspec +48 -0
  151. metadata +275 -0
data/t/.gitignore ADDED
@@ -0,0 +1,4 @@
1
+ /random_blob
2
+ /.dep+*
3
+ /*.crt
4
+ /*.key
data/t/GNUmakefile ADDED
@@ -0,0 +1,5 @@
1
+ # there used to be more, here, but we stopped relying on recursive make
2
+ all::
3
+ $(MAKE) -C .. test-integration
4
+
5
+ .PHONY: all
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;
@@ -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
@@ -0,0 +1,11 @@
1
+ use Rack::ContentType, "text/plain"
2
+ fifo_path = ENV["TEST_FIFO"] or abort "TEST_FIFO not set"
3
+ run lambda { |env|
4
+ pid = fork do
5
+ File.open(fifo_path, "wb") do |fp|
6
+ fp.write "HIHI"
7
+ end
8
+ end
9
+ Process.detach(pid)
10
+ [ 200, {}, [ pid.to_s ] ]
11
+ }
data/t/env.ru ADDED
@@ -0,0 +1,3 @@
1
+ use Rack::ContentLength
2
+ use Rack::ContentType, "text/plain"
3
+ run lambda { |env| [ 200, {}, [ env.inspect << "\n" ] ] }
@@ -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