unicorn-fotopedia 0.99.1
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.
- data/.CHANGELOG.old +25 -0
- data/.document +19 -0
- data/.gitignore +21 -0
- data/.mailmap +26 -0
- data/CONTRIBUTORS +32 -0
- data/COPYING +339 -0
- data/DESIGN +105 -0
- data/Documentation/.gitignore +5 -0
- data/Documentation/GNUmakefile +30 -0
- data/Documentation/unicorn.1.txt +171 -0
- data/Documentation/unicorn_rails.1.txt +172 -0
- data/FAQ +52 -0
- data/GIT-VERSION-GEN +40 -0
- data/GNUmakefile +292 -0
- data/HACKING +116 -0
- data/ISSUES +36 -0
- data/KNOWN_ISSUES +50 -0
- data/LICENSE +55 -0
- data/PHILOSOPHY +145 -0
- data/README +149 -0
- data/Rakefile +191 -0
- data/SIGNALS +109 -0
- data/Sandbox +78 -0
- data/TODO +5 -0
- data/TUNING +70 -0
- data/bin/unicorn +126 -0
- data/bin/unicorn_rails +203 -0
- data/examples/big_app_gc.rb +33 -0
- data/examples/echo.ru +27 -0
- data/examples/git.ru +13 -0
- data/examples/init.sh +58 -0
- data/examples/logger_mp_safe.rb +25 -0
- data/examples/nginx.conf +139 -0
- data/examples/unicorn.conf.rb +78 -0
- data/ext/unicorn_http/CFLAGS +13 -0
- data/ext/unicorn_http/c_util.h +124 -0
- data/ext/unicorn_http/common_field_optimization.h +111 -0
- data/ext/unicorn_http/ext_help.h +77 -0
- data/ext/unicorn_http/extconf.rb +14 -0
- data/ext/unicorn_http/global_variables.h +89 -0
- data/ext/unicorn_http/unicorn_http.rl +714 -0
- data/ext/unicorn_http/unicorn_http_common.rl +75 -0
- data/lib/unicorn.rb +847 -0
- data/lib/unicorn/app/exec_cgi.rb +150 -0
- data/lib/unicorn/app/inetd.rb +109 -0
- data/lib/unicorn/app/old_rails.rb +33 -0
- data/lib/unicorn/app/old_rails/static.rb +58 -0
- data/lib/unicorn/cgi_wrapper.rb +145 -0
- data/lib/unicorn/configurator.rb +421 -0
- data/lib/unicorn/const.rb +34 -0
- data/lib/unicorn/http_request.rb +72 -0
- data/lib/unicorn/http_response.rb +75 -0
- data/lib/unicorn/launcher.rb +65 -0
- data/lib/unicorn/oob_gc.rb +58 -0
- data/lib/unicorn/socket_helper.rb +152 -0
- data/lib/unicorn/tee_input.rb +217 -0
- data/lib/unicorn/util.rb +90 -0
- data/local.mk.sample +62 -0
- data/setup.rb +1586 -0
- data/t/.gitignore +2 -0
- data/t/GNUmakefile +67 -0
- data/t/README +42 -0
- data/t/bin/content-md5-put +36 -0
- data/t/bin/sha1sum.rb +23 -0
- data/t/bin/unused_listen +40 -0
- data/t/bin/utee +12 -0
- data/t/env.ru +3 -0
- data/t/my-tap-lib.sh +200 -0
- data/t/t0000-http-basic.sh +50 -0
- data/t/t0001-reload-bad-config.sh +52 -0
- data/t/t0002-config-conflict.sh +49 -0
- data/t/test-lib.sh +100 -0
- data/test/aggregate.rb +15 -0
- data/test/benchmark/README +50 -0
- data/test/benchmark/dd.ru +18 -0
- data/test/exec/README +5 -0
- data/test/exec/test_exec.rb +1038 -0
- data/test/rails/app-1.2.3/.gitignore +2 -0
- data/test/rails/app-1.2.3/Rakefile +7 -0
- data/test/rails/app-1.2.3/app/controllers/application.rb +6 -0
- data/test/rails/app-1.2.3/app/controllers/foo_controller.rb +36 -0
- data/test/rails/app-1.2.3/app/helpers/application_helper.rb +4 -0
- data/test/rails/app-1.2.3/config/boot.rb +11 -0
- data/test/rails/app-1.2.3/config/database.yml +12 -0
- data/test/rails/app-1.2.3/config/environment.rb +13 -0
- data/test/rails/app-1.2.3/config/environments/development.rb +9 -0
- data/test/rails/app-1.2.3/config/environments/production.rb +5 -0
- data/test/rails/app-1.2.3/config/routes.rb +6 -0
- data/test/rails/app-1.2.3/db/.gitignore +0 -0
- data/test/rails/app-1.2.3/public/404.html +1 -0
- data/test/rails/app-1.2.3/public/500.html +1 -0
- data/test/rails/app-2.0.2/.gitignore +2 -0
- data/test/rails/app-2.0.2/Rakefile +7 -0
- data/test/rails/app-2.0.2/app/controllers/application.rb +4 -0
- data/test/rails/app-2.0.2/app/controllers/foo_controller.rb +36 -0
- data/test/rails/app-2.0.2/app/helpers/application_helper.rb +4 -0
- data/test/rails/app-2.0.2/config/boot.rb +11 -0
- data/test/rails/app-2.0.2/config/database.yml +12 -0
- data/test/rails/app-2.0.2/config/environment.rb +17 -0
- data/test/rails/app-2.0.2/config/environments/development.rb +8 -0
- data/test/rails/app-2.0.2/config/environments/production.rb +5 -0
- data/test/rails/app-2.0.2/config/routes.rb +6 -0
- data/test/rails/app-2.0.2/db/.gitignore +0 -0
- data/test/rails/app-2.0.2/public/404.html +1 -0
- data/test/rails/app-2.0.2/public/500.html +1 -0
- data/test/rails/app-2.1.2/.gitignore +2 -0
- data/test/rails/app-2.1.2/Rakefile +7 -0
- data/test/rails/app-2.1.2/app/controllers/application.rb +4 -0
- data/test/rails/app-2.1.2/app/controllers/foo_controller.rb +36 -0
- data/test/rails/app-2.1.2/app/helpers/application_helper.rb +4 -0
- data/test/rails/app-2.1.2/config/boot.rb +111 -0
- data/test/rails/app-2.1.2/config/database.yml +12 -0
- data/test/rails/app-2.1.2/config/environment.rb +17 -0
- data/test/rails/app-2.1.2/config/environments/development.rb +7 -0
- data/test/rails/app-2.1.2/config/environments/production.rb +5 -0
- data/test/rails/app-2.1.2/config/routes.rb +6 -0
- data/test/rails/app-2.1.2/db/.gitignore +0 -0
- data/test/rails/app-2.1.2/public/404.html +1 -0
- data/test/rails/app-2.1.2/public/500.html +1 -0
- data/test/rails/app-2.2.2/.gitignore +2 -0
- data/test/rails/app-2.2.2/Rakefile +7 -0
- data/test/rails/app-2.2.2/app/controllers/application.rb +4 -0
- data/test/rails/app-2.2.2/app/controllers/foo_controller.rb +36 -0
- data/test/rails/app-2.2.2/app/helpers/application_helper.rb +4 -0
- data/test/rails/app-2.2.2/config/boot.rb +111 -0
- data/test/rails/app-2.2.2/config/database.yml +12 -0
- data/test/rails/app-2.2.2/config/environment.rb +17 -0
- data/test/rails/app-2.2.2/config/environments/development.rb +7 -0
- data/test/rails/app-2.2.2/config/environments/production.rb +5 -0
- data/test/rails/app-2.2.2/config/routes.rb +6 -0
- data/test/rails/app-2.2.2/db/.gitignore +0 -0
- data/test/rails/app-2.2.2/public/404.html +1 -0
- data/test/rails/app-2.2.2/public/500.html +1 -0
- data/test/rails/app-2.3.5/.gitignore +2 -0
- data/test/rails/app-2.3.5/Rakefile +7 -0
- data/test/rails/app-2.3.5/app/controllers/application_controller.rb +5 -0
- data/test/rails/app-2.3.5/app/controllers/foo_controller.rb +36 -0
- data/test/rails/app-2.3.5/app/helpers/application_helper.rb +4 -0
- data/test/rails/app-2.3.5/config/boot.rb +109 -0
- data/test/rails/app-2.3.5/config/database.yml +12 -0
- data/test/rails/app-2.3.5/config/environment.rb +17 -0
- data/test/rails/app-2.3.5/config/environments/development.rb +7 -0
- data/test/rails/app-2.3.5/config/environments/production.rb +6 -0
- data/test/rails/app-2.3.5/config/routes.rb +6 -0
- data/test/rails/app-2.3.5/db/.gitignore +0 -0
- data/test/rails/app-2.3.5/public/404.html +1 -0
- data/test/rails/app-2.3.5/public/500.html +1 -0
- data/test/rails/app-2.3.5/public/x.txt +1 -0
- data/test/rails/test_rails.rb +280 -0
- data/test/test_helper.rb +301 -0
- data/test/unit/test_configurator.rb +150 -0
- data/test/unit/test_http_parser.rb +555 -0
- data/test/unit/test_http_parser_ng.rb +443 -0
- data/test/unit/test_request.rb +184 -0
- data/test/unit/test_response.rb +110 -0
- data/test/unit/test_server.rb +291 -0
- data/test/unit/test_signals.rb +206 -0
- data/test/unit/test_socket_helper.rb +147 -0
- data/test/unit/test_tee_input.rb +257 -0
- data/test/unit/test_upload.rb +298 -0
- data/test/unit/test_util.rb +96 -0
- data/unicorn.gemspec +52 -0
- metadata +283 -0
@@ -0,0 +1,52 @@
|
|
1
|
+
#!/bin/sh
|
2
|
+
. ./test-lib.sh
|
3
|
+
t_plan 7 "reload config.ru error with preload_app true"
|
4
|
+
|
5
|
+
t_begin "setup and start" && {
|
6
|
+
unicorn_setup
|
7
|
+
rtmpfiles ru
|
8
|
+
|
9
|
+
cat > $ru <<\EOF
|
10
|
+
use Rack::ContentLength
|
11
|
+
use Rack::ContentType, "text/plain"
|
12
|
+
x = { "hello" => "world" }
|
13
|
+
run lambda { |env| [ 200, {}, [ x.inspect << "\n" ] ] }
|
14
|
+
EOF
|
15
|
+
echo 'preload_app true' >> $unicorn_config
|
16
|
+
unicorn -D -c $unicorn_config $ru
|
17
|
+
unicorn_wait_start
|
18
|
+
}
|
19
|
+
|
20
|
+
t_begin "hit with curl" && {
|
21
|
+
out=$(curl -sSf http://$listen/)
|
22
|
+
test x"$out" = x'{"hello"=>"world"}'
|
23
|
+
}
|
24
|
+
|
25
|
+
t_begin "introduce syntax error in rackup file" && {
|
26
|
+
echo '...' >> $ru
|
27
|
+
}
|
28
|
+
|
29
|
+
t_begin "reload signal succeeds" && {
|
30
|
+
kill -HUP $unicorn_pid
|
31
|
+
while ! egrep '(done|error) reloading' $r_err >/dev/null
|
32
|
+
do
|
33
|
+
sleep 1
|
34
|
+
done
|
35
|
+
|
36
|
+
grep 'error reloading' $r_err >/dev/null
|
37
|
+
}
|
38
|
+
|
39
|
+
t_begin "hit with curl" && {
|
40
|
+
out=$(curl -sSf http://$listen/)
|
41
|
+
test x"$out" = x'{"hello"=>"world"}'
|
42
|
+
}
|
43
|
+
|
44
|
+
t_begin "killing succeeds" && {
|
45
|
+
kill $unicorn_pid
|
46
|
+
}
|
47
|
+
|
48
|
+
t_begin "check stderr" && {
|
49
|
+
check_stderr
|
50
|
+
}
|
51
|
+
|
52
|
+
t_done
|
@@ -0,0 +1,49 @@
|
|
1
|
+
#!/bin/sh
|
2
|
+
. ./test-lib.sh
|
3
|
+
t_plan 6 "config variables conflict with preload_app"
|
4
|
+
|
5
|
+
t_begin "setup and start" && {
|
6
|
+
unicorn_setup
|
7
|
+
rtmpfiles ru rutmp
|
8
|
+
|
9
|
+
cat > $ru <<\EOF
|
10
|
+
use Rack::ContentLength
|
11
|
+
use Rack::ContentType, "text/plain"
|
12
|
+
config = ru = { "hello" => "world" }
|
13
|
+
run lambda { |env| [ 200, {}, [ ru.inspect << "\n" ] ] }
|
14
|
+
EOF
|
15
|
+
echo 'preload_app true' >> $unicorn_config
|
16
|
+
unicorn -D -c $unicorn_config $ru
|
17
|
+
unicorn_wait_start
|
18
|
+
}
|
19
|
+
|
20
|
+
t_begin "hit with curl" && {
|
21
|
+
out=$(curl -sSf http://$listen/)
|
22
|
+
test x"$out" = x'{"hello"=>"world"}'
|
23
|
+
}
|
24
|
+
|
25
|
+
t_begin "modify rackup file" && {
|
26
|
+
sed -e 's/world/WORLD/' < $ru > $rutmp
|
27
|
+
mv $rutmp $ru
|
28
|
+
}
|
29
|
+
|
30
|
+
t_begin "reload signal succeeds" && {
|
31
|
+
kill -HUP $unicorn_pid
|
32
|
+
while ! egrep '(done|error) reloading' < $r_err >/dev/null
|
33
|
+
do
|
34
|
+
sleep 1
|
35
|
+
done
|
36
|
+
|
37
|
+
grep 'done reloading' $r_err >/dev/null
|
38
|
+
}
|
39
|
+
|
40
|
+
t_begin "hit with curl" && {
|
41
|
+
out=$(curl -sSf http://$listen/)
|
42
|
+
test x"$out" = x'{"hello"=>"WORLD"}'
|
43
|
+
}
|
44
|
+
|
45
|
+
t_begin "killing succeeds" && {
|
46
|
+
kill $unicorn_pid
|
47
|
+
}
|
48
|
+
|
49
|
+
t_done
|
data/t/test-lib.sh
ADDED
@@ -0,0 +1,100 @@
|
|
1
|
+
#!/bin/sh
|
2
|
+
# Copyright (c) 2009 Rainbows! hackers
|
3
|
+
# Copyright (c) 2010 Unicorn hackers
|
4
|
+
. ./my-tap-lib.sh
|
5
|
+
|
6
|
+
set +u
|
7
|
+
set -e
|
8
|
+
RUBY="${RUBY-ruby}"
|
9
|
+
RUBY_VERSION=${RUBY_VERSION-$($RUBY -e 'puts RUBY_VERSION')}
|
10
|
+
t_pfx=$PWD/trash/$T-$RUBY_VERSION
|
11
|
+
set -u
|
12
|
+
|
13
|
+
PATH=$PWD/bin:$PATH
|
14
|
+
export PATH
|
15
|
+
|
16
|
+
test -x $PWD/bin/unused_listen || die "must be run in 't' directory"
|
17
|
+
|
18
|
+
wait_for_pid () {
|
19
|
+
path="$1"
|
20
|
+
nr=30
|
21
|
+
while ! test -s "$path" && test $nr -gt 0
|
22
|
+
do
|
23
|
+
nr=$(($nr - 1))
|
24
|
+
sleep 1
|
25
|
+
done
|
26
|
+
}
|
27
|
+
|
28
|
+
# given a list of variable names, create temporary files and assign
|
29
|
+
# the pathnames to those variables
|
30
|
+
rtmpfiles () {
|
31
|
+
for id in "$@"
|
32
|
+
do
|
33
|
+
name=$id
|
34
|
+
_tmp=$t_pfx.$id
|
35
|
+
eval "$id=$_tmp"
|
36
|
+
|
37
|
+
case $name in
|
38
|
+
*fifo)
|
39
|
+
rm -f $_tmp
|
40
|
+
mkfifo $_tmp
|
41
|
+
T_RM_LIST="$T_RM_LIST $_tmp"
|
42
|
+
;;
|
43
|
+
*socket)
|
44
|
+
rm -f $_tmp
|
45
|
+
T_RM_LIST="$T_RM_LIST $_tmp"
|
46
|
+
;;
|
47
|
+
*)
|
48
|
+
> $_tmp
|
49
|
+
T_OK_RM_LIST="$T_OK_RM_LIST $_tmp"
|
50
|
+
;;
|
51
|
+
esac
|
52
|
+
done
|
53
|
+
}
|
54
|
+
|
55
|
+
dbgcat () {
|
56
|
+
id=$1
|
57
|
+
eval '_file=$'$id
|
58
|
+
echo "==> $id <=="
|
59
|
+
sed -e "s/^/$id:/" < $_file
|
60
|
+
}
|
61
|
+
|
62
|
+
check_stderr () {
|
63
|
+
set +u
|
64
|
+
_r_err=${1-${r_err}}
|
65
|
+
set -u
|
66
|
+
if grep -v $T $_r_err | grep -i Error
|
67
|
+
then
|
68
|
+
die "Errors found in $_r_err"
|
69
|
+
elif grep SIGKILL $_r_err
|
70
|
+
then
|
71
|
+
die "SIGKILL found in $_r_err"
|
72
|
+
fi
|
73
|
+
}
|
74
|
+
|
75
|
+
# unicorn_setup
|
76
|
+
unicorn_setup () {
|
77
|
+
eval $(unused_listen)
|
78
|
+
rtmpfiles unicorn_config pid r_err r_out fifo tmp ok
|
79
|
+
cat > $unicorn_config <<EOF
|
80
|
+
listen "$listen"
|
81
|
+
pid "$pid"
|
82
|
+
stderr_path "$r_err"
|
83
|
+
stdout_path "$r_out"
|
84
|
+
EOF
|
85
|
+
}
|
86
|
+
|
87
|
+
unicorn_wait_start () {
|
88
|
+
# no need to play tricks with FIFOs since we got "ready_pipe" now
|
89
|
+
unicorn_pid=$(cat $pid)
|
90
|
+
}
|
91
|
+
|
92
|
+
rsha1 () {
|
93
|
+
_cmd="$(which sha1sum 2>/dev/null || :)"
|
94
|
+
test -n "$_cmd" || _cmd="$(which openssl 2>/dev/null || :) sha1"
|
95
|
+
test "$_cmd" != " sha1" || _cmd="$(which gsha1sum 2>/dev/null || :)"
|
96
|
+
|
97
|
+
# last resort, see comments in sha1sum.rb for reasoning
|
98
|
+
test -n "$_cmd" || _cmd=sha1sum.rb
|
99
|
+
expr "$($_cmd < random_blob)" : '\([a-f0-9]\{40\}\)'
|
100
|
+
}
|
data/test/aggregate.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
#!/usr/bin/ruby -n
|
2
|
+
# -*- encoding: binary -*-
|
3
|
+
|
4
|
+
BEGIN { $tests = $assertions = $failures = $errors = 0 }
|
5
|
+
|
6
|
+
$_ =~ /(\d+) tests, (\d+) assertions, (\d+) failures, (\d+) errors/ or next
|
7
|
+
$tests += $1.to_i
|
8
|
+
$assertions += $2.to_i
|
9
|
+
$failures += $3.to_i
|
10
|
+
$errors += $4.to_i
|
11
|
+
|
12
|
+
END {
|
13
|
+
printf("\n%d tests, %d assertions, %d failures, %d errors\n",
|
14
|
+
$tests, $assertions, $failures, $errors)
|
15
|
+
}
|
@@ -0,0 +1,50 @@
|
|
1
|
+
= Performance
|
2
|
+
|
3
|
+
Unicorn is pretty fast, and we want it to get faster. Unicorn strives
|
4
|
+
to get HTTP requests to your application and write HTTP responses back
|
5
|
+
as quickly as possible. Unicorn does not do any background processing
|
6
|
+
while your app runs, so your app will get all the CPU time provided to
|
7
|
+
it by your OS kernel.
|
8
|
+
|
9
|
+
A gentle reminder: Unicorn is NOT for serving clients over slow network
|
10
|
+
connections. Use nginx (or something similar) to complement Unicorn if
|
11
|
+
you have slow clients.
|
12
|
+
|
13
|
+
== dd.ru
|
14
|
+
|
15
|
+
This is a pure I/O benchmark. In the context of Unicorn, this is the
|
16
|
+
only one that matters. It is a standard rackup-compatible .ru file and
|
17
|
+
may be used with other Rack-compatible servers.
|
18
|
+
|
19
|
+
unicorn -E none dd.ru
|
20
|
+
|
21
|
+
You can change the size and number of chunks in the response with
|
22
|
+
the "bs" and "count" environment variables. The following command
|
23
|
+
will cause dd.ru to return 4 chunks of 16384 bytes each, leading to
|
24
|
+
65536 byte response:
|
25
|
+
|
26
|
+
bs=16384 count=4 unicorn -E none dd.ru
|
27
|
+
|
28
|
+
Or if you want to add logging (small performance impact):
|
29
|
+
|
30
|
+
unicorn -E deployment dd.ru
|
31
|
+
|
32
|
+
Eric runs then runs clients on a LAN it in several different ways:
|
33
|
+
|
34
|
+
client@host1 -> unicorn@host1(tcp)
|
35
|
+
client@host2 -> unicorn@host1(tcp)
|
36
|
+
client@host3 -> nginx@host1 -> unicorn@host1(tcp)
|
37
|
+
client@host3 -> nginx@host1 -> unicorn@host1(unix)
|
38
|
+
client@host3 -> nginx@host2 -> unicorn@host1(tcp)
|
39
|
+
|
40
|
+
The benchmark client is usually httperf.
|
41
|
+
|
42
|
+
Another gentle reminder: performance with slow networks/clients
|
43
|
+
is NOT our problem. That is the job of nginx (or similar).
|
44
|
+
|
45
|
+
== Contributors
|
46
|
+
|
47
|
+
This directory is maintained independently in the "benchmark" branch
|
48
|
+
based against v0.1.0. Only changes to this directory (test/benchmarks)
|
49
|
+
are committed to this branch although the master branch may merge this
|
50
|
+
branch occassionaly.
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# This benchmark is the simplest test of the I/O facilities in
|
2
|
+
# unicorn. It is meant to return a fixed-sized blob to test
|
3
|
+
# the performance of things in Unicorn, _NOT_ the app.
|
4
|
+
#
|
5
|
+
# Adjusting this benchmark is done via the "bs" (byte size) and "count"
|
6
|
+
# environment variables. "count" designates the count of elements of
|
7
|
+
# "bs" length in the Rack response body. The defaults are bs=4096, count=1
|
8
|
+
# to return one 4096-byte chunk.
|
9
|
+
bs = ENV['bs'] ? ENV['bs'].to_i : 4096
|
10
|
+
count = ENV['count'] ? ENV['count'].to_i : 1
|
11
|
+
slice = (' ' * bs).freeze
|
12
|
+
body = (1..count).map { slice }.freeze
|
13
|
+
hdr = {
|
14
|
+
'Content-Length' => (bs * count).to_s.freeze,
|
15
|
+
'Content-Type' => 'text/plain'.freeze
|
16
|
+
}.freeze
|
17
|
+
response = [ 200, hdr, body ].freeze
|
18
|
+
run(lambda { |env| response })
|
data/test/exec/README
ADDED
@@ -0,0 +1,5 @@
|
|
1
|
+
These tests require the "unicorn" executable script to be installed in
|
2
|
+
PATH and rack being directly "require"-able ("rubygems" will not be
|
3
|
+
loaded for you). The tester is responsible for setting up RUBYLIB and
|
4
|
+
PATH environment variables (or running tests via GNU Make instead of
|
5
|
+
Rake).
|
@@ -0,0 +1,1038 @@
|
|
1
|
+
# -*- encoding: binary -*-
|
2
|
+
|
3
|
+
# Copyright (c) 2009 Eric Wong
|
4
|
+
FLOCK_PATH = File.expand_path(__FILE__)
|
5
|
+
require 'test/test_helper'
|
6
|
+
|
7
|
+
do_test = true
|
8
|
+
$unicorn_bin = ENV['UNICORN_TEST_BIN'] || "unicorn"
|
9
|
+
redirect_test_io do
|
10
|
+
do_test = system($unicorn_bin, '-v')
|
11
|
+
end
|
12
|
+
|
13
|
+
unless do_test
|
14
|
+
warn "#{$unicorn_bin} not found in PATH=#{ENV['PATH']}, " \
|
15
|
+
"skipping this test"
|
16
|
+
end
|
17
|
+
|
18
|
+
unless try_require('rack')
|
19
|
+
warn "Unable to load Rack, skipping this test"
|
20
|
+
do_test = false
|
21
|
+
end
|
22
|
+
|
23
|
+
class ExecTest < Test::Unit::TestCase
|
24
|
+
trap(:QUIT, 'IGNORE')
|
25
|
+
|
26
|
+
HI = <<-EOS
|
27
|
+
use Rack::ContentLength
|
28
|
+
run proc { |env| [ 200, { 'Content-Type' => 'text/plain' }, [ "HI\\n" ] ] }
|
29
|
+
EOS
|
30
|
+
|
31
|
+
SHOW_RACK_ENV = <<-EOS
|
32
|
+
use Rack::ContentLength
|
33
|
+
run proc { |env|
|
34
|
+
[ 200, { 'Content-Type' => 'text/plain' }, [ ENV['RACK_ENV'] ] ]
|
35
|
+
}
|
36
|
+
EOS
|
37
|
+
|
38
|
+
HELLO = <<-EOS
|
39
|
+
class Hello
|
40
|
+
def call(env)
|
41
|
+
[ 200, { 'Content-Type' => 'text/plain' }, [ "HI\\n" ] ]
|
42
|
+
end
|
43
|
+
end
|
44
|
+
EOS
|
45
|
+
|
46
|
+
COMMON_TMP = Tempfile.new('unicorn_tmp') unless defined?(COMMON_TMP)
|
47
|
+
|
48
|
+
HEAVY_CFG = <<-EOS
|
49
|
+
worker_processes 4
|
50
|
+
timeout 30
|
51
|
+
logger Logger.new('#{COMMON_TMP.path}')
|
52
|
+
before_fork do |server, worker|
|
53
|
+
server.logger.info "before_fork: worker=\#{worker.nr}"
|
54
|
+
end
|
55
|
+
EOS
|
56
|
+
|
57
|
+
def setup
|
58
|
+
@pwd = Dir.pwd
|
59
|
+
@tmpfile = Tempfile.new('unicorn_exec_test')
|
60
|
+
@tmpdir = @tmpfile.path
|
61
|
+
@tmpfile.close!
|
62
|
+
Dir.mkdir(@tmpdir)
|
63
|
+
Dir.chdir(@tmpdir)
|
64
|
+
@addr = ENV['UNICORN_TEST_ADDR'] || '127.0.0.1'
|
65
|
+
@port = unused_port(@addr)
|
66
|
+
@sockets = []
|
67
|
+
@start_pid = $$
|
68
|
+
end
|
69
|
+
|
70
|
+
def teardown
|
71
|
+
return if @start_pid != $$
|
72
|
+
Dir.chdir(@pwd)
|
73
|
+
FileUtils.rmtree(@tmpdir)
|
74
|
+
@sockets.each { |path| File.unlink(path) rescue nil }
|
75
|
+
loop do
|
76
|
+
Process.kill('-QUIT', 0)
|
77
|
+
begin
|
78
|
+
Process.waitpid(-1, Process::WNOHANG) or break
|
79
|
+
rescue Errno::ECHILD
|
80
|
+
break
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def test_working_directory_rel_path_config_file
|
86
|
+
other = Tempfile.new('unicorn.wd')
|
87
|
+
File.unlink(other.path)
|
88
|
+
Dir.mkdir(other.path)
|
89
|
+
File.open("config.ru", "wb") do |fp|
|
90
|
+
fp.syswrite <<EOF
|
91
|
+
use Rack::ContentLength
|
92
|
+
run proc { |env| [ 200, { 'Content-Type' => 'text/plain' }, [ Dir.pwd ] ] }
|
93
|
+
EOF
|
94
|
+
end
|
95
|
+
FileUtils.cp("config.ru", other.path + "/config.ru")
|
96
|
+
Dir.chdir(@tmpdir)
|
97
|
+
|
98
|
+
tmp = File.open('unicorn.config', 'wb')
|
99
|
+
tmp.syswrite <<EOF
|
100
|
+
working_directory '#@tmpdir'
|
101
|
+
listen '#@addr:#@port'
|
102
|
+
EOF
|
103
|
+
pid = xfork { redirect_test_io { exec($unicorn_bin, "-c#{tmp.path}") } }
|
104
|
+
wait_workers_ready("test_stderr.#{pid}.log", 1)
|
105
|
+
results = hit(["http://#@addr:#@port/"])
|
106
|
+
assert_equal @tmpdir, results.first
|
107
|
+
File.truncate("test_stderr.#{pid}.log", 0)
|
108
|
+
|
109
|
+
tmp.sysseek(0)
|
110
|
+
tmp.truncate(0)
|
111
|
+
tmp.syswrite <<EOF
|
112
|
+
working_directory '#{other.path}'
|
113
|
+
listen '#@addr:#@port'
|
114
|
+
EOF
|
115
|
+
|
116
|
+
Process.kill(:HUP, pid)
|
117
|
+
lines = []
|
118
|
+
re = /config_file=(.+) would not be accessible in working_directory=(.+)/
|
119
|
+
until lines.grep(re)
|
120
|
+
sleep 0.1
|
121
|
+
lines = File.readlines("test_stderr.#{pid}.log")
|
122
|
+
end
|
123
|
+
|
124
|
+
File.truncate("test_stderr.#{pid}.log", 0)
|
125
|
+
FileUtils.cp('unicorn.config', other.path + "/unicorn.config")
|
126
|
+
Process.kill(:HUP, pid)
|
127
|
+
wait_workers_ready("test_stderr.#{pid}.log", 1)
|
128
|
+
results = hit(["http://#@addr:#@port/"])
|
129
|
+
assert_equal other.path, results.first
|
130
|
+
|
131
|
+
Process.kill(:QUIT, pid)
|
132
|
+
ensure
|
133
|
+
FileUtils.rmtree(other.path)
|
134
|
+
end
|
135
|
+
|
136
|
+
def test_working_directory
|
137
|
+
other = Tempfile.new('unicorn.wd')
|
138
|
+
File.unlink(other.path)
|
139
|
+
Dir.mkdir(other.path)
|
140
|
+
File.open("config.ru", "wb") do |fp|
|
141
|
+
fp.syswrite <<EOF
|
142
|
+
use Rack::ContentLength
|
143
|
+
run proc { |env| [ 200, { 'Content-Type' => 'text/plain' }, [ Dir.pwd ] ] }
|
144
|
+
EOF
|
145
|
+
end
|
146
|
+
FileUtils.cp("config.ru", other.path + "/config.ru")
|
147
|
+
tmp = Tempfile.new('unicorn.config')
|
148
|
+
tmp.syswrite <<EOF
|
149
|
+
working_directory '#@tmpdir'
|
150
|
+
listen '#@addr:#@port'
|
151
|
+
EOF
|
152
|
+
pid = xfork { redirect_test_io { exec($unicorn_bin, "-c#{tmp.path}") } }
|
153
|
+
wait_workers_ready("test_stderr.#{pid}.log", 1)
|
154
|
+
results = hit(["http://#@addr:#@port/"])
|
155
|
+
assert_equal @tmpdir, results.first
|
156
|
+
File.truncate("test_stderr.#{pid}.log", 0)
|
157
|
+
|
158
|
+
tmp.sysseek(0)
|
159
|
+
tmp.truncate(0)
|
160
|
+
tmp.syswrite <<EOF
|
161
|
+
working_directory '#{other.path}'
|
162
|
+
listen '#@addr:#@port'
|
163
|
+
EOF
|
164
|
+
|
165
|
+
Process.kill(:HUP, pid)
|
166
|
+
wait_workers_ready("test_stderr.#{pid}.log", 1)
|
167
|
+
results = hit(["http://#@addr:#@port/"])
|
168
|
+
assert_equal other.path, results.first
|
169
|
+
|
170
|
+
Process.kill(:QUIT, pid)
|
171
|
+
ensure
|
172
|
+
FileUtils.rmtree(other.path)
|
173
|
+
end
|
174
|
+
|
175
|
+
def test_working_directory_controls_relative_paths
|
176
|
+
other = Tempfile.new('unicorn.wd')
|
177
|
+
File.unlink(other.path)
|
178
|
+
Dir.mkdir(other.path)
|
179
|
+
File.open("config.ru", "wb") do |fp|
|
180
|
+
fp.syswrite <<EOF
|
181
|
+
use Rack::ContentLength
|
182
|
+
run proc { |env| [ 200, { 'Content-Type' => 'text/plain' }, [ Dir.pwd ] ] }
|
183
|
+
EOF
|
184
|
+
end
|
185
|
+
FileUtils.cp("config.ru", other.path + "/config.ru")
|
186
|
+
system('mkfifo', "#{other.path}/fifo")
|
187
|
+
tmp = Tempfile.new('unicorn.config')
|
188
|
+
tmp.syswrite <<EOF
|
189
|
+
pid "pid_file_here"
|
190
|
+
stderr_path "stderr_log_here"
|
191
|
+
stdout_path "stdout_log_here"
|
192
|
+
working_directory '#{other.path}'
|
193
|
+
listen '#@addr:#@port'
|
194
|
+
after_fork do |server, worker|
|
195
|
+
File.open("fifo", "wb").close
|
196
|
+
end
|
197
|
+
EOF
|
198
|
+
pid = xfork { redirect_test_io { exec($unicorn_bin, "-c#{tmp.path}") } }
|
199
|
+
File.open("#{other.path}/fifo", "rb").close
|
200
|
+
|
201
|
+
assert ! File.exist?("stderr_log_here")
|
202
|
+
assert ! File.exist?("stdout_log_here")
|
203
|
+
assert ! File.exist?("pid_file_here")
|
204
|
+
|
205
|
+
assert ! File.exist?("#@tmpdir/stderr_log_here")
|
206
|
+
assert ! File.exist?("#@tmpdir/stdout_log_here")
|
207
|
+
assert ! File.exist?("#@tmpdir/pid_file_here")
|
208
|
+
|
209
|
+
assert File.exist?("#{other.path}/pid_file_here")
|
210
|
+
assert_equal "#{pid}\n", File.read("#{other.path}/pid_file_here")
|
211
|
+
assert File.exist?("#{other.path}/stderr_log_here")
|
212
|
+
assert File.exist?("#{other.path}/stdout_log_here")
|
213
|
+
wait_master_ready("#{other.path}/stderr_log_here")
|
214
|
+
|
215
|
+
Process.kill(:QUIT, pid)
|
216
|
+
ensure
|
217
|
+
FileUtils.rmtree(other.path)
|
218
|
+
end
|
219
|
+
|
220
|
+
|
221
|
+
def test_exit_signals
|
222
|
+
%w(INT TERM QUIT).each do |sig|
|
223
|
+
File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
|
224
|
+
pid = xfork { redirect_test_io { exec($unicorn_bin, "-l#@addr:#@port") } }
|
225
|
+
wait_master_ready("test_stderr.#{pid}.log")
|
226
|
+
wait_workers_ready("test_stderr.#{pid}.log", 1)
|
227
|
+
status = nil
|
228
|
+
assert_nothing_raised do
|
229
|
+
Process.kill(sig, pid)
|
230
|
+
pid, status = Process.waitpid2(pid)
|
231
|
+
end
|
232
|
+
reaped = File.readlines("test_stderr.#{pid}.log").grep(/reaped/)
|
233
|
+
assert_equal 1, reaped.size
|
234
|
+
assert status.exited?
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
def test_basic
|
239
|
+
File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
|
240
|
+
pid = fork do
|
241
|
+
redirect_test_io { exec($unicorn_bin, "-l", "#{@addr}:#{@port}") }
|
242
|
+
end
|
243
|
+
results = retry_hit(["http://#{@addr}:#{@port}/"])
|
244
|
+
assert_equal String, results[0].class
|
245
|
+
assert_shutdown(pid)
|
246
|
+
end
|
247
|
+
|
248
|
+
def test_rack_env_unset
|
249
|
+
File.open("config.ru", "wb") { |fp| fp.syswrite(SHOW_RACK_ENV) }
|
250
|
+
pid = fork { redirect_test_io { exec($unicorn_bin, "-l#@addr:#@port") } }
|
251
|
+
results = retry_hit(["http://#{@addr}:#{@port}/"])
|
252
|
+
assert_equal "development", results.first
|
253
|
+
assert_shutdown(pid)
|
254
|
+
end
|
255
|
+
|
256
|
+
def test_rack_env_cli_set
|
257
|
+
File.open("config.ru", "wb") { |fp| fp.syswrite(SHOW_RACK_ENV) }
|
258
|
+
pid = fork {
|
259
|
+
redirect_test_io { exec($unicorn_bin, "-l#@addr:#@port", "-Easdf") }
|
260
|
+
}
|
261
|
+
results = retry_hit(["http://#{@addr}:#{@port}/"])
|
262
|
+
assert_equal "asdf", results.first
|
263
|
+
assert_shutdown(pid)
|
264
|
+
end
|
265
|
+
|
266
|
+
def test_rack_env_ENV_set
|
267
|
+
File.open("config.ru", "wb") { |fp| fp.syswrite(SHOW_RACK_ENV) }
|
268
|
+
pid = fork {
|
269
|
+
ENV["RACK_ENV"] = "foobar"
|
270
|
+
redirect_test_io { exec($unicorn_bin, "-l#@addr:#@port") }
|
271
|
+
}
|
272
|
+
results = retry_hit(["http://#{@addr}:#{@port}/"])
|
273
|
+
assert_equal "foobar", results.first
|
274
|
+
assert_shutdown(pid)
|
275
|
+
end
|
276
|
+
|
277
|
+
def test_rack_env_cli_override_ENV
|
278
|
+
File.open("config.ru", "wb") { |fp| fp.syswrite(SHOW_RACK_ENV) }
|
279
|
+
pid = fork {
|
280
|
+
ENV["RACK_ENV"] = "foobar"
|
281
|
+
redirect_test_io { exec($unicorn_bin, "-l#@addr:#@port", "-Easdf") }
|
282
|
+
}
|
283
|
+
results = retry_hit(["http://#{@addr}:#{@port}/"])
|
284
|
+
assert_equal "asdf", results.first
|
285
|
+
assert_shutdown(pid)
|
286
|
+
end
|
287
|
+
|
288
|
+
def test_ttin_ttou
|
289
|
+
File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
|
290
|
+
pid = fork { redirect_test_io { exec($unicorn_bin, "-l#@addr:#@port") } }
|
291
|
+
log = "test_stderr.#{pid}.log"
|
292
|
+
wait_master_ready(log)
|
293
|
+
[ 2, 3].each { |i|
|
294
|
+
assert_nothing_raised { Process.kill(:TTIN, pid) }
|
295
|
+
wait_workers_ready(log, i)
|
296
|
+
}
|
297
|
+
File.truncate(log, 0)
|
298
|
+
reaped = nil
|
299
|
+
[ 2, 1, 0].each { |i|
|
300
|
+
assert_nothing_raised { Process.kill(:TTOU, pid) }
|
301
|
+
DEFAULT_TRIES.times {
|
302
|
+
sleep DEFAULT_RES
|
303
|
+
reaped = File.readlines(log).grep(/reaped.*\s*worker=#{i}$/)
|
304
|
+
break if reaped.size == 1
|
305
|
+
}
|
306
|
+
assert_equal 1, reaped.size
|
307
|
+
}
|
308
|
+
end
|
309
|
+
|
310
|
+
def test_help
|
311
|
+
redirect_test_io do
|
312
|
+
assert(system($unicorn_bin, "-h"), "help text returns true")
|
313
|
+
end
|
314
|
+
assert_equal 0, File.stat("test_stderr.#$$.log").size
|
315
|
+
assert_not_equal 0, File.stat("test_stdout.#$$.log").size
|
316
|
+
lines = File.readlines("test_stdout.#$$.log")
|
317
|
+
|
318
|
+
# Be considerate of the on-call technician working from their
|
319
|
+
# mobile phone or netbook on a slow connection :)
|
320
|
+
assert lines.size <= 24, "help height fits in an ANSI terminal window"
|
321
|
+
lines.each do |line|
|
322
|
+
assert line.size <= 80, "help width fits in an ANSI terminal window"
|
323
|
+
end
|
324
|
+
end
|
325
|
+
|
326
|
+
def test_broken_reexec_config
|
327
|
+
File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
|
328
|
+
pid_file = "#{@tmpdir}/test.pid"
|
329
|
+
old_file = "#{pid_file}.oldbin"
|
330
|
+
ucfg = Tempfile.new('unicorn_test_config')
|
331
|
+
ucfg.syswrite("listen %(#@addr:#@port)\n")
|
332
|
+
ucfg.syswrite("pid %(#{pid_file})\n")
|
333
|
+
ucfg.syswrite("logger Logger.new(%(#{@tmpdir}/log))\n")
|
334
|
+
pid = xfork do
|
335
|
+
redirect_test_io do
|
336
|
+
exec($unicorn_bin, "-D", "-l#{@addr}:#{@port}", "-c#{ucfg.path}")
|
337
|
+
end
|
338
|
+
end
|
339
|
+
results = retry_hit(["http://#{@addr}:#{@port}/"])
|
340
|
+
assert_equal String, results[0].class
|
341
|
+
|
342
|
+
wait_for_file(pid_file)
|
343
|
+
Process.waitpid(pid)
|
344
|
+
Process.kill(:USR2, File.read(pid_file).to_i)
|
345
|
+
wait_for_file(old_file)
|
346
|
+
wait_for_file(pid_file)
|
347
|
+
old_pid = File.read(old_file).to_i
|
348
|
+
Process.kill(:QUIT, old_pid)
|
349
|
+
wait_for_death(old_pid)
|
350
|
+
|
351
|
+
ucfg.syswrite("timeout %(#{pid_file})\n") # introduce a bug
|
352
|
+
current_pid = File.read(pid_file).to_i
|
353
|
+
Process.kill(:USR2, current_pid)
|
354
|
+
|
355
|
+
# wait for pid_file to restore itself
|
356
|
+
tries = DEFAULT_TRIES
|
357
|
+
begin
|
358
|
+
while current_pid != File.read(pid_file).to_i
|
359
|
+
sleep(DEFAULT_RES) and (tries -= 1) > 0
|
360
|
+
end
|
361
|
+
rescue Errno::ENOENT
|
362
|
+
(sleep(DEFAULT_RES) and (tries -= 1) > 0) and retry
|
363
|
+
end
|
364
|
+
assert_equal current_pid, File.read(pid_file).to_i
|
365
|
+
|
366
|
+
tries = DEFAULT_TRIES
|
367
|
+
while File.exist?(old_file)
|
368
|
+
(sleep(DEFAULT_RES) and (tries -= 1) > 0) or break
|
369
|
+
end
|
370
|
+
assert ! File.exist?(old_file), "oldbin=#{old_file} gone"
|
371
|
+
port2 = unused_port(@addr)
|
372
|
+
|
373
|
+
# fix the bug
|
374
|
+
ucfg.sysseek(0)
|
375
|
+
ucfg.truncate(0)
|
376
|
+
ucfg.syswrite("listen %(#@addr:#@port)\n")
|
377
|
+
ucfg.syswrite("listen %(#@addr:#{port2})\n")
|
378
|
+
ucfg.syswrite("pid %(#{pid_file})\n")
|
379
|
+
assert_nothing_raised { Process.kill(:USR2, current_pid) }
|
380
|
+
|
381
|
+
wait_for_file(old_file)
|
382
|
+
wait_for_file(pid_file)
|
383
|
+
new_pid = File.read(pid_file).to_i
|
384
|
+
assert_not_equal current_pid, new_pid
|
385
|
+
assert_equal current_pid, File.read(old_file).to_i
|
386
|
+
results = retry_hit(["http://#{@addr}:#{@port}/",
|
387
|
+
"http://#{@addr}:#{port2}/"])
|
388
|
+
assert_equal String, results[0].class
|
389
|
+
assert_equal String, results[1].class
|
390
|
+
|
391
|
+
assert_nothing_raised do
|
392
|
+
Process.kill(:QUIT, current_pid)
|
393
|
+
Process.kill(:QUIT, new_pid)
|
394
|
+
end
|
395
|
+
end
|
396
|
+
|
397
|
+
def test_broken_reexec_ru
|
398
|
+
File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
|
399
|
+
pid_file = "#{@tmpdir}/test.pid"
|
400
|
+
old_file = "#{pid_file}.oldbin"
|
401
|
+
ucfg = Tempfile.new('unicorn_test_config')
|
402
|
+
ucfg.syswrite("pid %(#{pid_file})\n")
|
403
|
+
ucfg.syswrite("logger Logger.new(%(#{@tmpdir}/log))\n")
|
404
|
+
pid = xfork do
|
405
|
+
redirect_test_io do
|
406
|
+
exec($unicorn_bin, "-D", "-l#{@addr}:#{@port}", "-c#{ucfg.path}")
|
407
|
+
end
|
408
|
+
end
|
409
|
+
results = retry_hit(["http://#{@addr}:#{@port}/"])
|
410
|
+
assert_equal String, results[0].class
|
411
|
+
|
412
|
+
wait_for_file(pid_file)
|
413
|
+
Process.waitpid(pid)
|
414
|
+
Process.kill(:USR2, File.read(pid_file).to_i)
|
415
|
+
wait_for_file(old_file)
|
416
|
+
wait_for_file(pid_file)
|
417
|
+
old_pid = File.read(old_file).to_i
|
418
|
+
Process.kill(:QUIT, old_pid)
|
419
|
+
wait_for_death(old_pid)
|
420
|
+
|
421
|
+
File.unlink("config.ru") # break reloading
|
422
|
+
current_pid = File.read(pid_file).to_i
|
423
|
+
Process.kill(:USR2, current_pid)
|
424
|
+
|
425
|
+
# wait for pid_file to restore itself
|
426
|
+
tries = DEFAULT_TRIES
|
427
|
+
begin
|
428
|
+
while current_pid != File.read(pid_file).to_i
|
429
|
+
sleep(DEFAULT_RES) and (tries -= 1) > 0
|
430
|
+
end
|
431
|
+
rescue Errno::ENOENT
|
432
|
+
(sleep(DEFAULT_RES) and (tries -= 1) > 0) and retry
|
433
|
+
end
|
434
|
+
|
435
|
+
tries = DEFAULT_TRIES
|
436
|
+
while File.exist?(old_file)
|
437
|
+
(sleep(DEFAULT_RES) and (tries -= 1) > 0) or break
|
438
|
+
end
|
439
|
+
assert ! File.exist?(old_file), "oldbin=#{old_file} gone"
|
440
|
+
assert_equal current_pid, File.read(pid_file).to_i
|
441
|
+
|
442
|
+
# fix the bug
|
443
|
+
File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
|
444
|
+
assert_nothing_raised { Process.kill(:USR2, current_pid) }
|
445
|
+
wait_for_file(old_file)
|
446
|
+
wait_for_file(pid_file)
|
447
|
+
new_pid = File.read(pid_file).to_i
|
448
|
+
assert_not_equal current_pid, new_pid
|
449
|
+
assert_equal current_pid, File.read(old_file).to_i
|
450
|
+
results = retry_hit(["http://#{@addr}:#{@port}/"])
|
451
|
+
assert_equal String, results[0].class
|
452
|
+
|
453
|
+
assert_nothing_raised do
|
454
|
+
Process.kill(:QUIT, current_pid)
|
455
|
+
Process.kill(:QUIT, new_pid)
|
456
|
+
end
|
457
|
+
end
|
458
|
+
|
459
|
+
def test_unicorn_config_listener_swap
|
460
|
+
port_cli = unused_port
|
461
|
+
File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
|
462
|
+
ucfg = Tempfile.new('unicorn_test_config')
|
463
|
+
ucfg.syswrite("listen '#@addr:#@port'\n")
|
464
|
+
pid = xfork do
|
465
|
+
redirect_test_io do
|
466
|
+
exec($unicorn_bin, "-c#{ucfg.path}", "-l#@addr:#{port_cli}")
|
467
|
+
end
|
468
|
+
end
|
469
|
+
results = retry_hit(["http://#@addr:#{port_cli}/"])
|
470
|
+
assert_equal String, results[0].class
|
471
|
+
results = retry_hit(["http://#@addr:#@port/"])
|
472
|
+
assert_equal String, results[0].class
|
473
|
+
|
474
|
+
port2 = unused_port(@addr)
|
475
|
+
ucfg.sysseek(0)
|
476
|
+
ucfg.truncate(0)
|
477
|
+
ucfg.syswrite("listen '#@addr:#{port2}'\n")
|
478
|
+
Process.kill(:HUP, pid)
|
479
|
+
|
480
|
+
results = retry_hit(["http://#@addr:#{port2}/"])
|
481
|
+
assert_equal String, results[0].class
|
482
|
+
results = retry_hit(["http://#@addr:#{port_cli}/"])
|
483
|
+
assert_equal String, results[0].class
|
484
|
+
assert_nothing_raised do
|
485
|
+
reuse = TCPServer.new(@addr, @port)
|
486
|
+
reuse.close
|
487
|
+
end
|
488
|
+
assert_shutdown(pid)
|
489
|
+
end
|
490
|
+
|
491
|
+
def test_unicorn_config_listen_with_options
|
492
|
+
File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
|
493
|
+
ucfg = Tempfile.new('unicorn_test_config')
|
494
|
+
ucfg.syswrite("listen '#{@addr}:#{@port}', :backlog => 512,\n")
|
495
|
+
ucfg.syswrite(" :rcvbuf => 4096,\n")
|
496
|
+
ucfg.syswrite(" :sndbuf => 4096\n")
|
497
|
+
pid = xfork do
|
498
|
+
redirect_test_io { exec($unicorn_bin, "-c#{ucfg.path}") }
|
499
|
+
end
|
500
|
+
results = retry_hit(["http://#{@addr}:#{@port}/"])
|
501
|
+
assert_equal String, results[0].class
|
502
|
+
assert_shutdown(pid)
|
503
|
+
end
|
504
|
+
|
505
|
+
def test_unicorn_config_per_worker_listen
|
506
|
+
port2 = unused_port
|
507
|
+
pid_spit = 'use Rack::ContentLength;' \
|
508
|
+
'run proc { |e| [ 200, {"Content-Type"=>"text/plain"}, ["#$$\\n"] ] }'
|
509
|
+
File.open("config.ru", "wb") { |fp| fp.syswrite(pid_spit) }
|
510
|
+
tmp = Tempfile.new('test.socket')
|
511
|
+
File.unlink(tmp.path)
|
512
|
+
ucfg = Tempfile.new('unicorn_test_config')
|
513
|
+
ucfg.syswrite("listen '#@addr:#@port'\n")
|
514
|
+
ucfg.syswrite("before_fork { |s,w|\n")
|
515
|
+
ucfg.syswrite(" s.listen('#{tmp.path}', :backlog => 5, :sndbuf => 8192)\n")
|
516
|
+
ucfg.syswrite(" s.listen('#@addr:#{port2}', :rcvbuf => 8192)\n")
|
517
|
+
ucfg.syswrite("\n}\n")
|
518
|
+
pid = xfork do
|
519
|
+
redirect_test_io { exec($unicorn_bin, "-c#{ucfg.path}") }
|
520
|
+
end
|
521
|
+
results = retry_hit(["http://#{@addr}:#{@port}/"])
|
522
|
+
assert_equal String, results[0].class
|
523
|
+
worker_pid = results[0].to_i
|
524
|
+
assert_not_equal pid, worker_pid
|
525
|
+
s = UNIXSocket.new(tmp.path)
|
526
|
+
s.syswrite("GET / HTTP/1.0\r\n\r\n")
|
527
|
+
results = ''
|
528
|
+
loop { results << s.sysread(4096) } rescue nil
|
529
|
+
assert_nothing_raised { s.close }
|
530
|
+
assert_equal worker_pid, results.split(/\r\n/).last.to_i
|
531
|
+
results = hit(["http://#@addr:#{port2}/"])
|
532
|
+
assert_equal String, results[0].class
|
533
|
+
assert_equal worker_pid, results[0].to_i
|
534
|
+
assert_shutdown(pid)
|
535
|
+
end
|
536
|
+
|
537
|
+
def test_unicorn_config_listen_augments_cli
|
538
|
+
port2 = unused_port(@addr)
|
539
|
+
File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
|
540
|
+
ucfg = Tempfile.new('unicorn_test_config')
|
541
|
+
ucfg.syswrite("listen '#{@addr}:#{@port}'\n")
|
542
|
+
pid = xfork do
|
543
|
+
redirect_test_io do
|
544
|
+
exec($unicorn_bin, "-c#{ucfg.path}", "-l#{@addr}:#{port2}")
|
545
|
+
end
|
546
|
+
end
|
547
|
+
uris = [@port, port2].map { |i| "http://#{@addr}:#{i}/" }
|
548
|
+
results = retry_hit(uris)
|
549
|
+
assert_equal results.size, uris.size
|
550
|
+
assert_equal String, results[0].class
|
551
|
+
assert_equal String, results[1].class
|
552
|
+
assert_shutdown(pid)
|
553
|
+
end
|
554
|
+
|
555
|
+
def test_weird_config_settings
|
556
|
+
File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
|
557
|
+
ucfg = Tempfile.new('unicorn_test_config')
|
558
|
+
ucfg.syswrite(HEAVY_CFG)
|
559
|
+
pid = xfork do
|
560
|
+
redirect_test_io do
|
561
|
+
exec($unicorn_bin, "-c#{ucfg.path}", "-l#{@addr}:#{@port}")
|
562
|
+
end
|
563
|
+
end
|
564
|
+
|
565
|
+
results = retry_hit(["http://#{@addr}:#{@port}/"])
|
566
|
+
assert_equal String, results[0].class
|
567
|
+
wait_master_ready(COMMON_TMP.path)
|
568
|
+
wait_workers_ready(COMMON_TMP.path, 4)
|
569
|
+
bf = File.readlines(COMMON_TMP.path).grep(/\bbefore_fork: worker=/)
|
570
|
+
assert_equal 4, bf.size
|
571
|
+
rotate = Tempfile.new('unicorn_rotate')
|
572
|
+
assert_nothing_raised do
|
573
|
+
File.rename(COMMON_TMP.path, rotate.path)
|
574
|
+
Process.kill(:USR1, pid)
|
575
|
+
end
|
576
|
+
wait_for_file(COMMON_TMP.path)
|
577
|
+
assert File.exist?(COMMON_TMP.path), "#{COMMON_TMP.path} exists"
|
578
|
+
# USR1 should've been passed to all workers
|
579
|
+
tries = DEFAULT_TRIES
|
580
|
+
log = File.readlines(rotate.path)
|
581
|
+
while (tries -= 1) > 0 &&
|
582
|
+
log.grep(/reopening logs\.\.\./).size < 5
|
583
|
+
sleep DEFAULT_RES
|
584
|
+
log = File.readlines(rotate.path)
|
585
|
+
end
|
586
|
+
assert_equal 5, log.grep(/reopening logs\.\.\./).size
|
587
|
+
assert_equal 0, log.grep(/done reopening logs/).size
|
588
|
+
|
589
|
+
tries = DEFAULT_TRIES
|
590
|
+
log = File.readlines(COMMON_TMP.path)
|
591
|
+
while (tries -= 1) > 0 && log.grep(/done reopening logs/).size < 5
|
592
|
+
sleep DEFAULT_RES
|
593
|
+
log = File.readlines(COMMON_TMP.path)
|
594
|
+
end
|
595
|
+
assert_equal 5, log.grep(/done reopening logs/).size
|
596
|
+
assert_equal 0, log.grep(/reopening logs\.\.\./).size
|
597
|
+
assert_nothing_raised { Process.kill(:QUIT, pid) }
|
598
|
+
status = nil
|
599
|
+
assert_nothing_raised { pid, status = Process.waitpid2(pid) }
|
600
|
+
assert status.success?, "exited successfully"
|
601
|
+
end
|
602
|
+
|
603
|
+
def test_read_embedded_cli_switches
|
604
|
+
File.open("config.ru", "wb") do |fp|
|
605
|
+
fp.syswrite("#\\ -p #{@port} -o #{@addr}\n")
|
606
|
+
fp.syswrite(HI)
|
607
|
+
end
|
608
|
+
pid = fork { redirect_test_io { exec($unicorn_bin) } }
|
609
|
+
results = retry_hit(["http://#{@addr}:#{@port}/"])
|
610
|
+
assert_equal String, results[0].class
|
611
|
+
assert_shutdown(pid)
|
612
|
+
end
|
613
|
+
|
614
|
+
def test_config_ru_alt_path
|
615
|
+
config_path = "#{@tmpdir}/foo.ru"
|
616
|
+
File.open(config_path, "wb") { |fp| fp.syswrite(HI) }
|
617
|
+
pid = fork do
|
618
|
+
redirect_test_io do
|
619
|
+
Dir.chdir("/")
|
620
|
+
exec($unicorn_bin, "-l#{@addr}:#{@port}", config_path)
|
621
|
+
end
|
622
|
+
end
|
623
|
+
results = retry_hit(["http://#{@addr}:#{@port}/"])
|
624
|
+
assert_equal String, results[0].class
|
625
|
+
assert_shutdown(pid)
|
626
|
+
end
|
627
|
+
|
628
|
+
def test_load_module
|
629
|
+
libdir = "#{@tmpdir}/lib"
|
630
|
+
FileUtils.mkpath([ libdir ])
|
631
|
+
config_path = "#{libdir}/hello.rb"
|
632
|
+
File.open(config_path, "wb") { |fp| fp.syswrite(HELLO) }
|
633
|
+
pid = fork do
|
634
|
+
redirect_test_io do
|
635
|
+
Dir.chdir("/")
|
636
|
+
exec($unicorn_bin, "-l#{@addr}:#{@port}", config_path)
|
637
|
+
end
|
638
|
+
end
|
639
|
+
results = retry_hit(["http://#{@addr}:#{@port}/"])
|
640
|
+
assert_equal String, results[0].class
|
641
|
+
assert_shutdown(pid)
|
642
|
+
end
|
643
|
+
|
644
|
+
def test_reexec
|
645
|
+
File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
|
646
|
+
pid_file = "#{@tmpdir}/test.pid"
|
647
|
+
pid = fork do
|
648
|
+
redirect_test_io do
|
649
|
+
exec($unicorn_bin, "-l#{@addr}:#{@port}", "-P#{pid_file}")
|
650
|
+
end
|
651
|
+
end
|
652
|
+
reexec_basic_test(pid, pid_file)
|
653
|
+
end
|
654
|
+
|
655
|
+
def test_reexec_alt_config
|
656
|
+
config_file = "#{@tmpdir}/foo.ru"
|
657
|
+
File.open(config_file, "wb") { |fp| fp.syswrite(HI) }
|
658
|
+
pid_file = "#{@tmpdir}/test.pid"
|
659
|
+
pid = fork do
|
660
|
+
redirect_test_io do
|
661
|
+
exec($unicorn_bin, "-l#{@addr}:#{@port}", "-P#{pid_file}", config_file)
|
662
|
+
end
|
663
|
+
end
|
664
|
+
reexec_basic_test(pid, pid_file)
|
665
|
+
end
|
666
|
+
|
667
|
+
def test_socket_unlinked_restore
|
668
|
+
results = nil
|
669
|
+
sock = Tempfile.new('unicorn_test_sock')
|
670
|
+
sock_path = sock.path
|
671
|
+
@sockets << sock_path
|
672
|
+
sock.close!
|
673
|
+
ucfg = Tempfile.new('unicorn_test_config')
|
674
|
+
ucfg.syswrite("listen \"#{sock_path}\"\n")
|
675
|
+
|
676
|
+
File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
|
677
|
+
pid = xfork { redirect_test_io { exec($unicorn_bin, "-c#{ucfg.path}") } }
|
678
|
+
wait_for_file(sock_path)
|
679
|
+
assert File.socket?(sock_path)
|
680
|
+
assert_nothing_raised do
|
681
|
+
sock = UNIXSocket.new(sock_path)
|
682
|
+
sock.syswrite("GET / HTTP/1.0\r\n\r\n")
|
683
|
+
results = sock.sysread(4096)
|
684
|
+
end
|
685
|
+
assert_equal String, results.class
|
686
|
+
assert_nothing_raised do
|
687
|
+
File.unlink(sock_path)
|
688
|
+
Process.kill(:HUP, pid)
|
689
|
+
end
|
690
|
+
wait_for_file(sock_path)
|
691
|
+
assert File.socket?(sock_path)
|
692
|
+
assert_nothing_raised do
|
693
|
+
sock = UNIXSocket.new(sock_path)
|
694
|
+
sock.syswrite("GET / HTTP/1.0\r\n\r\n")
|
695
|
+
results = sock.sysread(4096)
|
696
|
+
end
|
697
|
+
assert_equal String, results.class
|
698
|
+
end
|
699
|
+
|
700
|
+
def test_unicorn_config_file
|
701
|
+
pid_file = "#{@tmpdir}/test.pid"
|
702
|
+
sock = Tempfile.new('unicorn_test_sock')
|
703
|
+
sock_path = sock.path
|
704
|
+
sock.close!
|
705
|
+
@sockets << sock_path
|
706
|
+
|
707
|
+
log = Tempfile.new('unicorn_test_log')
|
708
|
+
ucfg = Tempfile.new('unicorn_test_config')
|
709
|
+
ucfg.syswrite("listen \"#{sock_path}\"\n")
|
710
|
+
ucfg.syswrite("pid \"#{pid_file}\"\n")
|
711
|
+
ucfg.syswrite("logger Logger.new('#{log.path}')\n")
|
712
|
+
ucfg.close
|
713
|
+
|
714
|
+
File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
|
715
|
+
pid = xfork do
|
716
|
+
redirect_test_io do
|
717
|
+
exec($unicorn_bin, "-l#{@addr}:#{@port}",
|
718
|
+
"-P#{pid_file}", "-c#{ucfg.path}")
|
719
|
+
end
|
720
|
+
end
|
721
|
+
results = retry_hit(["http://#{@addr}:#{@port}/"])
|
722
|
+
assert_equal String, results[0].class
|
723
|
+
wait_master_ready(log.path)
|
724
|
+
assert File.exist?(pid_file), "pid_file created"
|
725
|
+
assert_equal pid, File.read(pid_file).to_i
|
726
|
+
assert File.socket?(sock_path), "socket created"
|
727
|
+
assert_nothing_raised do
|
728
|
+
sock = UNIXSocket.new(sock_path)
|
729
|
+
sock.syswrite("GET / HTTP/1.0\r\n\r\n")
|
730
|
+
results = sock.sysread(4096)
|
731
|
+
end
|
732
|
+
assert_equal String, results.class
|
733
|
+
|
734
|
+
# try reloading the config
|
735
|
+
sock = Tempfile.new('new_test_sock')
|
736
|
+
new_sock_path = sock.path
|
737
|
+
@sockets << new_sock_path
|
738
|
+
sock.close!
|
739
|
+
new_log = Tempfile.new('unicorn_test_log')
|
740
|
+
new_log.sync = true
|
741
|
+
assert_equal 0, new_log.size
|
742
|
+
|
743
|
+
assert_nothing_raised do
|
744
|
+
ucfg = File.open(ucfg.path, "wb")
|
745
|
+
ucfg.syswrite("listen \"#{sock_path}\"\n")
|
746
|
+
ucfg.syswrite("listen \"#{new_sock_path}\"\n")
|
747
|
+
ucfg.syswrite("pid \"#{pid_file}\"\n")
|
748
|
+
ucfg.syswrite("logger Logger.new('#{new_log.path}')\n")
|
749
|
+
ucfg.close
|
750
|
+
Process.kill(:HUP, pid)
|
751
|
+
end
|
752
|
+
|
753
|
+
wait_for_file(new_sock_path)
|
754
|
+
assert File.socket?(new_sock_path), "socket exists"
|
755
|
+
@sockets.each do |path|
|
756
|
+
assert_nothing_raised do
|
757
|
+
sock = UNIXSocket.new(path)
|
758
|
+
sock.syswrite("GET / HTTP/1.0\r\n\r\n")
|
759
|
+
results = sock.sysread(4096)
|
760
|
+
end
|
761
|
+
assert_equal String, results.class
|
762
|
+
end
|
763
|
+
|
764
|
+
assert_not_equal 0, new_log.size
|
765
|
+
reexec_usr2_quit_test(pid, pid_file)
|
766
|
+
end
|
767
|
+
|
768
|
+
def test_daemonize_reexec
|
769
|
+
pid_file = "#{@tmpdir}/test.pid"
|
770
|
+
log = Tempfile.new('unicorn_test_log')
|
771
|
+
ucfg = Tempfile.new('unicorn_test_config')
|
772
|
+
ucfg.syswrite("pid \"#{pid_file}\"\n")
|
773
|
+
ucfg.syswrite("logger Logger.new('#{log.path}')\n")
|
774
|
+
ucfg.close
|
775
|
+
|
776
|
+
File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
|
777
|
+
pid = xfork do
|
778
|
+
redirect_test_io do
|
779
|
+
exec($unicorn_bin, "-D", "-l#{@addr}:#{@port}", "-c#{ucfg.path}")
|
780
|
+
end
|
781
|
+
end
|
782
|
+
results = retry_hit(["http://#{@addr}:#{@port}/"])
|
783
|
+
assert_equal String, results[0].class
|
784
|
+
wait_for_file(pid_file)
|
785
|
+
new_pid = File.read(pid_file).to_i
|
786
|
+
assert_not_equal pid, new_pid
|
787
|
+
pid, status = Process.waitpid2(pid)
|
788
|
+
assert status.success?, "original process exited successfully"
|
789
|
+
assert_nothing_raised { Process.kill(0, new_pid) }
|
790
|
+
reexec_usr2_quit_test(new_pid, pid_file)
|
791
|
+
end
|
792
|
+
|
793
|
+
def test_daemonize_redirect_fail
|
794
|
+
pid_file = "#{@tmpdir}/test.pid"
|
795
|
+
log = Tempfile.new('unicorn_test_log')
|
796
|
+
ucfg = Tempfile.new('unicorn_test_config')
|
797
|
+
ucfg.syswrite("pid #{pid_file}\"\n")
|
798
|
+
err = Tempfile.new('stderr')
|
799
|
+
out = Tempfile.new('stdout ')
|
800
|
+
|
801
|
+
File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
|
802
|
+
pid = xfork do
|
803
|
+
$stderr.reopen(err.path, "a")
|
804
|
+
$stdout.reopen(out.path, "a")
|
805
|
+
exec($unicorn_bin, "-D", "-l#{@addr}:#{@port}", "-c#{ucfg.path}")
|
806
|
+
end
|
807
|
+
pid, status = Process.waitpid2(pid)
|
808
|
+
assert ! status.success?, "original process exited successfully"
|
809
|
+
sleep 1 # can't waitpid on a daemonized process :<
|
810
|
+
assert err.stat.size > 0
|
811
|
+
end
|
812
|
+
|
813
|
+
def test_reexec_fd_leak
|
814
|
+
unless RUBY_PLATFORM =~ /linux/ # Solaris may work, too, but I forget...
|
815
|
+
warn "FD leak test only works on Linux at the moment"
|
816
|
+
return
|
817
|
+
end
|
818
|
+
pid_file = "#{@tmpdir}/test.pid"
|
819
|
+
log = Tempfile.new('unicorn_test_log')
|
820
|
+
log.sync = true
|
821
|
+
ucfg = Tempfile.new('unicorn_test_config')
|
822
|
+
ucfg.syswrite("pid \"#{pid_file}\"\n")
|
823
|
+
ucfg.syswrite("logger Logger.new('#{log.path}')\n")
|
824
|
+
ucfg.syswrite("stderr_path '#{log.path}'\n")
|
825
|
+
ucfg.syswrite("stdout_path '#{log.path}'\n")
|
826
|
+
ucfg.close
|
827
|
+
|
828
|
+
File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
|
829
|
+
pid = xfork do
|
830
|
+
redirect_test_io do
|
831
|
+
exec($unicorn_bin, "-D", "-l#{@addr}:#{@port}", "-c#{ucfg.path}")
|
832
|
+
end
|
833
|
+
end
|
834
|
+
|
835
|
+
wait_master_ready(log.path)
|
836
|
+
wait_workers_ready(log.path, 1)
|
837
|
+
File.truncate(log.path, 0)
|
838
|
+
wait_for_file(pid_file)
|
839
|
+
orig_pid = pid = File.read(pid_file).to_i
|
840
|
+
orig_fds = `ls -l /proc/#{pid}/fd`.split(/\n/)
|
841
|
+
assert $?.success?
|
842
|
+
expect_size = orig_fds.size
|
843
|
+
|
844
|
+
assert_nothing_raised do
|
845
|
+
Process.kill(:USR2, pid)
|
846
|
+
wait_for_file("#{pid_file}.oldbin")
|
847
|
+
Process.kill(:QUIT, pid)
|
848
|
+
end
|
849
|
+
wait_for_death(pid)
|
850
|
+
|
851
|
+
wait_master_ready(log.path)
|
852
|
+
wait_workers_ready(log.path, 1)
|
853
|
+
File.truncate(log.path, 0)
|
854
|
+
wait_for_file(pid_file)
|
855
|
+
pid = File.read(pid_file).to_i
|
856
|
+
assert_not_equal orig_pid, pid
|
857
|
+
curr_fds = `ls -l /proc/#{pid}/fd`.split(/\n/)
|
858
|
+
assert $?.success?
|
859
|
+
|
860
|
+
# we could've inherited descriptors the first time around
|
861
|
+
assert expect_size >= curr_fds.size, curr_fds.inspect
|
862
|
+
expect_size = curr_fds.size
|
863
|
+
|
864
|
+
assert_nothing_raised do
|
865
|
+
Process.kill(:USR2, pid)
|
866
|
+
wait_for_file("#{pid_file}.oldbin")
|
867
|
+
Process.kill(:QUIT, pid)
|
868
|
+
end
|
869
|
+
wait_for_death(pid)
|
870
|
+
|
871
|
+
wait_master_ready(log.path)
|
872
|
+
wait_workers_ready(log.path, 1)
|
873
|
+
File.truncate(log.path, 0)
|
874
|
+
wait_for_file(pid_file)
|
875
|
+
pid = File.read(pid_file).to_i
|
876
|
+
curr_fds = `ls -l /proc/#{pid}/fd`.split(/\n/)
|
877
|
+
assert $?.success?
|
878
|
+
assert_equal expect_size, curr_fds.size, curr_fds.inspect
|
879
|
+
|
880
|
+
Process.kill(:QUIT, pid)
|
881
|
+
wait_for_death(pid)
|
882
|
+
end
|
883
|
+
|
884
|
+
def hup_test_common(preload)
|
885
|
+
File.open("config.ru", "wb") { |fp| fp.syswrite(HI.gsub("HI", '#$$')) }
|
886
|
+
pid_file = Tempfile.new('pid')
|
887
|
+
ucfg = Tempfile.new('unicorn_test_config')
|
888
|
+
ucfg.syswrite("listen '#@addr:#@port'\n")
|
889
|
+
ucfg.syswrite("pid '#{pid_file.path}'\n")
|
890
|
+
ucfg.syswrite("preload_app true\n") if preload
|
891
|
+
ucfg.syswrite("stderr_path 'test_stderr.#$$.log'\n")
|
892
|
+
ucfg.syswrite("stdout_path 'test_stdout.#$$.log'\n")
|
893
|
+
pid = xfork {
|
894
|
+
redirect_test_io { exec($unicorn_bin, "-D", "-c", ucfg.path) }
|
895
|
+
}
|
896
|
+
_, status = Process.waitpid2(pid)
|
897
|
+
assert status.success?
|
898
|
+
wait_master_ready("test_stderr.#$$.log")
|
899
|
+
wait_workers_ready("test_stderr.#$$.log", 1)
|
900
|
+
uri = URI.parse("http://#@addr:#@port/")
|
901
|
+
pids = Tempfile.new('worker_pids')
|
902
|
+
hitter = fork {
|
903
|
+
bodies = Hash.new(0)
|
904
|
+
at_exit { pids.syswrite(bodies.inspect) }
|
905
|
+
trap(:TERM) { exit(0) }
|
906
|
+
loop {
|
907
|
+
rv = Net::HTTP.get(uri)
|
908
|
+
pid = rv.to_i
|
909
|
+
exit!(1) if pid <= 0
|
910
|
+
bodies[pid] += 1
|
911
|
+
}
|
912
|
+
}
|
913
|
+
sleep 5 # racy
|
914
|
+
daemon_pid = File.read(pid_file.path).to_i
|
915
|
+
assert daemon_pid > 0
|
916
|
+
Process.kill(:HUP, daemon_pid)
|
917
|
+
sleep 5 # racy
|
918
|
+
assert_nothing_raised { Process.kill(:TERM, hitter) }
|
919
|
+
_, hitter_status = Process.waitpid2(hitter)
|
920
|
+
assert hitter_status.success?
|
921
|
+
pids.sysseek(0)
|
922
|
+
pids = eval(pids.read)
|
923
|
+
assert_kind_of(Hash, pids)
|
924
|
+
assert_equal 2, pids.size
|
925
|
+
pids.keys.each { |x|
|
926
|
+
assert_kind_of(Integer, x)
|
927
|
+
assert x > 0
|
928
|
+
assert pids[x] > 0
|
929
|
+
}
|
930
|
+
assert_nothing_raised { Process.kill(:QUIT, daemon_pid) }
|
931
|
+
wait_for_death(daemon_pid)
|
932
|
+
end
|
933
|
+
|
934
|
+
def test_preload_app_hup
|
935
|
+
hup_test_common(true)
|
936
|
+
end
|
937
|
+
|
938
|
+
def test_hup
|
939
|
+
hup_test_common(false)
|
940
|
+
end
|
941
|
+
|
942
|
+
def test_default_listen_hup_holds_listener
|
943
|
+
default_listen_lock do
|
944
|
+
res, pid_path = default_listen_setup
|
945
|
+
daemon_pid = File.read(pid_path).to_i
|
946
|
+
assert_nothing_raised { Process.kill(:HUP, daemon_pid) }
|
947
|
+
wait_workers_ready("test_stderr.#$$.log", 1)
|
948
|
+
res2 = hit(["http://#{Unicorn::Const::DEFAULT_LISTEN}/"])
|
949
|
+
assert_match %r{\d+}, res2.first
|
950
|
+
assert res2.first != res.first
|
951
|
+
assert_nothing_raised { Process.kill(:QUIT, daemon_pid) }
|
952
|
+
wait_for_death(daemon_pid)
|
953
|
+
end
|
954
|
+
end
|
955
|
+
|
956
|
+
def test_default_listen_upgrade_holds_listener
|
957
|
+
default_listen_lock do
|
958
|
+
res, pid_path = default_listen_setup
|
959
|
+
daemon_pid = File.read(pid_path).to_i
|
960
|
+
assert_nothing_raised {
|
961
|
+
Process.kill(:USR2, daemon_pid)
|
962
|
+
wait_for_file("#{pid_path}.oldbin")
|
963
|
+
wait_for_file(pid_path)
|
964
|
+
Process.kill(:QUIT, daemon_pid)
|
965
|
+
wait_for_death(daemon_pid)
|
966
|
+
}
|
967
|
+
daemon_pid = File.read(pid_path).to_i
|
968
|
+
wait_workers_ready("test_stderr.#$$.log", 1)
|
969
|
+
File.truncate("test_stderr.#$$.log", 0)
|
970
|
+
|
971
|
+
res2 = hit(["http://#{Unicorn::Const::DEFAULT_LISTEN}/"])
|
972
|
+
assert_match %r{\d+}, res2.first
|
973
|
+
assert res2.first != res.first
|
974
|
+
|
975
|
+
assert_nothing_raised { Process.kill(:HUP, daemon_pid) }
|
976
|
+
wait_workers_ready("test_stderr.#$$.log", 1)
|
977
|
+
File.truncate("test_stderr.#$$.log", 0)
|
978
|
+
res3 = hit(["http://#{Unicorn::Const::DEFAULT_LISTEN}/"])
|
979
|
+
assert res2.first != res3.first
|
980
|
+
|
981
|
+
assert_nothing_raised { Process.kill(:QUIT, daemon_pid) }
|
982
|
+
wait_for_death(daemon_pid)
|
983
|
+
end
|
984
|
+
end
|
985
|
+
|
986
|
+
def default_listen_setup
|
987
|
+
File.open("config.ru", "wb") { |fp| fp.syswrite(HI.gsub("HI", '#$$')) }
|
988
|
+
pid_path = (tmp = Tempfile.new('pid')).path
|
989
|
+
tmp.close!
|
990
|
+
ucfg = Tempfile.new('unicorn_test_config')
|
991
|
+
ucfg.syswrite("pid '#{pid_path}'\n")
|
992
|
+
ucfg.syswrite("stderr_path 'test_stderr.#$$.log'\n")
|
993
|
+
ucfg.syswrite("stdout_path 'test_stdout.#$$.log'\n")
|
994
|
+
pid = xfork {
|
995
|
+
redirect_test_io { exec($unicorn_bin, "-D", "-c", ucfg.path) }
|
996
|
+
}
|
997
|
+
_, status = Process.waitpid2(pid)
|
998
|
+
assert status.success?
|
999
|
+
wait_master_ready("test_stderr.#$$.log")
|
1000
|
+
wait_workers_ready("test_stderr.#$$.log", 1)
|
1001
|
+
File.truncate("test_stderr.#$$.log", 0)
|
1002
|
+
res = hit(["http://#{Unicorn::Const::DEFAULT_LISTEN}/"])
|
1003
|
+
assert_match %r{\d+}, res.first
|
1004
|
+
[ res, pid_path ]
|
1005
|
+
end
|
1006
|
+
|
1007
|
+
# we need to flock() something to prevent these tests from running
|
1008
|
+
def default_listen_lock(&block)
|
1009
|
+
fp = File.open(FLOCK_PATH, "rb")
|
1010
|
+
begin
|
1011
|
+
fp.flock(File::LOCK_EX)
|
1012
|
+
begin
|
1013
|
+
TCPServer.new(Unicorn::Const::DEFAULT_HOST,
|
1014
|
+
Unicorn::Const::DEFAULT_PORT).close
|
1015
|
+
rescue Errno::EADDRINUSE, Errno::EACCES
|
1016
|
+
warn "can't bind to #{Unicorn::Const::DEFAULT_LISTEN}"
|
1017
|
+
return false
|
1018
|
+
end
|
1019
|
+
|
1020
|
+
# unused_port should never take this, but we may run an environment
|
1021
|
+
# where tests are being run against older unicorns...
|
1022
|
+
lock_path = "#{Dir::tmpdir}/unicorn_test." \
|
1023
|
+
"#{Unicorn::Const::DEFAULT_LISTEN}.lock"
|
1024
|
+
begin
|
1025
|
+
lock = File.open(lock_path, File::WRONLY|File::CREAT|File::EXCL, 0600)
|
1026
|
+
yield
|
1027
|
+
rescue Errno::EEXIST
|
1028
|
+
lock_path = nil
|
1029
|
+
return false
|
1030
|
+
ensure
|
1031
|
+
File.unlink(lock_path) if lock_path
|
1032
|
+
end
|
1033
|
+
ensure
|
1034
|
+
fp.flock(File::LOCK_UN)
|
1035
|
+
end
|
1036
|
+
end
|
1037
|
+
|
1038
|
+
end if do_test
|