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