unicorn 5.4.0 → 5.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (71) hide show
  1. checksums.yaml +4 -4
  2. data/.manifest +7 -3
  3. data/.olddoc.yml +12 -7
  4. data/Application_Timeouts +4 -4
  5. data/Documentation/.gitignore +1 -3
  6. data/Documentation/unicorn.1 +222 -0
  7. data/Documentation/unicorn_rails.1 +207 -0
  8. data/FAQ +1 -1
  9. data/GIT-VERSION-FILE +1 -1
  10. data/GIT-VERSION-GEN +1 -1
  11. data/GNUmakefile +16 -6
  12. data/HACKING +1 -1
  13. data/ISSUES +17 -22
  14. data/KNOWN_ISSUES +2 -2
  15. data/LATEST +18 -8
  16. data/LICENSE +2 -2
  17. data/Links +13 -11
  18. data/NEWS +94 -0
  19. data/README +18 -11
  20. data/SIGNALS +1 -1
  21. data/Sandbox +4 -4
  22. data/archive/slrnpull.conf +1 -1
  23. data/bin/unicorn +3 -1
  24. data/bin/unicorn_rails +2 -2
  25. data/examples/big_app_gc.rb +1 -1
  26. data/examples/logrotate.conf +3 -3
  27. data/examples/nginx.conf +4 -3
  28. data/examples/unicorn.conf.minimal.rb +2 -2
  29. data/examples/unicorn.conf.rb +2 -2
  30. data/examples/unicorn@.service +7 -0
  31. data/ext/unicorn_http/common_field_optimization.h +24 -6
  32. data/ext/unicorn_http/extconf.rb +30 -0
  33. data/ext/unicorn_http/global_variables.h +2 -2
  34. data/ext/unicorn_http/httpdate.c +2 -2
  35. data/ext/unicorn_http/unicorn_http.c +257 -224
  36. data/ext/unicorn_http/unicorn_http.rl +47 -14
  37. data/lib/unicorn/configurator.rb +25 -4
  38. data/lib/unicorn/http_request.rb +12 -2
  39. data/lib/unicorn/http_server.rb +50 -23
  40. data/lib/unicorn/launcher.rb +1 -1
  41. data/lib/unicorn/oob_gc.rb +2 -2
  42. data/lib/unicorn/socket_helper.rb +3 -2
  43. data/lib/unicorn/tmpio.rb +8 -2
  44. data/lib/unicorn/util.rb +3 -3
  45. data/lib/unicorn/version.rb +1 -1
  46. data/lib/unicorn/worker.rb +16 -2
  47. data/lib/unicorn.rb +23 -9
  48. data/man/man1/unicorn.1 +88 -85
  49. data/man/man1/unicorn_rails.1 +79 -81
  50. data/t/README +4 -4
  51. data/t/t0301-no-default-middleware-ignored-in-config.sh +25 -0
  52. data/t/t0301.ru +13 -0
  53. data/test/benchmark/README +14 -4
  54. data/test/benchmark/ddstream.ru +50 -0
  55. data/test/benchmark/readinput.ru +40 -0
  56. data/test/benchmark/uconnect.perl +66 -0
  57. data/test/exec/test_exec.rb +15 -14
  58. data/test/test_helper.rb +0 -26
  59. data/test/unit/test_ccc.rb +1 -1
  60. data/test/unit/test_http_parser.rb +16 -0
  61. data/test/unit/test_http_parser_ng.rb +81 -0
  62. data/test/unit/test_server.rb +35 -5
  63. data/test/unit/test_signals.rb +2 -2
  64. data/test/unit/test_socket_helper.rb +4 -4
  65. data/test/unit/test_upload.rb +4 -9
  66. data/test/unit/test_util.rb +25 -0
  67. data/unicorn.gemspec +3 -3
  68. metadata +13 -9
  69. data/Documentation/GNUmakefile +0 -30
  70. data/Documentation/unicorn.1.txt +0 -187
  71. data/Documentation/unicorn_rails.1.txt +0 -175
