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.
- checksums.yaml +7 -0
- data/.CHANGELOG.old +25 -0
- data/.document +28 -0
- data/.gitattributes +5 -0
- data/.gitignore +25 -0
- data/.mailmap +26 -0
- data/.manifest +144 -0
- data/.olddoc.yml +25 -0
- data/Application_Timeouts +77 -0
- data/CONTRIBUTORS +39 -0
- data/COPYING +674 -0
- data/DESIGN +99 -0
- data/Documentation/.gitignore +3 -0
- data/Documentation/unicorn.1 +222 -0
- data/Documentation/unicorn_rails.1 +207 -0
- data/FAQ +70 -0
- data/GIT-VERSION-FILE +1 -0
- data/GIT-VERSION-GEN +39 -0
- data/GNUmakefile +318 -0
- data/HACKING +117 -0
- data/ISSUES +102 -0
- data/KNOWN_ISSUES +79 -0
- data/LICENSE +67 -0
- data/Links +58 -0
- data/PHILOSOPHY +139 -0
- data/README +165 -0
- data/Rakefile +17 -0
- data/SIGNALS +123 -0
- data/Sandbox +104 -0
- data/TODO +1 -0
- data/TUNING +119 -0
- data/archive/.gitignore +3 -0
- data/archive/slrnpull.conf +4 -0
- data/bin/unicorn +129 -0
- data/bin/unicorn_rails +210 -0
- data/examples/big_app_gc.rb +3 -0
- data/examples/echo.ru +27 -0
- data/examples/init.sh +102 -0
- data/examples/logger_mp_safe.rb +26 -0
- data/examples/logrotate.conf +44 -0
- data/examples/nginx.conf +156 -0
- data/examples/unicorn.conf.minimal.rb +14 -0
- data/examples/unicorn.conf.rb +111 -0
- data/examples/unicorn.socket +11 -0
- data/examples/unicorn@.service +40 -0
- data/ext/unicorn_http/CFLAGS +13 -0
- data/ext/unicorn_http/c_util.h +115 -0
- data/ext/unicorn_http/common_field_optimization.h +128 -0
- data/ext/unicorn_http/epollexclusive.h +128 -0
- data/ext/unicorn_http/ext_help.h +38 -0
- data/ext/unicorn_http/extconf.rb +40 -0
- data/ext/unicorn_http/global_variables.h +97 -0
- data/ext/unicorn_http/httpdate.c +91 -0
- data/ext/unicorn_http/unicorn_http.c +4348 -0
- data/ext/unicorn_http/unicorn_http.rl +1054 -0
- data/ext/unicorn_http/unicorn_http_common.rl +76 -0
- data/lib/unicorn/app/old_rails/static.rb +60 -0
- data/lib/unicorn/app/old_rails.rb +36 -0
- data/lib/unicorn/cgi_wrapper.rb +148 -0
- data/lib/unicorn/configurator.rb +749 -0
- data/lib/unicorn/const.rb +22 -0
- data/lib/unicorn/http_request.rb +180 -0
- data/lib/unicorn/http_response.rb +95 -0
- data/lib/unicorn/http_server.rb +860 -0
- data/lib/unicorn/launcher.rb +63 -0
- data/lib/unicorn/oob_gc.rb +82 -0
- data/lib/unicorn/preread_input.rb +34 -0
- data/lib/unicorn/select_waiter.rb +7 -0
- data/lib/unicorn/socket_helper.rb +186 -0
- data/lib/unicorn/stream_input.rb +152 -0
- data/lib/unicorn/tee_input.rb +132 -0
- data/lib/unicorn/tmpio.rb +34 -0
- data/lib/unicorn/util.rb +91 -0
- data/lib/unicorn/version.rb +1 -0
- data/lib/unicorn/worker.rb +166 -0
- data/lib/unicorn.rb +137 -0
- data/man/man1/unicorn.1 +222 -0
- data/man/man1/unicorn_rails.1 +207 -0
- data/setup.rb +1587 -0
- data/t/.gitignore +4 -0
- data/t/GNUmakefile +5 -0
- data/t/README +49 -0
- data/t/active-unix-socket.t +110 -0
- data/t/back-out-of-upgrade.t +44 -0
- data/t/bin/unused_listen +40 -0
- data/t/client_body_buffer_size.ru +15 -0
- data/t/client_body_buffer_size.t +79 -0
- data/t/detach.ru +12 -0
- data/t/env.ru +4 -0
- data/t/fails-rack-lint.ru +6 -0
- data/t/heartbeat-timeout.ru +13 -0
- data/t/heartbeat-timeout.t +60 -0
- data/t/integration.ru +129 -0
- data/t/integration.t +509 -0
- data/t/lib.perl +309 -0
- data/t/listener_names.ru +5 -0
- data/t/my-tap-lib.sh +201 -0
- data/t/oob_gc.ru +18 -0
- data/t/oob_gc_path.ru +18 -0
- data/t/pid.ru +4 -0
- data/t/preread_input.ru +23 -0
- data/t/reload-bad-config.t +49 -0
- data/t/reopen-logs.ru +14 -0
- data/t/reopen-logs.t +36 -0
- data/t/t0010-reap-logging.sh +55 -0
- data/t/t0012-reload-empty-config.sh +86 -0
- data/t/t0013-rewindable-input-false.sh +24 -0
- data/t/t0013.ru +13 -0
- data/t/t0014-rewindable-input-true.sh +24 -0
- data/t/t0014.ru +13 -0
- data/t/t0015-configurator-internals.sh +25 -0
- data/t/t0020-at_exit-handler.sh +49 -0
- data/t/t0021-process_detach.sh +29 -0
- data/t/t0022-listener_names-preload_app.sh +32 -0
- data/t/t0300-no-default-middleware.sh +20 -0
- data/t/t0301-no-default-middleware-ignored-in-config.sh +25 -0
- data/t/t0301.ru +14 -0
- data/t/t9001-oob_gc.sh +47 -0
- data/t/t9002-oob_gc-path.sh +75 -0
- data/t/test-lib.sh +125 -0
- data/t/winch_ttin.t +64 -0
- data/t/working_directory.t +86 -0
- data/test/aggregate.rb +16 -0
- data/test/benchmark/README +60 -0
- data/test/benchmark/dd.ru +19 -0
- data/test/benchmark/ddstream.ru +51 -0
- data/test/benchmark/readinput.ru +41 -0
- data/test/benchmark/stack.ru +9 -0
- data/test/benchmark/uconnect.perl +66 -0
- data/test/exec/README +5 -0
- data/test/exec/test_exec.rb +1030 -0
- data/test/test_helper.rb +307 -0
- data/test/unit/test_configurator.rb +176 -0
- data/test/unit/test_droplet.rb +29 -0
- data/test/unit/test_http_parser.rb +885 -0
- data/test/unit/test_http_parser_ng.rb +715 -0
- data/test/unit/test_server.rb +245 -0
- data/test/unit/test_signals.rb +189 -0
- data/test/unit/test_socket_helper.rb +160 -0
- data/test/unit/test_stream_input.rb +211 -0
- data/test/unit/test_tee_input.rb +304 -0
- data/test/unit/test_util.rb +132 -0
- data/test/unit/test_waiter.rb +35 -0
- data/unicorn.gemspec +49 -0
- 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
|