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.
- checksums.yaml +7 -0
- data/.document +0 -1
- data/.gitattributes +5 -0
- data/.gitignore +2 -2
- data/.manifest +17 -21
- data/.olddoc.yml +25 -0
- data/Application_Timeouts +7 -7
- data/CONTRIBUTORS +6 -2
- data/DESIGN +2 -4
- data/Documentation/.gitignore +1 -3
- data/Documentation/unicorn.1 +222 -0
- data/Documentation/unicorn_rails.1 +207 -0
- data/FAQ +23 -6
- data/GIT-VERSION-FILE +1 -1
- data/GIT-VERSION-GEN +1 -1
- data/GNUmakefile +138 -92
- data/HACKING +10 -32
- data/ISSUES +80 -19
- data/KNOWN_ISSUES +18 -18
- data/LATEST +23 -27
- data/LICENSE +2 -2
- data/Links +24 -22
- data/NEWS +823 -0
- data/PHILOSOPHY +0 -6
- data/README +49 -48
- data/Rakefile +0 -44
- data/SIGNALS +2 -2
- data/Sandbox +11 -10
- data/TODO +0 -2
- data/TUNING +30 -9
- data/archive/.gitignore +3 -0
- data/archive/slrnpull.conf +4 -0
- data/bin/unicorn +4 -2
- data/bin/unicorn_rails +3 -3
- data/examples/big_app_gc.rb +1 -1
- data/examples/init.sh +36 -8
- data/examples/logrotate.conf +17 -2
- data/examples/nginx.conf +14 -14
- data/examples/unicorn.conf.minimal.rb +2 -2
- data/examples/unicorn.conf.rb +14 -6
- data/examples/unicorn.socket +11 -0
- data/examples/unicorn@.service +40 -0
- data/ext/unicorn_http/c_util.h +5 -13
- data/ext/unicorn_http/common_field_optimization.h +22 -5
- data/ext/unicorn_http/epollexclusive.h +124 -0
- data/ext/unicorn_http/ext_help.h +0 -44
- data/ext/unicorn_http/extconf.rb +32 -5
- data/ext/unicorn_http/global_variables.h +2 -2
- data/ext/unicorn_http/httpdate.c +3 -2
- data/ext/unicorn_http/unicorn_http.c +926 -638
- data/ext/unicorn_http/unicorn_http.rl +159 -170
- data/ext/unicorn_http/unicorn_http_common.rl +1 -1
- data/lib/unicorn/configurator.rb +110 -46
- data/lib/unicorn/const.rb +2 -25
- data/lib/unicorn/http_request.rb +110 -31
- data/lib/unicorn/http_response.rb +17 -31
- data/lib/unicorn/http_server.rb +272 -180
- data/lib/unicorn/launcher.rb +1 -1
- data/lib/unicorn/oob_gc.rb +16 -6
- data/lib/unicorn/select_waiter.rb +6 -0
- data/lib/unicorn/socket_helper.rb +58 -78
- data/lib/unicorn/stream_input.rb +8 -7
- data/lib/unicorn/tee_input.rb +16 -11
- data/lib/unicorn/tmpio.rb +10 -6
- data/lib/unicorn/util.rb +5 -4
- data/lib/unicorn/version.rb +1 -1
- data/lib/unicorn/worker.rb +36 -23
- data/lib/unicorn.rb +64 -44
- data/man/man1/unicorn.1 +124 -122
- data/man/man1/unicorn_rails.1 +113 -127
- data/t/.gitignore +0 -1
- data/t/GNUmakefile +3 -80
- data/t/README +4 -4
- data/t/t0002-parser-error.sh +3 -3
- data/t/t0011-active-unix-socket.sh +1 -1
- data/t/t0012-reload-empty-config.sh +2 -1
- data/t/t0301-no-default-middleware-ignored-in-config.sh +25 -0
- data/t/t0301.ru +13 -0
- data/t/test-lib.sh +4 -3
- data/test/benchmark/README +14 -4
- data/test/benchmark/ddstream.ru +50 -0
- data/test/benchmark/readinput.ru +40 -0
- data/test/benchmark/uconnect.perl +66 -0
- data/test/exec/test_exec.rb +74 -20
- data/test/test_helper.rb +42 -33
- data/test/unit/test_ccc.rb +91 -0
- data/test/unit/test_droplet.rb +1 -1
- data/test/unit/test_http_parser.rb +49 -19
- data/test/unit/test_http_parser_ng.rb +98 -115
- data/test/unit/test_request.rb +11 -11
- data/test/unit/test_response.rb +31 -19
- data/test/unit/test_server.rb +89 -15
- data/test/unit/test_signals.rb +9 -9
- data/test/unit/test_socket_helper.rb +16 -12
- data/test/unit/test_tee_input.rb +10 -0
- data/test/unit/test_upload.rb +10 -15
- data/test/unit/test_util.rb +32 -6
- data/test/unit/test_waiter.rb +34 -0
- data/unicorn.gemspec +28 -23
- data/unicorn_1 +1 -0
- data/unicorn_rails_1 +1 -0
- metadata +68 -134
- data/.wrongdoc.yml +0 -10
- data/ChangeLog +0 -4861
- data/Documentation/GNUmakefile +0 -30
- data/Documentation/unicorn.1.txt +0 -178
- data/Documentation/unicorn_rails.1.txt +0 -175
- data/examples/git.ru +0 -13
- data/lib/unicorn/app/exec_cgi.rb +0 -154
- data/lib/unicorn/app/inetd.rb +0 -109
- data/lib/unicorn/ssl_client.rb +0 -11
- data/lib/unicorn/ssl_configurator.rb +0 -104
- data/lib/unicorn/ssl_server.rb +0 -42
- data/local.mk.sample +0 -59
- data/script/isolate_for_tests +0 -32
- data/t/hijack.ru +0 -42
- data/t/sslgen.sh +0 -71
- data/t/t0016-trust-x-forwarded-false.sh +0 -30
- data/t/t0017-trust-x-forwarded-true.sh +0 -30
- data/t/t0200-rack-hijack.sh +0 -27
- data/t/t0600-https-server-basic.sh +0 -48
- data/test/unit/test_http_parser_xftrust.rb +0 -38
- 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
|
+
}
|
data/test/exec/test_exec.rb
CHANGED
|
@@ -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
|
|
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
|
-
|
|
144
|
-
|
|
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
|
-
|
|
180
|
-
|
|
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
|
-
|
|
222
|
-
|
|
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 =
|
|
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,
|
|
621
|
+
wait_workers_ready(COMMON_TMP.path, HEAVY_WORKERS)
|
|
568
622
|
bf = File.readlines(COMMON_TMP.path).grep(/\bbefore_fork: worker=/)
|
|
569
|
-
assert_equal
|
|
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 <
|
|
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
|
|
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 <
|
|
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
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
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
|
-
|
|
41
|
-
|
|
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
|
-
|
|
46
|
-
|
|
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
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
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
|
|
295
|
-
|
|
296
|
-
|
|
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
|
data/test/unit/test_droplet.rb
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
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
|
-
|
|
851
|
-
|
|
852
|
-
|
|
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
|