unicorn-rupcio 6.1.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 (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/winch_ttin.t ADDED
@@ -0,0 +1,64 @@
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 POSIX qw(mkfifo);
7
+ my $u_sock = "$tmpdir/u.sock";
8
+ my $fifo = "$tmpdir/fifo";
9
+ mkfifo($fifo, 0666) or die "mkfifo($fifo): $!";
10
+
11
+ write_file '>', $u_conf, <<EOM;
12
+ pid "$tmpdir/pid"
13
+ listen "$u_sock"
14
+ stderr_path "$err_log"
15
+ after_fork do |server, worker|
16
+ # test script will block while reading from $fifo,
17
+ File.open("$fifo", "wb") { |fp| fp.syswrite worker.nr.to_s }
18
+ end
19
+ EOM
20
+
21
+ unicorn('-D', '-c', $u_conf, 't/integration.ru')->join;
22
+ is($?, 0, 'daemonized properly');
23
+ open my $fh, '<', "$tmpdir/pid";
24
+ chomp(my $pid = <$fh>);
25
+ ok(kill(0, $pid), 'daemonized PID works');
26
+ my $quit = sub { kill('QUIT', $pid) if $pid; $pid = undef };
27
+ END { $quit->() };
28
+
29
+ open $fh, '<', $fifo;
30
+ my $worker_nr = <$fh>;
31
+ close $fh;
32
+ is($worker_nr, '0', 'initial worker spawned');
33
+
34
+ my ($status, $hdr, $worker_pid) = do_req($u_sock, 'GET /pid HTTP/1.0');
35
+ like($status, qr/ 200\b/, 'got 200 response');
36
+ like($worker_pid, qr/\A[0-9]+\n\z/s, 'PID in response');
37
+ chomp $worker_pid;
38
+ ok(kill(0, $worker_pid), 'worker_pid is valid');
39
+
40
+ ok(kill('WINCH', $pid), 'SIGWINCH can be sent');
41
+
42
+ my $tries = 1000;
43
+ while (CORE::kill(0, $worker_pid) && --$tries) { sleep 0.01 }
44
+ ok(!CORE::kill(0, $worker_pid), 'worker not running');
45
+
46
+ ok(kill('TTIN', $pid), 'SIGTTIN to restart worker');
47
+
48
+ open $fh, '<', $fifo;
49
+ $worker_nr = <$fh>;
50
+ close $fh;
51
+ is($worker_nr, '0', 'worker restarted');
52
+
53
+ ($status, $hdr, my $new_worker_pid) = do_req($u_sock, 'GET /pid HTTP/1.0');
54
+ like($status, qr/ 200\b/, 'got 200 response');
55
+ like($new_worker_pid, qr/\A[0-9]+\n\z/, 'got new worker PID');
56
+ chomp $new_worker_pid;
57
+ ok(kill(0, $new_worker_pid), 'got a valid worker PID');
58
+ isnt($worker_pid, $new_worker_pid, 'worker PID changed');
59
+
60
+ $quit->();
61
+
62
+ check_stderr;
63
+ undef $tmpdir;
64
+ done_testing;
@@ -0,0 +1,86 @@
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
+ mkdir "$tmpdir/alt";
7
+ my $ru = "$tmpdir/alt/config.ru";
8
+ write_file '>', $u_conf, <<EOM;
9
+ pid "$pid_file"
10
+ preload_app true
11
+ stderr_path "$err_log"
12
+ working_directory "$tmpdir/alt" # the whole point of this test
13
+ before_fork { |_,_| \$master_ppid = Process.ppid }
14
+ EOM
15
+
16
+ my $common_ru = <<'EOM';
17
+ use Rack::ContentLength
18
+ use Rack::ContentType, 'text/plain'
19
+ run lambda { |env| [ 200, {}, [ "#{$master_ppid}\n" ] ] }
20
+ EOM
21
+
22
+ write_file '>', $ru, <<EOM;
23
+ #\\--daemonize --listen $u_sock
24
+ $common_ru
25
+ EOM
26
+
27
+ unicorn('-c', $u_conf)->join; # will daemonize
28
+ chomp($daemon_pid = slurp($pid_file));
29
+
30
+ my ($status, $hdr, $bdy) = do_req($u_sock, 'GET / HTTP/1.0');
31
+ is($bdy, "1\n", 'got expected $master_ppid');
32
+
33
+ stop_daemon;
34
+ check_stderr;
35
+
36
+ if ('test without CLI switches in config.ru') {
37
+ truncate $err_log, 0;
38
+ write_file '>', $ru, $common_ru;
39
+
40
+ unicorn('-D', '-l', $u_sock, '-c', $u_conf)->join; # will daemonize
41
+ chomp($daemon_pid = slurp($pid_file));
42
+
43
+ ($status, $hdr, $bdy) = do_req($u_sock, 'GET / HTTP/1.0');
44
+ is($bdy, "1\n", 'got expected $master_ppid');
45
+
46
+ stop_daemon;
47
+ check_stderr;
48
+ }
49
+
50
+ if ('ensures broken working_directory (missing config.ru) is OK') {
51
+ truncate $err_log, 0;
52
+ unlink $ru;
53
+
54
+ my $auto_reap = unicorn('-c', $u_conf);
55
+ $auto_reap->join;
56
+ isnt($?, 0, 'exited with error due to missing config.ru');
57
+
58
+ like(slurp($err_log), qr/rackup file \Q(config.ru)\E not readable/,
59
+ 'noted unreadability of config.ru in stderr');
60
+ }
61
+
62
+ if ('fooapp.rb (not config.ru) works with working_directory') {
63
+ truncate $err_log, 0;
64
+ my $fooapp = "$tmpdir/alt/fooapp.rb";
65
+ write_file '>', $fooapp, <<EOM;
66
+ class Fooapp
67
+ def self.call(env)
68
+ b = "dir=#{Dir.pwd}"
69
+ h = { 'content-type' => 'text/plain', 'content-length' => b.bytesize.to_s }
70
+ [ 200, h, [ b ] ]
71
+ end
72
+ end
73
+ EOM
74
+ my $srv = tcp_server;
75
+ my $auto_reap = unicorn(qw(-c), $u_conf, qw(-I. fooapp.rb),
76
+ { -C => '/', 3 => $srv });
77
+ ($status, $hdr, $bdy) = do_req($srv, 'GET / HTTP/1.0');
78
+ is($bdy, "dir=$tmpdir/alt",
79
+ 'fooapp.rb (w/o config.ru) w/ working_directory');
80
+ $auto_reap->join('TERM');
81
+ is($?, 0, 'fooapp.rb process exited');
82
+ check_stderr;
83
+ }
84
+
85
+ undef $tmpdir;
86
+ done_testing;
data/test/aggregate.rb ADDED
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/ruby -n
2
+ # -*- encoding: binary -*-
3
+ # frozen_string_literal: false
4
+
5
+ BEGIN { $tests = $assertions = $failures = $errors = 0 }
6
+
7
+ $_ =~ /(\d+) tests, (\d+) assertions, (\d+) failures, (\d+) errors/ or next
8
+ $tests += $1.to_i
9
+ $assertions += $2.to_i
10
+ $failures += $3.to_i
11
+ $errors += $4.to_i
12
+
13
+ END {
14
+ printf("\n%d tests, %d assertions, %d failures, %d errors\n",
15
+ $tests, $assertions, $failures, $errors)
16
+ }
@@ -0,0 +1,60 @@
1
+ = Performance
2
+
3
+ Unicorn is pretty fast, and we want it to get faster. Unicorn strives
4
+ to get HTTP requests to your application and write HTTP responses back
5
+ as quickly as possible. Unicorn does not do any background processing
6
+ while your app runs, so your app will get all the CPU time provided to
7
+ it by your OS kernel.
8
+
9
+ A gentle reminder: Unicorn is NOT for serving clients over slow network
10
+ connections. Use nginx (or something similar) to complement Unicorn if
11
+ you have slow clients.
12
+
13
+ == dd.ru
14
+
15
+ This is a pure I/O benchmark. In the context of Unicorn, this is the
16
+ only one that matters. It is a standard rackup-compatible .ru file and
17
+ may be used with other Rack-compatible servers.
18
+
19
+ unicorn -E none dd.ru
20
+
21
+ You can change the size and number of chunks in the response with
22
+ the "bs" and "count" environment variables. The following command
23
+ will cause dd.ru to return 4 chunks of 16384 bytes each, leading to
24
+ 65536 byte response:
25
+
26
+ bs=16384 count=4 unicorn -E none dd.ru
27
+
28
+ Or if you want to add logging (small performance impact):
29
+
30
+ unicorn -E deployment dd.ru
31
+
32
+ Eric runs then runs clients on a LAN it in several different ways:
33
+
34
+ client@host1 -> unicorn@host1(tcp)
35
+ client@host2 -> unicorn@host1(tcp)
36
+ client@host3 -> nginx@host1 -> unicorn@host1(tcp)
37
+ client@host3 -> nginx@host1 -> unicorn@host1(unix)
38
+ client@host3 -> nginx@host2 -> unicorn@host1(tcp)
39
+
40
+ The benchmark client is usually httperf.
41
+
42
+ Another gentle reminder: performance with slow networks/clients
43
+ is NOT our problem. That is the job of nginx (or similar).
44
+
45
+ == ddstream.ru
46
+
47
+ Standalone Rack app intended to show how BAD we are at slow clients.
48
+ See usage in comments.
49
+
50
+ == readinput.ru
51
+
52
+ Standalone Rack app intended to show how bad we are with slow uploaders.
53
+ See usage in comments.
54
+
55
+ == Contributors
56
+
57
+ This directory is intended to remain stable. Do not make changes
58
+ to benchmarking code which can change performance and invalidate
59
+ results across revisions. Instead, write new benchmarks and update
60
+ coments/documentation as necessary.
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: false
2
+ # This benchmark is the simplest test of the I/O facilities in
3
+ # unicorn. It is meant to return a fixed-sized blob to test
4
+ # the performance of things in Unicorn, _NOT_ the app.
5
+ #
6
+ # Adjusting this benchmark is done via the "bs" (byte size) and "count"
7
+ # environment variables. "count" designates the count of elements of
8
+ # "bs" length in the Rack response body. The defaults are bs=4096, count=1
9
+ # to return one 4096-byte chunk.
10
+ bs = ENV['bs'] ? ENV['bs'].to_i : 4096
11
+ count = ENV['count'] ? ENV['count'].to_i : 1
12
+ slice = (' ' * bs).freeze
13
+ body = (1..count).map { slice }.freeze
14
+ hdr = {
15
+ 'Content-Length' => (bs * count).to_s.freeze,
16
+ 'Content-Type' => 'text/plain'.freeze
17
+ }.freeze
18
+ response = [ 200, hdr, body ].freeze
19
+ run(lambda { |env| response })
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: false
2
+ # This app is intended to test large HTTP responses with or without
3
+ # a fully-buffering reverse proxy such as nginx. Without a fully-buffering
4
+ # reverse proxy, unicorn will be unresponsive when client count exceeds
5
+ # worker_processes.
6
+ #
7
+ # To demonstrate how bad unicorn is at slowly reading clients:
8
+ #
9
+ # # in one terminal, start unicorn with one worker:
10
+ # unicorn -E none -l 127.0.0.1:8080 test/benchmark/ddstream.ru
11
+ #
12
+ # # in a different terminal, start more slow curl processes than
13
+ # # unicorn workers and watch time outputs
14
+ # curl --limit-rate 8K --trace-time -vsN http://127.0.0.1:8080/ >/dev/null &
15
+ # curl --limit-rate 8K --trace-time -vsN http://127.0.0.1:8080/ >/dev/null &
16
+ # wait
17
+ #
18
+ # The last client won't see a response until the first one is done reading
19
+ #
20
+ # nginx note: do not change the default "proxy_buffering" behavior.
21
+ # Setting "proxy_buffering off" prevents nginx from protecting unicorn.
22
+
23
+ # totally standalone rack app to stream a giant response
24
+ class BigResponse
25
+ def initialize(bs, count)
26
+ @buf = "#{bs.to_s(16)}\r\n#{' ' * bs}\r\n"
27
+ @count = count
28
+ @res = [ 200,
29
+ { 'Transfer-Encoding' => -'chunked', 'Content-Type' => 'text/plain' },
30
+ self
31
+ ]
32
+ end
33
+
34
+ # rack response body iterator
35
+ def each
36
+ (1..@count).each { yield @buf }
37
+ yield -"0\r\n\r\n"
38
+ end
39
+
40
+ # rack app entry endpoint
41
+ def call(_env)
42
+ @res
43
+ end
44
+ end
45
+
46
+ # default to a giant (128M) response because kernel socket buffers
47
+ # can be ridiculously large on some systems
48
+ bs = ENV['bs'] ? ENV['bs'].to_i : 65536
49
+ count = ENV['count'] ? ENV['count'].to_i : 2048
50
+ warn "serving response with bs=#{bs} count=#{count} (#{bs*count} bytes)"
51
+ run BigResponse.new(bs, count)
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: false
2
+ # This app is intended to test large HTTP requests with or without
3
+ # a fully-buffering reverse proxy such as nginx. Without a fully-buffering
4
+ # reverse proxy, unicorn will be unresponsive when client count exceeds
5
+ # worker_processes.
6
+
7
+ DOC = <<DOC
8
+ To demonstrate how bad unicorn is at slowly uploading clients:
9
+
10
+ # in one terminal, start unicorn with one worker:
11
+ unicorn -E none -l 127.0.0.1:8080 test/benchmark/readinput.ru
12
+
13
+ # in a different terminal, upload 45M from multiple curl processes:
14
+ dd if=/dev/zero bs=45M count=1 | curl -T- -HExpect: --limit-rate 1M \
15
+ --trace-time -v http://127.0.0.1:8080/ &
16
+ dd if=/dev/zero bs=45M count=1 | curl -T- -HExpect: --limit-rate 1M \
17
+ --trace-time -v http://127.0.0.1:8080/ &
18
+ wait
19
+
20
+ # The last client won't see a response until the first one is done uploading
21
+ # You also won't be able to make GET requests to view this documentation
22
+ # while clients are uploading. You can also view the stderr debug output
23
+ # of unicorn (see logging code in #{__FILE__}).
24
+ DOC
25
+
26
+ run(lambda do |env|
27
+ input = env['rack.input']
28
+ buf = ''.b
29
+
30
+ # default logger contains timestamps, rely on that so users can
31
+ # see what the server is doing
32
+ l = env['rack.logger']
33
+
34
+ l.debug('BEGIN reading input ...') if l
35
+ :nop while input.read(16384, buf)
36
+ l.debug('DONE reading input ...') if l
37
+
38
+ buf.clear
39
+ [ 200, [ %W(Content-Length #{DOC.size}), %w(Content-Type text/plain) ],
40
+ [ DOC ] ]
41
+ end)
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: false
2
+ run(lambda { |env|
3
+ body = "#{caller.size}\n"
4
+ h = {
5
+ "Content-Length" => body.size.to_s,
6
+ "Content-Type" => "text/plain",
7
+ }
8
+ [ 200, h, [ body ] ]
9
+ })
@@ -0,0 +1,66 @@
1
+ #!/usr/bin/perl -w
2
+ # Benchmark script to spawn some processes and hammer a local unicorn
3
+ # to test accept loop performance. This only does Unix sockets.
4
+ # There's plenty of TCP benchmarking tools out there, and TCP port reuse
5
+ # has predictability problems since unicorn can't do persistent connections.
6
+ # Written in Perl for the same reason: predictability.
7
+ # Ruby GC is not as predictable as Perl refcounting.
8
+ use strict;
9
+ use Socket qw(AF_UNIX SOCK_STREAM sockaddr_un);
10
+ use POSIX qw(:sys_wait_h);
11
+ use Getopt::Std;
12
+ # -c / -n switches stolen from ab(1)
13
+ my $usage = "$0 [-c CONCURRENCY] [-n NUM_REQUESTS] SOCKET_PATH\n";
14
+ our $opt_c = 2;
15
+ our $opt_n = 1000;
16
+ getopts('c:n:') or die $usage;
17
+ my $unix_path = shift or die $usage;
18
+ use constant REQ => "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n";
19
+ use constant REQ_LEN => length(REQ);
20
+ use constant BUFSIZ => 8192;
21
+ $^F = 99; # don't waste syscall time with FD_CLOEXEC
22
+
23
+ my %workers; # pid => worker num
24
+ die "-n $opt_n not evenly divisible by -c $opt_c\n" if $opt_n % $opt_c;
25
+ my $n_per_worker = $opt_n / $opt_c;
26
+ my $addr = sockaddr_un($unix_path);
27
+
28
+ for my $num (1..$opt_c) {
29
+ defined(my $pid = fork) or die "fork failed: $!\n";
30
+ if ($pid) {
31
+ $workers{$pid} = $num;
32
+ } else {
33
+ work($n_per_worker);
34
+ }
35
+ }
36
+
37
+ reap_worker(0) while scalar keys %workers;
38
+ exit;
39
+
40
+ sub work {
41
+ my ($n) = @_;
42
+ my ($buf, $x);
43
+ for (1..$n) {
44
+ socket(S, AF_UNIX, SOCK_STREAM, 0) or die "socket: $!";
45
+ connect(S, $addr) or die "connect: $!";
46
+ defined($x = syswrite(S, REQ)) or die "write: $!";
47
+ $x == REQ_LEN or die "short write: $x != ".REQ_LEN."\n";
48
+ do {
49
+ $x = sysread(S, $buf, BUFSIZ);
50
+ unless (defined $x) {
51
+ next if $!{EINTR};
52
+ die "sysread: $!\n";
53
+ }
54
+ } until ($x == 0);
55
+ }
56
+ exit 0;
57
+ }
58
+
59
+ sub reap_worker {
60
+ my ($flags) = @_;
61
+ my $pid = waitpid(-1, $flags);
62
+ return if !defined $pid || $pid <= 0;
63
+ my $p = delete $workers{$pid} || '(unknown)';
64
+ warn("$pid [$p] exited with $?\n") if $?;
65
+ $p;
66
+ }
data/test/exec/README ADDED
@@ -0,0 +1,5 @@
1
+ These tests require the "unicorn" executable script to be installed in
2
+ PATH and rack being directly "require"-able ("rubygems" will not be
3
+ loaded for you). The tester is responsible for setting up RUBYLIB and
4
+ PATH environment variables (or running tests via GNU Make instead of
5
+ Rake).