giraffesoft-unicorn 0.93.5

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 (141) hide show
  1. data/.CHANGELOG.old +25 -0
  2. data/.document +16 -0
  3. data/.gitignore +20 -0
  4. data/.mailmap +26 -0
  5. data/CONTRIBUTORS +31 -0
  6. data/COPYING +339 -0
  7. data/DESIGN +105 -0
  8. data/Documentation/.gitignore +5 -0
  9. data/Documentation/GNUmakefile +30 -0
  10. data/Documentation/unicorn.1.txt +167 -0
  11. data/Documentation/unicorn_rails.1.txt +169 -0
  12. data/GIT-VERSION-GEN +40 -0
  13. data/GNUmakefile +270 -0
  14. data/HACKING +113 -0
  15. data/KNOWN_ISSUES +40 -0
  16. data/LICENSE +55 -0
  17. data/PHILOSOPHY +144 -0
  18. data/README +153 -0
  19. data/Rakefile +108 -0
  20. data/SIGNALS +97 -0
  21. data/TODO +16 -0
  22. data/TUNING +70 -0
  23. data/bin/unicorn +165 -0
  24. data/bin/unicorn_rails +208 -0
  25. data/examples/echo.ru +27 -0
  26. data/examples/git.ru +13 -0
  27. data/examples/init.sh +53 -0
  28. data/ext/unicorn_http/c_util.h +107 -0
  29. data/ext/unicorn_http/common_field_optimization.h +111 -0
  30. data/ext/unicorn_http/ext_help.h +73 -0
  31. data/ext/unicorn_http/extconf.rb +14 -0
  32. data/ext/unicorn_http/global_variables.h +91 -0
  33. data/ext/unicorn_http/unicorn_http.rl +715 -0
  34. data/ext/unicorn_http/unicorn_http_common.rl +74 -0
  35. data/lib/unicorn.rb +730 -0
  36. data/lib/unicorn/app/exec_cgi.rb +150 -0
  37. data/lib/unicorn/app/inetd.rb +109 -0
  38. data/lib/unicorn/app/old_rails.rb +31 -0
  39. data/lib/unicorn/app/old_rails/static.rb +60 -0
  40. data/lib/unicorn/cgi_wrapper.rb +145 -0
  41. data/lib/unicorn/configurator.rb +403 -0
  42. data/lib/unicorn/const.rb +37 -0
  43. data/lib/unicorn/http_request.rb +74 -0
  44. data/lib/unicorn/http_response.rb +74 -0
  45. data/lib/unicorn/launcher.rb +39 -0
  46. data/lib/unicorn/socket_helper.rb +138 -0
  47. data/lib/unicorn/tee_input.rb +174 -0
  48. data/lib/unicorn/util.rb +64 -0
  49. data/local.mk.sample +53 -0
  50. data/setup.rb +1586 -0
  51. data/test/aggregate.rb +15 -0
  52. data/test/benchmark/README +50 -0
  53. data/test/benchmark/dd.ru +18 -0
  54. data/test/exec/README +5 -0
  55. data/test/exec/test_exec.rb +855 -0
  56. data/test/rails/app-1.2.3/.gitignore +2 -0
  57. data/test/rails/app-1.2.3/Rakefile +7 -0
  58. data/test/rails/app-1.2.3/app/controllers/application.rb +6 -0
  59. data/test/rails/app-1.2.3/app/controllers/foo_controller.rb +36 -0
  60. data/test/rails/app-1.2.3/app/helpers/application_helper.rb +4 -0
  61. data/test/rails/app-1.2.3/config/boot.rb +11 -0
  62. data/test/rails/app-1.2.3/config/database.yml +12 -0
  63. data/test/rails/app-1.2.3/config/environment.rb +13 -0
  64. data/test/rails/app-1.2.3/config/environments/development.rb +9 -0
  65. data/test/rails/app-1.2.3/config/environments/production.rb +5 -0
  66. data/test/rails/app-1.2.3/config/routes.rb +6 -0
  67. data/test/rails/app-1.2.3/db/.gitignore +0 -0
  68. data/test/rails/app-1.2.3/public/404.html +1 -0
  69. data/test/rails/app-1.2.3/public/500.html +1 -0
  70. data/test/rails/app-2.0.2/.gitignore +2 -0
  71. data/test/rails/app-2.0.2/Rakefile +7 -0
  72. data/test/rails/app-2.0.2/app/controllers/application.rb +4 -0
  73. data/test/rails/app-2.0.2/app/controllers/foo_controller.rb +36 -0
  74. data/test/rails/app-2.0.2/app/helpers/application_helper.rb +4 -0
  75. data/test/rails/app-2.0.2/config/boot.rb +11 -0
  76. data/test/rails/app-2.0.2/config/database.yml +12 -0
  77. data/test/rails/app-2.0.2/config/environment.rb +17 -0
  78. data/test/rails/app-2.0.2/config/environments/development.rb +8 -0
  79. data/test/rails/app-2.0.2/config/environments/production.rb +5 -0
  80. data/test/rails/app-2.0.2/config/routes.rb +6 -0
  81. data/test/rails/app-2.0.2/db/.gitignore +0 -0
  82. data/test/rails/app-2.0.2/public/404.html +1 -0
  83. data/test/rails/app-2.0.2/public/500.html +1 -0
  84. data/test/rails/app-2.1.2/.gitignore +2 -0
  85. data/test/rails/app-2.1.2/Rakefile +7 -0
  86. data/test/rails/app-2.1.2/app/controllers/application.rb +4 -0
  87. data/test/rails/app-2.1.2/app/controllers/foo_controller.rb +36 -0
  88. data/test/rails/app-2.1.2/app/helpers/application_helper.rb +4 -0
  89. data/test/rails/app-2.1.2/config/boot.rb +111 -0
  90. data/test/rails/app-2.1.2/config/database.yml +12 -0
  91. data/test/rails/app-2.1.2/config/environment.rb +17 -0
  92. data/test/rails/app-2.1.2/config/environments/development.rb +7 -0
  93. data/test/rails/app-2.1.2/config/environments/production.rb +5 -0
  94. data/test/rails/app-2.1.2/config/routes.rb +6 -0
  95. data/test/rails/app-2.1.2/db/.gitignore +0 -0
  96. data/test/rails/app-2.1.2/public/404.html +1 -0
  97. data/test/rails/app-2.1.2/public/500.html +1 -0
  98. data/test/rails/app-2.2.2/.gitignore +2 -0
  99. data/test/rails/app-2.2.2/Rakefile +7 -0
  100. data/test/rails/app-2.2.2/app/controllers/application.rb +4 -0
  101. data/test/rails/app-2.2.2/app/controllers/foo_controller.rb +36 -0
  102. data/test/rails/app-2.2.2/app/helpers/application_helper.rb +4 -0
  103. data/test/rails/app-2.2.2/config/boot.rb +111 -0
  104. data/test/rails/app-2.2.2/config/database.yml +12 -0
  105. data/test/rails/app-2.2.2/config/environment.rb +17 -0
  106. data/test/rails/app-2.2.2/config/environments/development.rb +7 -0
  107. data/test/rails/app-2.2.2/config/environments/production.rb +5 -0
  108. data/test/rails/app-2.2.2/config/routes.rb +6 -0
  109. data/test/rails/app-2.2.2/db/.gitignore +0 -0
  110. data/test/rails/app-2.2.2/public/404.html +1 -0
  111. data/test/rails/app-2.2.2/public/500.html +1 -0
  112. data/test/rails/app-2.3.3.1/.gitignore +2 -0
  113. data/test/rails/app-2.3.3.1/Rakefile +7 -0
  114. data/test/rails/app-2.3.3.1/app/controllers/application_controller.rb +5 -0
  115. data/test/rails/app-2.3.3.1/app/controllers/foo_controller.rb +36 -0
  116. data/test/rails/app-2.3.3.1/app/helpers/application_helper.rb +4 -0
  117. data/test/rails/app-2.3.3.1/config/boot.rb +109 -0
  118. data/test/rails/app-2.3.3.1/config/database.yml +12 -0
  119. data/test/rails/app-2.3.3.1/config/environment.rb +17 -0
  120. data/test/rails/app-2.3.3.1/config/environments/development.rb +7 -0
  121. data/test/rails/app-2.3.3.1/config/environments/production.rb +6 -0
  122. data/test/rails/app-2.3.3.1/config/routes.rb +6 -0
  123. data/test/rails/app-2.3.3.1/db/.gitignore +0 -0
  124. data/test/rails/app-2.3.3.1/public/404.html +1 -0
  125. data/test/rails/app-2.3.3.1/public/500.html +1 -0
  126. data/test/rails/app-2.3.3.1/public/x.txt +1 -0
  127. data/test/rails/test_rails.rb +280 -0
  128. data/test/test_helper.rb +296 -0
  129. data/test/unit/test_configurator.rb +150 -0
  130. data/test/unit/test_http_parser.rb +492 -0
  131. data/test/unit/test_http_parser_ng.rb +308 -0
  132. data/test/unit/test_request.rb +184 -0
  133. data/test/unit/test_response.rb +110 -0
  134. data/test/unit/test_server.rb +188 -0
  135. data/test/unit/test_signals.rb +202 -0
  136. data/test/unit/test_socket_helper.rb +133 -0
  137. data/test/unit/test_tee_input.rb +229 -0
  138. data/test/unit/test_upload.rb +297 -0
  139. data/test/unit/test_util.rb +96 -0
  140. data/unicorn.gemspec +42 -0
  141. metadata +228 -0
