unicorn-maintained 6.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (151) hide show
  1. checksums.yaml +7 -0
  2. data/.CHANGELOG.old +25 -0
  3. data/.document +28 -0
  4. data/.gitattributes +5 -0
  5. data/.gitignore +25 -0
  6. data/.mailmap +26 -0
  7. data/.manifest +149 -0
  8. data/.olddoc.yml +25 -0
  9. data/Application_Timeouts +77 -0
  10. data/CONTRIBUTORS +39 -0
  11. data/COPYING +674 -0
  12. data/DESIGN +99 -0
  13. data/Documentation/.gitignore +3 -0
  14. data/Documentation/unicorn.1 +222 -0
  15. data/Documentation/unicorn_rails.1 +207 -0
  16. data/FAQ +70 -0
  17. data/GIT-VERSION-FILE +1 -0
  18. data/GIT-VERSION-GEN +39 -0
  19. data/GNUmakefile +317 -0
  20. data/HACKING +112 -0
  21. data/ISSUES +102 -0
  22. data/KNOWN_ISSUES +79 -0
  23. data/LATEST +1 -0
  24. data/LICENSE +67 -0
  25. data/Links +58 -0
  26. data/NEWS +1 -0
  27. data/PHILOSOPHY +139 -0
  28. data/README +156 -0
  29. data/Rakefile +16 -0
  30. data/SIGNALS +123 -0
  31. data/Sandbox +104 -0
  32. data/TODO +3 -0
  33. data/TUNING +119 -0
  34. data/archive/.gitignore +3 -0
  35. data/archive/slrnpull.conf +4 -0
  36. data/bin/unicorn +128 -0
  37. data/bin/unicorn_rails +209 -0
  38. data/examples/big_app_gc.rb +2 -0
  39. data/examples/echo.ru +26 -0
  40. data/examples/init.sh +102 -0
  41. data/examples/logger_mp_safe.rb +25 -0
  42. data/examples/logrotate.conf +44 -0
  43. data/examples/nginx.conf +156 -0
  44. data/examples/unicorn.conf.minimal.rb +13 -0
  45. data/examples/unicorn.conf.rb +110 -0
  46. data/examples/unicorn.socket +11 -0
  47. data/examples/unicorn@.service +40 -0
  48. data/ext/unicorn_http/CFLAGS +13 -0
  49. data/ext/unicorn_http/c_util.h +116 -0
  50. data/ext/unicorn_http/common_field_optimization.h +128 -0
  51. data/ext/unicorn_http/epollexclusive.h +128 -0
  52. data/ext/unicorn_http/ext_help.h +38 -0
  53. data/ext/unicorn_http/extconf.rb +39 -0
  54. data/ext/unicorn_http/global_variables.h +97 -0
  55. data/ext/unicorn_http/httpdate.c +91 -0
  56. data/ext/unicorn_http/unicorn_http.c +4334 -0
  57. data/ext/unicorn_http/unicorn_http.rl +1040 -0
  58. data/ext/unicorn_http/unicorn_http_common.rl +76 -0
  59. data/lib/unicorn/app/old_rails/static.rb +59 -0
  60. data/lib/unicorn/app/old_rails.rb +35 -0
  61. data/lib/unicorn/cgi_wrapper.rb +147 -0
  62. data/lib/unicorn/configurator.rb +748 -0
  63. data/lib/unicorn/const.rb +21 -0
  64. data/lib/unicorn/http_request.rb +201 -0
  65. data/lib/unicorn/http_response.rb +93 -0
  66. data/lib/unicorn/http_server.rb +859 -0
  67. data/lib/unicorn/launcher.rb +62 -0
  68. data/lib/unicorn/oob_gc.rb +81 -0
  69. data/lib/unicorn/preread_input.rb +33 -0
  70. data/lib/unicorn/select_waiter.rb +6 -0
  71. data/lib/unicorn/socket_helper.rb +185 -0
  72. data/lib/unicorn/stream_input.rb +151 -0
  73. data/lib/unicorn/tee_input.rb +131 -0
  74. data/lib/unicorn/tmpio.rb +33 -0
  75. data/lib/unicorn/util.rb +90 -0
  76. data/lib/unicorn/version.rb +1 -0
  77. data/lib/unicorn/worker.rb +165 -0
  78. data/lib/unicorn.rb +136 -0
  79. data/man/man1/unicorn.1 +222 -0
  80. data/man/man1/unicorn_rails.1 +207 -0
  81. data/setup.rb +1586 -0
  82. data/t/.gitignore +4 -0
  83. data/t/GNUmakefile +5 -0
  84. data/t/README +49 -0
  85. data/t/active-unix-socket.t +117 -0
  86. data/t/bin/unused_listen +40 -0
  87. data/t/broken-app.ru +12 -0
  88. data/t/client_body_buffer_size.ru +14 -0
  89. data/t/client_body_buffer_size.t +80 -0
  90. data/t/detach.ru +11 -0
  91. data/t/env.ru +3 -0
  92. data/t/fails-rack-lint.ru +5 -0
  93. data/t/heartbeat-timeout.ru +12 -0
  94. data/t/heartbeat-timeout.t +62 -0
  95. data/t/integration.ru +115 -0
  96. data/t/integration.t +356 -0
  97. data/t/lib.perl +258 -0
  98. data/t/listener_names.ru +4 -0
  99. data/t/my-tap-lib.sh +201 -0
  100. data/t/oob_gc.ru +17 -0
  101. data/t/oob_gc_path.ru +17 -0
  102. data/t/pid.ru +3 -0
  103. data/t/preread_input.ru +22 -0
  104. data/t/reload-bad-config.t +54 -0
  105. data/t/reopen-logs.ru +13 -0
  106. data/t/reopen-logs.t +39 -0
  107. data/t/t0008-back_out_of_upgrade.sh +110 -0
  108. data/t/t0009-broken-app.sh +56 -0
  109. data/t/t0010-reap-logging.sh +55 -0
  110. data/t/t0012-reload-empty-config.sh +86 -0
  111. data/t/t0013-rewindable-input-false.sh +24 -0
  112. data/t/t0013.ru +12 -0
  113. data/t/t0014-rewindable-input-true.sh +24 -0
  114. data/t/t0014.ru +12 -0
  115. data/t/t0015-configurator-internals.sh +25 -0
  116. data/t/t0020-at_exit-handler.sh +49 -0
  117. data/t/t0021-process_detach.sh +29 -0
  118. data/t/t0022-listener_names-preload_app.sh +32 -0
  119. data/t/t0300-no-default-middleware.sh +20 -0
  120. data/t/t0301-no-default-middleware-ignored-in-config.sh +25 -0
  121. data/t/t0301.ru +13 -0
  122. data/t/t9001-oob_gc.sh +47 -0
  123. data/t/t9002-oob_gc-path.sh +75 -0
  124. data/t/test-lib.sh +125 -0
  125. data/t/winch_ttin.t +67 -0
  126. data/t/working_directory.t +94 -0
  127. data/test/aggregate.rb +15 -0
  128. data/test/benchmark/README +60 -0
  129. data/test/benchmark/dd.ru +18 -0
  130. data/test/benchmark/ddstream.ru +50 -0
  131. data/test/benchmark/readinput.ru +40 -0
  132. data/test/benchmark/stack.ru +8 -0
  133. data/test/benchmark/uconnect.perl +66 -0
  134. data/test/exec/README +5 -0
  135. data/test/exec/test_exec.rb +1029 -0
  136. data/test/test_helper.rb +306 -0
  137. data/test/unit/test_ccc.rb +91 -0
  138. data/test/unit/test_configurator.rb +175 -0
  139. data/test/unit/test_droplet.rb +28 -0
  140. data/test/unit/test_http_parser.rb +884 -0
  141. data/test/unit/test_http_parser_ng.rb +714 -0
  142. data/test/unit/test_request.rb +169 -0
  143. data/test/unit/test_server.rb +244 -0
  144. data/test/unit/test_signals.rb +188 -0
  145. data/test/unit/test_socket_helper.rb +159 -0
  146. data/test/unit/test_stream_input.rb +210 -0
  147. data/test/unit/test_tee_input.rb +303 -0
  148. data/test/unit/test_util.rb +131 -0
  149. data/test/unit/test_waiter.rb +34 -0
  150. data/unicorn.gemspec +48 -0
  151. metadata +275 -0
