unicorn-fork 6.1.1

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