unicorn 5.4.0 → 5.6.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 (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)