unicorn-shopify 4.8.2.5.23

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 (150) hide show
  1. checksums.yaml +7 -0
  2. data/.CHANGELOG.old +25 -0
  3. data/.document +28 -0
  4. data/.gitignore +25 -0
  5. data/.mailmap +26 -0
  6. data/.olddoc.yml +15 -0
  7. data/Application_Timeouts +77 -0
  8. data/CONTRIBUTORS +35 -0
  9. data/COPYING +674 -0
  10. data/DESIGN +97 -0
  11. data/Documentation/.gitignore +5 -0
  12. data/Documentation/GNUmakefile +30 -0
  13. data/Documentation/unicorn.1.txt +185 -0
  14. data/Documentation/unicorn_rails.1.txt +175 -0
  15. data/FAQ +61 -0
  16. data/GIT-VERSION-GEN +39 -0
  17. data/GNUmakefile +252 -0
  18. data/HACKING +120 -0
  19. data/ISSUES +100 -0
  20. data/KNOWN_ISSUES +79 -0
  21. data/LICENSE +67 -0
  22. data/Links +59 -0
  23. data/PHILOSOPHY +145 -0
  24. data/README +145 -0
  25. data/Rakefile +16 -0
  26. data/SIGNALS +123 -0
  27. data/Sandbox +103 -0
  28. data/TODO +5 -0
  29. data/TUNING +101 -0
  30. data/archive/.gitignore +3 -0
  31. data/archive/slrnpull.conf +4 -0
  32. data/bin/unicorn +126 -0
  33. data/bin/unicorn_rails +209 -0
  34. data/examples/big_app_gc.rb +2 -0
  35. data/examples/echo.ru +27 -0
  36. data/examples/init.sh +74 -0
  37. data/examples/logger_mp_safe.rb +25 -0
  38. data/examples/logrotate.conf +29 -0
  39. data/examples/nginx.conf +156 -0
  40. data/examples/unicorn.conf.minimal.rb +13 -0
  41. data/examples/unicorn.conf.rb +113 -0
  42. data/ext/unicorn_http/CFLAGS +13 -0
  43. data/ext/unicorn_http/c_util.h +124 -0
  44. data/ext/unicorn_http/common_field_optimization.h +111 -0
  45. data/ext/unicorn_http/ext_help.h +82 -0
  46. data/ext/unicorn_http/extconf.rb +10 -0
  47. data/ext/unicorn_http/global_variables.h +97 -0
  48. data/ext/unicorn_http/httpdate.c +78 -0
  49. data/ext/unicorn_http/unicorn_http.rl +934 -0
  50. data/ext/unicorn_http/unicorn_http_common.rl +76 -0
  51. data/lib/unicorn.rb +112 -0
  52. data/lib/unicorn/app/old_rails.rb +35 -0
  53. data/lib/unicorn/app/old_rails/static.rb +59 -0
  54. data/lib/unicorn/cgi_wrapper.rb +147 -0
  55. data/lib/unicorn/configurator.rb +686 -0
  56. data/lib/unicorn/const.rb +21 -0
  57. data/lib/unicorn/http_request.rb +125 -0
  58. data/lib/unicorn/http_response.rb +73 -0
  59. data/lib/unicorn/http_server.rb +816 -0
  60. data/lib/unicorn/launcher.rb +62 -0
  61. data/lib/unicorn/oob_gc.rb +81 -0
  62. data/lib/unicorn/preread_input.rb +33 -0
  63. data/lib/unicorn/socket_helper.rb +197 -0
  64. data/lib/unicorn/stream_input.rb +146 -0
  65. data/lib/unicorn/tee_input.rb +133 -0
  66. data/lib/unicorn/tmpio.rb +27 -0
  67. data/lib/unicorn/util.rb +90 -0
  68. data/lib/unicorn/worker.rb +140 -0
  69. data/setup.rb +1586 -0
  70. data/t/.gitignore +4 -0
  71. data/t/GNUmakefile +74 -0
  72. data/t/README +42 -0
  73. data/t/before_murder.ru +7 -0
  74. data/t/bin/content-md5-put +36 -0
  75. data/t/bin/sha1sum.rb +17 -0
  76. data/t/bin/unused_listen +40 -0
  77. data/t/broken-app.ru +12 -0
  78. data/t/detach.ru +11 -0
  79. data/t/env.ru +3 -0
  80. data/t/fails-rack-lint.ru +5 -0
  81. data/t/heartbeat-timeout.ru +12 -0
  82. data/t/hijack.ru +42 -0
  83. data/t/listener_names.ru +4 -0
  84. data/t/my-tap-lib.sh +201 -0
  85. data/t/oob_gc.ru +20 -0
  86. data/t/oob_gc_path.ru +20 -0
  87. data/t/pid.ru +3 -0
  88. data/t/preread_input.ru +17 -0
  89. data/t/rack-input-tests.ru +21 -0
  90. data/t/t0000-http-basic.sh +50 -0
  91. data/t/t0001-reload-bad-config.sh +53 -0
  92. data/t/t0002-config-conflict.sh +49 -0
  93. data/t/t0002-parser-error.sh +94 -0
  94. data/t/t0003-working_directory.sh +51 -0
  95. data/t/t0004-heartbeat-timeout.sh +69 -0
  96. data/t/t0004-working_directory_broken.sh +24 -0
  97. data/t/t0005-working_directory_app.rb.sh +40 -0
  98. data/t/t0006-reopen-logs.sh +83 -0
  99. data/t/t0006.ru +13 -0
  100. data/t/t0007-working_directory_no_embed_cli.sh +44 -0
  101. data/t/t0008-back_out_of_upgrade.sh +110 -0
  102. data/t/t0009-broken-app.sh +56 -0
  103. data/t/t0009-winch_ttin.sh +59 -0
  104. data/t/t0010-reap-logging.sh +55 -0
  105. data/t/t0011-active-unix-socket.sh +79 -0
  106. data/t/t0012-reload-empty-config.sh +85 -0
  107. data/t/t0013-rewindable-input-false.sh +24 -0
  108. data/t/t0013.ru +12 -0
  109. data/t/t0014-rewindable-input-true.sh +24 -0
  110. data/t/t0014.ru +12 -0
  111. data/t/t0015-configurator-internals.sh +25 -0
  112. data/t/t0018-write-on-close.sh +23 -0
  113. data/t/t0019-max_header_len.sh +49 -0
  114. data/t/t0020-at_exit-handler.sh +49 -0
  115. data/t/t0021-process_detach.sh +29 -0
  116. data/t/t0022-listener_names-preload_app.sh +32 -0
  117. data/t/t0023-before-murder.sh +40 -0
  118. data/t/t0024-before-murder_once.sh +52 -0
  119. data/t/t0100-rack-input-tests.sh +124 -0
  120. data/t/t0116-client_body_buffer_size.sh +80 -0
  121. data/t/t0116.ru +16 -0
  122. data/t/t0200-rack-hijack.sh +27 -0
  123. data/t/t0300-no-default-middleware.sh +20 -0
  124. data/t/t9000-preread-input.sh +48 -0
  125. data/t/t9001-oob_gc.sh +47 -0
  126. data/t/t9002-oob_gc-path.sh +75 -0
  127. data/t/test-lib.sh +128 -0
  128. data/t/write-on-close.ru +11 -0
  129. data/test/aggregate.rb +15 -0
  130. data/test/benchmark/README +50 -0
  131. data/test/benchmark/dd.ru +18 -0
  132. data/test/benchmark/stack.ru +8 -0
  133. data/test/exec/README +5 -0
  134. data/test/exec/test_exec.rb +1047 -0
  135. data/test/test_helper.rb +297 -0
  136. data/test/unit/test_configurator.rb +175 -0
  137. data/test/unit/test_droplet.rb +28 -0
  138. data/test/unit/test_http_parser.rb +854 -0
  139. data/test/unit/test_http_parser_ng.rb +622 -0
  140. data/test/unit/test_request.rb +182 -0
  141. data/test/unit/test_response.rb +93 -0
  142. data/test/unit/test_server.rb +268 -0
  143. data/test/unit/test_signals.rb +188 -0
  144. data/test/unit/test_socket_helper.rb +197 -0
  145. data/test/unit/test_stream_input.rb +203 -0
  146. data/test/unit/test_tee_input.rb +304 -0
  147. data/test/unit/test_upload.rb +306 -0
  148. data/test/unit/test_util.rb +105 -0
  149. data/unicorn.gemspec +41 -0
  150. metadata +311 -0