@@ -0,0 +1,50 @@
1
+ # This app is intended to test large HTTP responses with or without
2
+ # a fully-buffering reverse proxy such as nginx. Without a fully-buffering
3
+ # reverse proxy, unicorn will be unresponsive when client count exceeds
4
+ # worker_processes.
5
+ #
6
+ # To demonstrate how bad unicorn is at slowly reading clients:
7
+ #
8
+ # # in one terminal, start unicorn with one worker:
9
+ # unicorn -E none -l 127.0.0.1:8080 test/benchmark/ddstream.ru
10
+ #
11
+ # # in a different terminal, start more slow curl processes than
12
+ # # unicorn workers and watch time outputs
13
+ # curl --limit-rate 8K --trace-time -vsN http://127.0.0.1:8080/ >/dev/null &
14
+ # curl --limit-rate 8K --trace-time -vsN http://127.0.0.1:8080/ >/dev/null &
15
+ # wait
16
+ #
17
+ # The last client won't see a response until the first one is done reading
18
+ #
19
+ # nginx note: do not change the default "proxy_buffering" behavior.
20
+ # Setting "proxy_buffering off" prevents nginx from protecting unicorn.
21
+
22
+ # totally standalone rack app to stream a giant response
23
+ class BigResponse
24
+ def initialize(bs, count)
25
+ @buf = "#{bs.to_s(16)}\r\n#{' ' * bs}\r\n"
26
+ @count = count
27
+ @res = [ 200,
28
+ { 'Transfer-Encoding' => -'chunked', 'Content-Type' => 'text/plain' },
29
+ self
30
+ ]
31
+ end
32
+
33
+ # rack response body iterator
34
+ def each
35
+ (1..@count).each { yield @buf }
36
+ yield -"0\r\n\r\n"
37
+ end
38
+
39
+ # rack app entry endpoint
40
+ def call(_env)
41
+ @res
42
+ end
43
+ end
44
+
45
+ # default to a giant (128M) response because kernel socket buffers
46
+ # can be ridiculously large on some systems
47
+ bs = ENV['bs'] ? ENV['bs'].to_i : 65536
48
+ count = ENV['count'] ? ENV['count'].to_i : 2048
49
+ warn "serving response with bs=#{bs} count=#{count} (#{bs*count} bytes)"
50
+ run BigResponse.new(bs, count)
@@ -0,0 +1,40 @@
1
+ # This app is intended to test large HTTP requests with or without
2
+ # a fully-buffering reverse proxy such as nginx. Without a fully-buffering
3
+ # reverse proxy, unicorn will be unresponsive when client count exceeds
4
+ # worker_processes.
5
+
6
+ DOC = <<DOC
7
+ To demonstrate how bad unicorn is at slowly uploading clients:
8
+
9
+ # in one terminal, start unicorn with one worker:
10
+ unicorn -E none -l 127.0.0.1:8080 test/benchmark/readinput.ru
11
+
12
+ # in a different terminal, upload 45M from multiple curl processes:
13
+ dd if=/dev/zero bs=45M count=1 | curl -T- -HExpect: --limit-rate 1M \
14
+ --trace-time -v http://127.0.0.1:8080/ &
15
+ dd if=/dev/zero bs=45M count=1 | curl -T- -HExpect: --limit-rate 1M \
16
+ --trace-time -v http://127.0.0.1:8080/ &
17
+ wait
18
+
19
+ # The last client won't see a response until the first one is done uploading
20
+ # You also won't be able to make GET requests to view this documentation
21
+ # while clients are uploading. You can also view the stderr debug output
22
+ # of unicorn (see logging code in #{__FILE__}).
23
+ DOC
24
+
25
+ run(lambda do |env|
26
+ input = env['rack.input']
27
+ buf = ''.b
28
+
29
+ # default logger contains timestamps, rely on that so users can
30
+ # see what the server is doing
31
+ l = env['rack.logger']
32
+
33
+ l.debug('BEGIN reading input ...') if l
34
+ :nop while input.read(16384, buf)
35
+ l.debug('DONE reading input ...') if l
36
+
37
+ buf.clear
38
+ [ 200, [ %W(Content-Length #{DOC.size}), %w(Content-Type text/plain) ],
39
+ [ DOC ] ]
40
+ end)
@@ -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
+ }
@@ -45,8 +45,9 @@ end
45
45
 
46
46
  COMMON_TMP = Tempfile.new('unicorn_tmp') unless defined?(COMMON_TMP)
47
47
 
48
+ HEAVY_WORKERS = 2
48
49
  HEAVY_CFG = <<-EOS
49
- worker_processes 4
50
+ worker_processes #{HEAVY_WORKERS}
50
51
  timeout 30
51
52
  logger Logger.new('#{COMMON_TMP.path}')
52
53
  before_fork do |server, worker|
@@ -193,8 +194,8 @@ EOF
193
194
  assert_equal other.path, results.first
194
195
 
195
196
  Process.kill(:QUIT, pid)
196
- ensure
197
- FileUtils.rmtree(other.path)
197
+ ensure
198
+ FileUtils.rmtree(other.path)
198
199
  end
199
200
 
200
201
  def test_working_directory
@@ -229,8 +230,8 @@ EOF
229
230
  assert_equal other.path, results.first
230
231
 
231
232
  Process.kill(:QUIT, pid)
232
- ensure
233
- FileUtils.rmtree(other.path)
233
+ ensure
234
+ FileUtils.rmtree(other.path)
234
235
  end
235
236
 
236
237
  def test_working_directory_controls_relative_paths
@@ -271,11 +272,10 @@ EOF
271
272
  wait_master_ready("#{other.path}/stderr_log_here")
272
273
 
273
274
  Process.kill(:QUIT, pid)
274
- ensure
275
- FileUtils.rmtree(other.path)
275
+ ensure
276
+ FileUtils.rmtree(other.path)
276
277
  end
277
278
 
278
-
279
279
  def test_exit_signals
280
280
  %w(INT TERM QUIT).each do |sig|
281
281
  File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
@@ -607,6 +607,7 @@ EOF
607
607
  def test_weird_config_settings
608
608
  File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
609
609
  ucfg = Tempfile.new('unicorn_test_config')
610
+ proc_total = HEAVY_WORKERS + 1 # + 1 for master
610
611
  ucfg.syswrite(HEAVY_CFG)
611
612
  pid = xfork do
612
613
  redirect_test_io do
@@ -617,9 +618,9 @@ EOF
617
618
  results = retry_hit(["http://#{@addr}:#{@port}/"])
618
619
  assert_equal String, results[0].class
619
620
  wait_master_ready(COMMON_TMP.path)
620
- wait_workers_ready(COMMON_TMP.path, 4)
621
+ wait_workers_ready(COMMON_TMP.path, HEAVY_WORKERS)
621
622
  bf = File.readlines(COMMON_TMP.path).grep(/\bbefore_fork: worker=/)
622
- assert_equal 4, bf.size
623
+ assert_equal HEAVY_WORKERS, bf.size
623
624
  rotate = Tempfile.new('unicorn_rotate')
624
625
 
625
626
  File.rename(COMMON_TMP.path, rotate.path)
@@ -631,20 +632,20 @@ EOF
631
632
  tries = DEFAULT_TRIES
632
633
  log = File.readlines(rotate.path)
633
634
  while (tries -= 1) > 0 &&
634
- log.grep(/reopening logs\.\.\./).size < 5
635
+ log.grep(/reopening logs\.\.\./).size < proc_total
635
636
  sleep DEFAULT_RES
636
637
  log = File.readlines(rotate.path)
637
638
  end
638
- assert_equal 5, log.grep(/reopening logs\.\.\./).size
639
+ assert_equal proc_total, log.grep(/reopening logs\.\.\./).size
639
640
  assert_equal 0, log.grep(/done reopening logs/).size
640
641
 
641
642
  tries = DEFAULT_TRIES
642
643
  log = File.readlines(COMMON_TMP.path)
643
- while (tries -= 1) > 0 && log.grep(/done reopening logs/).size < 5
644
+ while (tries -= 1) > 0 && log.grep(/done reopening logs/).size < proc_total
644
645
  sleep DEFAULT_RES
645
646
  log = File.readlines(COMMON_TMP.path)
646
647
  end
647
- assert_equal 5, log.grep(/done reopening logs/).size
648
+ assert_equal proc_total, log.grep(/done reopening logs/).size
648
649
  assert_equal 0, log.grep(/reopening logs\.\.\./).size
649
650
 
650
651
  Process.kill(:QUIT, pid)
data/test/test_helper.rb CHANGED
@@ -265,32 +265,6 @@ def wait_for_death(pid)
265
265
  raise "PID:#{pid} never died!"
266
266
  end
267
267
 
268
- # executes +cmd+ and chunks its STDOUT
269
- def chunked_spawn(stdout, *cmd)
270
- fork {
271
- crd, cwr = IO.pipe
272
- crd.binmode
273
- cwr.binmode
274
- crd.sync = cwr.sync = true
275
-
276
- pid = fork {
277
- STDOUT.reopen(cwr)
278
- crd.close
279
- cwr.close
280
- exec(*cmd)
281
- }
282
- cwr.close
283
- begin
284
- buf = crd.readpartial(16384)
285
- stdout.write("#{'%x' % buf.size}\r\n#{buf}")
286
- rescue EOFError
287
- stdout.write("0\r\n")
288
- pid, status = Process.waitpid(pid)
289
- exit status.exitstatus
290
- end while true
291
- }
292
- end
293
-
294
268
  def reset_sig_handlers
295
269
  %w(WINCH QUIT INT TERM USR1 USR2 HUP TTIN TTOU CHLD).each do |sig|
296
270
  trap(sig, "DEFAULT")
@@ -44,7 +44,7 @@ class TestCccTCPI < Test::Unit::TestCase
44
44
  # make sure the server is running, at least
45
45
  client = TCPSocket.new(host, port)
46
46
  client.write("GET / HTTP/1.1\r\nHost: example.com\r\n\r\n")
47
- assert client.wait_readable(10), 'never got response from server'
47
+ assert client.wait(10), 'never got response from server'
48
48
  res = client.read
49
49
  assert_match %r{\AHTTP/1\.1 200}, res, 'got part of first response'
50
50
  assert_match %r{\r\n\r\n\z}, res, 'got end of response, server is ready'
@@ -865,4 +865,20 @@ class HttpParserTest < Test::Unit::TestCase
865
865
  rescue LoadError
866
866
  # not all Ruby implementations have objspace
867
867
  end
868
+
869
+ def test_dedupe
870
+ parser = HttpParser.new
871
+ # n.b. String#freeze optimization doesn't work under modern test-unit
872
+ exp = -'HTTP_HOST'
873
+ get = "GET / HTTP/1.1\r\nHost: example.com\r\nHavpbea-fhpxf: true\r\n\r\n"
874
+ assert parser.add_parse(get)
875
+ key = parser.env.keys.detect { |k| k == exp }
876
+ assert_same exp, key
877
+
878
+ if RUBY_VERSION.to_r >= 2.6 # 2.6.0-rc1+
879
+ exp = -'HTTP_HAVPBEA_FHPXF'
880
+ key = parser.env.keys.detect { |k| k == exp }
881
+ assert_same exp, key
882
+ end
883
+ end if RUBY_VERSION.to_r >= 2.5 && RUBY_ENGINE == 'ruby'
868
884
  end
@@ -11,6 +11,20 @@ class HttpParserNgTest < Test::Unit::TestCase
11
11
  @parser = HttpParser.new
12
12
  end
13
13
 
14
+ # RFC 7230 allows gzip/deflate/compress Transfer-Encoding,
15
+ # but "chunked" must be last if used
16
+ def test_is_chunked
17
+ [ 'chunked,chunked', 'chunked,gzip', 'chunked,gzip,chunked' ].each do |x|
18
+ assert_raise(HttpParserError) { HttpParser.is_chunked?(x) }
19
+ end
20
+ [ 'gzip, chunked', 'gzip,chunked', 'gzip ,chunked' ].each do |x|
21
+ assert HttpParser.is_chunked?(x)
22
+ end
23
+ [ 'gzip', 'xhunked', 'xchunked' ].each do |x|
24
+ assert !HttpParser.is_chunked?(x)
25
+ end
26
+ end
27
+
14
28
  def test_parser_max_len
15
29
  assert_raises(RangeError) do
16
30
  HttpParser.max_header_len = 0xffffffff + 1
@@ -566,6 +580,73 @@ class HttpParserNgTest < Test::Unit::TestCase
566
580
  end
567
581
  end
568
582
 
583
+ def test_duplicate_content_length
584
+ str = "PUT / HTTP/1.1\r\n" \
585
+ "Content-Length: 1\r\n" \
586
+ "Content-Length: 9\r\n" \
587
+ "\r\n"
588
+ assert_raises(HttpParserError) { @parser.headers({}, str) }
589
+ end
590
+
591
+ def test_chunked_overrides_content_length
592
+ order = [ 'Transfer-Encoding: chunked', 'Content-Length: 666' ]
593
+ %w(a b).each do |x|
594
+ str = "PUT /#{x} HTTP/1.1\r\n" \
595
+ "#{order.join("\r\n")}" \
596
+ "\r\n\r\na\r\nhelloworld\r\n0\r\n\r\n"
597
+ order.reverse!
598
+ env = @parser.headers({}, str)
599
+ assert_nil @parser.content_length
600
+ assert_equal 'chunked', env['HTTP_TRANSFER_ENCODING']
601
+ assert_equal '666', env['CONTENT_LENGTH'],
602
+ 'Content-Length logged so the app can log a possible client bug/attack'
603
+ @parser.filter_body(dst = '', str)
604
+ assert_equal 'helloworld', dst
605
+ @parser.parse # handle the non-existent trailer
606
+ assert @parser.next?
607
+ end
608
+ end
609
+
610
+ def test_chunked_order_good
611
+ str = "PUT /x HTTP/1.1\r\n" \
612
+ "Transfer-Encoding: gzip\r\n" \
613
+ "Transfer-Encoding: chunked\r\n" \
614
+ "\r\n"
615
+ env = @parser.headers({}, str)
616
+ assert_equal 'gzip,chunked', env['HTTP_TRANSFER_ENCODING']
617
+ assert_nil @parser.content_length
618
+
619
+ @parser.clear
620
+ str = "PUT /x HTTP/1.1\r\n" \
621
+ "Transfer-Encoding: gzip, chunked\r\n" \
622
+ "\r\n"
623
+ env = @parser.headers({}, str)
624
+ assert_equal 'gzip, chunked', env['HTTP_TRANSFER_ENCODING']
625
+ assert_nil @parser.content_length
626
+ end
627
+
628
+ def test_chunked_order_bad
629
+ str = "PUT /x HTTP/1.1\r\n" \
630
+ "Transfer-Encoding: chunked\r\n" \
631
+ "Transfer-Encoding: gzip\r\n" \
632
+ "\r\n"
633
+ assert_raise(HttpParserError) { @parser.headers({}, str) }
634
+ end
635
+
636
+ def test_double_chunked
637
+ str = "PUT /x HTTP/1.1\r\n" \
638
+ "Transfer-Encoding: chunked\r\n" \
639
+ "Transfer-Encoding: chunked\r\n" \
640
+ "\r\n"
641
+ assert_raise(HttpParserError) { @parser.headers({}, str) }
642
+
643
+ @parser.clear
644
+ str = "PUT /x HTTP/1.1\r\n" \
645
+ "Transfer-Encoding: chunked,chunked\r\n" \
646
+ "\r\n"
647
+ assert_raise(HttpParserError) { @parser.headers({}, str) }
648
+ end
649
+
569
650
  def test_backtrace_is_empty
570
651
  begin
571
652
  @parser.headers({}, "AAADFSFDSFD\r\n\r\n")
@@ -17,12 +17,22 @@ class TestHandler
17
17
  while env['rack.input'].read(4096)
18
18
  end
19
19
  [200, { 'Content-Type' => 'text/plain' }, ['hello!\n']]
20
- rescue Unicorn::ClientShutdown, Unicorn::HttpParserError => e
21
- $stderr.syswrite("#{e.class}: #{e.message} #{e.backtrace.empty?}\n")
22
- raise e
20
+ rescue Unicorn::ClientShutdown, Unicorn::HttpParserError => e
21
+ $stderr.syswrite("#{e.class}: #{e.message} #{e.backtrace.empty?}\n")
22
+ raise e
23
23
  end
24
24
  end
25
25
 
26
+ class TestEarlyHintsHandler
27
+ def call(env)
28
+ while env['rack.input'].read(4096)
29
+ end
30
+ env['rack.early_hints'].call(
31
+ "Link" => "</style.css>; rel=preload; as=style\n</script.js>; rel=preload"
32
+ )
33
+ [200, { 'Content-Type' => 'text/plain' }, ['hello!\n']]
34
+ end
35
+ end
26
36
 
27
37
  class WebServerTest < Test::Unit::TestCase
28
38
 
@@ -80,8 +90,28 @@ class WebServerTest < Test::Unit::TestCase
80
90
  loader_pid = tmp.sysread(4096).to_i
81
91
  assert_equal $$, loader_pid
82
92
  assert worker_pid != loader_pid
83
- ensure
84
- tmp.close!
93
+ ensure
94
+ tmp.close!
95
+ end
96
+
97
+ def test_early_hints
98
+ teardown
99
+ redirect_test_io do
100
+ @server = HttpServer.new(TestEarlyHintsHandler.new,
101
+ :listeners => [ "127.0.0.1:#@port"],
102
+ :early_hints => true)
103
+ @server.start
104
+ end
105
+
106
+ sock = TCPSocket.new('127.0.0.1', @port)
107
+ sock.syswrite("GET / HTTP/1.0\r\n\r\n")
108
+
109
+ responses = sock.read(4096)
110
+ assert_match %r{\AHTTP/1.[01] 103\b}, responses
111
+ assert_match %r{^Link: </style\.css>}, responses
112
+ assert_match %r{^Link: </script\.js>}, responses
113
+
114
+ assert_match %r{^HTTP/1.[01] 200\b}, responses
85
115
  end
86
116
 
87
117
  def test_broken_app
@@ -114,8 +114,8 @@ class SignalsTest < Test::Unit::TestCase
114
114
  assert_nil buf
115
115
  assert diff > 1.0, "diff was #{diff.inspect}"
116
116
  assert diff < 60.0
117
- ensure
118
- Process.kill(:TERM, pid) rescue nil
117
+ ensure
118
+ Process.kill(:TERM, pid) rescue nil
119
119
  end
120
120
 
121
121
  def test_response_write
@@ -57,8 +57,8 @@ class TestSocketHelper < Test::Unit::TestCase
57
57
  assert File.readable?(@unix_listener_path), "not readable"
58
58
  assert File.writable?(@unix_listener_path), "not writable"
59
59
  assert_equal 0777, File.umask
60
- ensure
61
- File.umask(old_umask)
60
+ ensure
61
+ File.umask(old_umask)
62
62
  end
63
63
 
64
64
  def test_bind_listen_unix_umask
@@ -71,8 +71,8 @@ class TestSocketHelper < Test::Unit::TestCase
71
71
  assert_equal @unix_listener_path, sock_name(@unix_listener)
72
72
  assert_equal 0140700, File.stat(@unix_listener_path).mode
73
73
  assert_equal 0777, File.umask
74
- ensure
75
- File.umask(old_umask)
74
+ ensure
75
+ File.umask(old_umask)
76
76
  end
77
77
 
78
78
  def test_bind_listen_unix_idempotent
@@ -236,15 +236,10 @@ class UploadTest < Test::Unit::TestCase
236
236
  resp = Tempfile.new('resp')
237
237
  resp.sync = true
238
238
 
239
- rd, wr = IO.pipe
240
- wr.sync = rd.sync = true
241
- pid = fork {
242
- STDIN.reopen(rd)
243
- rd.close
244
- wr.close
245
- STDOUT.reopen(resp)
246
- exec cmd
247
- }
239
+ rd, wr = IO.pipe.each do |io|
240
+ io.sync = io.close_on_exec = true
241
+ end
242
+ pid = spawn(*cmd, { 0 => rd, 1 => resp })
248
243
  rd.close
249
244
 
250
245
  tmp.rewind
@@ -102,4 +102,29 @@ class TestUtil < Test::Unit::TestCase
102
102
  }
