unicorn 0.2.3 → 0.4.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +1 -1
- data/.gitignore +1 -0
- data/CHANGELOG +1 -0
- data/DESIGN +4 -0
- data/GNUmakefile +30 -6
- data/Manifest +62 -3
- data/README +52 -42
- data/SIGNALS +17 -17
- data/TODO +27 -5
- data/bin/unicorn +15 -13
- data/bin/unicorn_rails +59 -22
- data/ext/unicorn/http11/http11.c +25 -104
- data/ext/unicorn/http11/http11_parser.c +24 -23
- data/ext/unicorn/http11/http11_parser.h +1 -3
- data/ext/unicorn/http11/http11_parser.rl +2 -1
- data/lib/unicorn.rb +58 -44
- data/lib/unicorn/app/old_rails.rb +23 -0
- data/lib/unicorn/app/old_rails/static.rb +58 -0
- data/lib/unicorn/cgi_wrapper.rb +151 -0
- data/lib/unicorn/configurator.rb +71 -31
- data/lib/unicorn/const.rb +9 -34
- data/lib/unicorn/http_request.rb +63 -66
- data/lib/unicorn/http_response.rb +6 -1
- data/lib/unicorn/socket.rb +15 -2
- data/test/benchmark/README +55 -0
- data/test/benchmark/big_request.rb +35 -0
- data/test/benchmark/dd.ru +18 -0
- data/test/benchmark/request.rb +47 -0
- data/test/benchmark/response.rb +29 -0
- data/test/exec/test_exec.rb +41 -157
- data/test/rails/app-1.2.3/.gitignore +2 -0
- data/test/rails/app-1.2.3/app/controllers/application.rb +4 -0
- data/test/rails/app-1.2.3/app/controllers/foo_controller.rb +34 -0
- data/test/rails/app-1.2.3/app/helpers/application_helper.rb +2 -0
- data/test/rails/app-1.2.3/config/boot.rb +9 -0
- data/test/rails/app-1.2.3/config/database.yml +12 -0
- data/test/rails/app-1.2.3/config/environment.rb +10 -0
- data/test/rails/app-1.2.3/config/environments/development.rb +7 -0
- data/test/rails/app-1.2.3/config/environments/production.rb +3 -0
- data/test/rails/app-1.2.3/config/routes.rb +4 -0
- data/test/rails/app-1.2.3/db/.gitignore +0 -0
- data/test/rails/app-1.2.3/public/404.html +1 -0
- data/test/rails/app-1.2.3/public/500.html +1 -0
- data/test/rails/app-2.0.2/.gitignore +2 -0
- data/test/rails/app-2.0.2/app/controllers/application.rb +2 -0
- data/test/rails/app-2.0.2/app/controllers/foo_controller.rb +34 -0
- data/test/rails/app-2.0.2/app/helpers/application_helper.rb +2 -0
- data/test/rails/app-2.0.2/config/boot.rb +9 -0
- data/test/rails/app-2.0.2/config/database.yml +12 -0
- data/test/rails/app-2.0.2/config/environment.rb +14 -0
- data/test/rails/app-2.0.2/config/environments/development.rb +6 -0
- data/test/rails/app-2.0.2/config/environments/production.rb +3 -0
- data/test/rails/app-2.0.2/config/routes.rb +4 -0
- data/test/rails/app-2.0.2/db/.gitignore +0 -0
- data/test/rails/app-2.0.2/public/404.html +1 -0
- data/test/rails/app-2.0.2/public/500.html +1 -0
- data/test/rails/app-2.2.2/.gitignore +2 -0
- data/test/rails/app-2.2.2/app/controllers/application.rb +2 -0
- data/test/rails/app-2.2.2/app/controllers/foo_controller.rb +34 -0
- data/test/rails/app-2.2.2/app/helpers/application_helper.rb +2 -0
- data/test/rails/app-2.2.2/config/boot.rb +109 -0
- data/test/rails/app-2.2.2/config/database.yml +12 -0
- data/test/rails/app-2.2.2/config/environment.rb +14 -0
- data/test/rails/app-2.2.2/config/environments/development.rb +5 -0
- data/test/rails/app-2.2.2/config/environments/production.rb +3 -0
- data/test/rails/app-2.2.2/config/routes.rb +4 -0
- data/test/rails/app-2.2.2/db/.gitignore +0 -0
- data/test/rails/app-2.2.2/public/404.html +1 -0
- data/test/rails/app-2.2.2/public/500.html +1 -0
- data/test/rails/app-2.3.2.1/.gitignore +2 -0
- data/test/rails/app-2.3.2.1/app/controllers/application_controller.rb +3 -0
- data/test/rails/app-2.3.2.1/app/controllers/foo_controller.rb +34 -0
- data/test/rails/app-2.3.2.1/app/helpers/application_helper.rb +2 -0
- data/test/rails/app-2.3.2.1/config/boot.rb +107 -0
- data/test/rails/app-2.3.2.1/config/database.yml +12 -0
- data/test/rails/app-2.3.2.1/config/environment.rb +14 -0
- data/test/rails/app-2.3.2.1/config/environments/development.rb +5 -0
- data/test/rails/app-2.3.2.1/config/environments/production.rb +4 -0
- data/test/rails/app-2.3.2.1/config/routes.rb +4 -0
- data/test/rails/app-2.3.2.1/db/.gitignore +0 -0
- data/test/rails/app-2.3.2.1/public/404.html +1 -0
- data/test/rails/app-2.3.2.1/public/500.html +1 -0
- data/test/rails/test_rails.rb +243 -0
- data/test/test_helper.rb +149 -2
- data/test/unit/test_configurator.rb +46 -0
- data/test/unit/test_http_parser.rb +77 -36
- data/test/unit/test_request.rb +2 -0
- data/test/unit/test_response.rb +20 -4
- data/test/unit/test_server.rb +30 -1
- data/test/unit/test_socket_helper.rb +159 -0
- data/unicorn.gemspec +5 -5
- metadata +68 -5
- data/test/benchmark/previous.rb +0 -11
- data/test/benchmark/simple.rb +0 -11
- data/test/benchmark/utils.rb +0 -82
@@ -0,0 +1,55 @@
|
|
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
|
+
== request.rb, response.rb, big_request.rb
|
46
|
+
|
47
|
+
These are micro-benchmarks designed to test internal components
|
48
|
+
of Unicorn. It assumes the internal Unicorn API is mostly stable.
|
49
|
+
|
50
|
+
== Contributors
|
51
|
+
|
52
|
+
This directory is maintained independently in the "benchmark" branch
|
53
|
+
based against v0.1.0. Only changes to this directory (test/benchmarks)
|
54
|
+
are committed to this branch although the master branch may merge this
|
55
|
+
branch occassionaly.
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'benchmark'
|
2
|
+
require 'tempfile'
|
3
|
+
require 'unicorn'
|
4
|
+
nr = ENV['nr'] ? ENV['nr'].to_i : 100
|
5
|
+
bs = ENV['bs'] ? ENV['bs'].to_i : (1024 * 1024)
|
6
|
+
count = ENV['count'] ? ENV['count'].to_i : 4
|
7
|
+
length = bs * count
|
8
|
+
slice = (' ' * bs).freeze
|
9
|
+
|
10
|
+
big = Tempfile.new('')
|
11
|
+
def big.unicorn_peeraddr; '127.0.0.1'; end
|
12
|
+
big.syswrite(
|
13
|
+
"PUT /hello/world/puturl?abcd=efg&hi#anchor HTTP/1.0\r\n" \
|
14
|
+
"Host: localhost\r\n" \
|
15
|
+
"Accept: */*\r\n" \
|
16
|
+
"Content-Length: #{length}\r\n" \
|
17
|
+
"User-Agent: test-user-agent 0.1.0 (Mozilla compatible) 5.0 asdfadfasda\r\n" \
|
18
|
+
"\r\n")
|
19
|
+
count.times { big.syswrite(slice) }
|
20
|
+
big.sysseek(0)
|
21
|
+
big.fsync
|
22
|
+
|
23
|
+
include Unicorn
|
24
|
+
request = HttpRequest.new(Logger.new($stderr))
|
25
|
+
|
26
|
+
Benchmark.bmbm do |x|
|
27
|
+
x.report("big") do
|
28
|
+
for i in 1..nr
|
29
|
+
request.read(big)
|
30
|
+
request.reset
|
31
|
+
big.sysseek(0)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
@@ -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 })
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'benchmark'
|
2
|
+
require 'unicorn'
|
3
|
+
nr = ENV['nr'] ? ENV['nr'].to_i : 100000
|
4
|
+
|
5
|
+
class TestClient
|
6
|
+
def initialize(response)
|
7
|
+
@response = (response.join("\r\n") << "\r\n\r\n").freeze
|
8
|
+
end
|
9
|
+
def sysread(len, buf)
|
10
|
+
buf.replace(@response)
|
11
|
+
end
|
12
|
+
|
13
|
+
def unicorn_peeraddr
|
14
|
+
'127.0.0.1'
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
small = TestClient.new([
|
19
|
+
'GET / HTTP/1.0',
|
20
|
+
'Host: localhost',
|
21
|
+
'Accept: */*',
|
22
|
+
'User-Agent: test-user-agent 0.1.0'
|
23
|
+
])
|
24
|
+
|
25
|
+
medium = TestClient.new([
|
26
|
+
'GET /hello/world/geturl?abcd=efg&hi#anchor HTTP/1.0',
|
27
|
+
'Host: localhost',
|
28
|
+
'Accept: */*',
|
29
|
+
'User-Agent: test-user-agent 0.1.0 (Mozilla compatible) 5.0 asdfadfasda'
|
30
|
+
])
|
31
|
+
|
32
|
+
include Unicorn
|
33
|
+
request = HttpRequest.new(Logger.new($stderr))
|
34
|
+
Benchmark.bmbm do |x|
|
35
|
+
x.report("small") do
|
36
|
+
for i in 1..nr
|
37
|
+
request.read(small)
|
38
|
+
request.reset
|
39
|
+
end
|
40
|
+
end
|
41
|
+
x.report("medium") do
|
42
|
+
for i in 1..nr
|
43
|
+
request.read(medium)
|
44
|
+
request.reset
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'benchmark'
|
2
|
+
require 'unicorn'
|
3
|
+
|
4
|
+
class NullWriter
|
5
|
+
def syswrite(buf); buf.size; end
|
6
|
+
def close; end
|
7
|
+
end
|
8
|
+
|
9
|
+
include Unicorn
|
10
|
+
|
11
|
+
socket = NullWriter.new
|
12
|
+
bs = ENV['bs'] ? ENV['bs'].to_i : 4096
|
13
|
+
count = ENV['count'] ? ENV['count'].to_i : 1
|
14
|
+
slice = (' ' * bs).freeze
|
15
|
+
body = (1..count).map { slice }.freeze
|
16
|
+
hdr = {
|
17
|
+
'Content-Length' => (bs * count).to_s.freeze,
|
18
|
+
'Content-Type' => 'text/plain'.freeze
|
19
|
+
}.freeze
|
20
|
+
response = [ 200, hdr, body ].freeze
|
21
|
+
|
22
|
+
nr = ENV['nr'] ? ENV['nr'].to_i : 100000
|
23
|
+
Benchmark.bmbm do |x|
|
24
|
+
x.report do
|
25
|
+
for i in 1..nr
|
26
|
+
HttpResponse.write(socket.dup, response)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
data/test/exec/test_exec.rb
CHANGED
@@ -1,43 +1,34 @@
|
|
1
1
|
# Copyright (c) 2009 Eric Wong
|
2
|
-
STDIN.sync = STDOUT.sync = STDERR.sync = true
|
3
2
|
require 'test/test_helper'
|
4
|
-
require 'pathname'
|
5
|
-
require 'tempfile'
|
6
|
-
require 'fileutils'
|
7
3
|
|
8
4
|
do_test = true
|
9
|
-
DEFAULT_TRIES = 1000
|
10
|
-
DEFAULT_RES = 0.2
|
11
|
-
|
12
5
|
$unicorn_bin = ENV['UNICORN_TEST_BIN'] || "unicorn"
|
13
6
|
redirect_test_io do
|
14
7
|
do_test = system($unicorn_bin, '-v')
|
15
8
|
end
|
16
9
|
|
17
10
|
unless do_test
|
18
|
-
|
19
|
-
|
11
|
+
warn "#{$unicorn_bin} not found in PATH=#{ENV['PATH']}, " \
|
12
|
+
"skipping this test"
|
20
13
|
end
|
21
14
|
|
22
|
-
|
23
|
-
|
24
|
-
rescue LoadError
|
25
|
-
STDERR.puts "Unable to load Rack, skipping this test"
|
15
|
+
unless try_require('rack')
|
16
|
+
warn "Unable to load Rack, skipping this test"
|
26
17
|
do_test = false
|
27
18
|
end
|
28
19
|
|
29
20
|
class ExecTest < Test::Unit::TestCase
|
30
|
-
trap(
|
21
|
+
trap(:QUIT, 'IGNORE')
|
31
22
|
|
32
23
|
HI = <<-EOS
|
33
24
|
use Rack::ContentLength
|
34
|
-
run proc { |env| [ 200, { 'Content-Type' => 'text/plain' }, "HI\\n" ] }
|
25
|
+
run proc { |env| [ 200, { 'Content-Type' => 'text/plain' }, [ "HI\\n" ] ] }
|
35
26
|
EOS
|
36
27
|
|
37
28
|
HELLO = <<-EOS
|
38
29
|
class Hello
|
39
30
|
def call(env)
|
40
|
-
[ 200, { 'Content-Type' => 'text/plain' }, "HI\\n" ]
|
31
|
+
[ 200, { 'Content-Type' => 'text/plain' }, [ "HI\\n" ] ]
|
41
32
|
end
|
42
33
|
end
|
43
34
|
EOS
|
@@ -47,7 +38,6 @@ end
|
|
47
38
|
HEAVY_CFG = <<-EOS
|
48
39
|
worker_processes 4
|
49
40
|
timeout 30
|
50
|
-
backlog 128
|
51
41
|
logger Logger.new('#{COMMON_TMP.path}')
|
52
42
|
before_fork do |server, worker_nr|
|
53
43
|
server.logger.info "before_fork: worker=\#{worker_nr}"
|
@@ -113,7 +103,7 @@ end
|
|
113
103
|
pid_file = "#{@tmpdir}/test.pid"
|
114
104
|
old_file = "#{pid_file}.oldbin"
|
115
105
|
ucfg = Tempfile.new('unicorn_test_config')
|
116
|
-
ucfg.syswrite("
|
106
|
+
ucfg.syswrite("listen %(#@addr:#@port)\n")
|
117
107
|
ucfg.syswrite("pid %(#{pid_file})\n")
|
118
108
|
ucfg.syswrite("logger Logger.new(%(#{@tmpdir}/log))\n")
|
119
109
|
pid = xfork do
|
@@ -126,14 +116,16 @@ end
|
|
126
116
|
|
127
117
|
wait_for_file(pid_file)
|
128
118
|
Process.waitpid(pid)
|
129
|
-
Process.kill(
|
119
|
+
Process.kill(:USR2, File.read(pid_file).to_i)
|
130
120
|
wait_for_file(old_file)
|
131
121
|
wait_for_file(pid_file)
|
132
|
-
|
122
|
+
old_pid = File.read(old_file).to_i
|
123
|
+
Process.kill(:QUIT, old_pid)
|
124
|
+
wait_for_death(old_pid)
|
133
125
|
|
134
126
|
ucfg.syswrite("timeout %(#{pid_file})\n") # introduce a bug
|
135
127
|
current_pid = File.read(pid_file).to_i
|
136
|
-
Process.kill(
|
128
|
+
Process.kill(:USR2, current_pid)
|
137
129
|
|
138
130
|
# wait for pid_file to restore itself
|
139
131
|
tries = DEFAULT_TRIES
|
@@ -156,9 +148,11 @@ end
|
|
156
148
|
# fix the bug
|
157
149
|
ucfg.sysseek(0)
|
158
150
|
ucfg.truncate(0)
|
159
|
-
ucfg.syswrite("
|
151
|
+
ucfg.syswrite("listen %(#@addr:#@port)\n")
|
152
|
+
ucfg.syswrite("listen %(#@addr:#{port2})\n")
|
160
153
|
ucfg.syswrite("pid %(#{pid_file})\n")
|
161
|
-
Process.kill(
|
154
|
+
assert_nothing_raised { Process.kill(:USR2, current_pid) }
|
155
|
+
|
162
156
|
wait_for_file(old_file)
|
163
157
|
wait_for_file(pid_file)
|
164
158
|
new_pid = File.read(pid_file).to_i
|
@@ -170,8 +164,8 @@ end
|
|
170
164
|
assert_equal String, results[1].class
|
171
165
|
|
172
166
|
assert_nothing_raised do
|
173
|
-
Process.kill(
|
174
|
-
Process.kill(
|
167
|
+
Process.kill(:QUIT, current_pid)
|
168
|
+
Process.kill(:QUIT, new_pid)
|
175
169
|
end
|
176
170
|
end
|
177
171
|
|
@@ -192,14 +186,16 @@ end
|
|
192
186
|
|
193
187
|
wait_for_file(pid_file)
|
194
188
|
Process.waitpid(pid)
|
195
|
-
Process.kill(
|
189
|
+
Process.kill(:USR2, File.read(pid_file).to_i)
|
196
190
|
wait_for_file(old_file)
|
197
191
|
wait_for_file(pid_file)
|
198
|
-
|
192
|
+
old_pid = File.read(old_file).to_i
|
193
|
+
Process.kill(:QUIT, old_pid)
|
194
|
+
wait_for_death(old_pid)
|
199
195
|
|
200
196
|
File.unlink("config.ru") # break reloading
|
201
197
|
current_pid = File.read(pid_file).to_i
|
202
|
-
Process.kill(
|
198
|
+
Process.kill(:USR2, current_pid)
|
203
199
|
|
204
200
|
# wait for pid_file to restore itself
|
205
201
|
tries = DEFAULT_TRIES
|
@@ -210,17 +206,17 @@ end
|
|
210
206
|
rescue Errno::ENOENT
|
211
207
|
(sleep(DEFAULT_RES) and (tries -= 1) > 0) and retry
|
212
208
|
end
|
213
|
-
assert_equal current_pid, File.read(pid_file).to_i
|
214
209
|
|
215
210
|
tries = DEFAULT_TRIES
|
216
211
|
while File.exist?(old_file)
|
217
212
|
(sleep(DEFAULT_RES) and (tries -= 1) > 0) or break
|
218
213
|
end
|
219
214
|
assert ! File.exist?(old_file), "oldbin=#{old_file} gone"
|
215
|
+
assert_equal current_pid, File.read(pid_file).to_i
|
220
216
|
|
221
217
|
# fix the bug
|
222
218
|
File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
|
223
|
-
Process.kill(
|
219
|
+
assert_nothing_raised { Process.kill(:USR2, current_pid) }
|
224
220
|
wait_for_file(old_file)
|
225
221
|
wait_for_file(pid_file)
|
226
222
|
new_pid = File.read(pid_file).to_i
|
@@ -230,24 +226,21 @@ end
|
|
230
226
|
assert_equal String, results[0].class
|
231
227
|
|
232
228
|
assert_nothing_raised do
|
233
|
-
Process.kill(
|
234
|
-
Process.kill(
|
229
|
+
Process.kill(:QUIT, current_pid)
|
230
|
+
Process.kill(:QUIT, new_pid)
|
235
231
|
end
|
236
232
|
end
|
237
233
|
|
238
|
-
def
|
239
|
-
port2 = unused_port(@addr)
|
234
|
+
def test_unicorn_config_listen_with_options
|
240
235
|
File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
|
241
|
-
# listeners = [ ... ] => should _override_ command-line options
|
242
236
|
ucfg = Tempfile.new('unicorn_test_config')
|
243
|
-
ucfg.syswrite("
|
237
|
+
ucfg.syswrite("listen '#{@addr}:#{@port}', :backlog => 512,\n")
|
238
|
+
ucfg.syswrite(" :rcvbuf => 4096,\n")
|
239
|
+
ucfg.syswrite(" :sndbuf => 4096\n")
|
244
240
|
pid = xfork do
|
245
|
-
redirect_test_io
|
246
|
-
exec($unicorn_bin, "-c#{ucfg.path}", "-l#{@addr}:#{port2}")
|
247
|
-
end
|
241
|
+
redirect_test_io { exec($unicorn_bin, "-c#{ucfg.path}") }
|
248
242
|
end
|
249
243
|
results = retry_hit(["http://#{@addr}:#{@port}/"])
|
250
|
-
assert_raises(Errno::ECONNREFUSED) { TCPSocket.new(@addr, port2) }
|
251
244
|
assert_equal String, results[0].class
|
252
245
|
assert_shutdown(pid)
|
253
246
|
end
|
@@ -289,29 +282,30 @@ end
|
|
289
282
|
rotate = Tempfile.new('unicorn_rotate')
|
290
283
|
assert_nothing_raised do
|
291
284
|
File.rename(COMMON_TMP.path, rotate.path)
|
292
|
-
Process.kill(
|
285
|
+
Process.kill(:USR1, pid)
|
293
286
|
end
|
294
287
|
wait_for_file(COMMON_TMP.path)
|
295
288
|
assert File.exist?(COMMON_TMP.path), "#{COMMON_TMP.path} exists"
|
296
289
|
# USR1 should've been passed to all workers
|
297
290
|
tries = DEFAULT_TRIES
|
298
291
|
log = File.readlines(rotate.path)
|
299
|
-
while (tries -= 1) > 0 &&
|
292
|
+
while (tries -= 1) > 0 &&
|
293
|
+
log.grep(/rotating logs\.\.\./).size < 5
|
300
294
|
sleep DEFAULT_RES
|
301
295
|
log = File.readlines(rotate.path)
|
302
296
|
end
|
303
|
-
assert_equal
|
297
|
+
assert_equal 5, log.grep(/rotating logs\.\.\./).size
|
304
298
|
assert_equal 0, log.grep(/done rotating logs/).size
|
305
299
|
|
306
300
|
tries = DEFAULT_TRIES
|
307
301
|
log = File.readlines(COMMON_TMP.path)
|
308
|
-
while (tries -= 1) > 0 && log.grep(/done rotating logs/).size <
|
302
|
+
while (tries -= 1) > 0 && log.grep(/done rotating logs/).size < 5
|
309
303
|
sleep DEFAULT_RES
|
310
304
|
log = File.readlines(COMMON_TMP.path)
|
311
305
|
end
|
312
|
-
assert_equal
|
306
|
+
assert_equal 5, log.grep(/done rotating logs/).size
|
313
307
|
assert_equal 0, log.grep(/rotating logs\.\.\./).size
|
314
|
-
assert_nothing_raised { Process.kill(
|
308
|
+
assert_nothing_raised { Process.kill(:QUIT, pid) }
|
315
309
|
status = nil
|
316
310
|
assert_nothing_raised { pid, status = Process.waitpid2(pid) }
|
317
311
|
assert status.success?, "exited successfully"
|
@@ -430,7 +424,7 @@ end
|
|
430
424
|
ucfg.syswrite("pid \"#{pid_file}\"\n")
|
431
425
|
ucfg.syswrite("logger Logger.new('#{new_log.path}')\n")
|
432
426
|
ucfg.close
|
433
|
-
Process.kill(
|
427
|
+
Process.kill(:HUP, pid)
|
434
428
|
end
|
435
429
|
|
436
430
|
wait_for_file(new_sock_path)
|
@@ -473,114 +467,4 @@ end
|
|
473
467
|
reexec_usr2_quit_test(new_pid, pid_file)
|
474
468
|
end
|
475
469
|
|
476
|
-
private
|
477
|
-
|
478
|
-
# sometimes the server may not come up right away
|
479
|
-
def retry_hit(uris = [])
|
480
|
-
tries = DEFAULT_TRIES
|
481
|
-
begin
|
482
|
-
hit(uris)
|
483
|
-
rescue Errno::ECONNREFUSED => err
|
484
|
-
if (tries -= 1) > 0
|
485
|
-
sleep DEFAULT_RES
|
486
|
-
retry
|
487
|
-
end
|
488
|
-
raise err
|
489
|
-
end
|
490
|
-
end
|
491
|
-
|
492
|
-
def assert_shutdown(pid)
|
493
|
-
wait_master_ready("#{@tmpdir}/test_stderr.#{pid}.log")
|
494
|
-
assert_nothing_raised { Process.kill('QUIT', pid) }
|
495
|
-
status = nil
|
496
|
-
assert_nothing_raised { pid, status = Process.waitpid2(pid) }
|
497
|
-
assert status.success?, "exited successfully"
|
498
|
-
end
|
499
|
-
|
500
|
-
def wait_workers_ready(path, nr_workers)
|
501
|
-
tries = DEFAULT_TRIES
|
502
|
-
lines = []
|
503
|
-
while (tries -= 1) > 0
|
504
|
-
begin
|
505
|
-
lines = File.readlines(path).grep(/worker=\d+ spawned/)
|
506
|
-
lines.size == nr_workers and return
|
507
|
-
rescue Errno::ENOENT
|
508
|
-
end
|
509
|
-
sleep DEFAULT_RES
|
510
|
-
end
|
511
|
-
raise "#{nr_workers} workers never became ready:" \
|
512
|
-
"\n\t#{lines.join("\n\t")}\n"
|
513
|
-
end
|
514
|
-
|
515
|
-
def wait_master_ready(master_log)
|
516
|
-
tries = DEFAULT_TRIES
|
517
|
-
while (tries -= 1) > 0
|
518
|
-
begin
|
519
|
-
File.readlines(master_log).grep(/master process ready/)[0] and return
|
520
|
-
rescue Errno::ENOENT
|
521
|
-
end
|
522
|
-
sleep DEFAULT_RES
|
523
|
-
end
|
524
|
-
raise "master process never became ready"
|
525
|
-
end
|
526
|
-
|
527
|
-
def reexec_usr2_quit_test(pid, pid_file)
|
528
|
-
assert File.exist?(pid_file), "pid file OK"
|
529
|
-
assert ! File.exist?("#{pid_file}.oldbin"), "oldbin pid file"
|
530
|
-
assert_nothing_raised { Process.kill('USR2', pid) }
|
531
|
-
assert_nothing_raised { retry_hit(["http://#{@addr}:#{@port}/"]) }
|
532
|
-
wait_for_file("#{pid_file}.oldbin")
|
533
|
-
wait_for_file(pid_file)
|
534
|
-
|
535
|
-
# kill old master process
|
536
|
-
assert_not_equal pid, File.read(pid_file).to_i
|
537
|
-
assert_equal pid, File.read("#{pid_file}.oldbin").to_i
|
538
|
-
assert_nothing_raised { Process.kill('QUIT', pid) }
|
539
|
-
assert_not_equal pid, File.read(pid_file).to_i
|
540
|
-
assert_nothing_raised { retry_hit(["http://#{@addr}:#{@port}/"]) }
|
541
|
-
wait_for_file(pid_file)
|
542
|
-
assert_nothing_raised { retry_hit(["http://#{@addr}:#{@port}/"]) }
|
543
|
-
assert_nothing_raised { Process.kill('QUIT', File.read(pid_file).to_i) }
|
544
|
-
end
|
545
|
-
|
546
|
-
def reexec_basic_test(pid, pid_file)
|
547
|
-
results = retry_hit(["http://#{@addr}:#{@port}/"])
|
548
|
-
assert_equal String, results[0].class
|
549
|
-
assert_nothing_raised { Process.kill(0, pid) }
|
550
|
-
master_log = "#{@tmpdir}/test_stderr.#{pid}.log"
|
551
|
-
wait_master_ready(master_log)
|
552
|
-
File.truncate(master_log, 0)
|
553
|
-
nr = 50
|
554
|
-
kill_point = 2
|
555
|
-
assert_nothing_raised do
|
556
|
-
nr.times do |i|
|
557
|
-
hit(["http://#{@addr}:#{@port}/#{i}"])
|
558
|
-
i == kill_point and Process.kill('HUP', pid)
|
559
|
-
end
|
560
|
-
end
|
561
|
-
wait_master_ready(master_log)
|
562
|
-
assert File.exist?(pid_file), "pid=#{pid_file} exists"
|
563
|
-
new_pid = File.read(pid_file).to_i
|
564
|
-
assert_not_equal pid, new_pid
|
565
|
-
assert_nothing_raised { Process.kill(0, new_pid) }
|
566
|
-
assert_nothing_raised { Process.kill('QUIT', new_pid) }
|
567
|
-
end
|
568
|
-
|
569
|
-
def wait_for_file(path)
|
570
|
-
tries = DEFAULT_TRIES
|
571
|
-
while (tries -= 1) > 0 && ! File.exist?(path)
|
572
|
-
sleep DEFAULT_RES
|
573
|
-
end
|
574
|
-
assert File.exist?(path), "path=#{path} exists #{caller.inspect}"
|
575
|
-
end
|
576
|
-
|
577
|
-
def xfork(&block)
|
578
|
-
fork do
|
579
|
-
ObjectSpace.each_object(Tempfile) do |tmp|
|
580
|
-
ObjectSpace.undefine_finalizer(tmp)
|
581
|
-
end
|
582
|
-
yield
|
583
|
-
end
|
584
|
-
end
|
585
|
-
|
586
470
|
end if do_test
|