@@ -0,0 +1,75 @@
1
+ #!/bin/sh
2
+ . ./test-lib.sh
3
+ t_plan 12 "OobGC test with limited path"
4
+
5
+ t_begin "setup and start" && {
6
+ unicorn_setup
7
+ unicorn -D -c $unicorn_config oob_gc_path.ru
8
+ unicorn_wait_start
9
+ }
10
+
11
+ t_begin "test default is noop" && {
12
+ test xfalse = x$(curl -vsSf http://$listen/ 2>> $tmp)
13
+ test xfalse = x$(curl -vsSf http://$listen/ 2>> $tmp)
14
+ test xfalse = x$(curl -vsSf http://$listen/ 2>> $tmp)
15
+ test xfalse = x$(curl -vsSf http://$listen/ 2>> $tmp)
16
+ test xfalse = x$(curl -vsSf http://$listen/ 2>> $tmp)
17
+ test xfalse = x$(curl -vsSf http://$listen/ 2>> $tmp)
18
+ test xfalse = x$(curl -vsSf http://$listen/ 2>> $tmp)
19
+ test xfalse = x$(curl -vsSf http://$listen/ 2>> $tmp)
20
+ test xfalse = x$(curl -vsSf http://$listen/ 2>> $tmp)
21
+ }
22
+
23
+ t_begin "4 bad requests to bump counter" && {
24
+ test xfalse = x$(curl -vsSf http://$listen/BAD 2>> $tmp)
25
+ test xfalse = x$(curl -vsSf http://$listen/BAD 2>> $tmp)
26
+ test xfalse = x$(curl -vsSf http://$listen/BAD 2>> $tmp)
27
+ test xfalse = x$(curl -vsSf http://$listen/BAD 2>> $tmp)
28
+ }
29
+
30
+ t_begin "GC-starting request returns immediately" && {
31
+ test xfalse = x$(curl -vsSf http://$listen/BAD 2>> $tmp)
32
+ }
33
+
34
+ t_begin "GC was started after 5 requests" && {
35
+ test xtrue = x$(curl -vsSf http://$listen/ 2>> $tmp)
36
+ }
37
+
38
+ t_begin "reset GC" && {
39
+ test xfalse = x$(curl -vsSf -X POST http://$listen/gc_reset 2>> $tmp)
40
+ }
41
+
42
+ t_begin "test default is noop" && {
43
+ test xfalse = x$(curl -vsSf http://$listen/ 2>> $tmp)
44
+ test xfalse = x$(curl -vsSf http://$listen/ 2>> $tmp)
45
+ test xfalse = x$(curl -vsSf http://$listen/ 2>> $tmp)
46
+ test xfalse = x$(curl -vsSf http://$listen/ 2>> $tmp)
47
+ test xfalse = x$(curl -vsSf http://$listen/ 2>> $tmp)
48
+ test xfalse = x$(curl -vsSf http://$listen/ 2>> $tmp)
49
+ test xfalse = x$(curl -vsSf http://$listen/ 2>> $tmp)
50
+ test xfalse = x$(curl -vsSf http://$listen/ 2>> $tmp)
51
+ test xfalse = x$(curl -vsSf http://$listen/ 2>> $tmp)
52
+ }
53
+
54
+ t_begin "4 bad requests to bump counter" && {
55
+ test xfalse = x$(curl -vsSf http://$listen/BAD 2>> $tmp)
56
+ test xfalse = x$(curl -vsSf http://$listen/BAD 2>> $tmp)
57
+ test xfalse = x$(curl -vsSf http://$listen/BAD 2>> $tmp)
58
+ test xfalse = x$(curl -vsSf http://$listen/BAD 2>> $tmp)
59
+ }
60
+
61
+ t_begin "GC-starting request returns immediately" && {
62
+ test xfalse = x$(curl -vsSf http://$listen/BAD 2>> $tmp)
63
+ }
64
+
65
+ t_begin "GC was started after 5 requests" && {
66
+ test xtrue = x$(curl -vsSf http://$listen/ 2>> $tmp)
67
+ }
68
+
69
+ t_begin "killing succeeds" && {
70
+ kill -QUIT $unicorn_pid
71
+ }
72
+
73
+ t_begin "check_stderr" && check_stderr
74
+
75
+ t_done
@@ -0,0 +1,128 @@
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
+
8
+ # sometimes we rely on http_proxy to avoid wasting bandwidth with Isolate
9
+ # and multiple Ruby versions
10
+ NO_PROXY=${UNICORN_TEST_ADDR-127.0.0.1}
11
+ export NO_PROXY
12
+
13
+ set -e
14
+ RUBY="${RUBY-ruby}"
15
+ RUBY_VERSION=${RUBY_VERSION-$($RUBY -e 'puts RUBY_VERSION')}
16
+ RUBY_ENGINE=${RUBY_ENGINE-$($RUBY -e 'puts((RUBY_ENGINE rescue "ruby"))')}
17
+ t_pfx=$PWD/trash/$T-$RUBY_ENGINE-$RUBY_VERSION
18
+ set -u
19
+
20
+ PATH=$PWD/bin:$PATH
21
+ export PATH
22
+
23
+ test -x $PWD/bin/unused_listen || die "must be run in 't' directory"
24
+
25
+ wait_for_pid () {
26
+ path="$1"
27
+ nr=30
28
+ while ! test -s "$path" && test $nr -gt 0
29
+ do
30
+ nr=$(($nr - 1))
31
+ sleep 1
32
+ done
33
+ }
34
+
35
+ # "unix_time" is not in POSIX, but in GNU, and FreeBSD 9.0 (possibly earlier)
36
+ unix_time () {
37
+ $RUBY -e 'puts Time.now.to_i'
38
+ }
39
+
40
+ # "wc -l" outputs leading whitespace on *BSDs, filter it out for portability
41
+ count_lines () {
42
+ wc -l | tr -d '[:space:]'
43
+ }
44
+
45
+ # "wc -c" outputs leading whitespace on *BSDs, filter it out for portability
46
+ count_bytes () {
47
+ wc -c | tr -d '[:space:]'
48
+ }
49
+
50
+ # given a list of variable names, create temporary files and assign
51
+ # the pathnames to those variables
52
+ rtmpfiles () {
53
+ for id in "$@"
54
+ do
55
+ name=$id
56
+
57
+ case $name in
58
+ *fifo)
59
+ _tmp=$t_pfx.$id
60
+ eval "$id=$_tmp"
61
+ rm -f $_tmp
62
+ mkfifo $_tmp
63
+ T_RM_LIST="$T_RM_LIST $_tmp"
64
+ ;;
65
+ *socket)
66
+ _tmp="$(mktemp -t $id.$$.XXXXXXXX)"
67
+ if test $(printf "$_tmp" |count_bytes) -gt 108
68
+ then
69
+ echo >&2 "$_tmp too long, tests may fail"
70
+ echo >&2 "Try to set TMPDIR to a shorter path"
71
+ fi
72
+ eval "$id=$_tmp"
73
+ rm -f $_tmp
74
+ T_RM_LIST="$T_RM_LIST $_tmp"
75
+ ;;
76
+ *)
77
+ _tmp=$t_pfx.$id
78
+ eval "$id=$_tmp"
79
+ > $_tmp
80
+ T_OK_RM_LIST="$T_OK_RM_LIST $_tmp"
81
+ ;;
82
+ esac
83
+ done
84
+ }
85
+
86
+ dbgcat () {
87
+ id=$1
88
+ eval '_file=$'$id
89
+ echo "==> $id <=="
90
+ sed -e "s/^/$id:/" < $_file
91
+ }
92
+
93
+ check_stderr () {
94
+ set +u
95
+ _r_err=${1-${r_err}}
96
+ set -u
97
+ if grep -v $T $_r_err | grep -i Error
98
+ then
99
+ die "Errors found in $_r_err"
100
+ elif grep SIGKILL $_r_err
101
+ then
102
+ die "SIGKILL found in $_r_err"
103
+ fi
104
+ }
105
+
106
+ # unicorn_setup
107
+ unicorn_setup () {
108
+ eval $(unused_listen)
109
+ port=$(expr $listen : '[^:]*:\([0-9]\+\)')
110
+ host=$(expr $listen : '\([^:]*\):[0-9]\+')
111
+
112
+ rtmpfiles unicorn_config pid r_err r_out fifo tmp ok
113
+ cat > $unicorn_config <<EOF
114
+ listen "$listen"
115
+ pid "$pid"
116
+ stderr_path "$r_err"
117
+ stdout_path "$r_out"
118
+ EOF
119
+ }
120
+
121
+ unicorn_wait_start () {
122
+ # no need to play tricks with FIFOs since we got "ready_pipe" now
123
+ unicorn_pid=$(cat $pid)
124
+ }
125
+
126
+ rsha1 () {
127
+ sha1sum.rb
128
+ }
@@ -0,0 +1,11 @@
1
+ class WriteOnClose
2
+ def each(&block)
3
+ @callback = block
4
+ end
5
+
6
+ def close
7
+ @callback.call "7\r\nGoodbye\r\n0\r\n\r\n"
8
+ end
9
+ end
10
+ use Rack::ContentType, "text/plain"
11
+ run(lambda { |_| [ 200, [%w(Transfer-Encoding chunked)], WriteOnClose.new ] })
@@ -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,8 @@
1
+ run(lambda { |env|
2
+ body = "#{caller.size}\n"
3
+ h = {
4
+ "Content-Length" => body.size.to_s,
5
+ "Content-Type" => "text/plain",
6
+ }
7
+ [ 200, h, [ body ] ]
8
+ })
@@ -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,1047 @@
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
+ WORKING_DIRECTORY_CHECK_RU = <<-EOS
58
+ use Rack::ContentLength
59
+ run lambda { |env|
60
+ pwd = ENV['PWD']
61
+ a = ::File.stat(pwd)
62
+ b = ::File.stat(Dir.pwd)
63
+ if (a.ino == b.ino && a.dev == b.dev)
64
+ [ 200, { 'Content-Type' => 'text/plain' }, [ pwd ] ]
65
+ else
66
+ [ 404, { 'Content-Type' => 'text/plain' }, [] ]
67
+ end
68
+ }
69
+ EOS
70
+
71
+ def setup
72
+ @pwd = Dir.pwd
73
+ @tmpfile = Tempfile.new('unicorn_exec_test')
74
+ @tmpdir = @tmpfile.path
75
+ @tmpfile.close!
76
+ Dir.mkdir(@tmpdir)
77
+ Dir.chdir(@tmpdir)
78
+ @addr = ENV['UNICORN_TEST_ADDR'] || '127.0.0.1'
79
+ @port = unused_port(@addr)
80
+ @sockets = []
81
+ @start_pid = $$
82
+ end
83
+
84
+ def teardown
85
+ return if @start_pid != $$
86
+ Dir.chdir(@pwd)
87
+ FileUtils.rmtree(@tmpdir)
88
+ @sockets.each { |path| File.unlink(path) rescue nil }
89
+ loop do
90
+ Process.kill('-QUIT', 0)
91
+ begin
92
+ Process.waitpid(-1, Process::WNOHANG) or break
93
+ rescue Errno::ECHILD
94
+ break
95
+ end
96
+ end
97
+ end
98
+
99
+ def test_working_directory_rel_path_config_file
100
+ other = Tempfile.new('unicorn.wd')
101
+ File.unlink(other.path)
102
+ Dir.mkdir(other.path)
103
+ File.open("config.ru", "wb") do |fp|
104
+ fp.syswrite WORKING_DIRECTORY_CHECK_RU
105
+ end
106
+ FileUtils.cp("config.ru", other.path + "/config.ru")
107
+ Dir.chdir(@tmpdir)
108
+
109
+ tmp = File.open('unicorn.config', 'wb')
110
+ tmp.syswrite <<EOF
111
+ working_directory '#@tmpdir'
112
+ listen '#@addr:#@port'
113
+ EOF
114
+ pid = xfork { redirect_test_io { exec($unicorn_bin, "-c#{tmp.path}") } }
115
+ wait_workers_ready("test_stderr.#{pid}.log", 1)
116
+ results = hit(["http://#@addr:#@port/"])
117
+ assert_equal @tmpdir, results.first
118
+ File.truncate("test_stderr.#{pid}.log", 0)
119
+
120
+ tmp.sysseek(0)
121
+ tmp.truncate(0)
122
+ tmp.syswrite <<EOF
123
+ working_directory '#{other.path}'
124
+ listen '#@addr:#@port'
125
+ EOF
126
+
127
+ Process.kill(:HUP, pid)
128
+ lines = []
129
+ re = /config_file=(.+) would not be accessible in working_directory=(.+)/
130
+ until lines.grep(re)
131
+ sleep 0.1
132
+ lines = File.readlines("test_stderr.#{pid}.log")
133
+ end
134
+
135
+ File.truncate("test_stderr.#{pid}.log", 0)
136
+ FileUtils.cp('unicorn.config', other.path + "/unicorn.config")
137
+ Process.kill(:HUP, pid)
138
+ wait_workers_ready("test_stderr.#{pid}.log", 1)
139
+ results = hit(["http://#@addr:#@port/"])
140
+ assert_equal other.path, results.first
141
+
142
+ Process.kill(:QUIT, pid)
143
+ ensure
144
+ FileUtils.rmtree(other.path)
145
+ end
146
+
147
+ def test_working_directory
148
+ other = Tempfile.new('unicorn.wd')
149
+ File.unlink(other.path)
150
+ Dir.mkdir(other.path)
151
+ File.open("config.ru", "wb") do |fp|
152
+ fp.syswrite WORKING_DIRECTORY_CHECK_RU
153
+ end
154
+ FileUtils.cp("config.ru", other.path + "/config.ru")
155
+ tmp = Tempfile.new('unicorn.config')
156
+ tmp.syswrite <<EOF
157
+ working_directory '#@tmpdir'
158
+ listen '#@addr:#@port'
159
+ EOF
160
+ pid = xfork { redirect_test_io { exec($unicorn_bin, "-c#{tmp.path}") } }
161
+ wait_workers_ready("test_stderr.#{pid}.log", 1)
162
+ results = hit(["http://#@addr:#@port/"])
163
+ assert_equal @tmpdir, results.first
164
+ File.truncate("test_stderr.#{pid}.log", 0)
165
+
166
+ tmp.sysseek(0)
167
+ tmp.truncate(0)
168
+ tmp.syswrite <<EOF
169
+ working_directory '#{other.path}'
170
+ listen '#@addr:#@port'
171
+ EOF
172
+
173
+ Process.kill(:HUP, pid)
174
+ wait_workers_ready("test_stderr.#{pid}.log", 1)
175
+ results = hit(["http://#@addr:#@port/"])
176
+ assert_equal other.path, results.first
177
+
178
+ Process.kill(:QUIT, pid)
179
+ ensure
180
+ FileUtils.rmtree(other.path)
181
+ end
182
+
183
+ def test_working_directory_controls_relative_paths
184
+ other = Tempfile.new('unicorn.wd')
185
+ File.unlink(other.path)
186
+ Dir.mkdir(other.path)
187
+ File.open("config.ru", "wb") do |fp|
188
+ fp.syswrite WORKING_DIRECTORY_CHECK_RU
189
+ end
190
+ FileUtils.cp("config.ru", other.path + "/config.ru")
191
+ system('mkfifo', "#{other.path}/fifo")
192
+ tmp = Tempfile.new('unicorn.config')
193
+ tmp.syswrite <<EOF
194
+ pid "pid_file_here"
195
+ stderr_path "stderr_log_here"
196
+ stdout_path "stdout_log_here"
197
+ working_directory '#{other.path}'
198
+ listen '#@addr:#@port'
199
+ after_fork do |server, worker|
200
+ File.open("fifo", "wb").close
201
+ end
202
+ EOF
203
+ pid = xfork { redirect_test_io { exec($unicorn_bin, "-c#{tmp.path}") } }
204
+ File.open("#{other.path}/fifo", "rb").close
205
+
206
+ assert ! File.exist?("stderr_log_here")
207
+ assert ! File.exist?("stdout_log_here")
208
+ assert ! File.exist?("pid_file_here")
209
+
210
+ assert ! File.exist?("#@tmpdir/stderr_log_here")
211
+ assert ! File.exist?("#@tmpdir/stdout_log_here")
212
+ assert ! File.exist?("#@tmpdir/pid_file_here")
213
+
214
+ assert File.exist?("#{other.path}/pid_file_here")
215
+ assert_equal "#{pid}\n", File.read("#{other.path}/pid_file_here")
216
+ assert File.exist?("#{other.path}/stderr_log_here")
217
+ assert File.exist?("#{other.path}/stdout_log_here")
218
+ wait_master_ready("#{other.path}/stderr_log_here")
219
+
220
+ Process.kill(:QUIT, pid)
221
+ ensure
222
+ FileUtils.rmtree(other.path)
223
+ end
224
+
225
+
226
+ def test_exit_signals
227
+ %w(INT TERM QUIT).each do |sig|
228
+ File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
229
+ pid = xfork { redirect_test_io { exec($unicorn_bin, "-l#@addr:#@port") } }
230
+ wait_master_ready("test_stderr.#{pid}.log")
231
+ wait_workers_ready("test_stderr.#{pid}.log", 1)
232
+
233
+ Process.kill(sig, pid)
234
+ pid, status = Process.waitpid2(pid)
235
+
236
+ reaped = File.readlines("test_stderr.#{pid}.log").grep(/reaped/)
237
+ assert_equal 1, reaped.size
238
+ assert status.exited?
239
+ end
240
+ end
241
+
242
+ def test_basic
243
+ File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
244
+ pid = fork do
245
+ redirect_test_io { exec($unicorn_bin, "-l", "#{@addr}:#{@port}") }
246
+ end
247
+ results = retry_hit(["http://#{@addr}:#{@port}/"])
248
+ assert_equal String, results[0].class
249
+ assert_shutdown(pid)
250
+ end
251
+
252
+ def test_rack_env_unset
253
+ File.open("config.ru", "wb") { |fp| fp.syswrite(SHOW_RACK_ENV) }
254
+ pid = fork { redirect_test_io { exec($unicorn_bin, "-l#@addr:#@port") } }
255
+ results = retry_hit(["http://#{@addr}:#{@port}/"])
256
+ assert_equal "development", results.first
257
+ assert_shutdown(pid)
258
+ end
259
+
260
+ def test_rack_env_cli_set
261
+ File.open("config.ru", "wb") { |fp| fp.syswrite(SHOW_RACK_ENV) }
262
+ pid = fork {
263
+ redirect_test_io { exec($unicorn_bin, "-l#@addr:#@port", "-Easdf") }
264
+ }
265
+ results = retry_hit(["http://#{@addr}:#{@port}/"])
266
+ assert_equal "asdf", results.first
267
+ assert_shutdown(pid)
268
+ end
269
+
270
+ def test_rack_env_ENV_set
271
+ File.open("config.ru", "wb") { |fp| fp.syswrite(SHOW_RACK_ENV) }
272
+ pid = fork {
273
+ ENV["RACK_ENV"] = "foobar"
274
+ redirect_test_io { exec($unicorn_bin, "-l#@addr:#@port") }
275
+ }
276
+ results = retry_hit(["http://#{@addr}:#{@port}/"])
277
+ assert_equal "foobar", results.first
278
+ assert_shutdown(pid)
279
+ end
280
+
281
+ def test_rack_env_cli_override_ENV
282
+ File.open("config.ru", "wb") { |fp| fp.syswrite(SHOW_RACK_ENV) }
283
+ pid = fork {
284
+ ENV["RACK_ENV"] = "foobar"
285
+ redirect_test_io { exec($unicorn_bin, "-l#@addr:#@port", "-Easdf") }
286
+ }
287
+ results = retry_hit(["http://#{@addr}:#{@port}/"])
288
+ assert_equal "asdf", results.first
289
+ assert_shutdown(pid)
290
+ end
291
+
292
+ def test_ttin_ttou
293
+ File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
294
+ pid = fork { redirect_test_io { exec($unicorn_bin, "-l#@addr:#@port") } }
295
+ log = "test_stderr.#{pid}.log"
296
+ wait_master_ready(log)
297
+ [ 2, 3].each { |i|
298
+ Process.kill(:TTIN, pid)
299
+ wait_workers_ready(log, i)
300
+ }
301
+ File.truncate(log, 0)
302
+ reaped = nil
303
+ [ 2, 1, 0].each { |i|
304
+ Process.kill(:TTOU, pid)
305
+ DEFAULT_TRIES.times {
306
+ sleep DEFAULT_RES
307
+ reaped = File.readlines(log).grep(/reaped.*\s*worker=#{i}$/)
308
+ break if reaped.size == 1
309
+ }
310
+ assert_equal 1, reaped.size
311
+ }
312
+ end
313
+
314
+ def test_help
315
+ redirect_test_io do
316
+ assert(system($unicorn_bin, "-h"), "help text returns true")
317
+ end
318
+ assert_equal 0, File.stat("test_stderr.#$$.log").size
319
+ assert_not_equal 0, File.stat("test_stdout.#$$.log").size
320
+ lines = File.readlines("test_stdout.#$$.log")
321
+
322
+ # Be considerate of the on-call technician working from their
323
+ # mobile phone or netbook on a slow connection :)
324
+ assert lines.size <= 24, "help height fits in an ANSI terminal window"
325
+ lines.each do |line|
326
+ line.chomp!
327
+ assert line.size <= 80, "help width fits in an ANSI terminal window"
328
+ end
329
+ end
330
+
331
+ def test_broken_reexec_config
332
+ File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
333
+ pid_file = "#{@tmpdir}/test.pid"
334
+ old_file = "#{pid_file}.oldbin"
335
+ ucfg = Tempfile.new('unicorn_test_config')
336
+ ucfg.syswrite("listen %(#@addr:#@port)\n")
337
+ ucfg.syswrite("pid %(#{pid_file})\n")
338
+ ucfg.syswrite("logger Logger.new(%(#{@tmpdir}/log))\n")
339
+ pid = xfork do
340
+ redirect_test_io do
341
+ exec($unicorn_bin, "-D", "-l#{@addr}:#{@port}", "-c#{ucfg.path}")
342
+ end
343
+ end
344
+ results = retry_hit(["http://#{@addr}:#{@port}/"])
345
+ assert_equal String, results[0].class
346
+
347
+ wait_for_file(pid_file)
348
+ Process.waitpid(pid)
349
+ Process.kill(:USR2, File.read(pid_file).to_i)
350
+ wait_for_file(old_file)
351
+ wait_for_file(pid_file)
352
+ old_pid = File.read(old_file).to_i
353
+ Process.kill(:QUIT, old_pid)
354
+ wait_for_death(old_pid)
355
+
356
+ ucfg.syswrite("timeout %(#{pid_file})\n") # introduce a bug
357
+ current_pid = File.read(pid_file).to_i
358
+ Process.kill(:USR2, current_pid)
359
+
360
+ # wait for pid_file to restore itself
361
+ tries = DEFAULT_TRIES
362
+ begin
363
+ while current_pid != File.read(pid_file).to_i
364
+ sleep(DEFAULT_RES) and (tries -= 1) > 0
365
+ end
366
+ rescue Errno::ENOENT
367
+ (sleep(DEFAULT_RES) and (tries -= 1) > 0) and retry
368
+ end
369
+ assert_equal current_pid, File.read(pid_file).to_i
370
+
371
+ tries = DEFAULT_TRIES
372
+ while File.exist?(old_file)
373
+ (sleep(DEFAULT_RES) and (tries -= 1) > 0) or break
374
+ end
375
+ assert ! File.exist?(old_file), "oldbin=#{old_file} gone"
376
+ port2 = unused_port(@addr)
377
+
378
+ # fix the bug
379
+ ucfg.sysseek(0)
380
+ ucfg.truncate(0)
381
+ ucfg.syswrite("listen %(#@addr:#@port)\n")
382
+ ucfg.syswrite("listen %(#@addr:#{port2})\n")
383
+ ucfg.syswrite("pid %(#{pid_file})\n")
384
+ Process.kill(:USR2, current_pid)
385
+
386
+ wait_for_file(old_file)
387
+ wait_for_file(pid_file)
388
+ new_pid = File.read(pid_file).to_i
389
+ assert_not_equal current_pid, new_pid
390
+ assert_equal current_pid, File.read(old_file).to_i
391
+ results = retry_hit(["http://#{@addr}:#{@port}/",
392
+ "http://#{@addr}:#{port2}/"])
393
+ assert_equal String, results[0].class
394
+ assert_equal String, results[1].class
395
+
396
+ Process.kill(:QUIT, current_pid)
397
+ Process.kill(:QUIT, new_pid)
398
+ end
399
+
400
+ def test_broken_reexec_ru
401
+ File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
402
+ pid_file = "#{@tmpdir}/test.pid"
403
+ old_file = "#{pid_file}.oldbin"
404
+ ucfg = Tempfile.new('unicorn_test_config')
405
+ ucfg.syswrite("pid %(#{pid_file})\n")
406
+ ucfg.syswrite("logger Logger.new(%(#{@tmpdir}/log))\n")
407
+ pid = xfork do
408
+ redirect_test_io do
409
+ exec($unicorn_bin, "-D", "-l#{@addr}:#{@port}", "-c#{ucfg.path}")
410
+ end
411
+ end
412
+ results = retry_hit(["http://#{@addr}:#{@port}/"])
413
+ assert_equal String, results[0].class
414
+
415
+ wait_for_file(pid_file)
416
+ Process.waitpid(pid)
417
+ Process.kill(:USR2, File.read(pid_file).to_i)
418
+ wait_for_file(old_file)
419
+ wait_for_file(pid_file)
420
+ old_pid = File.read(old_file).to_i
421
+ Process.kill(:QUIT, old_pid)
422
+ wait_for_death(old_pid)
423
+
424
+ File.unlink("config.ru") # break reloading
425
+ current_pid = File.read(pid_file).to_i
426
+ Process.kill(:USR2, current_pid)
427
+
428
+ # wait for pid_file to restore itself
429
+ tries = DEFAULT_TRIES
430
+ begin
431
+ while current_pid != File.read(pid_file).to_i
432
+ sleep(DEFAULT_RES) and (tries -= 1) > 0
433
+ end
434
+ rescue Errno::ENOENT
435
+ (sleep(DEFAULT_RES) and (tries -= 1) > 0) and retry
436
+ end
437
+
438
+ tries = DEFAULT_TRIES
439
+ while File.exist?(old_file)
440
+ (sleep(DEFAULT_RES) and (tries -= 1) > 0) or break
441
+ end
442
+ assert ! File.exist?(old_file), "oldbin=#{old_file} gone"
443
+ assert_equal current_pid, File.read(pid_file).to_i
444
+
445
+ # fix the bug
446
+ File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
447
+ Process.kill(:USR2, current_pid)
448
+ wait_for_file(old_file)
449
+ wait_for_file(pid_file)
450
+ new_pid = File.read(pid_file).to_i
451
+ assert_not_equal current_pid, new_pid
452
+ assert_equal current_pid, File.read(old_file).to_i
453
+ results = retry_hit(["http://#{@addr}:#{@port}/"])
454
+ assert_equal String, results[0].class
455
+
456
+ Process.kill(:QUIT, current_pid)
457
+ Process.kill(:QUIT, new_pid)
458
+ end
459
+
460
+ def test_unicorn_config_listener_swap
461
+ port_cli = unused_port
462
+ File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
463
+ ucfg = Tempfile.new('unicorn_test_config')
464
+ ucfg.syswrite("listen '#@addr:#@port'\n")
465
+ pid = xfork do
466
+ redirect_test_io do
467
+ exec($unicorn_bin, "-c#{ucfg.path}", "-l#@addr:#{port_cli}")
468
+ end
469
+ end
470
+ results = retry_hit(["http://#@addr:#{port_cli}/"])
471
+ assert_equal String, results[0].class
472
+ results = retry_hit(["http://#@addr:#@port/"])
473
+ assert_equal String, results[0].class
474
+
475
+ port2 = unused_port(@addr)
476
+ ucfg.sysseek(0)
477
+ ucfg.truncate(0)
478
+ ucfg.syswrite("listen '#@addr:#{port2}'\n")
479
+ Process.kill(:HUP, pid)
480
+
481
+ results = retry_hit(["http://#@addr:#{port2}/"])
482
+ assert_equal String, results[0].class
483
+ results = retry_hit(["http://#@addr:#{port_cli}/"])
484
+ assert_equal String, results[0].class
485
+ reuse = TCPServer.new(@addr, @port)
486
+ reuse.close
487
+ assert_shutdown(pid)
488
+ end
489
+
490
+ def test_unicorn_config_listen_with_options
491
+ File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
492
+ ucfg = Tempfile.new('unicorn_test_config')
493
+ ucfg.syswrite("listen '#{@addr}:#{@port}', :backlog => 512,\n")
494
+ ucfg.syswrite(" :rcvbuf => 4096,\n")
495
+ ucfg.syswrite(" :sndbuf => 4096\n")
496
+ pid = xfork do
497
+ redirect_test_io { exec($unicorn_bin, "-c#{ucfg.path}") }
498
+ end
499
+ results = retry_hit(["http://#{@addr}:#{@port}/"])
500
+ assert_equal String, results[0].class
501
+ assert_shutdown(pid)
502
+ end
503
+
504
+ def test_unicorn_config_per_worker_listen
505
+ port2 = unused_port
506
+ pid_spit = 'use Rack::ContentLength;' \
507
+ 'run proc { |e| [ 200, {"Content-Type"=>"text/plain"}, ["#$$\\n"] ] }'
508
+ File.open("config.ru", "wb") { |fp| fp.syswrite(pid_spit) }
509
+ tmp = Tempfile.new('test.socket')
510
+ File.unlink(tmp.path)
511
+ ucfg = Tempfile.new('unicorn_test_config')
512
+ ucfg.syswrite("listen '#@addr:#@port'\n")
513
+ ucfg.syswrite("after_fork { |s,w|\n")
514
+ ucfg.syswrite(" s.listen('#{tmp.path}', :backlog => 5, :sndbuf => 8192)\n")
515
+ ucfg.syswrite(" s.listen('#@addr:#{port2}', :rcvbuf => 8192)\n")
516
+ ucfg.syswrite("\n}\n")
517
+ pid = xfork do
518
+ redirect_test_io { exec($unicorn_bin, "-c#{ucfg.path}") }
519
+ end
520
+ results = retry_hit(["http://#{@addr}:#{@port}/"])
521
+ assert_equal String, results[0].class
522
+ worker_pid = results[0].to_i
523
+ assert_not_equal pid, worker_pid
524
+ s = UNIXSocket.new(tmp.path)
525
+ s.syswrite("GET / HTTP/1.0\r\n\r\n")
526
+ results = ''
527
+ loop { results << s.sysread(4096) } rescue nil
528
+ s.close
529
+ assert_equal worker_pid, results.split(/\r\n/).last.to_i
530
+ results = hit(["http://#@addr:#{port2}/"])
531
+ assert_equal String, results[0].class
532
+ assert_equal worker_pid, results[0].to_i
533
+ assert_shutdown(pid)
534
+ end
535
+
536
+ def test_unicorn_config_listen_augments_cli
537
+ port2 = unused_port(@addr)
538
+ File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
539
+ ucfg = Tempfile.new('unicorn_test_config')
540
+ ucfg.syswrite("listen '#{@addr}:#{@port}'\n")
541
+ pid = xfork do
542
+ redirect_test_io do
543
+ exec($unicorn_bin, "-c#{ucfg.path}", "-l#{@addr}:#{port2}")
544
+ end
545
+ end
546
+ uris = [@port, port2].map { |i| "http://#{@addr}:#{i}/" }
547
+ results = retry_hit(uris)
548
+ assert_equal results.size, uris.size
549
+ assert_equal String, results[0].class
550
+ assert_equal String, results[1].class
551
+ assert_shutdown(pid)
552
+ end
553
+
554
+ def test_weird_config_settings
555
+ File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
556
+ ucfg = Tempfile.new('unicorn_test_config')
557
+ ucfg.syswrite(HEAVY_CFG)
558
+ pid = xfork do
559
+ redirect_test_io do
560
+ exec($unicorn_bin, "-c#{ucfg.path}", "-l#{@addr}:#{@port}")
561
+ end
562
+ end
563
+
564
+ results = retry_hit(["http://#{@addr}:#{@port}/"])
565
+ assert_equal String, results[0].class
566
+ wait_master_ready(COMMON_TMP.path)
567
+ wait_workers_ready(COMMON_TMP.path, 4)
568
+ bf = File.readlines(COMMON_TMP.path).grep(/\bbefore_fork: worker=/)
569
+ assert_equal 4, bf.size
570
+ rotate = Tempfile.new('unicorn_rotate')
571
+
572
+ File.rename(COMMON_TMP.path, rotate.path)
573
+ Process.kill(:USR1, pid)
574
+
575
+ wait_for_file(COMMON_TMP.path)
576
+ assert File.exist?(COMMON_TMP.path), "#{COMMON_TMP.path} exists"
577
+ # USR1 should've been passed to all workers
578
+ tries = DEFAULT_TRIES
579
+ log = File.readlines(rotate.path)
580
+ while (tries -= 1) > 0 &&
581
+ log.grep(/reopening logs\.\.\./).size < 5
582
+ sleep DEFAULT_RES
583
+ log = File.readlines(rotate.path)
584
+ end
585
+ assert_equal 5, log.grep(/reopening logs\.\.\./).size
586
+ assert_equal 0, log.grep(/done reopening logs/).size
587
+
588
+ tries = DEFAULT_TRIES
589
+ log = File.readlines(COMMON_TMP.path)
590
+ while (tries -= 1) > 0 && log.grep(/done reopening logs/).size < 5
591
+ sleep DEFAULT_RES
592
+ log = File.readlines(COMMON_TMP.path)
593
+ end
594
+ assert_equal 5, log.grep(/done reopening logs/).size
595
+ assert_equal 0, log.grep(/reopening logs\.\.\./).size
596
+
597
+ Process.kill(:QUIT, pid)
598
+ pid, status = Process.waitpid2(pid)
599
+
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
+
681
+ sock = UNIXSocket.new(sock_path)
682
+ sock.syswrite("GET / HTTP/1.0\r\n\r\n")
683
+ results = sock.sysread(4096)
684
+
685
+ assert_equal String, results.class
686
+ File.unlink(sock_path)
687
+ Process.kill(:HUP, pid)
688
+ wait_for_file(sock_path)
689
+ assert File.socket?(sock_path)
690
+
691
+ sock = UNIXSocket.new(sock_path)
692
+ sock.syswrite("GET / HTTP/1.0\r\n\r\n")
693
+ results = sock.sysread(4096)
694
+
695
+ assert_equal String, results.class
696
+ end
697
+
698
+ def test_unicorn_config_file
699
+ pid_file = "#{@tmpdir}/test.pid"
700
+ sock = Tempfile.new('unicorn_test_sock')
701
+ sock_path = sock.path
702
+ sock.close!
703
+ @sockets << sock_path
704
+
705
+ log = Tempfile.new('unicorn_test_log')
706
+ ucfg = Tempfile.new('unicorn_test_config')
707
+ ucfg.syswrite("listen \"#{sock_path}\"\n")
708
+ ucfg.syswrite("pid \"#{pid_file}\"\n")
709
+ ucfg.syswrite("logger Logger.new('#{log.path}')\n")
710
+ ucfg.close
711
+
712
+ File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
713
+ pid = xfork do
714
+ redirect_test_io do
715
+ exec($unicorn_bin, "-l#{@addr}:#{@port}",
716
+ "-P#{pid_file}", "-c#{ucfg.path}")
717
+ end
718
+ end
719
+ results = retry_hit(["http://#{@addr}:#{@port}/"])
720
+ assert_equal String, results[0].class
721
+ wait_master_ready(log.path)
722
+ assert File.exist?(pid_file), "pid_file created"
723
+ assert_equal pid, File.read(pid_file).to_i
724
+ assert File.socket?(sock_path), "socket created"
725
+
726
+ sock = UNIXSocket.new(sock_path)
727
+ sock.syswrite("GET / HTTP/1.0\r\n\r\n")
728
+ results = sock.sysread(4096)
729
+
730
+ assert_equal String, results.class
731
+
732
+ # try reloading the config
733
+ sock = Tempfile.new('new_test_sock')
734
+ new_sock_path = sock.path
735
+ @sockets << new_sock_path
736
+ sock.close!
737
+ new_log = Tempfile.new('unicorn_test_log')
738
+ new_log.sync = true
739
+ assert_equal 0, new_log.size
740
+
741
+ ucfg = File.open(ucfg.path, "wb")
742
+ ucfg.syswrite("listen \"#{sock_path}\"\n")
743
+ ucfg.syswrite("listen \"#{new_sock_path}\"\n")
744
+ ucfg.syswrite("pid \"#{pid_file}\"\n")
745
+ ucfg.syswrite("logger Logger.new('#{new_log.path}')\n")
746
+ ucfg.close
747
+ Process.kill(:HUP, pid)
748
+
749
+ wait_for_file(new_sock_path)
750
+ assert File.socket?(new_sock_path), "socket exists"
751
+ @sockets.each do |path|
752
+ sock = UNIXSocket.new(path)
753
+ sock.syswrite("GET / HTTP/1.0\r\n\r\n")
754
+ results = sock.sysread(4096)
755
+ assert_equal String, results.class
756
+ end
757
+
758
+ assert_not_equal 0, new_log.size
759
+ reexec_usr2_quit_test(pid, pid_file)
760
+ end
761
+
762
+ def test_daemonize_reexec
763
+ pid_file = "#{@tmpdir}/test.pid"
764
+ log = Tempfile.new('unicorn_test_log')
765
+ ucfg = Tempfile.new('unicorn_test_config')
766
+ ucfg.syswrite("pid \"#{pid_file}\"\n")
767
+ ucfg.syswrite("logger Logger.new('#{log.path}')\n")
768
+ ucfg.close
769
+
770
+ File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
771
+ pid = xfork do
772
+ redirect_test_io do
773
+ exec($unicorn_bin, "-D", "-l#{@addr}:#{@port}", "-c#{ucfg.path}")
774
+ end
775
+ end
776
+ results = retry_hit(["http://#{@addr}:#{@port}/"])
777
+ assert_equal String, results[0].class
778
+ wait_for_file(pid_file)
779
+ new_pid = File.read(pid_file).to_i
780
+ assert_not_equal pid, new_pid
781
+ pid, status = Process.waitpid2(pid)
782
+ assert status.success?, "original process exited successfully"
783
+ Process.kill(0, new_pid)
784
+ reexec_usr2_quit_test(new_pid, pid_file)
785
+ end
786
+
787
+ def test_daemonize_redirect_fail
788
+ pid_file = "#{@tmpdir}/test.pid"
789
+ ucfg = Tempfile.new('unicorn_test_config')
790
+ ucfg.syswrite("pid #{pid_file}\"\n")
791
+ err = Tempfile.new('stderr')
792
+ out = Tempfile.new('stdout ')
793
+
794
+ File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
795
+ pid = xfork do
796
+ $stderr.reopen(err.path, "a")
797
+ $stdout.reopen(out.path, "a")
798
+ exec($unicorn_bin, "-D", "-l#{@addr}:#{@port}", "-c#{ucfg.path}")
799
+ end
800
+ pid, status = Process.waitpid2(pid)
801
+ assert ! status.success?, "original process exited successfully"
802
+ sleep 1 # can't waitpid on a daemonized process :<
803
+ assert err.stat.size > 0
804
+ end
805
+
806
+ def test_reexec_fd_leak
807
+ unless RUBY_PLATFORM =~ /linux/ # Solaris may work, too, but I forget...
808
+ warn "FD leak test only works on Linux at the moment"
809
+ return
810
+ end
811
+ pid_file = "#{@tmpdir}/test.pid"
812
+ log = Tempfile.new('unicorn_test_log')
813
+ log.sync = true
814
+ ucfg = Tempfile.new('unicorn_test_config')
815
+ ucfg.syswrite("pid \"#{pid_file}\"\n")
816
+ ucfg.syswrite("logger Logger.new('#{log.path}')\n")
817
+ ucfg.syswrite("stderr_path '#{log.path}'\n")
818
+ ucfg.syswrite("stdout_path '#{log.path}'\n")
819
+ ucfg.close
820
+
821
+ File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
822
+ pid = xfork do
823
+ redirect_test_io do
824
+ exec($unicorn_bin, "-D", "-l#{@addr}:#{@port}", "-c#{ucfg.path}")
825
+ end
826
+ end
827
+
828
+ wait_master_ready(log.path)
829
+ wait_workers_ready(log.path, 1)
830
+ File.truncate(log.path, 0)
831
+ wait_for_file(pid_file)
832
+ orig_pid = pid = File.read(pid_file).to_i
833
+ orig_fds = `ls -l /proc/#{pid}/fd`.split(/\n/)
834
+ assert $?.success?
835
+ expect_size = orig_fds.size
836
+
837
+ Process.kill(:USR2, pid)
838
+ wait_for_file("#{pid_file}.oldbin")
839
+ Process.kill(:QUIT, pid)
840
+
841
+ wait_for_death(pid)
842
+
843
+ wait_master_ready(log.path)
844
+ wait_workers_ready(log.path, 1)
845
+ File.truncate(log.path, 0)
846
+ wait_for_file(pid_file)
847
+ pid = File.read(pid_file).to_i
848
+ assert_not_equal orig_pid, pid
849
+ curr_fds = `ls -l /proc/#{pid}/fd`.split(/\n/)
850
+ assert $?.success?
851
+
852
+ # we could've inherited descriptors the first time around
853
+ assert expect_size >= curr_fds.size, curr_fds.inspect
854
+ expect_size = curr_fds.size
855
+
856
+ Process.kill(:USR2, pid)
857
+ wait_for_file("#{pid_file}.oldbin")
858
+ Process.kill(:QUIT, pid)
859
+
860
+ wait_for_death(pid)
861
+
862
+ wait_master_ready(log.path)
863
+ wait_workers_ready(log.path, 1)
864
+ File.truncate(log.path, 0)
865
+ wait_for_file(pid_file)
866
+ pid = File.read(pid_file).to_i
867
+ curr_fds = `ls -l /proc/#{pid}/fd`.split(/\n/)
868
+ assert $?.success?
869
+ assert_equal expect_size, curr_fds.size, curr_fds.inspect
870
+
871
+ Process.kill(:QUIT, pid)
872
+ wait_for_death(pid)
873
+ end
874
+
875
+ def hup_test_common(preload, check_client=false)
876
+ File.open("config.ru", "wb") { |fp| fp.syswrite(HI.gsub("HI", '#$$')) }
877
+ pid_file = Tempfile.new('pid')
878
+ ucfg = Tempfile.new('unicorn_test_config')
879
+ ucfg.syswrite("listen '#@addr:#@port'\n")
880
+ ucfg.syswrite("pid '#{pid_file.path}'\n")
881
+ ucfg.syswrite("preload_app true\n") if preload
882
+ ucfg.syswrite("check_client_connection true\n") if check_client
883
+ ucfg.syswrite("stderr_path 'test_stderr.#$$.log'\n")
884
+ ucfg.syswrite("stdout_path 'test_stdout.#$$.log'\n")
885
+ pid = xfork {
886
+ redirect_test_io { exec($unicorn_bin, "-D", "-c", ucfg.path) }
887
+ }
888
+ _, status = Process.waitpid2(pid)
889
+ assert status.success?
890
+ wait_master_ready("test_stderr.#$$.log")
891
+ wait_workers_ready("test_stderr.#$$.log", 1)
892
+ uri = URI.parse("http://#@addr:#@port/")
893
+ pids = Tempfile.new('worker_pids')
894
+ r, w = IO.pipe
895
+ hitter = fork {
896
+ r.close
897
+ bodies = Hash.new(0)
898
+ at_exit { pids.syswrite(bodies.inspect) }
899
+ trap(:TERM) { exit(0) }
900
+ nr = 0
901
+ loop {
902
+ rv = Net::HTTP.get(uri)
903
+ pid = rv.to_i
904
+ exit!(1) if pid <= 0
905
+ bodies[pid] += 1
906
+ nr += 1
907
+ if nr == 1
908
+ w.syswrite('1')
909
+ elsif bodies.size > 1
910
+ w.syswrite('2')
911
+ sleep
912
+ end
913
+ }
914
+ }
915
+ w.close
916
+ assert_equal '1', r.read(1)
917
+ daemon_pid = File.read(pid_file.path).to_i
918
+ assert daemon_pid > 0
919
+ Process.kill(:HUP, daemon_pid)
920
+ assert_equal '2', r.read(1)
921
+ Process.kill(:TERM, hitter)
922
+ _, hitter_status = Process.waitpid2(hitter)
923
+ assert(hitter_status.success?,
924
+ "invalid: #{hitter_status.inspect} #{File.read(pids.path)}" \
925
+ "#{File.read("test_stderr.#$$.log")}")
926
+ pids.sysseek(0)
927
+ pids = eval(pids.read)
928
+ assert_kind_of(Hash, pids)
929
+ assert_equal 2, pids.size
930
+ pids.keys.each { |x|
931
+ assert_kind_of(Integer, x)
932
+ assert x > 0
933
+ assert pids[x] > 0
934
+ }
935
+ Process.kill(:QUIT, daemon_pid)
936
+ wait_for_death(daemon_pid)
937
+ end
938
+
939
+ def test_preload_app_hup
940
+ hup_test_common(true)
941
+ end
942
+
943
+ def test_hup
944
+ hup_test_common(false)
945
+ end
946
+
947
+ def test_check_client_hup
948
+ hup_test_common(false, true)
949
+ end
950
+
951
+ def test_default_listen_hup_holds_listener
952
+ default_listen_lock do
953
+ res, pid_path = default_listen_setup
954
+ daemon_pid = File.read(pid_path).to_i
955
+ Process.kill(:HUP, daemon_pid)
956
+ wait_workers_ready("test_stderr.#$$.log", 1)
957
+ res2 = hit(["http://#{Unicorn::Const::DEFAULT_LISTEN}/"])
958
+ assert_match %r{\d+}, res2.first
959
+ assert res2.first != res.first
960
+ Process.kill(:QUIT, daemon_pid)
961
+ wait_for_death(daemon_pid)
962
+ end
963
+ end
964
+
965
+ def test_default_listen_upgrade_holds_listener
966
+ default_listen_lock do
967
+ res, pid_path = default_listen_setup
968
+ daemon_pid = File.read(pid_path).to_i
969
+
970
+ Process.kill(:USR2, daemon_pid)
971
+ wait_for_file("#{pid_path}.oldbin")
972
+ wait_for_file(pid_path)
973
+ Process.kill(:QUIT, daemon_pid)
974
+ wait_for_death(daemon_pid)
975
+
976
+ daemon_pid = File.read(pid_path).to_i
977
+ wait_workers_ready("test_stderr.#$$.log", 1)
978
+ File.truncate("test_stderr.#$$.log", 0)
979
+
980
+ res2 = hit(["http://#{Unicorn::Const::DEFAULT_LISTEN}/"])
981
+ assert_match %r{\d+}, res2.first
982
+ assert res2.first != res.first
983
+
984
+ Process.kill(:HUP, daemon_pid)
985
+ wait_workers_ready("test_stderr.#$$.log", 1)
986
+ File.truncate("test_stderr.#$$.log", 0)
987
+ res3 = hit(["http://#{Unicorn::Const::DEFAULT_LISTEN}/"])
988
+ assert res2.first != res3.first
989
+
990
+ Process.kill(:QUIT, daemon_pid)
991
+ wait_for_death(daemon_pid)
992
+ end
993
+ end
994
+
995
+ def default_listen_setup
996
+ File.open("config.ru", "wb") { |fp| fp.syswrite(HI.gsub("HI", '#$$')) }
997
+ pid_path = (tmp = Tempfile.new('pid')).path
998
+ tmp.close!
999
+ ucfg = Tempfile.new('unicorn_test_config')
1000
+ ucfg.syswrite("pid '#{pid_path}'\n")
1001
+ ucfg.syswrite("stderr_path 'test_stderr.#$$.log'\n")
1002
+ ucfg.syswrite("stdout_path 'test_stdout.#$$.log'\n")
1003
+ pid = xfork {
1004
+ redirect_test_io { exec($unicorn_bin, "-D", "-c", ucfg.path) }
1005
+ }
1006
+ _, status = Process.waitpid2(pid)
1007
+ assert status.success?
1008
+ wait_master_ready("test_stderr.#$$.log")
1009
+ wait_workers_ready("test_stderr.#$$.log", 1)
1010
+ File.truncate("test_stderr.#$$.log", 0)
1011
+ res = hit(["http://#{Unicorn::Const::DEFAULT_LISTEN}/"])
1012
+ assert_match %r{\d+}, res.first
1013
+ [ res, pid_path ]
1014
+ end
1015
+
1016
+ # we need to flock() something to prevent these tests from running
1017
+ def default_listen_lock(&block)
1018
+ fp = File.open(FLOCK_PATH, "rb")
1019
+ begin
1020
+ fp.flock(File::LOCK_EX)
1021
+ begin
1022
+ TCPServer.new(Unicorn::Const::DEFAULT_HOST,
1023
+ Unicorn::Const::DEFAULT_PORT).close
1024
+ rescue Errno::EADDRINUSE, Errno::EACCES
1025
+ warn "can't bind to #{Unicorn::Const::DEFAULT_LISTEN}"
1026
+ return false
1027
+ end
1028
+
1029
+ # unused_port should never take this, but we may run an environment
1030
+ # where tests are being run against older unicorns...
1031
+ lock_path = "#{Dir::tmpdir}/unicorn_test." \
1032
+ "#{Unicorn::Const::DEFAULT_LISTEN}.lock"
1033
+ begin
1034
+ File.open(lock_path, File::WRONLY|File::CREAT|File::EXCL, 0600)
1035
+ yield
1036
+ rescue Errno::EEXIST
1037
+ lock_path = nil
1038
+ return false
1039
+ ensure
1040
+ File.unlink(lock_path) if lock_path
1041
+ end
1042
+ ensure
1043
+ fp.flock(File::LOCK_UN)
1044
+ end
1045
+ end
1046
+
1047
+ end if do_test