unicorn 4.8.0 → 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 (123) hide show
  1. checksums.yaml +7 -0
  2. data/.document +0 -1
  3. data/.gitattributes +5 -0
  4. data/.gitignore +2 -2
  5. data/.manifest +17 -21
  6. data/.olddoc.yml +25 -0
  7. data/Application_Timeouts +7 -7
  8. data/CONTRIBUTORS +6 -2
  9. data/DESIGN +2 -4
  10. data/Documentation/.gitignore +1 -3
  11. data/Documentation/unicorn.1 +222 -0
  12. data/Documentation/unicorn_rails.1 +207 -0
  13. data/FAQ +23 -6
  14. data/GIT-VERSION-FILE +1 -1
  15. data/GIT-VERSION-GEN +1 -1
  16. data/GNUmakefile +138 -92
  17. data/HACKING +10 -32
  18. data/ISSUES +80 -19
  19. data/KNOWN_ISSUES +18 -18
  20. data/LATEST +23 -27
  21. data/LICENSE +2 -2
  22. data/Links +24 -22
  23. data/NEWS +823 -0
  24. data/PHILOSOPHY +0 -6
  25. data/README +49 -48
  26. data/Rakefile +0 -44
  27. data/SIGNALS +2 -2
  28. data/Sandbox +11 -10
  29. data/TODO +0 -2
  30. data/TUNING +30 -9
  31. data/archive/.gitignore +3 -0
  32. data/archive/slrnpull.conf +4 -0
  33. data/bin/unicorn +4 -2
  34. data/bin/unicorn_rails +3 -3
  35. data/examples/big_app_gc.rb +1 -1
  36. data/examples/init.sh +36 -8
  37. data/examples/logrotate.conf +17 -2
  38. data/examples/nginx.conf +14 -14
  39. data/examples/unicorn.conf.minimal.rb +2 -2
  40. data/examples/unicorn.conf.rb +14 -6
  41. data/examples/unicorn.socket +11 -0
  42. data/examples/unicorn@.service +40 -0
  43. data/ext/unicorn_http/c_util.h +5 -13
  44. data/ext/unicorn_http/common_field_optimization.h +22 -5
  45. data/ext/unicorn_http/epollexclusive.h +124 -0
  46. data/ext/unicorn_http/ext_help.h +0 -44
  47. data/ext/unicorn_http/extconf.rb +32 -5
  48. data/ext/unicorn_http/global_variables.h +2 -2
  49. data/ext/unicorn_http/httpdate.c +3 -2
  50. data/ext/unicorn_http/unicorn_http.c +926 -638
  51. data/ext/unicorn_http/unicorn_http.rl +159 -170
  52. data/ext/unicorn_http/unicorn_http_common.rl +1 -1
  53. data/lib/unicorn/configurator.rb +110 -46
  54. data/lib/unicorn/const.rb +2 -25
  55. data/lib/unicorn/http_request.rb +110 -31
  56. data/lib/unicorn/http_response.rb +17 -31
  57. data/lib/unicorn/http_server.rb +272 -180
  58. data/lib/unicorn/launcher.rb +1 -1
  59. data/lib/unicorn/oob_gc.rb +16 -6
  60. data/lib/unicorn/select_waiter.rb +6 -0
  61. data/lib/unicorn/socket_helper.rb +58 -78
  62. data/lib/unicorn/stream_input.rb +8 -7
  63. data/lib/unicorn/tee_input.rb +16 -11
  64. data/lib/unicorn/tmpio.rb +10 -6
  65. data/lib/unicorn/util.rb +5 -4
  66. data/lib/unicorn/version.rb +1 -1
  67. data/lib/unicorn/worker.rb +36 -23
  68. data/lib/unicorn.rb +64 -44
  69. data/man/man1/unicorn.1 +124 -122
  70. data/man/man1/unicorn_rails.1 +113 -127
  71. data/t/.gitignore +0 -1
  72. data/t/GNUmakefile +3 -80
  73. data/t/README +4 -4
  74. data/t/t0002-parser-error.sh +3 -3
  75. data/t/t0011-active-unix-socket.sh +1 -1
  76. data/t/t0012-reload-empty-config.sh +2 -1
  77. data/t/t0301-no-default-middleware-ignored-in-config.sh +25 -0
  78. data/t/t0301.ru +13 -0
  79. data/t/test-lib.sh +4 -3
  80. data/test/benchmark/README +14 -4
  81. data/test/benchmark/ddstream.ru +50 -0
  82. data/test/benchmark/readinput.ru +40 -0
  83. data/test/benchmark/uconnect.perl +66 -0
  84. data/test/exec/test_exec.rb +74 -20
  85. data/test/test_helper.rb +42 -33
  86. data/test/unit/test_ccc.rb +91 -0
  87. data/test/unit/test_droplet.rb +1 -1
  88. data/test/unit/test_http_parser.rb +49 -19
  89. data/test/unit/test_http_parser_ng.rb +98 -115
  90. data/test/unit/test_request.rb +11 -11
  91. data/test/unit/test_response.rb +31 -19
  92. data/test/unit/test_server.rb +89 -15
  93. data/test/unit/test_signals.rb +9 -9
  94. data/test/unit/test_socket_helper.rb +16 -12
  95. data/test/unit/test_tee_input.rb +10 -0
  96. data/test/unit/test_upload.rb +10 -15
  97. data/test/unit/test_util.rb +32 -6
  98. data/test/unit/test_waiter.rb +34 -0
  99. data/unicorn.gemspec +28 -23
  100. data/unicorn_1 +1 -0
  101. data/unicorn_rails_1 +1 -0
  102. metadata +68 -134
  103. data/.wrongdoc.yml +0 -10
  104. data/ChangeLog +0 -4861
  105. data/Documentation/GNUmakefile +0 -30
  106. data/Documentation/unicorn.1.txt +0 -178
  107. data/Documentation/unicorn_rails.1.txt +0 -175
  108. data/examples/git.ru +0 -13
  109. data/lib/unicorn/app/exec_cgi.rb +0 -154
  110. data/lib/unicorn/app/inetd.rb +0 -109
  111. data/lib/unicorn/ssl_client.rb +0 -11
  112. data/lib/unicorn/ssl_configurator.rb +0 -104
  113. data/lib/unicorn/ssl_server.rb +0 -42
  114. data/local.mk.sample +0 -59
  115. data/script/isolate_for_tests +0 -32
  116. data/t/hijack.ru +0 -42
  117. data/t/sslgen.sh +0 -71
  118. data/t/t0016-trust-x-forwarded-false.sh +0 -30
  119. data/t/t0017-trust-x-forwarded-true.sh +0 -30
  120. data/t/t0200-rack-hijack.sh +0 -27
  121. data/t/t0600-https-server-basic.sh +0 -48
  122. data/test/unit/test_http_parser_xftrust.rb +0 -38
  123. data/test/unit/test_sni_hostnames.rb +0 -47
