unicorn 4.7.0 → 6.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (119) 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 +14 -21
  6. data/.olddoc.yml +22 -0
  7. data/Application_Timeouts +7 -7
  8. data/DESIGN +2 -4
  9. data/Documentation/.gitignore +1 -3
  10. data/Documentation/unicorn.1 +222 -0
  11. data/Documentation/unicorn_rails.1 +207 -0
  12. data/FAQ +23 -6
  13. data/GIT-VERSION-FILE +1 -1
  14. data/GIT-VERSION-GEN +1 -1
  15. data/GNUmakefile +139 -92
  16. data/HACKING +13 -28
  17. data/ISSUES +82 -19
  18. data/KNOWN_ISSUES +18 -18
  19. data/LATEST +22 -44
  20. data/LICENSE +2 -2
  21. data/Links +24 -22
  22. data/NEWS +729 -0
  23. data/PHILOSOPHY +0 -6
  24. data/README +50 -48
  25. data/Rakefile +0 -44
  26. data/SIGNALS +12 -3
  27. data/Sandbox +11 -10
  28. data/TODO +0 -2
  29. data/TUNING +30 -9
  30. data/archive/.gitignore +3 -0
  31. data/archive/slrnpull.conf +4 -0
  32. data/bin/unicorn +4 -2
  33. data/bin/unicorn_rails +3 -3
  34. data/examples/big_app_gc.rb +1 -1
  35. data/examples/init.sh +36 -8
  36. data/examples/logrotate.conf +17 -2
  37. data/examples/nginx.conf +14 -14
  38. data/examples/unicorn.conf.minimal.rb +2 -2
  39. data/examples/unicorn.conf.rb +14 -6
  40. data/examples/unicorn.socket +11 -0
  41. data/examples/unicorn@.service +40 -0
  42. data/ext/unicorn_http/common_field_optimization.h +23 -5
  43. data/ext/unicorn_http/ext_help.h +0 -20
  44. data/ext/unicorn_http/extconf.rb +37 -1
  45. data/ext/unicorn_http/global_variables.h +1 -1
  46. data/ext/unicorn_http/httpdate.c +2 -2
  47. data/ext/unicorn_http/unicorn_http.c +940 -644
  48. data/ext/unicorn_http/unicorn_http.rl +167 -170
  49. data/ext/unicorn_http/unicorn_http_common.rl +1 -1
  50. data/lib/unicorn/configurator.rb +110 -46
  51. data/lib/unicorn/const.rb +2 -25
  52. data/lib/unicorn/http_request.rb +110 -31
  53. data/lib/unicorn/http_response.rb +17 -31
  54. data/lib/unicorn/http_server.rb +292 -199
  55. data/lib/unicorn/launcher.rb +1 -1
  56. data/lib/unicorn/oob_gc.rb +16 -6
  57. data/lib/unicorn/socket_helper.rb +58 -78
  58. data/lib/unicorn/stream_input.rb +9 -11
  59. data/lib/unicorn/tee_input.rb +16 -11
  60. data/lib/unicorn/tmpio.rb +10 -6
  61. data/lib/unicorn/util.rb +5 -4
  62. data/lib/unicorn/version.rb +1 -1
  63. data/lib/unicorn/worker.rb +99 -22
  64. data/lib/unicorn.rb +69 -42
  65. data/man/man1/unicorn.1 +124 -122
  66. data/man/man1/unicorn_rails.1 +113 -127
  67. data/t/.gitignore +0 -1
  68. data/t/GNUmakefile +3 -80
  69. data/t/README +4 -4
  70. data/t/t0002-parser-error.sh +3 -3
  71. data/t/t0011-active-unix-socket.sh +1 -1
  72. data/t/t0012-reload-empty-config.sh +2 -1
  73. data/t/t0300-no-default-middleware.sh +6 -1
  74. data/t/t0301-no-default-middleware-ignored-in-config.sh +25 -0
  75. data/t/t0301.ru +13 -0
  76. data/t/test-lib.sh +2 -2
  77. data/test/benchmark/README +14 -4
  78. data/test/benchmark/ddstream.ru +50 -0
  79. data/test/benchmark/readinput.ru +40 -0
  80. data/test/benchmark/uconnect.perl +66 -0
  81. data/test/exec/test_exec.rb +74 -20
  82. data/test/test_helper.rb +42 -33
  83. data/test/unit/test_ccc.rb +91 -0
  84. data/test/unit/test_droplet.rb +1 -1
  85. data/test/unit/test_http_parser.rb +49 -19
  86. data/test/unit/test_http_parser_ng.rb +98 -115
  87. data/test/unit/test_request.rb +11 -11
  88. data/test/unit/test_response.rb +31 -19
  89. data/test/unit/test_server.rb +89 -15
  90. data/test/unit/test_signals.rb +9 -9
  91. data/test/unit/test_socket_helper.rb +20 -14
  92. data/test/unit/test_tee_input.rb +10 -0
  93. data/test/unit/test_upload.rb +10 -15
  94. data/test/unit/test_util.rb +28 -3
  95. data/unicorn.gemspec +28 -23
  96. data/unicorn_1 +1 -0
  97. data/unicorn_rails_1 +1 -0
  98. metadata +64 -134
  99. data/.wrongdoc.yml +0 -10
  100. data/ChangeLog +0 -4694
  101. data/Documentation/GNUmakefile +0 -30
  102. data/Documentation/unicorn.1.txt +0 -178
  103. data/Documentation/unicorn_rails.1.txt +0 -175
  104. data/examples/git.ru +0 -13
  105. data/lib/unicorn/app/exec_cgi.rb +0 -154
  106. data/lib/unicorn/app/inetd.rb +0 -109
  107. data/lib/unicorn/ssl_client.rb +0 -11
  108. data/lib/unicorn/ssl_configurator.rb +0 -104
  109. data/lib/unicorn/ssl_server.rb +0 -42
  110. data/local.mk.sample +0 -59
  111. data/script/isolate_for_tests +0 -32
  112. data/t/hijack.ru +0 -42
  113. data/t/sslgen.sh +0 -71
  114. data/t/t0016-trust-x-forwarded-false.sh +0 -30
  115. data/t/t0017-trust-x-forwarded-true.sh +0 -30
  116. data/t/t0200-rack-hijack.sh +0 -27
  117. data/t/t0600-https-server-basic.sh +0 -48
  118. data/test/unit/test_http_parser_xftrust.rb +0 -38
  119. 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