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.
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