unicorn-academia 4.7.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (159) hide show
  1. checksums.yaml +7 -0
  2. data/.CHANGELOG.old +25 -0
  3. data/.document +29 -0
  4. data/.gitignore +25 -0
  5. data/.mailmap +26 -0
  6. data/.wrongdoc.yml +10 -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 +178 -0
  14. data/Documentation/unicorn_rails.1.txt +175 -0
  15. data/FAQ +53 -0
  16. data/GIT-VERSION-GEN +39 -0
  17. data/GNUmakefile +266 -0
  18. data/HACKING +134 -0
  19. data/ISSUES +36 -0
  20. data/KNOWN_ISSUES +79 -0
  21. data/LICENSE +67 -0
  22. data/Links +56 -0
  23. data/PHILOSOPHY +145 -0
  24. data/README +150 -0
  25. data/Rakefile +60 -0
  26. data/SIGNALS +114 -0
  27. data/Sandbox +103 -0
  28. data/TODO +5 -0
  29. data/TUNING +98 -0
  30. data/bin/unicorn +126 -0
  31. data/bin/unicorn_rails +209 -0
  32. data/examples/big_app_gc.rb +2 -0
  33. data/examples/echo.ru +27 -0
  34. data/examples/git.ru +13 -0
  35. data/examples/init.sh +74 -0
  36. data/examples/logger_mp_safe.rb +25 -0
  37. data/examples/logrotate.conf +29 -0
  38. data/examples/nginx.conf +156 -0
  39. data/examples/unicorn.conf.minimal.rb +13 -0
  40. data/examples/unicorn.conf.rb +102 -0
  41. data/ext/unicorn_http/CFLAGS +13 -0
  42. data/ext/unicorn_http/c_util.h +124 -0
  43. data/ext/unicorn_http/common_field_optimization.h +111 -0
  44. data/ext/unicorn_http/ext_help.h +82 -0
  45. data/ext/unicorn_http/extconf.rb +10 -0
  46. data/ext/unicorn_http/global_variables.h +97 -0
  47. data/ext/unicorn_http/httpdate.c +78 -0
  48. data/ext/unicorn_http/unicorn_http.rl +1036 -0
  49. data/ext/unicorn_http/unicorn_http_common.rl +76 -0
  50. data/lib/unicorn.rb +113 -0
  51. data/lib/unicorn/app/exec_cgi.rb +154 -0
  52. data/lib/unicorn/app/inetd.rb +109 -0
  53. data/lib/unicorn/app/old_rails.rb +35 -0
  54. data/lib/unicorn/app/old_rails/static.rb +59 -0
  55. data/lib/unicorn/cgi_wrapper.rb +147 -0
  56. data/lib/unicorn/configurator.rb +679 -0
  57. data/lib/unicorn/const.rb +44 -0
  58. data/lib/unicorn/http_request.rb +122 -0
  59. data/lib/unicorn/http_response.rb +75 -0
  60. data/lib/unicorn/http_server.rb +808 -0
  61. data/lib/unicorn/launcher.rb +62 -0
  62. data/lib/unicorn/oob_gc.rb +71 -0
  63. data/lib/unicorn/preread_input.rb +33 -0
  64. data/lib/unicorn/socket_helper.rb +231 -0
  65. data/lib/unicorn/ssl_client.rb +11 -0
  66. data/lib/unicorn/ssl_configurator.rb +104 -0
  67. data/lib/unicorn/ssl_server.rb +42 -0
  68. data/lib/unicorn/stream_input.rb +149 -0
  69. data/lib/unicorn/tee_input.rb +126 -0
  70. data/lib/unicorn/tmpio.rb +29 -0
  71. data/lib/unicorn/util.rb +89 -0
  72. data/lib/unicorn/worker.rb +88 -0
  73. data/local.mk.sample +59 -0
  74. data/script/isolate_for_tests +32 -0
  75. data/setup.rb +1586 -0
  76. data/t/.gitignore +5 -0
  77. data/t/GNUmakefile +82 -0
  78. data/t/README +42 -0
  79. data/t/bin/content-md5-put +36 -0
  80. data/t/bin/sha1sum.rb +17 -0
  81. data/t/bin/unused_listen +40 -0
  82. data/t/broken-app.ru +12 -0
  83. data/t/detach.ru +11 -0
  84. data/t/env.ru +3 -0
  85. data/t/fails-rack-lint.ru +5 -0
  86. data/t/heartbeat-timeout.ru +12 -0
  87. data/t/hijack.ru +42 -0
  88. data/t/listener_names.ru +4 -0
  89. data/t/my-tap-lib.sh +201 -0
  90. data/t/oob_gc.ru +20 -0
  91. data/t/oob_gc_path.ru +20 -0
  92. data/t/pid.ru +3 -0
  93. data/t/preread_input.ru +17 -0
  94. data/t/rack-input-tests.ru +21 -0
  95. data/t/sslgen.sh +71 -0
  96. data/t/t0000-http-basic.sh +50 -0
  97. data/t/t0001-reload-bad-config.sh +53 -0
  98. data/t/t0002-config-conflict.sh +49 -0
  99. data/t/t0002-parser-error.sh +94 -0
  100. data/t/t0003-working_directory.sh +51 -0
  101. data/t/t0004-heartbeat-timeout.sh +69 -0
  102. data/t/t0004-working_directory_broken.sh +24 -0
  103. data/t/t0005-working_directory_app.rb.sh +40 -0
  104. data/t/t0006-reopen-logs.sh +83 -0
  105. data/t/t0006.ru +13 -0
  106. data/t/t0007-working_directory_no_embed_cli.sh +44 -0
  107. data/t/t0008-back_out_of_upgrade.sh +110 -0
  108. data/t/t0009-broken-app.sh +56 -0
  109. data/t/t0009-winch_ttin.sh +59 -0
  110. data/t/t0010-reap-logging.sh +55 -0
  111. data/t/t0011-active-unix-socket.sh +79 -0
  112. data/t/t0012-reload-empty-config.sh +85 -0
  113. data/t/t0013-rewindable-input-false.sh +24 -0
  114. data/t/t0013.ru +12 -0
  115. data/t/t0014-rewindable-input-true.sh +24 -0
  116. data/t/t0014.ru +12 -0
  117. data/t/t0015-configurator-internals.sh +25 -0
  118. data/t/t0016-trust-x-forwarded-false.sh +30 -0
  119. data/t/t0017-trust-x-forwarded-true.sh +30 -0
  120. data/t/t0018-write-on-close.sh +23 -0
  121. data/t/t0019-max_header_len.sh +49 -0
  122. data/t/t0020-at_exit-handler.sh +49 -0
  123. data/t/t0021-process_detach.sh +29 -0
  124. data/t/t0022-listener_names-preload_app.sh +32 -0
  125. data/t/t0100-rack-input-tests.sh +124 -0
  126. data/t/t0116-client_body_buffer_size.sh +80 -0
  127. data/t/t0116.ru +16 -0
  128. data/t/t0200-rack-hijack.sh +27 -0
  129. data/t/t0300-no-default-middleware.sh +15 -0
  130. data/t/t0600-https-server-basic.sh +48 -0
  131. data/t/t9000-preread-input.sh +48 -0
  132. data/t/t9001-oob_gc.sh +47 -0
  133. data/t/t9002-oob_gc-path.sh +75 -0
  134. data/t/test-lib.sh +128 -0
  135. data/t/write-on-close.ru +11 -0
  136. data/test/aggregate.rb +15 -0
  137. data/test/benchmark/README +50 -0
  138. data/test/benchmark/dd.ru +18 -0
  139. data/test/benchmark/stack.ru +8 -0
  140. data/test/exec/README +5 -0
  141. data/test/exec/test_exec.rb +1047 -0
  142. data/test/test_helper.rb +297 -0
  143. data/test/unit/test_configurator.rb +175 -0
  144. data/test/unit/test_droplet.rb +28 -0
  145. data/test/unit/test_http_parser.rb +854 -0
  146. data/test/unit/test_http_parser_ng.rb +731 -0
  147. data/test/unit/test_http_parser_xftrust.rb +38 -0
  148. data/test/unit/test_request.rb +182 -0
  149. data/test/unit/test_response.rb +99 -0
  150. data/test/unit/test_server.rb +268 -0
  151. data/test/unit/test_signals.rb +188 -0
  152. data/test/unit/test_sni_hostnames.rb +47 -0
  153. data/test/unit/test_socket_helper.rb +195 -0
  154. data/test/unit/test_stream_input.rb +203 -0
  155. data/test/unit/test_tee_input.rb +294 -0
  156. data/test/unit/test_upload.rb +306 -0
  157. data/test/unit/test_util.rb +105 -0
  158. data/unicorn-academia.gemspec +44 -0
  159. metadata +328 -0
@@ -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
+ })
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,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