data/test/aggregate.rb ADDED
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/ruby -n
2
+ # -*- encoding: binary -*-
3
+
4
+ BEGIN { $tests = $assertions = $failures = $errors = 0 }
5
+
6
+ $_ =~ /(\d+) tests, (\d+) assertions, (\d+) failures, (\d+) errors/ or next
7
+ $tests += $1.to_i
8
+ $assertions += $2.to_i
9
+ $failures += $3.to_i
10
+ $errors += $4.to_i
11
+
12
+ END {
13
+ printf("\n%d tests, %d assertions, %d failures, %d errors\n",
14
+ $tests, $assertions, $failures, $errors)
15
+ }
@@ -0,0 +1,50 @@
1
+ = Performance
2
+
3
+ Unicorn is pretty fast, and we want it to get faster. Unicorn strives
4
+ to get HTTP requests to your application and write HTTP responses back
5
+ as quickly as possible. Unicorn does not do any background processing
6
+ while your app runs, so your app will get all the CPU time provided to
7
+ it by your OS kernel.
8
+
9
+ A gentle reminder: Unicorn is NOT for serving clients over slow network
10
+ connections. Use nginx (or something similar) to complement Unicorn if
11
+ you have slow clients.
12
+
13
+ == dd.ru
14
+
15
+ This is a pure I/O benchmark. In the context of Unicorn, this is the
16
+ only one that matters. It is a standard rackup-compatible .ru file and
17
+ may be used with other Rack-compatible servers.
18
+
19
+ unicorn -E none dd.ru
20
+
21
+ You can change the size and number of chunks in the response with
22
+ the "bs" and "count" environment variables. The following command
23
+ will cause dd.ru to return 4 chunks of 16384 bytes each, leading to
24
+ 65536 byte response:
25
+
26
+ bs=16384 count=4 unicorn -E none dd.ru
27
+
28
+ Or if you want to add logging (small performance impact):
29
+
30
+ unicorn -E deployment dd.ru
31
+
32
+ Eric runs then runs clients on a LAN it in several different ways:
33
+
34
+ client@host1 -> unicorn@host1(tcp)
35
+ client@host2 -> unicorn@host1(tcp)
36
+ client@host3 -> nginx@host1 -> unicorn@host1(tcp)
37
+ client@host3 -> nginx@host1 -> unicorn@host1(unix)
38
+ client@host3 -> nginx@host2 -> unicorn@host1(tcp)
39
+
40
+ The benchmark client is usually httperf.
41
+
42
+ Another gentle reminder: performance with slow networks/clients
43
+ is NOT our problem. That is the job of nginx (or similar).
44
+
45
+ == Contributors
46
+
47
+ This directory is maintained independently in the "benchmark" branch
48
+ based against v0.1.0. Only changes to this directory (test/benchmarks)
49
+ are committed to this branch although the master branch may merge this
50
+ branch occassionaly.
@@ -0,0 +1,18 @@
1
+ # This benchmark is the simplest test of the I/O facilities in
2
+ # unicorn. It is meant to return a fixed-sized blob to test
3
+ # the performance of things in Unicorn, _NOT_ the app.
4
+ #
5
+ # Adjusting this benchmark is done via the "bs" (byte size) and "count"
6
+ # environment variables. "count" designates the count of elements of
7
+ # "bs" length in the Rack response body. The defaults are bs=4096, count=1
8
+ # to return one 4096-byte chunk.
9
+ bs = ENV['bs'] ? ENV['bs'].to_i : 4096
10
+ count = ENV['count'] ? ENV['count'].to_i : 1
11
+ slice = (' ' * bs).freeze
12
+ body = (1..count).map { slice }.freeze
13
+ hdr = {
14
+ 'Content-Length' => (bs * count).to_s.freeze,
15
+ 'Content-Type' => 'text/plain'.freeze
16
+ }.freeze
17
+ response = [ 200, hdr, body ].freeze
18
+ run(lambda { |env| response })
data/test/exec/README ADDED
@@ -0,0 +1,5 @@
1
+ These tests require the "unicorn" executable script to be installed in
2
+ PATH and rack being directly "require"-able ("rubygems" will not be
3
+ loaded for you). The tester is responsible for setting up RUBYLIB and
4
+ PATH environment variables (or running tests via GNU Make instead of
5
+ Rake).
@@ -0,0 +1,855 @@
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
+ HELLO = <<-EOS
32
+ class Hello
33
+ def call(env)
34
+ [ 200, { 'Content-Type' => 'text/plain' }, [ "HI\\n" ] ]
35
+ end
36
+ end
37
+ EOS
38
+
39
+ COMMON_TMP = Tempfile.new('unicorn_tmp') unless defined?(COMMON_TMP)
40
+
41
+ HEAVY_CFG = <<-EOS
42
+ worker_processes 4
43
+ timeout 30
44
+ logger Logger.new('#{COMMON_TMP.path}')
45
+ before_fork do |server, worker|
46
+ server.logger.info "before_fork: worker=\#{worker.nr}"
47
+ end
48
+ EOS
49
+
50
+ def setup
51
+ @pwd = Dir.pwd
52
+ @tmpfile = Tempfile.new('unicorn_exec_test')
53
+ @tmpdir = @tmpfile.path
54
+ @tmpfile.close!
55
+ Dir.mkdir(@tmpdir)
56
+ Dir.chdir(@tmpdir)
57
+ @addr = ENV['UNICORN_TEST_ADDR'] || '127.0.0.1'
58
+ @port = unused_port(@addr)
59
+ @sockets = []
60
+ @start_pid = $$
61
+ end
62
+
63
+ def teardown
64
+ return if @start_pid != $$
65
+ Dir.chdir(@pwd)
66
+ FileUtils.rmtree(@tmpdir)
67
+ @sockets.each { |path| File.unlink(path) rescue nil }
68
+ loop do
69
+ Process.kill('-QUIT', 0)
70
+ begin
71
+ Process.waitpid(-1, Process::WNOHANG) or break
72
+ rescue Errno::ECHILD
73
+ break
74
+ end
75
+ end
76
+ end
77
+
78
+ def test_exit_signals
79
+ %w(INT TERM QUIT).each do |sig|
80
+ File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
81
+ pid = xfork { redirect_test_io { exec($unicorn_bin, "-l#@addr:#@port") } }
82
+ wait_master_ready("test_stderr.#{pid}.log")
83
+ wait_workers_ready("test_stderr.#{pid}.log", 1)
84
+ status = nil
85
+ assert_nothing_raised do
86
+ Process.kill(sig, pid)
87
+ pid, status = Process.waitpid2(pid)
88
+ end
89
+ reaped = File.readlines("test_stderr.#{pid}.log").grep(/reaped/)
90
+ assert_equal 1, reaped.size
91
+ assert status.exited?
92
+ end
93
+ end
94
+
95
+ def test_basic
96
+ File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
97
+ pid = fork do
98
+ redirect_test_io { exec($unicorn_bin, "-l", "#{@addr}:#{@port}") }
99
+ end
100
+ results = retry_hit(["http://#{@addr}:#{@port}/"])
101
+ assert_equal String, results[0].class
102
+ assert_shutdown(pid)
103
+ end
104
+
105
+ def test_ttin_ttou
106
+ File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
107
+ pid = fork { redirect_test_io { exec($unicorn_bin, "-l#@addr:#@port") } }
108
+ log = "test_stderr.#{pid}.log"
109
+ wait_master_ready(log)
110
+ [ 2, 3].each { |i|
111
+ assert_nothing_raised { Process.kill(:TTIN, pid) }
112
+ wait_workers_ready(log, i)
113
+ }
114
+ File.truncate(log, 0)
115
+ reaped = nil
116
+ [ 2, 1, 0].each { |i|
117
+ assert_nothing_raised { Process.kill(:TTOU, pid) }
118
+ DEFAULT_TRIES.times {
119
+ sleep DEFAULT_RES
120
+ reaped = File.readlines(log).grep(/reaped.*\s*worker=#{i}$/)
121
+ break if reaped.size == 1
122
+ }
123
+ assert_equal 1, reaped.size
124
+ }
125
+ end
126
+
127
+ def test_help
128
+ redirect_test_io do
129
+ assert(system($unicorn_bin, "-h"), "help text returns true")
130
+ end
131
+ assert_equal 0, File.stat("test_stderr.#$$.log").size
132
+ assert_not_equal 0, File.stat("test_stdout.#$$.log").size
133
+ lines = File.readlines("test_stdout.#$$.log")
134
+
135
+ # Be considerate of the on-call technician working from their
136
+ # mobile phone or netbook on a slow connection :)
137
+ assert lines.size <= 24, "help height fits in an ANSI terminal window"
138
+ lines.each do |line|
139
+ assert line.size <= 80, "help width fits in an ANSI terminal window"
140
+ end
141
+ end
142
+
143
+ def test_broken_reexec_config
144
+ File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
145
+ pid_file = "#{@tmpdir}/test.pid"
146
+ old_file = "#{pid_file}.oldbin"
147
+ ucfg = Tempfile.new('unicorn_test_config')
148
+ ucfg.syswrite("listen %(#@addr:#@port)\n")
149
+ ucfg.syswrite("pid %(#{pid_file})\n")
150
+ ucfg.syswrite("logger Logger.new(%(#{@tmpdir}/log))\n")
151
+ pid = xfork do
152
+ redirect_test_io do
153
+ exec($unicorn_bin, "-D", "-l#{@addr}:#{@port}", "-c#{ucfg.path}")
154
+ end
155
+ end
156
+ results = retry_hit(["http://#{@addr}:#{@port}/"])
157
+ assert_equal String, results[0].class
158
+
159
+ wait_for_file(pid_file)
160
+ Process.waitpid(pid)
161
+ Process.kill(:USR2, File.read(pid_file).to_i)
162
+ wait_for_file(old_file)
163
+ wait_for_file(pid_file)
164
+ old_pid = File.read(old_file).to_i
165
+ Process.kill(:QUIT, old_pid)
166
+ wait_for_death(old_pid)
167
+
168
+ ucfg.syswrite("timeout %(#{pid_file})\n") # introduce a bug
169
+ current_pid = File.read(pid_file).to_i
170
+ Process.kill(:USR2, current_pid)
171
+
172
+ # wait for pid_file to restore itself
173
+ tries = DEFAULT_TRIES
174
+ begin
175
+ while current_pid != File.read(pid_file).to_i
176
+ sleep(DEFAULT_RES) and (tries -= 1) > 0
177
+ end
178
+ rescue Errno::ENOENT
179
+ (sleep(DEFAULT_RES) and (tries -= 1) > 0) and retry
180
+ end
181
+ assert_equal current_pid, File.read(pid_file).to_i
182
+
183
+ tries = DEFAULT_TRIES
184
+ while File.exist?(old_file)
185
+ (sleep(DEFAULT_RES) and (tries -= 1) > 0) or break
186
+ end
187
+ assert ! File.exist?(old_file), "oldbin=#{old_file} gone"
188
+ port2 = unused_port(@addr)
189
+
190
+ # fix the bug
191
+ ucfg.sysseek(0)
192
+ ucfg.truncate(0)
193
+ ucfg.syswrite("listen %(#@addr:#@port)\n")
194
+ ucfg.syswrite("listen %(#@addr:#{port2})\n")
195
+ ucfg.syswrite("pid %(#{pid_file})\n")
196
+ assert_nothing_raised { Process.kill(:USR2, current_pid) }
197
+
198
+ wait_for_file(old_file)
199
+ wait_for_file(pid_file)
200
+ new_pid = File.read(pid_file).to_i
201
+ assert_not_equal current_pid, new_pid
202
+ assert_equal current_pid, File.read(old_file).to_i
203
+ results = retry_hit(["http://#{@addr}:#{@port}/",
204
+ "http://#{@addr}:#{port2}/"])
205
+ assert_equal String, results[0].class
206
+ assert_equal String, results[1].class
207
+
208
+ assert_nothing_raised do
209
+ Process.kill(:QUIT, current_pid)
210
+ Process.kill(:QUIT, new_pid)
211
+ end
212
+ end
213
+
214
+ def test_broken_reexec_ru
215
+ File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
216
+ pid_file = "#{@tmpdir}/test.pid"
217
+ old_file = "#{pid_file}.oldbin"
218
+ ucfg = Tempfile.new('unicorn_test_config')
219
+ ucfg.syswrite("pid %(#{pid_file})\n")
220
+ ucfg.syswrite("logger Logger.new(%(#{@tmpdir}/log))\n")
221
+ pid = xfork do
222
+ redirect_test_io do
223
+ exec($unicorn_bin, "-D", "-l#{@addr}:#{@port}", "-c#{ucfg.path}")
224
+ end
225
+ end
226
+ results = retry_hit(["http://#{@addr}:#{@port}/"])
227
+ assert_equal String, results[0].class
228
+
229
+ wait_for_file(pid_file)
230
+ Process.waitpid(pid)
231
+ Process.kill(:USR2, File.read(pid_file).to_i)
232
+ wait_for_file(old_file)
233
+ wait_for_file(pid_file)
234
+ old_pid = File.read(old_file).to_i
235
+ Process.kill(:QUIT, old_pid)
236
+ wait_for_death(old_pid)
237
+
238
+ File.unlink("config.ru") # break reloading
239
+ current_pid = File.read(pid_file).to_i
240
+ Process.kill(:USR2, current_pid)
241
+
242
+ # wait for pid_file to restore itself
243
+ tries = DEFAULT_TRIES
244
+ begin
245
+ while current_pid != File.read(pid_file).to_i
246
+ sleep(DEFAULT_RES) and (tries -= 1) > 0
247
+ end
248
+ rescue Errno::ENOENT
249
+ (sleep(DEFAULT_RES) and (tries -= 1) > 0) and retry
250
+ end
251
+
252
+ tries = DEFAULT_TRIES
253
+ while File.exist?(old_file)
254
+ (sleep(DEFAULT_RES) and (tries -= 1) > 0) or break
255
+ end
256
+ assert ! File.exist?(old_file), "oldbin=#{old_file} gone"
257
+ assert_equal current_pid, File.read(pid_file).to_i
258
+
259
+ # fix the bug
260
+ File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
261
+ assert_nothing_raised { Process.kill(:USR2, current_pid) }
262
+ wait_for_file(old_file)
263
+ wait_for_file(pid_file)
264
+ new_pid = File.read(pid_file).to_i
265
+ assert_not_equal current_pid, new_pid
266
+ assert_equal current_pid, File.read(old_file).to_i
267
+ results = retry_hit(["http://#{@addr}:#{@port}/"])
268
+ assert_equal String, results[0].class
269
+
270
+ assert_nothing_raised do
271
+ Process.kill(:QUIT, current_pid)
272
+ Process.kill(:QUIT, new_pid)
273
+ end
274
+ end
275
+
276
+ def test_unicorn_config_listener_swap
277
+ port_cli = unused_port
278
+ File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
279
+ ucfg = Tempfile.new('unicorn_test_config')
280
+ ucfg.syswrite("listen '#@addr:#@port'\n")
281
+ pid = xfork do
282
+ redirect_test_io do
283
+ exec($unicorn_bin, "-c#{ucfg.path}", "-l#@addr:#{port_cli}")
284
+ end
285
+ end
286
+ results = retry_hit(["http://#@addr:#{port_cli}/"])
287
+ assert_equal String, results[0].class
288
+ results = retry_hit(["http://#@addr:#@port/"])
289
+ assert_equal String, results[0].class
290
+
291
+ port2 = unused_port(@addr)
292
+ ucfg.sysseek(0)
293
+ ucfg.truncate(0)
294
+ ucfg.syswrite("listen '#@addr:#{port2}'\n")
295
+ Process.kill(:HUP, pid)
296
+
297
+ results = retry_hit(["http://#@addr:#{port2}/"])
298
+ assert_equal String, results[0].class
299
+ results = retry_hit(["http://#@addr:#{port_cli}/"])
300
+ assert_equal String, results[0].class
301
+ assert_nothing_raised do
302
+ reuse = TCPServer.new(@addr, @port)
303
+ reuse.close
304
+ end
305
+ assert_shutdown(pid)
306
+ end
307
+
308
+ def test_unicorn_config_listen_with_options
309
+ File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
310
+ ucfg = Tempfile.new('unicorn_test_config')
311
+ ucfg.syswrite("listen '#{@addr}:#{@port}', :backlog => 512,\n")
312
+ ucfg.syswrite(" :rcvbuf => 4096,\n")
313
+ ucfg.syswrite(" :sndbuf => 4096\n")
314
+ pid = xfork do
315
+ redirect_test_io { exec($unicorn_bin, "-c#{ucfg.path}") }
316
+ end
317
+ results = retry_hit(["http://#{@addr}:#{@port}/"])
318
+ assert_equal String, results[0].class
319
+ assert_shutdown(pid)
320
+ end
321
+
322
+ def test_unicorn_config_per_worker_listen
323
+ port2 = unused_port
324
+ pid_spit = 'use Rack::ContentLength;' \
325
+ 'run proc { |e| [ 200, {"Content-Type"=>"text/plain"}, ["#$$\\n"] ] }'
326
+ File.open("config.ru", "wb") { |fp| fp.syswrite(pid_spit) }
327
+ tmp = Tempfile.new('test.socket')
328
+ File.unlink(tmp.path)
329
+ ucfg = Tempfile.new('unicorn_test_config')
330
+ ucfg.syswrite("listen '#@addr:#@port'\n")
331
+ ucfg.syswrite("before_fork { |s,w|\n")
332
+ ucfg.syswrite(" s.listen('#{tmp.path}', :backlog => 5, :sndbuf => 8192)\n")
333
+ ucfg.syswrite(" s.listen('#@addr:#{port2}', :rcvbuf => 8192)\n")
334
+ ucfg.syswrite("\n}\n")
335
+ pid = xfork do
336
+ redirect_test_io { exec($unicorn_bin, "-c#{ucfg.path}") }
337
+ end
338
+ results = retry_hit(["http://#{@addr}:#{@port}/"])
339
+ assert_equal String, results[0].class
340
+ worker_pid = results[0].to_i
341
+ assert_not_equal pid, worker_pid
342
+ s = UNIXSocket.new(tmp.path)
343
+ s.syswrite("GET / HTTP/1.0\r\n\r\n")
344
+ results = ''
345
+ loop { results << s.sysread(4096) } rescue nil
346
+ assert_nothing_raised { s.close }
347
+ assert_equal worker_pid, results.split(/\r\n/).last.to_i
348
+ results = hit(["http://#@addr:#{port2}/"])
349
+ assert_equal String, results[0].class
350
+ assert_equal worker_pid, results[0].to_i
351
+ assert_shutdown(pid)
352
+ end
353
+
354
+ def test_unicorn_config_listen_augments_cli
355
+ port2 = unused_port(@addr)
356
+ File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
357
+ ucfg = Tempfile.new('unicorn_test_config')
358
+ ucfg.syswrite("listen '#{@addr}:#{@port}'\n")
359
+ pid = xfork do
360
+ redirect_test_io do
361
+ exec($unicorn_bin, "-c#{ucfg.path}", "-l#{@addr}:#{port2}")
362
+ end
363
+ end
364
+ uris = [@port, port2].map { |i| "http://#{@addr}:#{i}/" }
365
+ results = retry_hit(uris)
366
+ assert_equal results.size, uris.size
367
+ assert_equal String, results[0].class
368
+ assert_equal String, results[1].class
369
+ assert_shutdown(pid)
370
+ end
371
+
372
+ def test_weird_config_settings
373
+ File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
374
+ ucfg = Tempfile.new('unicorn_test_config')
375
+ ucfg.syswrite(HEAVY_CFG)
376
+ pid = xfork do
377
+ redirect_test_io do
378
+ exec($unicorn_bin, "-c#{ucfg.path}", "-l#{@addr}:#{@port}")
379
+ end
380
+ end
381
+
382
+ results = retry_hit(["http://#{@addr}:#{@port}/"])
383
+ assert_equal String, results[0].class
384
+ wait_master_ready(COMMON_TMP.path)
385
+ wait_workers_ready(COMMON_TMP.path, 4)
386
+ bf = File.readlines(COMMON_TMP.path).grep(/\bbefore_fork: worker=/)
387
+ assert_equal 4, bf.size
388
+ rotate = Tempfile.new('unicorn_rotate')
389
+ assert_nothing_raised do
390
+ File.rename(COMMON_TMP.path, rotate.path)
391
+ Process.kill(:USR1, pid)
392
+ end
393
+ wait_for_file(COMMON_TMP.path)
394
+ assert File.exist?(COMMON_TMP.path), "#{COMMON_TMP.path} exists"
395
+ # USR1 should've been passed to all workers
396
+ tries = DEFAULT_TRIES
397
+ log = File.readlines(rotate.path)
398
+ while (tries -= 1) > 0 &&
399
+ log.grep(/reopening logs\.\.\./).size < 5
400
+ sleep DEFAULT_RES
401
+ log = File.readlines(rotate.path)
402
+ end
403
+ assert_equal 5, log.grep(/reopening logs\.\.\./).size
404
+ assert_equal 0, log.grep(/done reopening logs/).size
405
+
406
+ tries = DEFAULT_TRIES
407
+ log = File.readlines(COMMON_TMP.path)
408
+ while (tries -= 1) > 0 && log.grep(/done reopening logs/).size < 5
409
+ sleep DEFAULT_RES
410
+ log = File.readlines(COMMON_TMP.path)
411
+ end
412
+ assert_equal 5, log.grep(/done reopening logs/).size
413
+ assert_equal 0, log.grep(/reopening logs\.\.\./).size
414
+ assert_nothing_raised { Process.kill(:QUIT, pid) }
415
+ status = nil
416
+ assert_nothing_raised { pid, status = Process.waitpid2(pid) }
417
+ assert status.success?, "exited successfully"
418
+ end
419
+
420
+ def test_read_embedded_cli_switches
421
+ File.open("config.ru", "wb") do |fp|
422
+ fp.syswrite("#\\ -p #{@port} -o #{@addr}\n")
423
+ fp.syswrite(HI)
424
+ end
425
+ pid = fork { redirect_test_io { exec($unicorn_bin) } }
426
+ results = retry_hit(["http://#{@addr}:#{@port}/"])
427
+ assert_equal String, results[0].class
428
+ assert_shutdown(pid)
429
+ end
430
+
431
+ def test_config_ru_alt_path
432
+ config_path = "#{@tmpdir}/foo.ru"
433
+ File.open(config_path, "wb") { |fp| fp.syswrite(HI) }
434
+ pid = fork do
435
+ redirect_test_io do
436
+ Dir.chdir("/")
437
+ exec($unicorn_bin, "-l#{@addr}:#{@port}", config_path)
438
+ end
439
+ end
440
+ results = retry_hit(["http://#{@addr}:#{@port}/"])
441
+ assert_equal String, results[0].class
442
+ assert_shutdown(pid)
443
+ end
444
+
445
+ def test_load_module
446
+ libdir = "#{@tmpdir}/lib"
447
+ FileUtils.mkpath([ libdir ])
448
+ config_path = "#{libdir}/hello.rb"
449
+ File.open(config_path, "wb") { |fp| fp.syswrite(HELLO) }
450
+ pid = fork do
451
+ redirect_test_io do
452
+ Dir.chdir("/")
453
+ exec($unicorn_bin, "-l#{@addr}:#{@port}", config_path)
454
+ end
455
+ end
456
+ results = retry_hit(["http://#{@addr}:#{@port}/"])
457
+ assert_equal String, results[0].class
458
+ assert_shutdown(pid)
459
+ end
460
+
461
+ def test_reexec
462
+ File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
463
+ pid_file = "#{@tmpdir}/test.pid"
464
+ pid = fork do
465
+ redirect_test_io do
466
+ exec($unicorn_bin, "-l#{@addr}:#{@port}", "-P#{pid_file}")
467
+ end
468
+ end
469
+ reexec_basic_test(pid, pid_file)
470
+ end
471
+
472
+ def test_reexec_alt_config
473
+ config_file = "#{@tmpdir}/foo.ru"
474
+ File.open(config_file, "wb") { |fp| fp.syswrite(HI) }
475
+ pid_file = "#{@tmpdir}/test.pid"
476
+ pid = fork do
477
+ redirect_test_io do
478
+ exec($unicorn_bin, "-l#{@addr}:#{@port}", "-P#{pid_file}", config_file)
479
+ end
480
+ end
481
+ reexec_basic_test(pid, pid_file)
482
+ end
483
+
484
+ def test_socket_unlinked_restore
485
+ results = nil
486
+ sock = Tempfile.new('unicorn_test_sock')
487
+ sock_path = sock.path
488
+ @sockets << sock_path
489
+ sock.close!
490
+ ucfg = Tempfile.new('unicorn_test_config')
491
+ ucfg.syswrite("listen \"#{sock_path}\"\n")
492
+
493
+ File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
494
+ pid = xfork { redirect_test_io { exec($unicorn_bin, "-c#{ucfg.path}") } }
495
+ wait_for_file(sock_path)
496
+ assert File.socket?(sock_path)
497
+ assert_nothing_raised do
498
+ sock = UNIXSocket.new(sock_path)
499
+ sock.syswrite("GET / HTTP/1.0\r\n\r\n")
500
+ results = sock.sysread(4096)
501
+ end
502
+ assert_equal String, results.class
503
+ assert_nothing_raised do
504
+ File.unlink(sock_path)
505
+ Process.kill(:HUP, pid)
506
+ end
507
+ wait_for_file(sock_path)
508
+ assert File.socket?(sock_path)
509
+ assert_nothing_raised do
510
+ sock = UNIXSocket.new(sock_path)
511
+ sock.syswrite("GET / HTTP/1.0\r\n\r\n")
512
+ results = sock.sysread(4096)
513
+ end
514
+ assert_equal String, results.class
515
+ end
516
+
517
+ def test_unicorn_config_file
518
+ pid_file = "#{@tmpdir}/test.pid"
519
+ sock = Tempfile.new('unicorn_test_sock')
520
+ sock_path = sock.path
521
+ sock.close!
522
+ @sockets << sock_path
523
+
524
+ log = Tempfile.new('unicorn_test_log')
525
+ ucfg = Tempfile.new('unicorn_test_config')
526
+ ucfg.syswrite("listen \"#{sock_path}\"\n")
527
+ ucfg.syswrite("pid \"#{pid_file}\"\n")
528
+ ucfg.syswrite("logger Logger.new('#{log.path}')\n")
529
+ ucfg.close
530
+
531
+ File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
532
+ pid = xfork do
533
+ redirect_test_io do
534
+ exec($unicorn_bin, "-l#{@addr}:#{@port}",
535
+ "-P#{pid_file}", "-c#{ucfg.path}")
536
+ end
537
+ end
538
+ results = retry_hit(["http://#{@addr}:#{@port}/"])
539
+ assert_equal String, results[0].class
540
+ wait_master_ready(log.path)
541
+ assert File.exist?(pid_file), "pid_file created"
542
+ assert_equal pid, File.read(pid_file).to_i
543
+ assert File.socket?(sock_path), "socket created"
544
+ assert_nothing_raised do
545
+ sock = UNIXSocket.new(sock_path)
546
+ sock.syswrite("GET / HTTP/1.0\r\n\r\n")
547
+ results = sock.sysread(4096)
548
+ end
549
+ assert_equal String, results.class
550
+
551
+ # try reloading the config
552
+ sock = Tempfile.new('new_test_sock')
553
+ new_sock_path = sock.path
554
+ @sockets << new_sock_path
555
+ sock.close!
556
+ new_log = Tempfile.new('unicorn_test_log')
557
+ new_log.sync = true
558
+ assert_equal 0, new_log.size
559
+
560
+ assert_nothing_raised do
561
+ ucfg = File.open(ucfg.path, "wb")
562
+ ucfg.syswrite("listen \"#{sock_path}\"\n")
563
+ ucfg.syswrite("listen \"#{new_sock_path}\"\n")
564
+ ucfg.syswrite("pid \"#{pid_file}\"\n")
565
+ ucfg.syswrite("logger Logger.new('#{new_log.path}')\n")
566
+ ucfg.close
567
+ Process.kill(:HUP, pid)
568
+ end
569
+
570
+ wait_for_file(new_sock_path)
571
+ assert File.socket?(new_sock_path), "socket exists"
572
+ @sockets.each do |path|
573
+ assert_nothing_raised do
574
+ sock = UNIXSocket.new(path)
575
+ sock.syswrite("GET / HTTP/1.0\r\n\r\n")
576
+ results = sock.sysread(4096)
577
+ end
578
+ assert_equal String, results.class
579
+ end
580
+
581
+ assert_not_equal 0, new_log.size
582
+ reexec_usr2_quit_test(pid, pid_file)
583
+ end
584
+
585
+ def test_daemonize_reexec
586
+ pid_file = "#{@tmpdir}/test.pid"
587
+ log = Tempfile.new('unicorn_test_log')
588
+ ucfg = Tempfile.new('unicorn_test_config')
589
+ ucfg.syswrite("pid \"#{pid_file}\"\n")
590
+ ucfg.syswrite("logger Logger.new('#{log.path}')\n")
591
+ ucfg.close
592
+
593
+ File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
594
+ pid = xfork do
595
+ redirect_test_io do
596
+ exec($unicorn_bin, "-D", "-l#{@addr}:#{@port}", "-c#{ucfg.path}")
597
+ end
598
+ end
599
+ results = retry_hit(["http://#{@addr}:#{@port}/"])
600
+ assert_equal String, results[0].class
601
+ wait_for_file(pid_file)
602
+ new_pid = File.read(pid_file).to_i
603
+ assert_not_equal pid, new_pid
604
+ pid, status = Process.waitpid2(pid)
605
+ assert status.success?, "original process exited successfully"
606
+ assert_nothing_raised { Process.kill(0, new_pid) }
607
+ reexec_usr2_quit_test(new_pid, pid_file)
608
+ end
609
+
610
+ def test_daemonize_redirect_fail
611
+ pid_file = "#{@tmpdir}/test.pid"
612
+ log = Tempfile.new('unicorn_test_log')
613
+ ucfg = Tempfile.new('unicorn_test_config')
614
+ ucfg.syswrite("pid #{pid_file}\"\n")
615
+ err = Tempfile.new('stderr')
616
+ out = Tempfile.new('stdout ')
617
+
618
+ File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
619
+ pid = xfork do
620
+ $stderr.reopen(err.path, "a")
621
+ $stdout.reopen(out.path, "a")
622
+ exec($unicorn_bin, "-D", "-l#{@addr}:#{@port}", "-c#{ucfg.path}")
623
+ end
624
+ pid, status = Process.waitpid2(pid)
625
+ assert status.success?, "original process exited successfully"
626
+ sleep 1 # can't waitpid on a daemonized process :<
627
+ assert err.stat.size > 0
628
+ end
629
+
630
+ def test_reexec_fd_leak
631
+ unless RUBY_PLATFORM =~ /linux/ # Solaris may work, too, but I forget...
632
+ warn "FD leak test only works on Linux at the moment"
633
+ return
634
+ end
635
+ pid_file = "#{@tmpdir}/test.pid"
636
+ log = Tempfile.new('unicorn_test_log')
637
+ log.sync = true
638
+ ucfg = Tempfile.new('unicorn_test_config')
639
+ ucfg.syswrite("pid \"#{pid_file}\"\n")
640
+ ucfg.syswrite("logger Logger.new('#{log.path}')\n")
641
+ ucfg.syswrite("stderr_path '#{log.path}'\n")
642
+ ucfg.syswrite("stdout_path '#{log.path}'\n")
643
+ ucfg.close
644
+
645
+ File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
646
+ pid = xfork do
647
+ redirect_test_io do
648
+ exec($unicorn_bin, "-D", "-l#{@addr}:#{@port}", "-c#{ucfg.path}")
649
+ end
650
+ end
651
+
652
+ wait_master_ready(log.path)
653
+ wait_workers_ready(log.path, 1)
654
+ File.truncate(log.path, 0)
655
+ wait_for_file(pid_file)
656
+ orig_pid = pid = File.read(pid_file).to_i
657
+ orig_fds = `ls -l /proc/#{pid}/fd`.split(/\n/)
658
+ assert $?.success?
659
+ expect_size = orig_fds.size
660
+
661
+ assert_nothing_raised do
662
+ Process.kill(:USR2, pid)
663
+ wait_for_file("#{pid_file}.oldbin")
664
+ Process.kill(:QUIT, pid)
665
+ end
666
+ wait_for_death(pid)
667
+
668
+ wait_master_ready(log.path)
669
+ wait_workers_ready(log.path, 1)
670
+ File.truncate(log.path, 0)
671
+ wait_for_file(pid_file)
672
+ pid = File.read(pid_file).to_i
673
+ assert_not_equal orig_pid, pid
674
+ curr_fds = `ls -l /proc/#{pid}/fd`.split(/\n/)
675
+ assert $?.success?
676
+
677
+ # we could've inherited descriptors the first time around
678
+ assert expect_size >= curr_fds.size, curr_fds.inspect
679
+ expect_size = curr_fds.size
680
+
681
+ assert_nothing_raised do
682
+ Process.kill(:USR2, pid)
683
+ wait_for_file("#{pid_file}.oldbin")
684
+ Process.kill(:QUIT, pid)
685
+ end
686
+ wait_for_death(pid)
687
+
688
+ wait_master_ready(log.path)
689
+ wait_workers_ready(log.path, 1)
690
+ File.truncate(log.path, 0)
691
+ wait_for_file(pid_file)
692
+ pid = File.read(pid_file).to_i
693
+ curr_fds = `ls -l /proc/#{pid}/fd`.split(/\n/)
694
+ assert $?.success?
695
+ assert_equal expect_size, curr_fds.size, curr_fds.inspect
696
+
697
+ Process.kill(:QUIT, pid)
698
+ wait_for_death(pid)
699
+ end
700
+
701
+ def hup_test_common(preload)
702
+ File.open("config.ru", "wb") { |fp| fp.syswrite(HI.gsub("HI", '#$$')) }
703
+ pid_file = Tempfile.new('pid')
704
+ ucfg = Tempfile.new('unicorn_test_config')
705
+ ucfg.syswrite("listen '#@addr:#@port'\n")
706
+ ucfg.syswrite("pid '#{pid_file.path}'\n")
707
+ ucfg.syswrite("preload_app true\n") if preload
708
+ ucfg.syswrite("stderr_path 'test_stderr.#$$.log'\n")
709
+ ucfg.syswrite("stdout_path 'test_stdout.#$$.log'\n")
710
+ pid = xfork {
711
+ redirect_test_io { exec($unicorn_bin, "-D", "-c", ucfg.path) }
712
+ }
713
+ _, status = Process.waitpid2(pid)
714
+ assert status.success?
715
+ wait_master_ready("test_stderr.#$$.log")
716
+ wait_workers_ready("test_stderr.#$$.log", 1)
717
+ uri = URI.parse("http://#@addr:#@port/")
718
+ pids = Tempfile.new('worker_pids')
719
+ hitter = fork {
720
+ bodies = Hash.new(0)
721
+ at_exit { pids.syswrite(bodies.inspect) }
722
+ trap(:TERM) { exit(0) }
723
+ loop {
724
+ rv = Net::HTTP.get(uri)
725
+ pid = rv.to_i
726
+ exit!(1) if pid <= 0
727
+ bodies[pid] += 1
728
+ }
729
+ }
730
+ sleep 1 # racy
731
+ daemon_pid = File.read(pid_file.path).to_i
732
+ assert daemon_pid > 0
733
+ Process.kill(:HUP, daemon_pid)
734
+ sleep 1 # racy
735
+ assert_nothing_raised { Process.kill(:TERM, hitter) }
736
+ _, hitter_status = Process.waitpid2(hitter)
737
+ assert hitter_status.success?
738
+ pids.sysseek(0)
739
+ pids = eval(pids.read)
740
+ assert_kind_of(Hash, pids)
741
+ assert_equal 2, pids.size
742
+ pids.keys.each { |x|
743
+ assert_kind_of(Integer, x)
744
+ assert x > 0
745
+ assert pids[x] > 0
746
+ }
747
+ assert_nothing_raised { Process.kill(:QUIT, daemon_pid) }
748
+ wait_for_death(daemon_pid)
749
+ end
750
+
751
+ def test_preload_app_hup
752
+ hup_test_common(true)
753
+ end
754
+
755
+ def test_hup
756
+ hup_test_common(false)
757
+ end
758
+
759
+ def test_default_listen_hup_holds_listener
760
+ default_listen_lock do
761
+ res, pid_path = default_listen_setup
762
+ daemon_pid = File.read(pid_path).to_i
763
+ assert_nothing_raised { Process.kill(:HUP, daemon_pid) }
764
+ wait_workers_ready("test_stderr.#$$.log", 1)
765
+ res2 = hit(["http://#{Unicorn::Const::DEFAULT_LISTEN}/"])
766
+ assert_match %r{\d+}, res2.first
767
+ assert res2.first != res.first
768
+ assert_nothing_raised { Process.kill(:QUIT, daemon_pid) }
769
+ wait_for_death(daemon_pid)
770
+ end
771
+ end
772
+
773
+ def test_default_listen_upgrade_holds_listener
774
+ default_listen_lock do
775
+ res, pid_path = default_listen_setup
776
+ daemon_pid = File.read(pid_path).to_i
777
+ assert_nothing_raised {
778
+ Process.kill(:USR2, daemon_pid)
779
+ wait_for_file("#{pid_path}.oldbin")
780
+ wait_for_file(pid_path)
781
+ Process.kill(:QUIT, daemon_pid)
782
+ wait_for_death(daemon_pid)
783
+ }
784
+ daemon_pid = File.read(pid_path).to_i
785
+ wait_workers_ready("test_stderr.#$$.log", 1)
786
+ File.truncate("test_stderr.#$$.log", 0)
787
+
788
+ res2 = hit(["http://#{Unicorn::Const::DEFAULT_LISTEN}/"])
789
+ assert_match %r{\d+}, res2.first
790
+ assert res2.first != res.first
791
+
792
+ assert_nothing_raised { Process.kill(:HUP, daemon_pid) }
793
+ wait_workers_ready("test_stderr.#$$.log", 1)
794
+ File.truncate("test_stderr.#$$.log", 0)
795
+ res3 = hit(["http://#{Unicorn::Const::DEFAULT_LISTEN}/"])
796
+ assert res2.first != res3.first
797
+
798
+ assert_nothing_raised { Process.kill(:QUIT, daemon_pid) }
799
+ wait_for_death(daemon_pid)
800
+ end
801
+ end
802
+
803
+ def default_listen_setup
804
+ File.open("config.ru", "wb") { |fp| fp.syswrite(HI.gsub("HI", '#$$')) }
805
+ pid_path = (tmp = Tempfile.new('pid')).path
806
+ tmp.close!
807
+ ucfg = Tempfile.new('unicorn_test_config')
808
+ ucfg.syswrite("pid '#{pid_path}'\n")
809
+ ucfg.syswrite("stderr_path 'test_stderr.#$$.log'\n")
810
+ ucfg.syswrite("stdout_path 'test_stdout.#$$.log'\n")
811
+ pid = xfork {
812
+ redirect_test_io { exec($unicorn_bin, "-D", "-c", ucfg.path) }
813
+ }
814
+ _, status = Process.waitpid2(pid)
815
+ assert status.success?
816
+ wait_master_ready("test_stderr.#$$.log")
817
+ wait_workers_ready("test_stderr.#$$.log", 1)
818
+ File.truncate("test_stderr.#$$.log", 0)
819
+ res = hit(["http://#{Unicorn::Const::DEFAULT_LISTEN}/"])
820
+ assert_match %r{\d+}, res.first
821
+ [ res, pid_path ]
822
+ end
823
+
824
+ # we need to flock() something to prevent these tests from running
825
+ def default_listen_lock(&block)
826
+ fp = File.open(FLOCK_PATH, "rb")
827
+ begin
828
+ fp.flock(File::LOCK_EX)
829
+ begin
830
+ TCPServer.new(Unicorn::Const::DEFAULT_HOST,
831
+ Unicorn::Const::DEFAULT_PORT).close
832
+ rescue Errno::EADDRINUSE, Errno::EACCES
833
+ warn "can't bind to #{Unicorn::Const::DEFAULT_LISTEN}"
834
+ return false
835
+ end
836
+
837
+ # unused_port should never take this, but we may run an environment
838
+ # where tests are being run against older unicorns...
839
+ lock_path = "#{Dir::tmpdir}/unicorn_test." \
840
+ "#{Unicorn::Const::DEFAULT_LISTEN}.lock"
841
+ begin
842
+ lock = File.open(lock_path, File::WRONLY|File::CREAT|File::EXCL, 0600)
843
+ yield
844
+ rescue Errno::EEXIST
845
+ lock_path = nil
846
+ return false
847
+ ensure
848
+ File.unlink(lock_path) if lock_path
849
+ end
850
+ ensure
851
+ fp.flock(File::LOCK_UN)
852
+ end
853
+ end
854
+
855
+ end if do_test