103
103
  tmp.close!
104
104
  end
105
+
106
+ def test_pipe
107
+ r, w = Unicorn.pipe
108
+ assert r
109
+ assert w
110
+
111
+ return if RUBY_PLATFORM !~ /linux/
112
+
113
+ begin
114
+ f_getpipe_sz = 1032
115
+ IO.pipe do |a, b|
116
+ a_sz = a.fcntl(f_getpipe_sz)
117
+ b.fcntl(f_getpipe_sz)
118
+ assert_kind_of Integer, a_sz
119
+ r_sz = r.fcntl(f_getpipe_sz)
120
+ assert_equal Raindrops::PAGE_SIZE, r_sz
121
+ assert_operator a_sz, :>=, r_sz
122
+ end
123
+ rescue Errno::EINVAL
124
+ # Linux <= 2.6.34
125
+ end
126
+ ensure
127
+ w.close
128
+ r.close
129
+ end
105
130
  end
data/unicorn.gemspec CHANGED
@@ -11,18 +11,18 @@ end.compact
11
11
 
12
12
  Gem::Specification.new do |s|
13
13
  s.name = %q{unicorn}
14
- s.version = (ENV['VERSION'] || '5.4.0').dup
14
+ s.version = (ENV['VERSION'] || '5.6.0').dup
15
15
  s.authors = ['unicorn hackers']
