giraffesoft-unicorn 0.93.5

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