unicorn-maintained 6.2.0

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.
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