unicorn 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.document +12 -0
- data/.gitignore +12 -0
- data/CHANGELOG +19 -0
- data/CONTRIBUTORS +20 -0
- data/DESIGN +80 -0
- data/GNUmakefile +111 -0
- data/LICENSE +53 -0
- data/Manifest +41 -0
- data/README +73 -0
- data/Rakefile +37 -0
- data/SIGNALS +34 -0
- data/TODO +5 -0
- data/bin/unicorn +189 -0
- data/ext/unicorn/http11/ext_help.h +15 -0
- data/ext/unicorn/http11/extconf.rb +5 -0
- data/ext/unicorn/http11/http11.c +526 -0
- data/ext/unicorn/http11/http11_parser.c +1220 -0
- data/ext/unicorn/http11/http11_parser.h +45 -0
- data/ext/unicorn/http11/http11_parser.rl +153 -0
- data/ext/unicorn/http11/http11_parser_common.rl +55 -0
- data/lib/unicorn.rb +548 -0
- data/lib/unicorn/configurator.rb +253 -0
- data/lib/unicorn/const.rb +116 -0
- data/lib/unicorn/http_request.rb +178 -0
- data/lib/unicorn/http_response.rb +72 -0
- data/lib/unicorn/socket.rb +142 -0
- data/lib/unicorn/util.rb +40 -0
- data/setup.rb +1585 -0
- data/test/aggregate.rb +13 -0
- data/test/benchmark/previous.rb +11 -0
- data/test/benchmark/simple.rb +11 -0
- data/test/benchmark/utils.rb +82 -0
- data/test/exec/README +5 -0
- data/test/exec/test_exec.rb +570 -0
- data/test/test_helper.rb +103 -0
- data/test/tools/trickletest.rb +45 -0
- data/test/unit/test_configurator.rb +48 -0
- data/test/unit/test_http_parser.rb +161 -0
- data/test/unit/test_response.rb +45 -0
- data/test/unit/test_server.rb +96 -0
- data/test/unit/test_upload.rb +151 -0
- data/unicorn.gemspec +35 -0
- metadata +122 -0
data/test/aggregate.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
#!/usr/bin/ruby -n
|
2
|
+
BEGIN { $tests = $assertions = $failures = $errors = 0 }
|
3
|
+
|
4
|
+
$_ =~ /(\d+) tests, (\d+) assertions, (\d+) failures, (\d+) errors/ or next
|
5
|
+
$tests += $1.to_i
|
6
|
+
$assertions += $2.to_i
|
7
|
+
$failures += $3.to_i
|
8
|
+
$errors += $4.to_i
|
9
|
+
|
10
|
+
END {
|
11
|
+
printf("\n%d tests, %d assertions, %d failures, %d errors\n",
|
12
|
+
$tests, $assertions, $failures, $errors)
|
13
|
+
}
|
@@ -0,0 +1,11 @@
|
|
1
|
+
# Benchmark to compare Mongrel performance against
|
2
|
+
# previous Mongrel version (the one installed as a gem).
|
3
|
+
#
|
4
|
+
# Run with:
|
5
|
+
#
|
6
|
+
# ruby previous.rb [num of request]
|
7
|
+
#
|
8
|
+
|
9
|
+
require File.dirname(__FILE__) + '/utils'
|
10
|
+
|
11
|
+
benchmark "print", %w(current gem), 1000, [1, 10, 100]
|
@@ -0,0 +1,11 @@
|
|
1
|
+
#
|
2
|
+
# Simple benchmark to compare Mongrel performance against
|
3
|
+
# other webservers supported by Rack.
|
4
|
+
#
|
5
|
+
|
6
|
+
require File.dirname(__FILE__) + '/utils'
|
7
|
+
|
8
|
+
libs = %w(current gem WEBrick EMongrel Thin)
|
9
|
+
libs = ARGV if ARGV.any?
|
10
|
+
|
11
|
+
benchmark "print", libs, 1000, [1, 10, 100]
|
@@ -0,0 +1,82 @@
|
|
1
|
+
|
2
|
+
require 'rubygems'
|
3
|
+
require 'rack'
|
4
|
+
require 'rack/lobster'
|
5
|
+
|
6
|
+
def run(handler_name, n=1000, c=1)
|
7
|
+
port = 7000
|
8
|
+
|
9
|
+
server = fork do
|
10
|
+
[STDOUT, STDERR].each { |o| o.reopen "/dev/null" }
|
11
|
+
|
12
|
+
case handler_name
|
13
|
+
when 'EMongrel'
|
14
|
+
require 'swiftcore/evented_mongrel'
|
15
|
+
handler_name = 'Mongrel'
|
16
|
+
|
17
|
+
when 'Thin'
|
18
|
+
require 'thin'
|
19
|
+
hander_name = 'Thin'
|
20
|
+
|
21
|
+
when 'gem' # Load the current Mongrel gem
|
22
|
+
require 'mongrel'
|
23
|
+
handler_name = 'Mongrel'
|
24
|
+
|
25
|
+
when 'current' # Load the current Mongrel version under /lib
|
26
|
+
require File.dirname(__FILE__) + '/../lib/mongrel'
|
27
|
+
handler_name = 'Mongrel'
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
app = Rack::Lobster.new
|
32
|
+
|
33
|
+
handler = Rack::Handler.const_get(handler_name)
|
34
|
+
handler.run app, :Host => '0.0.0.0', :Port => port
|
35
|
+
end
|
36
|
+
|
37
|
+
sleep 2
|
38
|
+
|
39
|
+
out = `nice -n20 ab -c #{c} -n #{n} http://127.0.0.1:#{port}/ 2> /dev/null`
|
40
|
+
|
41
|
+
Process.kill('SIGKILL', server)
|
42
|
+
Process.wait
|
43
|
+
|
44
|
+
if requests = out.match(/^Requests.+?(\d+\.\d+)/)
|
45
|
+
requests[1].to_i
|
46
|
+
else
|
47
|
+
0
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def benchmark(type, servers, request, concurrency_levels)
|
52
|
+
send "#{type}_benchmark", servers, request, concurrency_levels
|
53
|
+
end
|
54
|
+
|
55
|
+
def graph_benchmark(servers, request, concurrency_levels)
|
56
|
+
require '/usr/local/lib/ruby/gems/1.8/gems/gruff-0.2.9/lib/gruff'
|
57
|
+
g = Gruff::Area.new
|
58
|
+
g.title = "Server benchmark"
|
59
|
+
|
60
|
+
servers.each do |server|
|
61
|
+
g.data(server, concurrency_levels.collect { |c| print '.'; run(server, request, c) })
|
62
|
+
end
|
63
|
+
puts
|
64
|
+
|
65
|
+
g.x_axis_label = 'Concurrency'
|
66
|
+
g.y_axis_label = 'Requests / sec'
|
67
|
+
g.labels = {}
|
68
|
+
concurrency_levels.each_with_index { |c, i| g.labels[i] = c.to_s }
|
69
|
+
|
70
|
+
g.write('bench.png')
|
71
|
+
`open bench.png`
|
72
|
+
end
|
73
|
+
|
74
|
+
def print_benchmark(servers, request, concurrency_levels)
|
75
|
+
puts 'server request concurrency req/s'
|
76
|
+
puts '=' * 42
|
77
|
+
concurrency_levels.each do |c|
|
78
|
+
servers.each do |server|
|
79
|
+
puts "#{server.ljust(8)} #{request} #{c.to_s.ljust(4)} #{run(server, request, c)}"
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
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,570 @@
|
|
1
|
+
# Copyright (c) 2009 Eric Wong
|
2
|
+
STDIN.sync = STDOUT.sync = STDERR.sync = true
|
3
|
+
require 'test/test_helper'
|
4
|
+
require 'pathname'
|
5
|
+
require 'tempfile'
|
6
|
+
require 'fileutils'
|
7
|
+
|
8
|
+
do_test = true
|
9
|
+
DEFAULT_TRIES = 1000
|
10
|
+
DEFAULT_RES = 0.2
|
11
|
+
|
12
|
+
$unicorn_bin = ENV['UNICORN_TEST_BIN'] || "unicorn"
|
13
|
+
redirect_test_io do
|
14
|
+
do_test = system($unicorn_bin, '-v')
|
15
|
+
end
|
16
|
+
|
17
|
+
unless do_test
|
18
|
+
STDERR.puts "#{$unicorn_bin} not found in PATH=#{ENV['PATH']}, " \
|
19
|
+
"skipping this test"
|
20
|
+
end
|
21
|
+
|
22
|
+
begin
|
23
|
+
require 'rack'
|
24
|
+
rescue LoadError
|
25
|
+
STDERR.puts "Unable to load Rack, skipping this test"
|
26
|
+
do_test = false
|
27
|
+
end
|
28
|
+
|
29
|
+
class ExecTest < Test::Unit::TestCase
|
30
|
+
trap('QUIT', 'IGNORE')
|
31
|
+
|
32
|
+
HI = <<-EOS
|
33
|
+
use Rack::ContentLength
|
34
|
+
run proc { |env| [ 200, { 'Content-Type' => 'text/plain' }, "HI\\n" ] }
|
35
|
+
EOS
|
36
|
+
|
37
|
+
HELLO = <<-EOS
|
38
|
+
class Hello
|
39
|
+
def call(env)
|
40
|
+
[ 200, { 'Content-Type' => 'text/plain' }, "HI\\n" ]
|
41
|
+
end
|
42
|
+
end
|
43
|
+
EOS
|
44
|
+
|
45
|
+
COMMON_TMP = Tempfile.new('unicorn_tmp') unless defined?(COMMON_TMP)
|
46
|
+
|
47
|
+
HEAVY_CFG = <<-EOS
|
48
|
+
worker_processes 4
|
49
|
+
timeout 30
|
50
|
+
backlog 128
|
51
|
+
logger Logger.new('#{COMMON_TMP.path}')
|
52
|
+
before_fork do |server, worker_nr|
|
53
|
+
server.logger.info "before_fork: worker=\#{worker_nr}"
|
54
|
+
end
|
55
|
+
EOS
|
56
|
+
|
57
|
+
def setup
|
58
|
+
@pwd = Dir.pwd
|
59
|
+
@tmpfile = Tempfile.new('unicorn_exec_test')
|
60
|
+
@tmpdir = @tmpfile.path
|
61
|
+
@tmpfile.close!
|
62
|
+
Dir.mkdir(@tmpdir)
|
63
|
+
Dir.chdir(@tmpdir)
|
64
|
+
@addr = ENV['UNICORN_TEST_ADDR'] || '127.0.0.1'
|
65
|
+
@port = unused_port(@addr)
|
66
|
+
@sockets = []
|
67
|
+
@start_pid = $$
|
68
|
+
end
|
69
|
+
|
70
|
+
def teardown
|
71
|
+
return if @start_pid != $$
|
72
|
+
Dir.chdir(@pwd)
|
73
|
+
FileUtils.rmtree(@tmpdir)
|
74
|
+
@sockets.each { |path| File.unlink(path) rescue nil }
|
75
|
+
loop do
|
76
|
+
Process.kill('-QUIT', 0)
|
77
|
+
begin
|
78
|
+
Process.waitpid(-1, Process::WNOHANG) or break
|
79
|
+
rescue Errno::ECHILD
|
80
|
+
break
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def test_basic
|
86
|
+
File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
|
87
|
+
pid = fork do
|
88
|
+
redirect_test_io { exec($unicorn_bin, "-l", "#{@addr}:#{@port}") }
|
89
|
+
end
|
90
|
+
results = retry_hit(["http://#{@addr}:#{@port}/"])
|
91
|
+
assert_equal String, results[0].class
|
92
|
+
assert_shutdown(pid)
|
93
|
+
end
|
94
|
+
|
95
|
+
def test_help
|
96
|
+
redirect_test_io do
|
97
|
+
assert(system($unicorn_bin, "-h"), "help text returns true")
|
98
|
+
end
|
99
|
+
assert_equal 0, File.stat("test_stderr.#$$.log").size
|
100
|
+
assert_not_equal 0, File.stat("test_stdout.#$$.log").size
|
101
|
+
lines = File.readlines("test_stdout.#$$.log")
|
102
|
+
|
103
|
+
# Be considerate of the on-call technician working from their
|
104
|
+
# mobile phone or netbook on a slow connection :)
|
105
|
+
assert lines.size <= 24, "help height fits in an ANSI terminal window"
|
106
|
+
lines.each do |line|
|
107
|
+
assert line.size <= 80, "help width fits in an ANSI terminal window"
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def test_broken_reexec_config
|
112
|
+
File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
|
113
|
+
pid_file = "#{@tmpdir}/test.pid"
|
114
|
+
old_file = "#{pid_file}.oldbin"
|
115
|
+
ucfg = Tempfile.new('unicorn_test_config')
|
116
|
+
ucfg.syswrite("listeners %w(#{@addr}:#{@port})\n")
|
117
|
+
ucfg.syswrite("pid %(#{pid_file})\n")
|
118
|
+
ucfg.syswrite("logger Logger.new(%(#{@tmpdir}/log))\n")
|
119
|
+
pid = xfork do
|
120
|
+
redirect_test_io do
|
121
|
+
exec($unicorn_bin, "-D", "-l#{@addr}:#{@port}", "-c#{ucfg.path}")
|
122
|
+
end
|
123
|
+
end
|
124
|
+
results = retry_hit(["http://#{@addr}:#{@port}/"])
|
125
|
+
assert_equal String, results[0].class
|
126
|
+
|
127
|
+
wait_for_file(pid_file)
|
128
|
+
Process.waitpid(pid)
|
129
|
+
Process.kill('USR2', File.read(pid_file).to_i)
|
130
|
+
wait_for_file(old_file)
|
131
|
+
wait_for_file(pid_file)
|
132
|
+
Process.kill('QUIT', File.read(old_file).to_i)
|
133
|
+
|
134
|
+
ucfg.syswrite("timeout %(#{pid_file})\n") # introduce a bug
|
135
|
+
current_pid = File.read(pid_file).to_i
|
136
|
+
Process.kill('USR2', current_pid)
|
137
|
+
|
138
|
+
# wait for pid_file to restore itself
|
139
|
+
tries = DEFAULT_TRIES
|
140
|
+
begin
|
141
|
+
while current_pid != File.read(pid_file).to_i
|
142
|
+
sleep(DEFAULT_RES) and (tries -= 1) > 0
|
143
|
+
end
|
144
|
+
rescue Errno::ENOENT
|
145
|
+
(sleep(DEFAULT_RES) and (tries -= 1) > 0) and retry
|
146
|
+
end
|
147
|
+
assert_equal current_pid, File.read(pid_file).to_i
|
148
|
+
|
149
|
+
tries = DEFAULT_TRIES
|
150
|
+
while File.exist?(old_file)
|
151
|
+
(sleep(DEFAULT_RES) and (tries -= 1) > 0) or break
|
152
|
+
end
|
153
|
+
assert ! File.exist?(old_file), "oldbin=#{old_file} gone"
|
154
|
+
port2 = unused_port(@addr)
|
155
|
+
|
156
|
+
# fix the bug
|
157
|
+
ucfg.sysseek(0)
|
158
|
+
ucfg.truncate(0)
|
159
|
+
ucfg.syswrite("listeners %w(#{@addr}:#{@port} #{@addr}:#{port2})\n")
|
160
|
+
ucfg.syswrite("pid %(#{pid_file})\n")
|
161
|
+
Process.kill('USR2', current_pid)
|
162
|
+
wait_for_file(old_file)
|
163
|
+
wait_for_file(pid_file)
|
164
|
+
new_pid = File.read(pid_file).to_i
|
165
|
+
assert_not_equal current_pid, new_pid
|
166
|
+
assert_equal current_pid, File.read(old_file).to_i
|
167
|
+
results = retry_hit(["http://#{@addr}:#{@port}/",
|
168
|
+
"http://#{@addr}:#{port2}/"])
|
169
|
+
assert_equal String, results[0].class
|
170
|
+
assert_equal String, results[1].class
|
171
|
+
|
172
|
+
assert_nothing_raised do
|
173
|
+
Process.kill('QUIT', current_pid)
|
174
|
+
Process.kill('QUIT', new_pid)
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
def test_broken_reexec_ru
|
179
|
+
File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
|
180
|
+
pid_file = "#{@tmpdir}/test.pid"
|
181
|
+
old_file = "#{pid_file}.oldbin"
|
182
|
+
ucfg = Tempfile.new('unicorn_test_config')
|
183
|
+
ucfg.syswrite("pid %(#{pid_file})\n")
|
184
|
+
ucfg.syswrite("logger Logger.new(%(#{@tmpdir}/log))\n")
|
185
|
+
pid = xfork do
|
186
|
+
redirect_test_io do
|
187
|
+
exec($unicorn_bin, "-D", "-l#{@addr}:#{@port}", "-c#{ucfg.path}")
|
188
|
+
end
|
189
|
+
end
|
190
|
+
results = retry_hit(["http://#{@addr}:#{@port}/"])
|
191
|
+
assert_equal String, results[0].class
|
192
|
+
|
193
|
+
wait_for_file(pid_file)
|
194
|
+
Process.waitpid(pid)
|
195
|
+
Process.kill('USR2', File.read(pid_file).to_i)
|
196
|
+
wait_for_file(old_file)
|
197
|
+
wait_for_file(pid_file)
|
198
|
+
Process.kill('QUIT', File.read(old_file).to_i)
|
199
|
+
|
200
|
+
File.unlink("config.ru") # break reloading
|
201
|
+
current_pid = File.read(pid_file).to_i
|
202
|
+
Process.kill('USR2', current_pid)
|
203
|
+
|
204
|
+
# wait for pid_file to restore itself
|
205
|
+
tries = DEFAULT_TRIES
|
206
|
+
begin
|
207
|
+
while current_pid != File.read(pid_file).to_i
|
208
|
+
sleep(DEFAULT_RES) and (tries -= 1) > 0
|
209
|
+
end
|
210
|
+
rescue Errno::ENOENT
|
211
|
+
(sleep(DEFAULT_RES) and (tries -= 1) > 0) and retry
|
212
|
+
end
|
213
|
+
assert_equal current_pid, File.read(pid_file).to_i
|
214
|
+
|
215
|
+
tries = DEFAULT_TRIES
|
216
|
+
while File.exist?(old_file)
|
217
|
+
(sleep(DEFAULT_RES) and (tries -= 1) > 0) or break
|
218
|
+
end
|
219
|
+
assert ! File.exist?(old_file), "oldbin=#{old_file} gone"
|
220
|
+
|
221
|
+
# fix the bug
|
222
|
+
File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
|
223
|
+
Process.kill('USR2', current_pid)
|
224
|
+
wait_for_file(old_file)
|
225
|
+
wait_for_file(pid_file)
|
226
|
+
new_pid = File.read(pid_file).to_i
|
227
|
+
assert_not_equal current_pid, new_pid
|
228
|
+
assert_equal current_pid, File.read(old_file).to_i
|
229
|
+
results = retry_hit(["http://#{@addr}:#{@port}/"])
|
230
|
+
assert_equal String, results[0].class
|
231
|
+
|
232
|
+
assert_nothing_raised do
|
233
|
+
Process.kill('QUIT', current_pid)
|
234
|
+
Process.kill('QUIT', new_pid)
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
def test_unicorn_config_listeners_overrides_cli
|
239
|
+
port2 = unused_port(@addr)
|
240
|
+
File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
|
241
|
+
# listeners = [ ... ] => should _override_ command-line options
|
242
|
+
ucfg = Tempfile.new('unicorn_test_config')
|
243
|
+
ucfg.syswrite("listeners %w(#{@addr}:#{@port})\n")
|
244
|
+
pid = xfork do
|
245
|
+
redirect_test_io do
|
246
|
+
exec($unicorn_bin, "-c#{ucfg.path}", "-l#{@addr}:#{port2}")
|
247
|
+
end
|
248
|
+
end
|
249
|
+
results = retry_hit(["http://#{@addr}:#{@port}/"])
|
250
|
+
assert_raises(Errno::ECONNREFUSED) { TCPSocket.new(@addr, port2) }
|
251
|
+
assert_equal String, results[0].class
|
252
|
+
assert_shutdown(pid)
|
253
|
+
end
|
254
|
+
|
255
|
+
def test_unicorn_config_listen_augments_cli
|
256
|
+
port2 = unused_port(@addr)
|
257
|
+
File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
|
258
|
+
ucfg = Tempfile.new('unicorn_test_config')
|
259
|
+
ucfg.syswrite("listen '#{@addr}:#{@port}'\n")
|
260
|
+
pid = xfork do
|
261
|
+
redirect_test_io do
|
262
|
+
exec($unicorn_bin, "-c#{ucfg.path}", "-l#{@addr}:#{port2}")
|
263
|
+
end
|
264
|
+
end
|
265
|
+
uris = [@port, port2].map { |i| "http://#{@addr}:#{i}/" }
|
266
|
+
results = retry_hit(uris)
|
267
|
+
assert_equal results.size, uris.size
|
268
|
+
assert_equal String, results[0].class
|
269
|
+
assert_equal String, results[1].class
|
270
|
+
assert_shutdown(pid)
|
271
|
+
end
|
272
|
+
|
273
|
+
def test_weird_config_settings
|
274
|
+
File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
|
275
|
+
ucfg = Tempfile.new('unicorn_test_config')
|
276
|
+
ucfg.syswrite(HEAVY_CFG)
|
277
|
+
pid = xfork do
|
278
|
+
redirect_test_io do
|
279
|
+
exec($unicorn_bin, "-c#{ucfg.path}", "-l#{@addr}:#{@port}")
|
280
|
+
end
|
281
|
+
end
|
282
|
+
|
283
|
+
results = retry_hit(["http://#{@addr}:#{@port}/"])
|
284
|
+
assert_equal String, results[0].class
|
285
|
+
wait_master_ready(COMMON_TMP.path)
|
286
|
+
bf = File.readlines(COMMON_TMP.path).grep(/\bbefore_fork: worker=/)
|
287
|
+
assert_equal 4, bf.size
|
288
|
+
rotate = Tempfile.new('unicorn_rotate')
|
289
|
+
assert_nothing_raised do
|
290
|
+
File.rename(COMMON_TMP.path, rotate.path)
|
291
|
+
Process.kill('USR1', pid)
|
292
|
+
end
|
293
|
+
wait_for_file(COMMON_TMP.path)
|
294
|
+
assert File.exist?(COMMON_TMP.path), "#{COMMON_TMP.path} exists"
|
295
|
+
# USR1 should've been passed to all workers
|
296
|
+
tries = DEFAULT_TRIES
|
297
|
+
log = File.readlines(rotate.path)
|
298
|
+
while (tries -= 1) > 0 && log.grep(/rotating logs\.\.\./).size < 4
|
299
|
+
sleep DEFAULT_RES
|
300
|
+
log = File.readlines(rotate.path)
|
301
|
+
end
|
302
|
+
assert_equal 4, log.grep(/rotating logs\.\.\./).size
|
303
|
+
assert_equal 0, log.grep(/done rotating logs/).size
|
304
|
+
|
305
|
+
tries = DEFAULT_TRIES
|
306
|
+
log = File.readlines(COMMON_TMP.path)
|
307
|
+
while (tries -= 1) > 0 && log.grep(/done rotating logs/).size < 4
|
308
|
+
sleep DEFAULT_RES
|
309
|
+
log = File.readlines(COMMON_TMP.path)
|
310
|
+
end
|
311
|
+
assert_equal 4, log.grep(/done rotating logs/).size
|
312
|
+
assert_equal 0, log.grep(/rotating logs\.\.\./).size
|
313
|
+
assert_nothing_raised { Process.kill('QUIT', pid) }
|
314
|
+
status = nil
|
315
|
+
assert_nothing_raised { pid, status = Process.waitpid2(pid) }
|
316
|
+
assert status.success?, "exited successfully"
|
317
|
+
end
|
318
|
+
|
319
|
+
def test_read_embedded_cli_switches
|
320
|
+
File.open("config.ru", "wb") do |fp|
|
321
|
+
fp.syswrite("#\\ -p #{@port} -o #{@addr}\n")
|
322
|
+
fp.syswrite(HI)
|
323
|
+
end
|
324
|
+
pid = fork { redirect_test_io { exec($unicorn_bin) } }
|
325
|
+
results = retry_hit(["http://#{@addr}:#{@port}/"])
|
326
|
+
assert_equal String, results[0].class
|
327
|
+
assert_shutdown(pid)
|
328
|
+
end
|
329
|
+
|
330
|
+
def test_config_ru_alt_path
|
331
|
+
config_path = "#{@tmpdir}/foo.ru"
|
332
|
+
File.open(config_path, "wb") { |fp| fp.syswrite(HI) }
|
333
|
+
pid = fork do
|
334
|
+
redirect_test_io do
|
335
|
+
Dir.chdir("/")
|
336
|
+
exec($unicorn_bin, "-l#{@addr}:#{@port}", config_path)
|
337
|
+
end
|
338
|
+
end
|
339
|
+
results = retry_hit(["http://#{@addr}:#{@port}/"])
|
340
|
+
assert_equal String, results[0].class
|
341
|
+
assert_shutdown(pid)
|
342
|
+
end
|
343
|
+
|
344
|
+
def test_load_module
|
345
|
+
libdir = "#{@tmpdir}/lib"
|
346
|
+
FileUtils.mkpath([ libdir ])
|
347
|
+
config_path = "#{libdir}/hello.rb"
|
348
|
+
File.open(config_path, "wb") { |fp| fp.syswrite(HELLO) }
|
349
|
+
pid = fork do
|
350
|
+
redirect_test_io do
|
351
|
+
Dir.chdir("/")
|
352
|
+
exec($unicorn_bin, "-l#{@addr}:#{@port}", config_path)
|
353
|
+
end
|
354
|
+
end
|
355
|
+
results = retry_hit(["http://#{@addr}:#{@port}/"])
|
356
|
+
assert_equal String, results[0].class
|
357
|
+
assert_shutdown(pid)
|
358
|
+
end
|
359
|
+
|
360
|
+
def test_reexec
|
361
|
+
File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
|
362
|
+
pid_file = "#{@tmpdir}/test.pid"
|
363
|
+
pid = fork do
|
364
|
+
redirect_test_io do
|
365
|
+
exec($unicorn_bin, "-l#{@addr}:#{@port}", "-P#{pid_file}")
|
366
|
+
end
|
367
|
+
end
|
368
|
+
reexec_basic_test(pid, pid_file)
|
369
|
+
end
|
370
|
+
|
371
|
+
def test_reexec_alt_config
|
372
|
+
config_file = "#{@tmpdir}/foo.ru"
|
373
|
+
File.open(config_file, "wb") { |fp| fp.syswrite(HI) }
|
374
|
+
pid_file = "#{@tmpdir}/test.pid"
|
375
|
+
pid = fork do
|
376
|
+
redirect_test_io do
|
377
|
+
exec($unicorn_bin, "-l#{@addr}:#{@port}", "-P#{pid_file}", config_file)
|
378
|
+
end
|
379
|
+
end
|
380
|
+
reexec_basic_test(pid, pid_file)
|
381
|
+
end
|
382
|
+
|
383
|
+
def test_unicorn_config_file
|
384
|
+
pid_file = "#{@tmpdir}/test.pid"
|
385
|
+
sock = Tempfile.new('unicorn_test_sock')
|
386
|
+
sock_path = sock.path
|
387
|
+
sock.close!
|
388
|
+
@sockets << sock_path
|
389
|
+
|
390
|
+
log = Tempfile.new('unicorn_test_log')
|
391
|
+
ucfg = Tempfile.new('unicorn_test_config')
|
392
|
+
ucfg.syswrite("listen \"#{sock_path}\"\n")
|
393
|
+
ucfg.syswrite("pid \"#{pid_file}\"\n")
|
394
|
+
ucfg.syswrite("logger Logger.new('#{log.path}')\n")
|
395
|
+
ucfg.close
|
396
|
+
|
397
|
+
File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
|
398
|
+
pid = xfork do
|
399
|
+
redirect_test_io do
|
400
|
+
exec($unicorn_bin, "-l#{@addr}:#{@port}",
|
401
|
+
"-P#{pid_file}", "-c#{ucfg.path}")
|
402
|
+
end
|
403
|
+
end
|
404
|
+
results = retry_hit(["http://#{@addr}:#{@port}/"])
|
405
|
+
assert_equal String, results[0].class
|
406
|
+
wait_master_ready(log.path)
|
407
|
+
assert File.exist?(pid_file), "pid_file created"
|
408
|
+
assert_equal pid, File.read(pid_file).to_i
|
409
|
+
assert File.socket?(sock_path), "socket created"
|
410
|
+
assert_nothing_raised do
|
411
|
+
sock = UNIXSocket.new(sock_path)
|
412
|
+
sock.syswrite("GET / HTTP/1.0\r\n\r\n")
|
413
|
+
results = sock.sysread(4096)
|
414
|
+
end
|
415
|
+
assert_equal String, results.class
|
416
|
+
|
417
|
+
# try reloading the config
|
418
|
+
sock = Tempfile.new('unicorn_test_sock')
|
419
|
+
new_sock_path = sock.path
|
420
|
+
@sockets << new_sock_path
|
421
|
+
sock.close!
|
422
|
+
new_log = Tempfile.new('unicorn_test_log')
|
423
|
+
new_log.sync = true
|
424
|
+
assert_equal 0, new_log.size
|
425
|
+
|
426
|
+
assert_nothing_raised do
|
427
|
+
ucfg = File.open(ucfg.path, "wb")
|
428
|
+
ucfg.syswrite("listen \"#{new_sock_path}\"\n")
|
429
|
+
ucfg.syswrite("pid \"#{pid_file}\"\n")
|
430
|
+
ucfg.syswrite("logger Logger.new('#{new_log.path}')\n")
|
431
|
+
ucfg.close
|
432
|
+
Process.kill('HUP', pid)
|
433
|
+
end
|
434
|
+
|
435
|
+
wait_for_file(new_sock_path)
|
436
|
+
assert File.socket?(new_sock_path), "socket exists"
|
437
|
+
@sockets.each do |path|
|
438
|
+
assert_nothing_raised do
|
439
|
+
sock = UNIXSocket.new(path)
|
440
|
+
sock.syswrite("GET / HTTP/1.0\r\n\r\n")
|
441
|
+
results = sock.sysread(4096)
|
442
|
+
end
|
443
|
+
assert_equal String, results.class
|
444
|
+
end
|
445
|
+
|
446
|
+
assert_not_equal 0, new_log.size
|
447
|
+
reexec_usr2_quit_test(pid, pid_file)
|
448
|
+
end
|
449
|
+
|
450
|
+
def test_daemonize_reexec
|
451
|
+
pid_file = "#{@tmpdir}/test.pid"
|
452
|
+
log = Tempfile.new('unicorn_test_log')
|
453
|
+
ucfg = Tempfile.new('unicorn_test_config')
|
454
|
+
ucfg.syswrite("pid \"#{pid_file}\"\n")
|
455
|
+
ucfg.syswrite("logger Logger.new('#{log.path}')\n")
|
456
|
+
ucfg.close
|
457
|
+
|
458
|
+
File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
|
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
|
+
wait_for_file(pid_file)
|
467
|
+
new_pid = File.read(pid_file).to_i
|
468
|
+
assert_not_equal pid, new_pid
|
469
|
+
pid, status = Process.waitpid2(pid)
|
470
|
+
assert status.success?, "original process exited successfully"
|
471
|
+
assert_nothing_raised { Process.kill(0, new_pid) }
|
472
|
+
reexec_usr2_quit_test(new_pid, pid_file)
|
473
|
+
end
|
474
|
+
|
475
|
+
private
|
476
|
+
|
477
|
+
# sometimes the server may not come up right away
|
478
|
+
def retry_hit(uris = [])
|
479
|
+
tries = DEFAULT_TRIES
|
480
|
+
begin
|
481
|
+
hit(uris)
|
482
|
+
rescue Errno::ECONNREFUSED => err
|
483
|
+
if (tries -= 1) > 0
|
484
|
+
sleep DEFAULT_RES
|
485
|
+
retry
|
486
|
+
end
|
487
|
+
raise err
|
488
|
+
end
|
489
|
+
end
|
490
|
+
|
491
|
+
def assert_shutdown(pid)
|
492
|
+
wait_master_ready("#{@tmpdir}/test_stderr.#{pid}.log")
|
493
|
+
assert_nothing_raised { Process.kill('QUIT', pid) }
|
494
|
+
status = nil
|
495
|
+
assert_nothing_raised { pid, status = Process.waitpid2(pid) }
|
496
|
+
assert status.success?, "exited successfully"
|
497
|
+
end
|
498
|
+
|
499
|
+
def wait_master_ready(master_log)
|
500
|
+
tries = DEFAULT_TRIES
|
501
|
+
while (tries -= 1) > 0
|
502
|
+
begin
|
503
|
+
File.readlines(master_log).grep(/master process ready/)[0] and return
|
504
|
+
rescue Errno::ENOENT
|
505
|
+
end
|
506
|
+
sleep DEFAULT_RES
|
507
|
+
end
|
508
|
+
raise "master process never became ready"
|
509
|
+
end
|
510
|
+
|
511
|
+
def reexec_usr2_quit_test(pid, pid_file)
|
512
|
+
assert File.exist?(pid_file), "pid file OK"
|
513
|
+
assert ! File.exist?("#{pid_file}.oldbin"), "oldbin pid file"
|
514
|
+
assert_nothing_raised { Process.kill('USR2', pid) }
|
515
|
+
assert_nothing_raised { retry_hit(["http://#{@addr}:#{@port}/"]) }
|
516
|
+
wait_for_file("#{pid_file}.oldbin")
|
517
|
+
wait_for_file(pid_file)
|
518
|
+
|
519
|
+
# kill old master process
|
520
|
+
assert_not_equal pid, File.read(pid_file).to_i
|
521
|
+
assert_equal pid, File.read("#{pid_file}.oldbin").to_i
|
522
|
+
assert_nothing_raised { Process.kill('QUIT', pid) }
|
523
|
+
assert_not_equal pid, File.read(pid_file).to_i
|
524
|
+
assert_nothing_raised { retry_hit(["http://#{@addr}:#{@port}/"]) }
|
525
|
+
wait_for_file(pid_file)
|
526
|
+
assert_nothing_raised { retry_hit(["http://#{@addr}:#{@port}/"]) }
|
527
|
+
assert_nothing_raised { Process.kill('QUIT', File.read(pid_file).to_i) }
|
528
|
+
end
|
529
|
+
|
530
|
+
def reexec_basic_test(pid, pid_file)
|
531
|
+
results = retry_hit(["http://#{@addr}:#{@port}/"])
|
532
|
+
assert_equal String, results[0].class
|
533
|
+
assert_nothing_raised { Process.kill(0, pid) }
|
534
|
+
master_log = "#{@tmpdir}/test_stderr.#{pid}.log"
|
535
|
+
wait_master_ready(master_log)
|
536
|
+
File.truncate(master_log, 0)
|
537
|
+
nr = 50
|
538
|
+
kill_point = 2
|
539
|
+
assert_nothing_raised do
|
540
|
+
nr.times do |i|
|
541
|
+
hit(["http://#{@addr}:#{@port}/#{i}"])
|
542
|
+
i == kill_point and Process.kill('HUP', pid)
|
543
|
+
end
|
544
|
+
end
|
545
|
+
wait_master_ready(master_log)
|
546
|
+
assert File.exist?(pid_file), "pid=#{pid_file} exists"
|
547
|
+
new_pid = File.read(pid_file).to_i
|
548
|
+
assert_not_equal pid, new_pid
|
549
|
+
assert_nothing_raised { Process.kill(0, new_pid) }
|
550
|
+
assert_nothing_raised { Process.kill('QUIT', new_pid) }
|
551
|
+
end
|
552
|
+
|
553
|
+
def wait_for_file(path)
|
554
|
+
tries = DEFAULT_TRIES
|
555
|
+
while (tries -= 1) > 0 && ! File.exist?(path)
|
556
|
+
sleep DEFAULT_RES
|
557
|
+
end
|
558
|
+
assert File.exist?(path), "path=#{path} exists"
|
559
|
+
end
|
560
|
+
|
561
|
+
def xfork(&block)
|
562
|
+
fork do
|
563
|
+
ObjectSpace.each_object(Tempfile) do |tmp|
|
564
|
+
ObjectSpace.undefine_finalizer(tmp)
|
565
|
+
end
|
566
|
+
yield
|
567
|
+
end
|
568
|
+
end
|
569
|
+
|
570
|
+
end if do_test
|