16
16
  s.summary = 'Rack HTTP server for fast clients and Unix'
17
17
  s.description = File.read('README').split("\n\n")[1]
18
- s.email = %q{unicorn-public@bogomips.org}
18
+ s.email = %q{unicorn-public@yhbt.net}
19
19
  s.executables = %w(unicorn unicorn_rails)
20
20
  s.extensions = %w(ext/unicorn_http/extconf.rb)
21
21
  s.extra_rdoc_files = IO.readlines('.document').map!(&:chomp!).keep_if do |f|
22
22
  File.exist?(f)
23
23
  end
24
24
  s.files = manifest
25
- s.homepage = 'https://bogomips.org/unicorn/'
25
+ s.homepage = 'https://yhbt.net/unicorn/'
26
26
  s.test_files = test_files
27
27
 
28
28
  # technically we need ">= 1.9.3", too, but avoid the array here since
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: unicorn
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.4.0
4
+ version: 5.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - unicorn hackers
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-12-23 00:00:00.000000000 Z
11
+ date: 2020-07-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rack
@@ -72,7 +72,7 @@ description: |-
72
72
  advantage of features in Unix/Unix-like kernels. Slow clients should
73
73
  only be served by placing a reverse proxy capable of fully buffering
74
74
  both the the request and response in between unicorn and slow clients.
