unicorn-maintained 6.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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