@@ -0,0 +1,1029 @@
1
+ # -*- encoding: binary -*-
2
+ # Don't add to this file, new tests are in Perl 5. See t/README
3
+ FLOCK_PATH = File.expand_path(__FILE__)
4
+ require './test/test_helper'
5
+
6
+ do_test = true
7
+ $unicorn_bin = ENV['UNICORN_TEST_BIN'] || "unicorn"
8
+ redirect_test_io do
9
+ do_test = system($unicorn_bin, '-v')
10
+ end
11
+
12
+ unless do_test
13
+ warn "#{$unicorn_bin} not found in PATH=#{ENV['PATH']}, " \
14
+ "skipping this test"
15
+ end
16
+
17
+ unless try_require('rack')
18
+ warn "Unable to load Rack, skipping this test"
19
+ do_test = false
20
+ end
21
+
22
+ class ExecTest < Test::Unit::TestCase
23
+ trap(:QUIT, 'IGNORE')
24
+
25
+ HI = <<-EOS
26
+ use Rack::ContentLength
27
+ run proc { |env| [ 200, { 'content-type' => 'text/plain' }, [ "HI\\n" ] ] }
28
+ EOS
29
+
30
+ SHOW_RACK_ENV = <<-EOS
31
+ use Rack::ContentLength
32
+ run proc { |env|
33
+ [ 200, { 'content-type' => 'text/plain' }, [ ENV['RACK_ENV'] ] ]
34
+ }
35
+ EOS
36
+
37
+ HELLO = <<-EOS
38
+ class Hello
39
+ def call(env)
40
+ [ 200, { 'content-type' => 'text/plain' }, [ "HI\\n" ] ]
41
+ end
42
+ end
43
+ EOS
44
+
45
+ COMMON_TMP = Tempfile.new('unicorn_tmp') unless defined?(COMMON_TMP)
46
+
47
+ HEAVY_WORKERS = 2
48
+ HEAVY_CFG = <<-EOS
49
+ worker_processes #{HEAVY_WORKERS}
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
+ begin
205
+ fifo = File.open("#{other.path}/fifo", "rb")
206
+ rescue Errno::EINTR
207
+ # OpenBSD raises Errno::EINTR when opening
208
+ return if RUBY_PLATFORM =~ /openbsd/
209
+ end
210
+ fifo.close
211
+
212
+ assert ! File.exist?("stderr_log_here")
213
+ assert ! File.exist?("stdout_log_here")
214
+ assert ! File.exist?("pid_file_here")
215
+
216
+ assert ! File.exist?("#@tmpdir/stderr_log_here")
217
+ assert ! File.exist?("#@tmpdir/stdout_log_here")
218
+ assert ! File.exist?("#@tmpdir/pid_file_here")
219
+
220
+ assert File.exist?("#{other.path}/pid_file_here")
221
+ assert_equal "#{pid}\n", File.read("#{other.path}/pid_file_here")
222
+ assert File.exist?("#{other.path}/stderr_log_here")
223
+ assert File.exist?("#{other.path}/stdout_log_here")
224
+ wait_master_ready("#{other.path}/stderr_log_here")
225
+
226
+ Process.kill(:QUIT, pid)
227
+ ensure
228
+ FileUtils.rmtree(other.path)
229
+ end
230
+
231
+ def test_exit_signals
232
+ %w(INT TERM QUIT).each do |sig|
233
+ File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
234
+ pid = xfork { redirect_test_io { exec($unicorn_bin, "-l#@addr:#@port") } }
235
+ wait_master_ready("test_stderr.#{pid}.log")
236
+ wait_workers_ready("test_stderr.#{pid}.log", 1)
237
+
238
+ Process.kill(sig, pid)
239
+ pid, status = Process.waitpid2(pid)
240
+
241
+ reaped = File.readlines("test_stderr.#{pid}.log").grep(/reaped/)
242
+ assert_equal 1, reaped.size
243
+ assert status.exited?
244
+ end
245
+ end
246
+
247
+ def test_rack_env_unset
248
+ File.open("config.ru", "wb") { |fp| fp.syswrite(SHOW_RACK_ENV) }
249
+ pid = fork { redirect_test_io { exec($unicorn_bin, "-l#@addr:#@port") } }
250
+ results = retry_hit(["http://#{@addr}:#{@port}/"])
251
+ assert_equal "development", results.first
252
+ assert_shutdown(pid)
253
+ end
254
+
255
+ def test_rack_env_cli_set
256
+ File.open("config.ru", "wb") { |fp| fp.syswrite(SHOW_RACK_ENV) }
257
+ pid = fork {
258
+ redirect_test_io { exec($unicorn_bin, "-l#@addr:#@port", "-Easdf") }
259
+ }
260
+ results = retry_hit(["http://#{@addr}:#{@port}/"])
261
+ assert_equal "asdf", results.first
262
+ assert_shutdown(pid)
263
+ end
264
+
265
+ def test_rack_env_ENV_set
266
+ File.open("config.ru", "wb") { |fp| fp.syswrite(SHOW_RACK_ENV) }
267
+ pid = fork {
268
+ ENV["RACK_ENV"] = "foobar"
269
+ redirect_test_io { exec($unicorn_bin, "-l#@addr:#@port") }
270
+ }
271
+ results = retry_hit(["http://#{@addr}:#{@port}/"])
272
+ assert_equal "foobar", results.first
273
+ assert_shutdown(pid)
274
+ end
275
+
276
+ def test_rack_env_cli_override_ENV
277
+ File.open("config.ru", "wb") { |fp| fp.syswrite(SHOW_RACK_ENV) }
278
+ pid = fork {
279
+ ENV["RACK_ENV"] = "foobar"
280
+ redirect_test_io { exec($unicorn_bin, "-l#@addr:#@port", "-Easdf") }
281
+ }
282
+ results = retry_hit(["http://#{@addr}:#{@port}/"])
283
+ assert_equal "asdf", results.first
284
+ assert_shutdown(pid)
285
+ end
286
+
287
+ def test_ttin_ttou
288
+ File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
289
+ pid = fork { redirect_test_io { exec($unicorn_bin, "-l#@addr:#@port") } }
290
+ log = "test_stderr.#{pid}.log"
291
+ wait_master_ready(log)
292
+ [ 2, 3].each { |i|
293
+ Process.kill(:TTIN, pid)
294
+ wait_workers_ready(log, i)
295
+ }
296
+ File.truncate(log, 0)
297
+ reaped = nil
298
+ [ 2, 1, 0].each { |i|
299
+ Process.kill(:TTOU, pid)
300
+ DEFAULT_TRIES.times {
301
+ sleep DEFAULT_RES
302
+ reaped = File.readlines(log).grep(/reaped.*\s*worker=#{i}$/)
303
+ break if reaped.size == 1
304
+ }
305
+ assert_equal 1, reaped.size
306
+ }
307
+ end
308
+
309
+ def test_help
310
+ redirect_test_io do
311
+ assert(system($unicorn_bin, "-h"), "help text returns true")
312
+ end
313
+ assert_equal 0, File.stat("test_stderr.#$$.log").size
314
+ assert_not_equal 0, File.stat("test_stdout.#$$.log").size
315
+ lines = File.readlines("test_stdout.#$$.log")
316
+
317
+ # Be considerate of the on-call technician working from their
318
+ # mobile phone or netbook on a slow connection :)
319
+ assert lines.size <= 24, "help height fits in an ANSI terminal window"
320
+ lines.each do |line|
321
+ line.chomp!
322
+ assert line.size <= 80, "help width fits in an ANSI terminal window"
323
+ end
324
+ end
325
+
326
+ def test_broken_reexec_config
327
+ File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
328
+ pid_file = "#{@tmpdir}/test.pid"
329
+ old_file = "#{pid_file}.oldbin"
330
+ ucfg = Tempfile.new('unicorn_test_config')
331
+ ucfg.syswrite("listen %(#@addr:#@port)\n")
332
+ ucfg.syswrite("pid %(#{pid_file})\n")
333
+ ucfg.syswrite("logger Logger.new(%(#{@tmpdir}/log))\n")
334
+ pid = xfork do
335
+ redirect_test_io do
336
+ exec($unicorn_bin, "-D", "-l#{@addr}:#{@port}", "-c#{ucfg.path}")
337
+ end
338
+ end
339
+ results = retry_hit(["http://#{@addr}:#{@port}/"])
340
+ assert_equal String, results[0].class
341
+
342
+ wait_for_file(pid_file)
343
+ Process.waitpid(pid)
344
+ Process.kill(:USR2, File.read(pid_file).to_i)
345
+ wait_for_file(old_file)
346
+ wait_for_file(pid_file)
347
+ old_pid = File.read(old_file).to_i
348
+ Process.kill(:QUIT, old_pid)
349
+ wait_for_death(old_pid)
350
+
351
+ ucfg.syswrite("timeout %(#{pid_file})\n") # introduce a bug
352
+ current_pid = File.read(pid_file).to_i
353
+ Process.kill(:USR2, current_pid)
354
+
355
+ # wait for pid_file to restore itself
356
+ tries = DEFAULT_TRIES
357
+ begin
358
+ while current_pid != File.read(pid_file).to_i
359
+ sleep(DEFAULT_RES) and (tries -= 1) > 0
360
+ end
361
+ rescue Errno::ENOENT
362
+ (sleep(DEFAULT_RES) and (tries -= 1) > 0) and retry
363
+ end
364
+ assert_equal current_pid, File.read(pid_file).to_i
365
+
366
+ tries = DEFAULT_TRIES
367
+ while File.exist?(old_file)
368
+ (sleep(DEFAULT_RES) and (tries -= 1) > 0) or break
369
+ end
370
+ assert ! File.exist?(old_file), "oldbin=#{old_file} gone"
371
+ port2 = unused_port(@addr)
372
+
373
+ # fix the bug
374
+ ucfg.sysseek(0)
375
+ ucfg.truncate(0)
376
+ ucfg.syswrite("listen %(#@addr:#@port)\n")
377
+ ucfg.syswrite("listen %(#@addr:#{port2})\n")
378
+ ucfg.syswrite("pid %(#{pid_file})\n")
379
+ Process.kill(:USR2, current_pid)
380
+
381
+ wait_for_file(old_file)
382
+ wait_for_file(pid_file)
383
+ new_pid = File.read(pid_file).to_i
384
+ assert_not_equal current_pid, new_pid
385
+ assert_equal current_pid, File.read(old_file).to_i
386
+ results = retry_hit(["http://#{@addr}:#{@port}/",
387
+ "http://#{@addr}:#{port2}/"])
388
+ assert_equal String, results[0].class
389
+ assert_equal String, results[1].class
390
+
391
+ Process.kill(:QUIT, current_pid)
392
+ Process.kill(:QUIT, new_pid)
393
+ end
394
+
395
+ def test_broken_reexec_ru
396
+ File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
397
+ pid_file = "#{@tmpdir}/test.pid"
398
+ old_file = "#{pid_file}.oldbin"
399
+ ucfg = Tempfile.new('unicorn_test_config')
400
+ ucfg.syswrite("pid %(#{pid_file})\n")
401
+ ucfg.syswrite("logger Logger.new(%(#{@tmpdir}/log))\n")
402
+ pid = xfork do
403
+ redirect_test_io do
404
+ exec($unicorn_bin, "-D", "-l#{@addr}:#{@port}", "-c#{ucfg.path}")
405
+ end
406
+ end
407
+ results = retry_hit(["http://#{@addr}:#{@port}/"])
408
+ assert_equal String, results[0].class
409
+
410
+ wait_for_file(pid_file)
411
+ Process.waitpid(pid)
412
+ Process.kill(:USR2, File.read(pid_file).to_i)
413
+ wait_for_file(old_file)
414
+ wait_for_file(pid_file)
415
+ old_pid = File.read(old_file).to_i
416
+ Process.kill(:QUIT, old_pid)
417
+ wait_for_death(old_pid)
418
+
419
+ File.unlink("config.ru") # break reloading
420
+ current_pid = File.read(pid_file).to_i
421
+ Process.kill(:USR2, current_pid)
422
+
423
+ # wait for pid_file to restore itself
424
+ tries = DEFAULT_TRIES
425
+ begin
426
+ while current_pid != File.read(pid_file).to_i
427
+ sleep(DEFAULT_RES) and (tries -= 1) > 0
428
+ end
429
+ rescue Errno::ENOENT
430
+ (sleep(DEFAULT_RES) and (tries -= 1) > 0) and retry
431
+ end
432
+
433
+ tries = DEFAULT_TRIES
434
+ while File.exist?(old_file)
435
+ (sleep(DEFAULT_RES) and (tries -= 1) > 0) or break
436
+ end
437
+ assert ! File.exist?(old_file), "oldbin=#{old_file} gone"
438
+ assert_equal current_pid, File.read(pid_file).to_i
439
+
440
+ # fix the bug
441
+ File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
442
+ Process.kill(:USR2, current_pid)
443
+ wait_for_file(old_file)
444
+ wait_for_file(pid_file)
445
+ new_pid = File.read(pid_file).to_i
446
+ assert_not_equal current_pid, new_pid
447
+ assert_equal current_pid, File.read(old_file).to_i
448
+ results = retry_hit(["http://#{@addr}:#{@port}/"])
449
+ assert_equal String, results[0].class
450
+
451
+ Process.kill(:QUIT, current_pid)
452
+ Process.kill(:QUIT, new_pid)
453
+ end
454
+
455
+ def test_unicorn_config_listener_swap
456
+ port_cli = unused_port
457
+ File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
458
+ ucfg = Tempfile.new('unicorn_test_config')
459
+ ucfg.syswrite("listen '#@addr:#@port'\n")
460
+ pid = xfork do
461
+ redirect_test_io do
462
+ exec($unicorn_bin, "-c#{ucfg.path}", "-l#@addr:#{port_cli}")
463
+ end
464
+ end
465
+ results = retry_hit(["http://#@addr:#{port_cli}/"])
466
+ assert_equal String, results[0].class
467
+ results = retry_hit(["http://#@addr:#@port/"])
468
+ assert_equal String, results[0].class
469
+
470
+ port2 = unused_port(@addr)
471
+ ucfg.sysseek(0)
472
+ ucfg.truncate(0)
473
+ ucfg.syswrite("listen '#@addr:#{port2}'\n")
474
+ Process.kill(:HUP, pid)
475
+
476
+ results = retry_hit(["http://#@addr:#{port2}/"])
477
+ assert_equal String, results[0].class
478
+ results = retry_hit(["http://#@addr:#{port_cli}/"])
479
+ assert_equal String, results[0].class
480
+ reuse = TCPServer.new(@addr, @port)
481
+ reuse.close
482
+ assert_shutdown(pid)
483
+ end
484
+
485
+ def test_unicorn_config_listen_with_options
486
+ File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
487
+ ucfg = Tempfile.new('unicorn_test_config')
488
+ ucfg.syswrite("listen '#{@addr}:#{@port}', :backlog => 512,\n")
489
+ ucfg.syswrite(" :rcvbuf => 4096,\n")
490
+ ucfg.syswrite(" :sndbuf => 4096\n")
491
+ pid = xfork do
492
+ redirect_test_io { exec($unicorn_bin, "-c#{ucfg.path}") }
493
+ end
494
+ results = retry_hit(["http://#{@addr}:#{@port}/"])
495
+ assert_equal String, results[0].class
496
+ assert_shutdown(pid)
497
+ end
498
+
499
+ def test_unicorn_config_per_worker_listen
500
+ port2 = unused_port
501
+ pid_spit = 'use Rack::ContentLength;' \
502
+ 'run proc { |e| [ 200, {"content-type"=>"text/plain"}, ["#$$\\n"] ] }'
503
+ File.open("config.ru", "wb") { |fp| fp.syswrite(pid_spit) }
504
+ tmp = Tempfile.new('test.socket')
505
+ File.unlink(tmp.path)
506
+ ucfg = Tempfile.new('unicorn_test_config')
507
+ ucfg.syswrite("listen '#@addr:#@port'\n")
508
+ ucfg.syswrite("after_fork { |s,w|\n")
509
+ ucfg.syswrite(" s.listen('#{tmp.path}', :backlog => 5, :sndbuf => 8192)\n")
510
+ ucfg.syswrite(" s.listen('#@addr:#{port2}', :rcvbuf => 8192)\n")
511
+ ucfg.syswrite("\n}\n")
512
+ pid = xfork do
513
+ redirect_test_io { exec($unicorn_bin, "-c#{ucfg.path}") }
514
+ end
515
+ results = retry_hit(["http://#{@addr}:#{@port}/"])
516
+ assert_equal String, results[0].class
517
+ worker_pid = results[0].to_i
518
+ assert_not_equal pid, worker_pid
519
+ s = unix_socket(tmp.path)
520
+ s.syswrite("GET / HTTP/1.0\r\n\r\n")
521
+ results = ''
522
+ loop { results << s.sysread(4096) } rescue nil
523
+ s.close
524
+ assert_equal worker_pid, results.split(/\r\n/).last.to_i
525
+ results = hit(["http://#@addr:#{port2}/"])
526
+ assert_equal String, results[0].class
527
+ assert_equal worker_pid, results[0].to_i
528
+ assert_shutdown(pid)
529
+ end
530
+
531
+ def test_unicorn_config_listen_augments_cli
532
+ port2 = unused_port(@addr)
533
+ File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
534
+ ucfg = Tempfile.new('unicorn_test_config')
535
+ ucfg.syswrite("listen '#{@addr}:#{@port}'\n")
536
+ pid = xfork do
537
+ redirect_test_io do
538
+ exec($unicorn_bin, "-c#{ucfg.path}", "-l#{@addr}:#{port2}")
539
+ end
540
+ end
541
+ uris = [@port, port2].map { |i| "http://#{@addr}:#{i}/" }
542
+ results = retry_hit(uris)
543
+ assert_equal results.size, uris.size
544
+ assert_equal String, results[0].class
545
+ assert_equal String, results[1].class
546
+ assert_shutdown(pid)
547
+ end
548
+
549
+ def test_weird_config_settings
550
+ File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
551
+ ucfg = Tempfile.new('unicorn_test_config')
552
+ proc_total = HEAVY_WORKERS + 1 # + 1 for master
553
+ ucfg.syswrite(HEAVY_CFG)
554
+ pid = xfork do
555
+ redirect_test_io do
556
+ exec($unicorn_bin, "-c#{ucfg.path}", "-l#{@addr}:#{@port}")
557
+ end
558
+ end
559
+
560
+ results = retry_hit(["http://#{@addr}:#{@port}/"])
561
+ assert_equal String, results[0].class
562
+ wait_master_ready(COMMON_TMP.path)
563
+ wait_workers_ready(COMMON_TMP.path, HEAVY_WORKERS)
564
+ bf = File.readlines(COMMON_TMP.path).grep(/\bbefore_fork: worker=/)
565
+ assert_equal HEAVY_WORKERS, bf.size
566
+ rotate = Tempfile.new('unicorn_rotate')
567
+
568
+ File.rename(COMMON_TMP.path, rotate.path)
569
+ Process.kill(:USR1, pid)
570
+
571
+ wait_for_file(COMMON_TMP.path)
572
+ assert File.exist?(COMMON_TMP.path), "#{COMMON_TMP.path} exists"
573
+ # USR1 should've been passed to all workers
574
+ tries = DEFAULT_TRIES
575
+ log = File.readlines(rotate.path)
576
+ while (tries -= 1) > 0 &&
577
+ log.grep(/reopening logs\.\.\./).size < proc_total
578
+ sleep DEFAULT_RES
579
+ log = File.readlines(rotate.path)
580
+ end
581
+ assert_equal proc_total, log.grep(/reopening logs\.\.\./).size
582
+ assert_equal 0, log.grep(/done reopening logs/).size
583
+
584
+ tries = DEFAULT_TRIES
585
+ log = File.readlines(COMMON_TMP.path)
586
+ while (tries -= 1) > 0 && log.grep(/done reopening logs/).size < proc_total
587
+ sleep DEFAULT_RES
588
+ log = File.readlines(COMMON_TMP.path)
589
+ end
590
+ assert_equal proc_total, log.grep(/done reopening logs/).size
591
+ assert_equal 0, log.grep(/reopening logs\.\.\./).size
592
+
593
+ Process.kill(:QUIT, pid)
594
+ pid, status = Process.waitpid2(pid)
595
+
596
+ assert status.success?, "exited successfully"
597
+ end
598
+
599
+ def test_read_embedded_cli_switches
600
+ File.open("config.ru", "wb") do |fp|
601
+ fp.syswrite("#\\ -p #{@port} -o #{@addr}\n")
602
+ fp.syswrite(HI)
603
+ end
604
+ pid = fork { redirect_test_io { exec($unicorn_bin) } }
605
+ results = retry_hit(["http://#{@addr}:#{@port}/"])
606
+ assert_equal String, results[0].class
607
+ assert_shutdown(pid)
608
+ end
609
+
610
+ def test_load_module
611
+ libdir = "#{@tmpdir}/lib"
612
+ FileUtils.mkpath([ libdir ])
613
+ config_path = "#{libdir}/hello.rb"
614
+ File.open(config_path, "wb") { |fp| fp.syswrite(HELLO) }
615
+ pid = fork do
616
+ redirect_test_io do
617
+ Dir.chdir("/")
618
+ exec($unicorn_bin, "-l#{@addr}:#{@port}", config_path)
619
+ end
620
+ end
621
+ results = retry_hit(["http://#{@addr}:#{@port}/"])
622
+ assert_equal String, results[0].class
623
+ assert_shutdown(pid)
624
+ end
625
+
626
+ def test_reexec
627
+ File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
628
+ pid_file = "#{@tmpdir}/test.pid"
629
+ pid = fork do
630
+ redirect_test_io do
631
+ exec($unicorn_bin, "-l#{@addr}:#{@port}", "-P#{pid_file}")
632
+ end
633
+ end
634
+ reexec_basic_test(pid, pid_file)
635
+ end
636
+
637
+ def test_reexec_alt_config
638
+ config_file = "#{@tmpdir}/foo.ru"
639
+ File.open(config_file, "wb") { |fp| fp.syswrite(HI) }
640
+ pid_file = "#{@tmpdir}/test.pid"
641
+ pid = fork do
642
+ redirect_test_io do
643
+ exec($unicorn_bin, "-l#{@addr}:#{@port}", "-P#{pid_file}", config_file)
644
+ end
645
+ end
646
+ reexec_basic_test(pid, pid_file)
647
+ end
648
+
649
+ def test_socket_unlinked_restore
650
+ results = nil
651
+ sock = Tempfile.new('unicorn_test_sock')
652
+ sock_path = sock.path
653
+ @sockets << sock_path
654
+ sock.close!
655
+ ucfg = Tempfile.new('unicorn_test_config')
656
+ ucfg.syswrite("listen \"#{sock_path}\"\n")
657
+
658
+ File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
659
+ pid = xfork { redirect_test_io { exec($unicorn_bin, "-c#{ucfg.path}") } }
660
+ wait_for_file(sock_path)
661
+ assert File.socket?(sock_path)
662
+
663
+ sock = unix_socket(sock_path)
664
+ sock.syswrite("GET / HTTP/1.0\r\n\r\n")
665
+ results = sock.sysread(4096)
666
+
667
+ assert_equal String, results.class
668
+ File.unlink(sock_path)
669
+ Process.kill(:HUP, pid)
670
+ wait_for_file(sock_path)
671
+ assert File.socket?(sock_path)
672
+
673
+ sock = unix_socket(sock_path)
674
+ sock.syswrite("GET / HTTP/1.0\r\n\r\n")
675
+ results = sock.sysread(4096)
676
+
677
+ assert_equal String, results.class
678
+ end
679
+
680
+ def test_unicorn_config_file
681
+ pid_file = "#{@tmpdir}/test.pid"
682
+ sock = Tempfile.new('unicorn_test_sock')
683
+ sock_path = sock.path
684
+ sock.close!
685
+ @sockets << sock_path
686
+
687
+ log = Tempfile.new('unicorn_test_log')
688
+ ucfg = Tempfile.new('unicorn_test_config')
689
+ ucfg.syswrite("listen \"#{sock_path}\"\n")
690
+ ucfg.syswrite("pid \"#{pid_file}\"\n")
691
+ ucfg.syswrite("logger Logger.new('#{log.path}')\n")
692
+ ucfg.close
693
+
694
+ File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
695
+ pid = xfork do
696
+ redirect_test_io do
697
+ exec($unicorn_bin, "-l#{@addr}:#{@port}",
698
+ "-P#{pid_file}", "-c#{ucfg.path}")
699
+ end
700
+ end
701
+ results = retry_hit(["http://#{@addr}:#{@port}/"])
702
+ assert_equal String, results[0].class
703
+ wait_master_ready(log.path)
704
+ assert File.exist?(pid_file), "pid_file created"
705
+ assert_equal pid, File.read(pid_file).to_i
706
+ assert File.socket?(sock_path), "socket created"
707
+
708
+ sock = unix_socket(sock_path)
709
+ sock.syswrite("GET / HTTP/1.0\r\n\r\n")
710
+ results = sock.sysread(4096)
711
+
712
+ assert_equal String, results.class
713
+
714
+ # try reloading the config
715
+ sock = Tempfile.new('new_test_sock')
716
+ new_sock_path = sock.path
717
+ @sockets << new_sock_path
718
+ sock.close!
719
+ new_log = Tempfile.new('unicorn_test_log')
720
+ new_log.sync = true
721
+ assert_equal 0, new_log.size
722
+
723
+ ucfg = File.open(ucfg.path, "wb")
724
+ ucfg.syswrite("listen \"#{sock_path}\"\n")
725
+ ucfg.syswrite("listen \"#{new_sock_path}\"\n")
726
+ ucfg.syswrite("pid \"#{pid_file}\"\n")
727
+ ucfg.syswrite("logger Logger.new('#{new_log.path}')\n")
728
+ ucfg.close
729
+ Process.kill(:HUP, pid)
730
+
731
+ wait_for_file(new_sock_path)
732
+ assert File.socket?(new_sock_path), "socket exists"
733
+ @sockets.each do |path|
734
+ sock = unix_socket(path)
735
+ sock.syswrite("GET / HTTP/1.0\r\n\r\n")
736
+ results = sock.sysread(4096)
737
+ assert_equal String, results.class
738
+ end
739
+
740
+ assert_not_equal 0, new_log.size
741
+ reexec_usr2_quit_test(pid, pid_file)
742
+ end
743
+
744
+ def test_daemonize_reexec
745
+ pid_file = "#{@tmpdir}/test.pid"
746
+ log = Tempfile.new('unicorn_test_log')
747
+ ucfg = Tempfile.new('unicorn_test_config')
748
+ ucfg.syswrite("pid \"#{pid_file}\"\n")
749
+ ucfg.syswrite("logger Logger.new('#{log.path}')\n")
750
+ ucfg.close
751
+
752
+ File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
753
+ pid = xfork do
754
+ redirect_test_io do
755
+ exec($unicorn_bin, "-D", "-l#{@addr}:#{@port}", "-c#{ucfg.path}")
756
+ end
757
+ end
758
+ results = retry_hit(["http://#{@addr}:#{@port}/"])
759
+ assert_equal String, results[0].class
760
+ wait_for_file(pid_file)
761
+ new_pid = File.read(pid_file).to_i
762
+ assert_not_equal pid, new_pid
763
+ pid, status = Process.waitpid2(pid)
764
+ assert status.success?, "original process exited successfully"
765
+ Process.kill(0, new_pid)
766
+ reexec_usr2_quit_test(new_pid, pid_file)
767
+ end
768
+
769
+ def test_daemonize_redirect_fail
770
+ pid_file = "#{@tmpdir}/test.pid"
771
+ ucfg = Tempfile.new('unicorn_test_config')
772
+ ucfg.syswrite("pid #{pid_file}\"\n")
773
+ err = Tempfile.new('stderr')
774
+ out = Tempfile.new('stdout ')
775
+
776
+ File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
777
+ pid = xfork do
778
+ $stderr.reopen(err.path, "a")
779
+ $stdout.reopen(out.path, "a")
780
+ exec($unicorn_bin, "-D", "-l#{@addr}:#{@port}", "-c#{ucfg.path}")
781
+ end
782
+ pid, status = Process.waitpid2(pid)
783
+ assert ! status.success?, "original process exited successfully"
784
+ sleep 1 # can't waitpid on a daemonized process :<
785
+ assert err.stat.size > 0
786
+ end
787
+
788
+ def test_reexec_fd_leak
789
+ unless RUBY_PLATFORM =~ /linux/ # Solaris may work, too, but I forget...
790
+ warn "FD leak test only works on Linux at the moment"
791
+ return
792
+ end
793
+ pid_file = "#{@tmpdir}/test.pid"
794
+ log = Tempfile.new('unicorn_test_log')
795
+ log.sync = true
796
+ ucfg = Tempfile.new('unicorn_test_config')
797
+ ucfg.syswrite("pid \"#{pid_file}\"\n")
798
+ ucfg.syswrite("logger Logger.new('#{log.path}')\n")
799
+ ucfg.syswrite("stderr_path '#{log.path}'\n")
800
+ ucfg.syswrite("stdout_path '#{log.path}'\n")
801
+ ucfg.close
802
+
803
+ File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
804
+ pid = xfork do
805
+ redirect_test_io do
806
+ exec($unicorn_bin, "-D", "-l#{@addr}:#{@port}", "-c#{ucfg.path}")
807
+ end
808
+ end
809
+
810
+ wait_master_ready(log.path)
811
+ wait_workers_ready(log.path, 1)
812
+ File.truncate(log.path, 0)
813
+ wait_for_file(pid_file)
814
+ orig_pid = pid = File.read(pid_file).to_i
815
+ orig_fds = `ls -l /proc/#{pid}/fd`.split(/\n/)
816
+ assert $?.success?
817
+ expect_size = orig_fds.size
818
+
819
+ Process.kill(:USR2, pid)
820
+ wait_for_file("#{pid_file}.oldbin")
821
+ Process.kill(:QUIT, pid)
822
+
823
+ wait_for_death(pid)
824
+
825
+ wait_master_ready(log.path)
826
+ wait_workers_ready(log.path, 1)
827
+ File.truncate(log.path, 0)
828
+ wait_for_file(pid_file)
829
+ pid = File.read(pid_file).to_i
830
+ assert_not_equal orig_pid, pid
831
+ curr_fds = `ls -l /proc/#{pid}/fd`.split(/\n/)
832
+ assert $?.success?
833
+
834
+ # we could've inherited descriptors the first time around
835
+ assert expect_size >= curr_fds.size, curr_fds.inspect
836
+ expect_size = curr_fds.size
837
+
838
+ Process.kill(:USR2, pid)
839
+ wait_for_file("#{pid_file}.oldbin")
840
+ Process.kill(:QUIT, pid)
841
+
842
+ wait_for_death(pid)
843
+
844
+ wait_master_ready(log.path)
845
+ wait_workers_ready(log.path, 1)
846
+ File.truncate(log.path, 0)
847
+ wait_for_file(pid_file)
848
+ pid = File.read(pid_file).to_i
849
+ curr_fds = `ls -l /proc/#{pid}/fd`.split(/\n/)
850
+ assert $?.success?
851
+ assert_equal expect_size, curr_fds.size, curr_fds.inspect
852
+
853
+ Process.kill(:QUIT, pid)
854
+ wait_for_death(pid)
855
+ end
856
+
857
+ def hup_test_common(preload, check_client=false)
858
+ File.open("config.ru", "wb") { |fp| fp.syswrite(HI.gsub("HI", '#$$')) }
859
+ pid_file = Tempfile.new('pid')
860
+ ucfg = Tempfile.new('unicorn_test_config')
861
+ ucfg.syswrite("listen '#@addr:#@port'\n")
862
+ ucfg.syswrite("pid '#{pid_file.path}'\n")
863
+ ucfg.syswrite("preload_app true\n") if preload
864
+ ucfg.syswrite("check_client_connection true\n") if check_client
865
+ ucfg.syswrite("stderr_path 'test_stderr.#$$.log'\n")
866
+ ucfg.syswrite("stdout_path 'test_stdout.#$$.log'\n")
867
+ pid = xfork {
868
+ redirect_test_io { exec($unicorn_bin, "-D", "-c", ucfg.path) }
869
+ }
870
+ _, status = Process.waitpid2(pid)
871
+ assert status.success?
872
+ wait_master_ready("test_stderr.#$$.log")
873
+ wait_workers_ready("test_stderr.#$$.log", 1)
874
+ uri = URI.parse("http://#@addr:#@port/")
875
+ pids = Tempfile.new('worker_pids')
876
+ r, w = IO.pipe
877
+ hitter = fork {
878
+ r.close
879
+ bodies = Hash.new(0)
880
+ at_exit { pids.syswrite(bodies.inspect) }
881
+ trap(:TERM) { exit(0) }
882
+ nr = 0
883
+ loop {
884
+ rv = Net::HTTP.get(uri)
885
+ pid = rv.to_i
886
+ exit!(1) if pid <= 0
887
+ bodies[pid] += 1
888
+ nr += 1
889
+ if nr == 1
890
+ w.syswrite('1')
891
+ elsif bodies.size > 1
892
+ w.syswrite('2')
893
+ sleep
894
+ end
895
+ }
896
+ }
897
+ w.close
898
+ assert_equal '1', r.read(1)
899
+ daemon_pid = File.read(pid_file.path).to_i
900
+ assert daemon_pid > 0
901
+ Process.kill(:HUP, daemon_pid)
902
+ assert_equal '2', r.read(1)
903
+ Process.kill(:TERM, hitter)
904
+ _, hitter_status = Process.waitpid2(hitter)
905
+ assert(hitter_status.success?,
906
+ "invalid: #{hitter_status.inspect} #{File.read(pids.path)}" \
907
+ "#{File.read("test_stderr.#$$.log")}")
908
+ pids.sysseek(0)
909
+ pids = eval(pids.read)
910
+ assert_kind_of(Hash, pids)
911
+ assert_equal 2, pids.size
912
+ pids.keys.each { |x|
913
+ assert_kind_of(Integer, x)
914
+ assert x > 0
915
+ assert pids[x] > 0
916
+ }
917
+ Process.kill(:QUIT, daemon_pid)
918
+ wait_for_death(daemon_pid)
919
+ end
920
+
921
+ def test_preload_app_hup
922
+ hup_test_common(true)
923
+ end
924
+
925
+ def test_hup
926
+ hup_test_common(false)
927
+ end
928
+
929
+ def test_check_client_hup
930
+ hup_test_common(false, true)
931
+ end
932
+
933
+ def test_default_listen_hup_holds_listener
934
+ default_listen_lock do
935
+ res, pid_path = default_listen_setup
936
+ daemon_pid = File.read(pid_path).to_i
937
+ Process.kill(:HUP, daemon_pid)
938
+ wait_workers_ready("test_stderr.#$$.log", 1)
939
+ res2 = hit(["http://#{Unicorn::Const::DEFAULT_LISTEN}/"])
940
+ assert_match %r{\d+}, res2.first
941
+ assert res2.first != res.first
942
+ Process.kill(:QUIT, daemon_pid)
943
+ wait_for_death(daemon_pid)
944
+ end
945
+ end
946
+
947
+ def test_default_listen_upgrade_holds_listener
948
+ default_listen_lock do
949
+ res, pid_path = default_listen_setup
950
+ daemon_pid = File.read(pid_path).to_i
951
+
952
+ Process.kill(:USR2, daemon_pid)
953
+ wait_for_file("#{pid_path}.oldbin")
954
+ wait_for_file(pid_path)
955
+ Process.kill(:QUIT, daemon_pid)
956
+ wait_for_death(daemon_pid)
957
+
958
+ daemon_pid = File.read(pid_path).to_i
959
+ wait_workers_ready("test_stderr.#$$.log", 1)
960
+ File.truncate("test_stderr.#$$.log", 0)
961
+
962
+ res2 = hit(["http://#{Unicorn::Const::DEFAULT_LISTEN}/"])
963
+ assert_match %r{\d+}, res2.first
964
+ assert res2.first != res.first
965
+
966
+ Process.kill(:HUP, daemon_pid)
967
+ wait_workers_ready("test_stderr.#$$.log", 1)
968
+ File.truncate("test_stderr.#$$.log", 0)
969
+ res3 = hit(["http://#{Unicorn::Const::DEFAULT_LISTEN}/"])
970
+ assert res2.first != res3.first
971
+
972
+ Process.kill(:QUIT, daemon_pid)
973
+ wait_for_death(daemon_pid)
974
+ end
975
+ end
976
+
977
+ def default_listen_setup
978
+ File.open("config.ru", "wb") { |fp| fp.syswrite(HI.gsub("HI", '#$$')) }
979
+ pid_path = (tmp = Tempfile.new('pid')).path
980
+ tmp.close!
981
+ ucfg = Tempfile.new('unicorn_test_config')
982
+ ucfg.syswrite("pid '#{pid_path}'\n")
983
+ ucfg.syswrite("stderr_path 'test_stderr.#$$.log'\n")
984
+ ucfg.syswrite("stdout_path 'test_stdout.#$$.log'\n")
985
+ pid = xfork {
986
+ redirect_test_io { exec($unicorn_bin, "-D", "-c", ucfg.path) }
987
+ }
988
+ _, status = Process.waitpid2(pid)
989
+ assert status.success?
990
+ wait_master_ready("test_stderr.#$$.log")
991
+ wait_workers_ready("test_stderr.#$$.log", 1)
992
+ File.truncate("test_stderr.#$$.log", 0)
993
+ res = hit(["http://#{Unicorn::Const::DEFAULT_LISTEN}/"])
994
+ assert_match %r{\d+}, res.first
995
+ [ res, pid_path ]
996
+ end
997
+
998
+ # we need to flock() something to prevent these tests from running
999
+ def default_listen_lock(&block)
1000
+ fp = File.open(FLOCK_PATH, "rb")
1001
+ begin
1002
+ fp.flock(File::LOCK_EX)
1003
+ begin
1004
+ TCPServer.new(Unicorn::Const::DEFAULT_HOST,
1005
+ Unicorn::Const::DEFAULT_PORT).close
1006
+ rescue Errno::EADDRINUSE, Errno::EACCES
1007
+ warn "can't bind to #{Unicorn::Const::DEFAULT_LISTEN}"
1008
+ return false
1009
+ end
1010
+
1011
+ # unused_port should never take this, but we may run an environment
1012
+ # where tests are being run against older unicorns...
1013
+ lock_path = "#{Dir::tmpdir}/unicorn_test." \
1014
+ "#{Unicorn::Const::DEFAULT_LISTEN}.lock"
1015
+ begin
1016
+ File.open(lock_path, File::WRONLY|File::CREAT|File::EXCL, 0600)
1017
+ yield
1018
+ rescue Errno::EEXIST
1019
+ lock_path = nil
1020
+ return false
1021
+ ensure
1022
+ File.unlink(lock_path) if lock_path
1023
+ end
1024
+ ensure
1025
+ fp.flock(File::LOCK_UN)
1026
+ end
1027
+ end
1028
+
1029
+ end if do_test