75
- email: unicorn-public@bogomips.org
75
+ email: unicorn-public@yhbt.net
76
76
  executables:
77
77
  - unicorn
78
78
  - unicorn_rails
@@ -120,9 +120,8 @@ files:
120
120
  - COPYING
121
121
  - DESIGN
122
122
  - Documentation/.gitignore
123
- - Documentation/GNUmakefile
124
- - Documentation/unicorn.1.txt
125
- - Documentation/unicorn_rails.1.txt
123
+ - Documentation/unicorn.1
124
+ - Documentation/unicorn_rails.1
126
125
  - FAQ
127
126
  - GIT-VERSION-FILE
128
127
  - GIT-VERSION-GEN
@@ -238,6 +237,8 @@ files:
238
237
  - t/t0116.ru
239
238
  - t/t0200-rack-hijack.sh
240
239
  - t/t0300-no-default-middleware.sh
240
+ - t/t0301-no-default-middleware-ignored-in-config.sh
241
+ - t/t0301.ru
241
242
  - t/t9000-preread-input.sh
242
243
  - t/t9001-oob_gc.sh
243
244
  - t/t9002-oob_gc-path.sh
@@ -246,7 +247,10 @@ files:
246
247
  - test/aggregate.rb
247
248
  - test/benchmark/README
