unicorn-fotopedia 0.99.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (163) hide show
  1. data/.CHANGELOG.old +25 -0
  2. data/.document +19 -0
  3. data/.gitignore +21 -0
  4. data/.mailmap +26 -0
  5. data/CONTRIBUTORS +32 -0
  6. data/COPYING +339 -0
  7. data/DESIGN +105 -0
  8. data/Documentation/.gitignore +5 -0
  9. data/Documentation/GNUmakefile +30 -0
  10. data/Documentation/unicorn.1.txt +171 -0
  11. data/Documentation/unicorn_rails.1.txt +172 -0
  12. data/FAQ +52 -0
  13. data/GIT-VERSION-GEN +40 -0
  14. data/GNUmakefile +292 -0
  15. data/HACKING +116 -0
  16. data/ISSUES +36 -0
  17. data/KNOWN_ISSUES +50 -0
  18. data/LICENSE +55 -0
  19. data/PHILOSOPHY +145 -0
  20. data/README +149 -0
  21. data/Rakefile +191 -0
  22. data/SIGNALS +109 -0
  23. data/Sandbox +78 -0
  24. data/TODO +5 -0
  25. data/TUNING +70 -0
  26. data/bin/unicorn +126 -0
  27. data/bin/unicorn_rails +203 -0
  28. data/examples/big_app_gc.rb +33 -0
  29. data/examples/echo.ru +27 -0
  30. data/examples/git.ru +13 -0
  31. data/examples/init.sh +58 -0
  32. data/examples/logger_mp_safe.rb +25 -0
  33. data/examples/nginx.conf +139 -0
  34. data/examples/unicorn.conf.rb +78 -0
  35. data/ext/unicorn_http/CFLAGS +13 -0
  36. data/ext/unicorn_http/c_util.h +124 -0
  37. data/ext/unicorn_http/common_field_optimization.h +111 -0
  38. data/ext/unicorn_http/ext_help.h +77 -0
  39. data/ext/unicorn_http/extconf.rb +14 -0
  40. data/ext/unicorn_http/global_variables.h +89 -0
  41. data/ext/unicorn_http/unicorn_http.rl +714 -0
  42. data/ext/unicorn_http/unicorn_http_common.rl +75 -0
  43. data/lib/unicorn.rb +847 -0
  44. data/lib/unicorn/app/exec_cgi.rb +150 -0
  45. data/lib/unicorn/app/inetd.rb +109 -0
  46. data/lib/unicorn/app/old_rails.rb +33 -0
  47. data/lib/unicorn/app/old_rails/static.rb +58 -0
  48. data/lib/unicorn/cgi_wrapper.rb +145 -0
  49. data/lib/unicorn/configurator.rb +421 -0
  50. data/lib/unicorn/const.rb +34 -0
  51. data/lib/unicorn/http_request.rb +72 -0
  52. data/lib/unicorn/http_response.rb +75 -0
  53. data/lib/unicorn/launcher.rb +65 -0
  54. data/lib/unicorn/oob_gc.rb +58 -0
  55. data/lib/unicorn/socket_helper.rb +152 -0
  56. data/lib/unicorn/tee_input.rb +217 -0
  57. data/lib/unicorn/util.rb +90 -0
  58. data/local.mk.sample +62 -0
  59. data/setup.rb +1586 -0
  60. data/t/.gitignore +2 -0
  61. data/t/GNUmakefile +67 -0
  62. data/t/README +42 -0
  63. data/t/bin/content-md5-put +36 -0
  64. data/t/bin/sha1sum.rb +23 -0
  65. data/t/bin/unused_listen +40 -0
  66. data/t/bin/utee +12 -0
  67. data/t/env.ru +3 -0
  68. data/t/my-tap-lib.sh +200 -0
  69. data/t/t0000-http-basic.sh +50 -0
  70. data/t/t0001-reload-bad-config.sh +52 -0
  71. data/t/t0002-config-conflict.sh +49 -0
  72. data/t/test-lib.sh +100 -0
  73. data/test/aggregate.rb +15 -0
  74. data/test/benchmark/README +50 -0
  75. data/test/benchmark/dd.ru +18 -0
  76. data/test/exec/README +5 -0
  77. data/test/exec/test_exec.rb +1038 -0
  78. data/test/rails/app-1.2.3/.gitignore +2 -0
  79. data/test/rails/app-1.2.3/Rakefile +7 -0
  80. data/test/rails/app-1.2.3/app/controllers/application.rb +6 -0
  81. data/test/rails/app-1.2.3/app/controllers/foo_controller.rb +36 -0
  82. data/test/rails/app-1.2.3/app/helpers/application_helper.rb +4 -0
  83. data/test/rails/app-1.2.3/config/boot.rb +11 -0
  84. data/test/rails/app-1.2.3/config/database.yml +12 -0
  85. data/test/rails/app-1.2.3/config/environment.rb +13 -0
  86. data/test/rails/app-1.2.3/config/environments/development.rb +9 -0
  87. data/test/rails/app-1.2.3/config/environments/production.rb +5 -0
  88. data/test/rails/app-1.2.3/config/routes.rb +6 -0
  89. data/test/rails/app-1.2.3/db/.gitignore +0 -0
  90. data/test/rails/app-1.2.3/public/404.html +1 -0
  91. data/test/rails/app-1.2.3/public/500.html +1 -0
  92. data/test/rails/app-2.0.2/.gitignore +2 -0
  93. data/test/rails/app-2.0.2/Rakefile +7 -0
  94. data/test/rails/app-2.0.2/app/controllers/application.rb +4 -0
  95. data/test/rails/app-2.0.2/app/controllers/foo_controller.rb +36 -0
  96. data/test/rails/app-2.0.2/app/helpers/application_helper.rb +4 -0
  97. data/test/rails/app-2.0.2/config/boot.rb +11 -0
  98. data/test/rails/app-2.0.2/config/database.yml +12 -0
  99. data/test/rails/app-2.0.2/config/environment.rb +17 -0
  100. data/test/rails/app-2.0.2/config/environments/development.rb +8 -0
  101. data/test/rails/app-2.0.2/config/environments/production.rb +5 -0
  102. data/test/rails/app-2.0.2/config/routes.rb +6 -0
  103. data/test/rails/app-2.0.2/db/.gitignore +0 -0
  104. data/test/rails/app-2.0.2/public/404.html +1 -0
  105. data/test/rails/app-2.0.2/public/500.html +1 -0
  106. data/test/rails/app-2.1.2/.gitignore +2 -0
  107. data/test/rails/app-2.1.2/Rakefile +7 -0
  108. data/test/rails/app-2.1.2/app/controllers/application.rb +4 -0
  109. data/test/rails/app-2.1.2/app/controllers/foo_controller.rb +36 -0
  110. data/test/rails/app-2.1.2/app/helpers/application_helper.rb +4 -0
  111. data/test/rails/app-2.1.2/config/boot.rb +111 -0
  112. data/test/rails/app-2.1.2/config/database.yml +12 -0
  113. data/test/rails/app-2.1.2/config/environment.rb +17 -0
  114. data/test/rails/app-2.1.2/config/environments/development.rb +7 -0
  115. data/test/rails/app-2.1.2/config/environments/production.rb +5 -0
  116. data/test/rails/app-2.1.2/config/routes.rb +6 -0
  117. data/test/rails/app-2.1.2/db/.gitignore +0 -0
  118. data/test/rails/app-2.1.2/public/404.html +1 -0
  119. data/test/rails/app-2.1.2/public/500.html +1 -0
  120. data/test/rails/app-2.2.2/.gitignore +2 -0
  121. data/test/rails/app-2.2.2/Rakefile +7 -0
  122. data/test/rails/app-2.2.2/app/controllers/application.rb +4 -0
  123. data/test/rails/app-2.2.2/app/controllers/foo_controller.rb +36 -0
  124. data/test/rails/app-2.2.2/app/helpers/application_helper.rb +4 -0
  125. data/test/rails/app-2.2.2/config/boot.rb +111 -0
  126. data/test/rails/app-2.2.2/config/database.yml +12 -0
  127. data/test/rails/app-2.2.2/config/environment.rb +17 -0
  128. data/test/rails/app-2.2.2/config/environments/development.rb +7 -0
  129. data/test/rails/app-2.2.2/config/environments/production.rb +5 -0
  130. data/test/rails/app-2.2.2/config/routes.rb +6 -0
  131. data/test/rails/app-2.2.2/db/.gitignore +0 -0
  132. data/test/rails/app-2.2.2/public/404.html +1 -0
  133. data/test/rails/app-2.2.2/public/500.html +1 -0
  134. data/test/rails/app-2.3.5/.gitignore +2 -0
  135. data/test/rails/app-2.3.5/Rakefile +7 -0
  136. data/test/rails/app-2.3.5/app/controllers/application_controller.rb +5 -0
  137. data/test/rails/app-2.3.5/app/controllers/foo_controller.rb +36 -0
  138. data/test/rails/app-2.3.5/app/helpers/application_helper.rb +4 -0
  139. data/test/rails/app-2.3.5/config/boot.rb +109 -0
  140. data/test/rails/app-2.3.5/config/database.yml +12 -0
  141. data/test/rails/app-2.3.5/config/environment.rb +17 -0
  142. data/test/rails/app-2.3.5/config/environments/development.rb +7 -0
  143. data/test/rails/app-2.3.5/config/environments/production.rb +6 -0
  144. data/test/rails/app-2.3.5/config/routes.rb +6 -0
  145. data/test/rails/app-2.3.5/db/.gitignore +0 -0
  146. data/test/rails/app-2.3.5/public/404.html +1 -0
  147. data/test/rails/app-2.3.5/public/500.html +1 -0
  148. data/test/rails/app-2.3.5/public/x.txt +1 -0
  149. data/test/rails/test_rails.rb +280 -0
  150. data/test/test_helper.rb +301 -0
  151. data/test/unit/test_configurator.rb +150 -0
  152. data/test/unit/test_http_parser.rb +555 -0
  153. data/test/unit/test_http_parser_ng.rb +443 -0
  154. data/test/unit/test_request.rb +184 -0
  155. data/test/unit/test_response.rb +110 -0
  156. data/test/unit/test_server.rb +291 -0
  157. data/test/unit/test_signals.rb +206 -0
  158. data/test/unit/test_socket_helper.rb +147 -0
  159. data/test/unit/test_tee_input.rb +257 -0
  160. data/test/unit/test_upload.rb +298 -0
  161. data/test/unit/test_util.rb +96 -0
  162. data/unicorn.gemspec +52 -0
  163. 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
@@ -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
+ }
@@ -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 })
@@ -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