@@ -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
+ }
@@ -2,7 +2,7 @@
2
2
 
3
3
  # Copyright (c) 2009 Eric Wong
4
4
  FLOCK_PATH = File.expand_path(__FILE__)
5
- require 'test/test_helper'
5
+ require './test/test_helper'
6
6
 
7
7
  do_test = true
8
8
  $unicorn_bin = ENV['UNICORN_TEST_BIN'] || "unicorn"
@@ -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|
@@ -96,6 +97,59 @@ run lambda { |env|
96
97
  end
97
98
  end
98
99
 
100
+ def test_sd_listen_fds_emulation
101
+ # [ruby-core:69895] [Bug #11336] fixed by r51576
102
+ return if RUBY_VERSION.to_f < 2.3
103
+
104
+ File.open("config.ru", "wb") { |fp| fp.write(HI) }
105
+ sock = TCPServer.new(@addr, @port)
106
+
107
+ [ %W(-l #@addr:#@port), nil ].each do |l|
108
+ sock.setsockopt(:SOL_SOCKET, :SO_KEEPALIVE, 0)
109
+
110
+ pid = xfork do
111
+ redirect_test_io do
112
+ # pretend to be systemd
113
+ ENV['LISTEN_PID'] = "#$$"
114
+ ENV['LISTEN_FDS'] = '1'
115
+
116
+ # 3 = SD_LISTEN_FDS_START
117
+ args = [ $unicorn_bin ]
118
+ args.concat(l) if l
119
+ args << { 3 => sock }
120
+ exec(*args)
121
+ end
122
+ end
123
+ res = hit(["http://#@addr:#@port/"])
124
+ assert_equal [ "HI\n" ], res
125
+ assert_shutdown(pid)
126
+ assert sock.getsockopt(:SOL_SOCKET, :SO_KEEPALIVE).bool,
127
+ 'unicorn should always set SO_KEEPALIVE on inherited sockets'
128
+ end
129
+ ensure
130
+ sock.close if sock
131
+ end
132
+
133
+ def test_inherit_listener_unspecified
134
+ File.open("config.ru", "wb") { |fp| fp.write(HI) }
135
+ sock = TCPServer.new(@addr, @port)
136
+ sock.setsockopt(:SOL_SOCKET, :SO_KEEPALIVE, 0)
137
+
138
+ pid = xfork do
139
+ redirect_test_io do
140
+ ENV['UNICORN_FD'] = sock.fileno.to_s
141
+ exec($unicorn_bin, sock.fileno => sock.fileno)
142
+ end
143
+ end
144
+ res = hit(["http://#@addr:#@port/"])
145
+ assert_equal [ "HI\n" ], res
146
+ assert_shutdown(pid)
147
+ assert sock.getsockopt(:SOL_SOCKET, :SO_KEEPALIVE).bool,
148
+ 'unicorn should always set SO_KEEPALIVE on inherited sockets'
149
+ ensure
150
+ sock.close if sock
151
+ end
152
+
99
153
  def test_working_directory_rel_path_config_file
100
154
  other = Tempfile.new('unicorn.wd')
101
155
  File.unlink(other.path)
@@ -140,8 +194,8 @@ EOF
140
194
  assert_equal other.path, results.first
141
195
 
142
196
  Process.kill(:QUIT, pid)
143
- ensure
144
- FileUtils.rmtree(other.path)
197
+ ensure
198
+ FileUtils.rmtree(other.path)
145
199
  end
146
200
 
147
201
  def test_working_directory
@@ -176,8 +230,8 @@ EOF
176
230
  assert_equal other.path, results.first
177
231
 
178
232
  Process.kill(:QUIT, pid)
179
- ensure
180
- FileUtils.rmtree(other.path)
233
+ ensure
234
+ FileUtils.rmtree(other.path)
181
235
  end
182
236
 
183
237
  def test_working_directory_controls_relative_paths
@@ -218,11 +272,10 @@ EOF
218
272
  wait_master_ready("#{other.path}/stderr_log_here")
219
273
 
220
274
  Process.kill(:QUIT, pid)
221
- ensure
222
- FileUtils.rmtree(other.path)
275
+ ensure
276
+ FileUtils.rmtree(other.path)
223
277
  end
224
278
 
225
-
226
279
  def test_exit_signals
227
280
  %w(INT TERM QUIT).each do |sig|
228
281
  File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
@@ -521,7 +574,7 @@ EOF
521
574
  assert_equal String, results[0].class
522
575
  worker_pid = results[0].to_i
523
576
  assert_not_equal pid, worker_pid
524
- s = UNIXSocket.new(tmp.path)
577
+ s = unix_socket(tmp.path)
525
578
  s.syswrite("GET / HTTP/1.0\r\n\r\n")
526
579
  results = ''
527
580
  loop { results << s.sysread(4096) } rescue nil
@@ -554,6 +607,7 @@ EOF
554
607
  def test_weird_config_settings
555
608
  File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
556
609
  ucfg = Tempfile.new('unicorn_test_config')
610
+ proc_total = HEAVY_WORKERS + 1 # + 1 for master
557
611
  ucfg.syswrite(HEAVY_CFG)
558
612
  pid = xfork do
559
613
  redirect_test_io do
@@ -564,9 +618,9 @@ EOF
564
618
  results = retry_hit(["http://#{@addr}:#{@port}/"])
565
619
  assert_equal String, results[0].class
566
620
  wait_master_ready(COMMON_TMP.path)
567
- wait_workers_ready(COMMON_TMP.path, 4)
621
+ wait_workers_ready(COMMON_TMP.path, HEAVY_WORKERS)
568
622
  bf = File.readlines(COMMON_TMP.path).grep(/\bbefore_fork: worker=/)
569
- assert_equal 4, bf.size
623
+ assert_equal HEAVY_WORKERS, bf.size
570
624
  rotate = Tempfile.new('unicorn_rotate')
571
625
 
572
626
  File.rename(COMMON_TMP.path, rotate.path)
@@ -578,20 +632,20 @@ EOF
578
632
  tries = DEFAULT_TRIES
579
633
  log = File.readlines(rotate.path)
580
634
  while (tries -= 1) > 0 &&
581
- log.grep(/reopening logs\.\.\./).size < 5
635
+ log.grep(/reopening logs\.\.\./).size < proc_total
582
636
  sleep DEFAULT_RES
583
637
  log = File.readlines(rotate.path)
584
638
  end
585
- assert_equal 5, log.grep(/reopening logs\.\.\./).size
639
+ assert_equal proc_total, log.grep(/reopening logs\.\.\./).size
586
640
  assert_equal 0, log.grep(/done reopening logs/).size
587
641
 
588
642
  tries = DEFAULT_TRIES
589
643
  log = File.readlines(COMMON_TMP.path)
590
- while (tries -= 1) > 0 && log.grep(/done reopening logs/).size < 5
644
+ while (tries -= 1) > 0 && log.grep(/done reopening logs/).size < proc_total
591
645
  sleep DEFAULT_RES
592
646
  log = File.readlines(COMMON_TMP.path)
593
647
  end
594
- assert_equal 5, log.grep(/done reopening logs/).size
648
+ assert_equal proc_total, log.grep(/done reopening logs/).size
595
649
  assert_equal 0, log.grep(/reopening logs\.\.\./).size
596
650
 
597
651
  Process.kill(:QUIT, pid)
@@ -678,7 +732,7 @@ EOF
678
732
  wait_for_file(sock_path)
679
733
  assert File.socket?(sock_path)
680
734
 
681
- sock = UNIXSocket.new(sock_path)
735
+ sock = unix_socket(sock_path)
682
736
  sock.syswrite("GET / HTTP/1.0\r\n\r\n")
683
737
  results = sock.sysread(4096)
684
738
 
@@ -688,7 +742,7 @@ EOF
688
742
  wait_for_file(sock_path)
689
743
  assert File.socket?(sock_path)
690
744
 
691
- sock = UNIXSocket.new(sock_path)
745
+ sock = unix_socket(sock_path)
692
746
  sock.syswrite("GET / HTTP/1.0\r\n\r\n")
693
747
  results = sock.sysread(4096)
694
748
 
@@ -723,7 +777,7 @@ EOF
723
777
  assert_equal pid, File.read(pid_file).to_i
724
778
  assert File.socket?(sock_path), "socket created"
725
779
 
726
- sock = UNIXSocket.new(sock_path)
780
+ sock = unix_socket(sock_path)
727
781
  sock.syswrite("GET / HTTP/1.0\r\n\r\n")
728
782
  results = sock.sysread(4096)
729
783
 
@@ -749,7 +803,7 @@ EOF
749
803
  wait_for_file(new_sock_path)
750
804
  assert File.socket?(new_sock_path), "socket exists"
751
805
  @sockets.each do |path|
752
- sock = UNIXSocket.new(path)
806
+ sock = unix_socket(path)
753
807
  sock.syswrite("GET / HTTP/1.0\r\n\r\n")
754
808
  results = sock.sysread(4096)
755
809
  assert_equal String, results.class
data/test/test_helper.rb CHANGED
@@ -1,10 +1,10 @@
1
1
  # -*- encoding: binary -*-
2
2
 
3
- # Copyright (c) 2005 Zed A. Shaw
3
+ # Copyright (c) 2005 Zed A. Shaw
4
4
  # You can redistribute it and/or modify it under the same terms as Ruby 1.8 or
5
5
  # the GPLv2+ (GPLv3+ preferred)
6
6
  #
7
- # Additional work donated by contributors. See http://mongrel.rubyforge.org/attributions.html
7
+ # Additional work donated by contributors. See git history
8
8
  # for more information.
9
9
 
10
10
  STDIN.sync = STDOUT.sync = STDERR.sync = true # buffering makes debugging hard
@@ -28,22 +28,43 @@ require 'tempfile'
28
28
  require 'fileutils'
29
29
  require 'logger'
30
30
  require 'unicorn'
31
+ require 'io/nonblock'
31
32
 
32
33
  if ENV['DEBUG']
33
34
  require 'ruby-debug'
34
35
  Debugger.start
35
36
  end
36
37
 
38
+ unless RUBY_VERSION < '3.1'
39
+ warn "Unicorn was only tested against MRI up to 3.0.\n" \
40
+ "It might not properly work with #{RUBY_VERSION}"
41
+ end
42
+
37
43
  def redirect_test_io
38
44
  orig_err = STDERR.dup
39
45
  orig_out = STDOUT.dup
40
- STDERR.reopen("test_stderr.#{$$}.log", "a")
41
- STDOUT.reopen("test_stdout.#{$$}.log", "a")
46
+ rdr_pid = $$
47
+ new_out = File.open("test_stdout.#$$.log", "a")
48
+ new_err = File.open("test_stderr.#$$.log", "a")
49
+ new_out.sync = new_err.sync = true
50
+
51
+ if tail = ENV['TAIL'] # "tail -F" if GNU, "tail -f" otherwise
52
+ require 'shellwords'
53
+ cmd = tail.shellsplit
54
+ cmd << new_out.path
55
+ cmd << new_err.path
56
+ pid = Process.spawn(*cmd, { 1 => 2, :pgroup => true })
57
+ sleep 0.1 # wait for tail(1) to startup
58
+ end
59
+ STDERR.reopen(new_err)
60
+ STDOUT.reopen(new_out)
42
61
  STDERR.sync = STDOUT.sync = true
43
62
 
44
63
  at_exit do
45
- File.unlink("test_stderr.#{$$}.log") rescue nil
46
- File.unlink("test_stdout.#{$$}.log") rescue nil
64
+ if rdr_pid == $$
65
+ File.unlink(new_out.path) rescue nil
66
+ File.unlink(new_err.path) rescue nil
67
+ end
47
68
  end
48
69
 
49
70
  begin
@@ -51,6 +72,7 @@ def redirect_test_io
51
72
  ensure
52
73
  STDERR.reopen(orig_err)
53
74
  STDOUT.reopen(orig_out)
75
+ Process.kill(:TERM, pid) if pid
54
76
  end
55
77
  end
56
78
 
@@ -265,33 +287,20 @@ def wait_for_death(pid)
265
287
  raise "PID:#{pid} never died!"
266
288
  end
267
289
 
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
- }
290
+ def reset_sig_handlers
291
+ %w(WINCH QUIT INT TERM USR1 USR2 HUP TTIN TTOU CHLD).each do |sig|
292
+ trap(sig, "DEFAULT")
293
+ end
292
294
  end
293
295
 
294
- def reset_sig_handlers
295
- sigs = %w(CHLD).concat(Unicorn::HttpServer::QUEUE_SIGS)
296
- sigs.each { |sig| trap(sig, "DEFAULT") }
296
+ def tcp_socket(*args)
297
+ sock = TCPSocket.new(*args)
298
+ sock.nonblock = false
299
+ sock
300
+ end
301
+
302
+ def unix_socket(*args)
303
+ sock = UNIXSocket.new(*args)
304
+ sock.nonblock = false
305
+ sock
297
306
  end
@@ -0,0 +1,91 @@
1
+ require 'socket'
2
+ require 'unicorn'
3
+ require 'io/wait'
4
+ require 'tempfile'
5
+ require 'test/unit'
6
+ require './test/test_helper'
7
+
8
+ class TestCccTCPI < Test::Unit::TestCase
9
+ def test_ccc_tcpi
10
+ start_pid = $$
11
+ host = '127.0.0.1'
12
+ srv = TCPServer.new(host, 0)
13
+ port = srv.addr[1]
14
+ err = Tempfile.new('unicorn_ccc')
15
+ rd, wr = IO.pipe
16
+ sleep_pipe = IO.pipe
17
+ pid = fork do
18
+ sleep_pipe[1].close
19
+ reqs = 0
20
+ rd.close
21
+ worker_pid = nil
22
+ app = lambda do |env|
23
+ worker_pid ||= begin
24
+ at_exit { wr.write(reqs.to_s) if worker_pid == $$ }
25
+ $$
26
+ end
27
+ reqs += 1
28
+
29
+ # will wake up when writer closes
30
+ sleep_pipe[0].read if env['PATH_INFO'] == '/sleep'
31
+
32
+ [ 200, [ %w(Content-Length 0), %w(Content-Type text/plain) ], [] ]
33
+ end
34
+ ENV['UNICORN_FD'] = srv.fileno.to_s
35
+ opts = {
36
+ listeners: [ "#{host}:#{port}" ],
37
+ stderr_path: err.path,
38
+ check_client_connection: true,
39
+ }
40
+ uni = Unicorn::HttpServer.new(app, opts)
41
+ uni.start.join
42
+ end
43
+ wr.close
44
+
45
+ # make sure the server is running, at least
46
+ client = tcp_socket(host, port)
47
+ client.write("GET / HTTP/1.1\r\nHost: example.com\r\n\r\n")
48
+ assert client.wait(10), 'never got response from server'
49
+ res = client.read
50
+ assert_match %r{\AHTTP/1\.1 200}, res, 'got part of first response'
51
+ assert_match %r{\r\n\r\n\z}, res, 'got end of response, server is ready'
52
+ client.close
53
+
54
+ # start a slow request...
55
+ sleeper = tcp_socket(host, port)
56
+ sleeper.write("GET /sleep HTTP/1.1\r\nHost: example.com\r\n\r\n")
57
+
58
+ # and a bunch of aborted ones
59
+ nr = 100
60
+ nr.times do |i|
61
+ client = tcp_socket(host, port)
62
+ client.write("GET /collections/#{rand(10000)} HTTP/1.1\r\n" \
63
+ "Host: example.com\r\n\r\n")
64
+ client.close
65
+ end
66
+ sleep_pipe[1].close # wake up the reader in the worker
67
+ res = sleeper.read
68
+ assert_match %r{\AHTTP/1\.1 200}, res, 'got part of first sleeper response'
69
+ assert_match %r{\r\n\r\n\z}, res, 'got end of sleeper response'
70
+ sleeper.close
71
+ kpid = pid
72
+ pid = nil
73
+ Process.kill(:QUIT, kpid)
74
+ _, status = Process.waitpid2(kpid)
75
+ assert status.success?
76
+ reqs = rd.read.to_i
77
+ warn "server got #{reqs} requests with #{nr} CCC aborted\n" if $DEBUG
78
+ assert_operator reqs, :<, nr
79
+ assert_operator reqs, :>=, 2, 'first 2 requests got through, at least'
80
+ ensure
81
+ return if start_pid != $$
82
+ srv.close if srv
83
+ if pid
84
+ Process.kill(:QUIT, pid)
85
+ _, status = Process.waitpid2(pid)
86
+ assert status.success?
87
+ end
88
+ err.close! if err
89
+ rd.close if rd
90
+ end
91
+ end
@@ -4,7 +4,7 @@ require 'unicorn'
4
4
  class TestDroplet < Test::Unit::TestCase
5
5
  def test_create_many_droplets
6
6
  now = Time.now.to_i
7
- tmp = (0..1024).map do |i|
7
+ (0..1024).each do |i|
8
8
  droplet = Unicorn::Worker.new(i)
9
9
  assert droplet.respond_to?(:tick)
10
10
  assert_equal 0, droplet.tick
@@ -1,13 +1,13 @@
1
1
  # -*- encoding: binary -*-
2
2
 
3
- # Copyright (c) 2005 Zed A. Shaw
3
+ # Copyright (c) 2005 Zed A. Shaw
4
4
  # You can redistribute it and/or modify it under the same terms as Ruby 1.8 or
5
5
  # the GPLv2+ (GPLv3+ preferred)
6
6
  #
7
- # Additional work donated by contributors. See http://mongrel.rubyforge.org/attributions.html
7
+ # Additional work donated by contributors. See git history
8
8
  # for more information.
9
9
 
10
- require 'test/test_helper'
10
+ require './test/test_helper'
11
11
 
12
12
  include Unicorn
13
13
 
@@ -230,6 +230,24 @@ class HttpParserTest < Test::Unit::TestCase
230
230
  assert_equal expect, req['HTTP_X_SSL_BULLSHIT']
231
231
  end
232
232
 
233
+ def test_multiline_header_0d0a
234
+ parser = HttpParser.new
235
+ parser.buf << "GET / HTTP/1.0\r\n" \
236
+ "X-Multiline-Header: foo bar\r\n\tcha cha\r\n\tzha zha\r\n\r\n"
237
+ req = parser.env
238
+ assert_equal req, parser.parse
239
+ assert_equal 'foo bar cha cha zha zha', req['HTTP_X_MULTILINE_HEADER']
240
+ end
241
+
242
+ def test_multiline_header_0a
243
+ parser = HttpParser.new
244
+ parser.buf << "GET / HTTP/1.0\n" \
245
+ "X-Multiline-Header: foo bar\n\tcha cha\n\tzha zha\n\n"
246
+ req = parser.env
247
+ assert_equal req, parser.parse
248
+ assert_equal 'foo bar cha cha zha zha', req['HTTP_X_MULTILINE_HEADER']
249
+ end
250
+
233
251
  def test_continuation_eats_leading_spaces
234
252
  parser = HttpParser.new
235
253
  header = "GET / HTTP/1.1\r\n" \
@@ -833,22 +851,34 @@ class HttpParserTest < Test::Unit::TestCase
833
851
  assert_equal '', parser.env['HTTP_HOST']
834
852
  end
835
853
 
836
- # so we don't care about the portability of this test
837
- # if it doesn't leak on Linux, it won't leak anywhere else
838
- # unless your C compiler or platform is otherwise broken
839
- LINUX_PROC_PID_STATUS = "/proc/self/status"
840
- def test_memory_leak
841
- match_rss = /^VmRSS:\s+(\d+)/
842
- if File.read(LINUX_PROC_PID_STATUS) =~ match_rss
843
- before = $1.to_i
844
- 1000000.times { Unicorn::HttpParser.new }
845
- File.read(LINUX_PROC_PID_STATUS) =~ match_rss
846
- after = $1.to_i
847
- diff = after - before
848
- assert(diff < 10000, "memory grew more than 10M: #{diff}")
854
+ def test_memsize
855
+ require 'objspace'
856
+ if ObjectSpace.respond_to?(:memsize_of)
857
+ n = ObjectSpace.memsize_of(Unicorn::HttpParser.new)
858
+ assert_kind_of Integer, n
859
+ # need to update this when 128-bit machines come out
860
+ # n.b. actual struct size on 64-bit is 56 bytes + 40 bytes for RVALUE
861
+ # Ruby <= 2.2 objspace did not count the 40-byte RVALUE, 2.3 does.
862
+ assert_operator n, :<=, 96
863
+ assert_operator n, :>, 0
849
864
  end
850
- end if RUBY_PLATFORM =~ /linux/ &&
851
- File.readable?(LINUX_PROC_PID_STATUS) &&
852
- !defined?(RUBY_ENGINE)
865
+ rescue LoadError
866
+ # not all Ruby implementations have objspace
867
+ end
853
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'
854
884
  end