248
249
  - test/benchmark/dd.ru
250
+ - test/benchmark/ddstream.ru
251
+ - test/benchmark/readinput.ru
249
252
  - test/benchmark/stack.ru
253
+ - test/benchmark/uconnect.perl
250
254
  - test/exec/README
251
255
  - test/exec/test_exec.rb
252
256
  - test/test_helper.rb
@@ -267,7 +271,7 @@ files:
267
271
  - unicorn.gemspec
268
272
  - unicorn_1
269
273
  - unicorn_rails_1
270
- homepage: https://bogomips.org/unicorn/
274
+ homepage: https://yhbt.net/unicorn/
271
275
  licenses:
272
276
  - GPL-2.0+
273
277
  - Ruby-1.8
@@ -287,8 +291,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
287
291
  - !ruby/object:Gem::Version
288
292
  version: '0'
289
293
  requirements: []
290
- rubyforge_project:
291
- rubygems_version: 2.7.3
294
+ rubygems_version: 3.0.2
292
295
  signing_key:
293
296
  specification_version: 4
294
297
  summary: Rack HTTP server for fast clients and Unix
@@ -298,4 +301,5 @@ test_files:
298
301
  - test/unit/test_http_parser_ng.rb
299
302
  - test/unit/test_request.rb
300
303
  - test/unit/test_server.rb
304
+ - test/unit/test_upload.rb
301
305
  - test/unit/test_util.rb
@@ -1,30 +0,0 @@
1
- all::
2
-
3
- PANDOC = pandoc
4
- PANDOC_OPTS = -f markdown --email-obfuscation=none
5
- pandoc = $(PANDOC) $(PANDOC_OPTS)
6
- pandoc_html = $(pandoc) --toc -t html --no-wrap
7
-
8
- man1 := $(addsuffix .1,unicorn unicorn_rails)
9
- html1 := $(addsuffix .html,$(man1))
10
-
11
- all:: html man
12
-
13
- html: $(html1)
14
- man: $(man1)
15
-
16
- install-html: html
17
- mkdir -p ../doc/man1
18
- install -m 644 $(html1) ../doc/man1
19
-
20
- install-man: man
21
- mkdir -p ../man/man1
22
- install -m 644 $(man1) ../man/man1
23
-
24
- %.1: %.1.txt
25
- $(pandoc) -s -t man < $< > $@+ && mv $@+ $@
26
- %.1.html: %.1.txt
27
- $(pandoc_html) < $< > $@+ && mv $@+ $@
28
-
29
- clean::
30
- $(RM) $(man1) $(html1)