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.
Files changed (145) 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 +144 -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 +318 -0
  20. data/HACKING +117 -0
  21. data/ISSUES +102 -0
  22. data/KNOWN_ISSUES +79 -0
  23. data/LICENSE +67 -0
  24. data/Links +58 -0
  25. data/PHILOSOPHY +139 -0
  26. data/README +165 -0
  27. data/Rakefile +17 -0
  28. data/SIGNALS +123 -0
  29. data/Sandbox +104 -0
  30. data/TODO +1 -0
  31. data/TUNING +119 -0
  32. data/archive/.gitignore +3 -0
  33. data/archive/slrnpull.conf +4 -0
  34. data/bin/unicorn +129 -0
  35. data/bin/unicorn_rails +210 -0
  36. data/examples/big_app_gc.rb +3 -0
  37. data/examples/echo.ru +27 -0
  38. data/examples/init.sh +102 -0
  39. data/examples/logger_mp_safe.rb +26 -0
  40. data/examples/logrotate.conf +44 -0
  41. data/examples/nginx.conf +156 -0
  42. data/examples/unicorn.conf.minimal.rb +14 -0
  43. data/examples/unicorn.conf.rb +111 -0
  44. data/examples/unicorn.socket +11 -0
  45. data/examples/unicorn@.service +40 -0
  46. data/ext/unicorn_http/CFLAGS +13 -0
  47. data/ext/unicorn_http/c_util.h +115 -0
  48. data/ext/unicorn_http/common_field_optimization.h +128 -0
  49. data/ext/unicorn_http/epollexclusive.h +128 -0
  50. data/ext/unicorn_http/ext_help.h +38 -0
  51. data/ext/unicorn_http/extconf.rb +40 -0
  52. data/ext/unicorn_http/global_variables.h +97 -0
  53. data/ext/unicorn_http/httpdate.c +91 -0
  54. data/ext/unicorn_http/unicorn_http.c +4348 -0
  55. data/ext/unicorn_http/unicorn_http.rl +1054 -0
  56. data/ext/unicorn_http/unicorn_http_common.rl +76 -0
  57. data/lib/unicorn/app/old_rails/static.rb +60 -0
  58. data/lib/unicorn/app/old_rails.rb +36 -0
  59. data/lib/unicorn/cgi_wrapper.rb +148 -0
  60. data/lib/unicorn/configurator.rb +749 -0
  61. data/lib/unicorn/const.rb +22 -0
  62. data/lib/unicorn/http_request.rb +180 -0
  63. data/lib/unicorn/http_response.rb +95 -0
  64. data/lib/unicorn/http_server.rb +860 -0
  65. data/lib/unicorn/launcher.rb +63 -0
  66. data/lib/unicorn/oob_gc.rb +82 -0
  67. data/lib/unicorn/preread_input.rb +34 -0
  68. data/lib/unicorn/select_waiter.rb +7 -0
  69. data/lib/unicorn/socket_helper.rb +186 -0
  70. data/lib/unicorn/stream_input.rb +152 -0
  71. data/lib/unicorn/tee_input.rb +132 -0
  72. data/lib/unicorn/tmpio.rb +34 -0
  73. data/lib/unicorn/util.rb +91 -0
  74. data/lib/unicorn/version.rb +1 -0
  75. data/lib/unicorn/worker.rb +166 -0
  76. data/lib/unicorn.rb +137 -0
  77. data/man/man1/unicorn.1 +222 -0
  78. data/man/man1/unicorn_rails.1 +207 -0
  79. data/setup.rb +1587 -0
  80. data/t/.gitignore +4 -0
  81. data/t/GNUmakefile +5 -0
  82. data/t/README +49 -0
  83. data/t/active-unix-socket.t +110 -0
  84. data/t/back-out-of-upgrade.t +44 -0
  85. data/t/bin/unused_listen +40 -0
  86. data/t/client_body_buffer_size.ru +15 -0
  87. data/t/client_body_buffer_size.t +79 -0
  88. data/t/detach.ru +12 -0
  89. data/t/env.ru +4 -0
  90. data/t/fails-rack-lint.ru +6 -0
  91. data/t/heartbeat-timeout.ru +13 -0
  92. data/t/heartbeat-timeout.t +60 -0
  93. data/t/integration.ru +129 -0
  94. data/t/integration.t +509 -0
  95. data/t/lib.perl +309 -0
  96. data/t/listener_names.ru +5 -0
  97. data/t/my-tap-lib.sh +201 -0
  98. data/t/oob_gc.ru +18 -0
  99. data/t/oob_gc_path.ru +18 -0
  100. data/t/pid.ru +4 -0
  101. data/t/preread_input.ru +23 -0
  102. data/t/reload-bad-config.t +49 -0
  103. data/t/reopen-logs.ru +14 -0
  104. data/t/reopen-logs.t +36 -0
  105. data/t/t0010-reap-logging.sh +55 -0
  106. data/t/t0012-reload-empty-config.sh +86 -0
  107. data/t/t0013-rewindable-input-false.sh +24 -0
  108. data/t/t0013.ru +13 -0
  109. data/t/t0014-rewindable-input-true.sh +24 -0
  110. data/t/t0014.ru +13 -0
  111. data/t/t0015-configurator-internals.sh +25 -0
  112. data/t/t0020-at_exit-handler.sh +49 -0
  113. data/t/t0021-process_detach.sh +29 -0
  114. data/t/t0022-listener_names-preload_app.sh +32 -0
  115. data/t/t0300-no-default-middleware.sh +20 -0
  116. data/t/t0301-no-default-middleware-ignored-in-config.sh +25 -0
  117. data/t/t0301.ru +14 -0
  118. data/t/t9001-oob_gc.sh +47 -0
  119. data/t/t9002-oob_gc-path.sh +75 -0
  120. data/t/test-lib.sh +125 -0
  121. data/t/winch_ttin.t +64 -0
  122. data/t/working_directory.t +86 -0
  123. data/test/aggregate.rb +16 -0
  124. data/test/benchmark/README +60 -0
  125. data/test/benchmark/dd.ru +19 -0
  126. data/test/benchmark/ddstream.ru +51 -0
  127. data/test/benchmark/readinput.ru +41 -0
  128. data/test/benchmark/stack.ru +9 -0
  129. data/test/benchmark/uconnect.perl +66 -0
  130. data/test/exec/README +5 -0
  131. data/test/exec/test_exec.rb +1030 -0
  132. data/test/test_helper.rb +307 -0
  133. data/test/unit/test_configurator.rb +176 -0
  134. data/test/unit/test_droplet.rb +29 -0
  135. data/test/unit/test_http_parser.rb +885 -0
  136. data/test/unit/test_http_parser_ng.rb +715 -0
  137. data/test/unit/test_server.rb +245 -0
  138. data/test/unit/test_signals.rb +189 -0
  139. data/test/unit/test_socket_helper.rb +160 -0
  140. data/test/unit/test_stream_input.rb +211 -0
  141. data/test/unit/test_tee_input.rb +304 -0
  142. data/test/unit/test_util.rb +132 -0
  143. data/test/unit/test_waiter.rb +35 -0
  144. data/unicorn.gemspec +49 -0
  145. metadata +266 -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,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;
@@ -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,4 @@
1
+ # frozen_string_literal: false
2
+ use Rack::ContentLength
3
+ use Rack::ContentType, "text/plain"
4
+ run lambda { |env| [ 200, {}, [ env.inspect << "\n" ] ] }
@@ -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