unicorn-simon 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +156 -0
- data/.olddoc.yml +18 -0
- data/Application_Timeouts +77 -0
- data/CONTRIBUTORS +35 -0
- data/COPYING +674 -0
- data/DESIGN +95 -0
- data/Documentation/.gitignore +5 -0
- data/Documentation/GNUmakefile +30 -0
- data/Documentation/unicorn.1.txt +187 -0
- data/Documentation/unicorn_rails.1.txt +175 -0
- data/FAQ +70 -0
- data/GIT-VERSION-FILE +1 -0
- data/GIT-VERSION-GEN +39 -0
- data/GNUmakefile +253 -0
- data/HACKING +120 -0
- data/ISSUES +90 -0
- data/KNOWN_ISSUES +79 -0
- data/LATEST +30 -0
- data/LICENSE +67 -0
- data/Links +56 -0
- data/NEWS +2465 -0
- data/PHILOSOPHY +139 -0
- data/README +138 -0
- data/Rakefile +16 -0
- data/SIGNALS +123 -0
- data/Sandbox +104 -0
- data/TODO +3 -0
- data/TUNING +119 -0
- data/archive/.gitignore +3 -0
- data/archive/slrnpull.conf +4 -0
- data/bin/unicorn +126 -0
- data/bin/unicorn_rails +209 -0
- data/examples/big_app_gc.rb +2 -0
- data/examples/echo.ru +27 -0
- data/examples/init.sh +102 -0
- data/examples/logger_mp_safe.rb +25 -0
- data/examples/logrotate.conf +44 -0
- data/examples/nginx.conf +155 -0
- data/examples/unicorn.conf.minimal.rb +13 -0
- data/examples/unicorn.conf.rb +110 -0
- data/examples/unicorn.socket +11 -0
- data/examples/unicorn@.service +33 -0
- data/ext/unicorn_http/CFLAGS +13 -0
- data/ext/unicorn_http/c_util.h +124 -0
- data/ext/unicorn_http/common_field_optimization.h +111 -0
- data/ext/unicorn_http/ext_help.h +62 -0
- data/ext/unicorn_http/extconf.rb +11 -0
- data/ext/unicorn_http/global_variables.h +97 -0
- data/ext/unicorn_http/httpdate.c +78 -0
- data/ext/unicorn_http/unicorn_http.c +4274 -0
- data/ext/unicorn_http/unicorn_http.rl +980 -0
- data/ext/unicorn_http/unicorn_http_common.rl +76 -0
- data/lib/unicorn/app/old_rails/static.rb +59 -0
- data/lib/unicorn/app/old_rails.rb +35 -0
- data/lib/unicorn/cgi_wrapper.rb +147 -0
- data/lib/unicorn/configurator.rb +664 -0
- data/lib/unicorn/const.rb +21 -0
- data/lib/unicorn/http_request.rb +122 -0
- data/lib/unicorn/http_response.rb +60 -0
- data/lib/unicorn/http_server.rb +824 -0
- data/lib/unicorn/launcher.rb +62 -0
- data/lib/unicorn/oob_gc.rb +82 -0
- data/lib/unicorn/preread_input.rb +33 -0
- data/lib/unicorn/socket_helper.rb +195 -0
- data/lib/unicorn/stream_input.rb +146 -0
- data/lib/unicorn/tee_input.rb +133 -0
- data/lib/unicorn/tmpio.rb +27 -0
- data/lib/unicorn/util.rb +90 -0
- data/lib/unicorn/version.rb +1 -0
- data/lib/unicorn/worker.rb +140 -0
- data/lib/unicorn.rb +123 -0
- data/man/man1/unicorn.1 +221 -0
- data/man/man1/unicorn_rails.1 +212 -0
- data/setup.rb +1586 -0
- data/t/.gitignore +4 -0
- data/t/GNUmakefile +74 -0
- data/t/README +42 -0
- data/t/bin/content-md5-put +36 -0
- data/t/bin/sha1sum.rb +17 -0
- data/t/bin/unused_listen +40 -0
- data/t/broken-app.ru +12 -0
- data/t/detach.ru +11 -0
- data/t/env.ru +3 -0
- data/t/fails-rack-lint.ru +5 -0
- data/t/heartbeat-timeout.ru +12 -0
- data/t/hijack.ru +43 -0
- data/t/listener_names.ru +4 -0
- data/t/my-tap-lib.sh +201 -0
- data/t/oob_gc.ru +20 -0
- data/t/oob_gc_path.ru +20 -0
- data/t/pid.ru +3 -0
- data/t/preread_input.ru +17 -0
- data/t/rack-input-tests.ru +21 -0
- data/t/t0000-http-basic.sh +50 -0
- data/t/t0001-reload-bad-config.sh +53 -0
- data/t/t0002-config-conflict.sh +49 -0
- data/t/t0002-parser-error.sh +94 -0
- data/t/t0003-working_directory.sh +51 -0
- data/t/t0004-heartbeat-timeout.sh +69 -0
- data/t/t0004-working_directory_broken.sh +24 -0
- data/t/t0005-working_directory_app.rb.sh +40 -0
- data/t/t0006-reopen-logs.sh +83 -0
- data/t/t0006.ru +13 -0
- data/t/t0007-working_directory_no_embed_cli.sh +44 -0
- data/t/t0008-back_out_of_upgrade.sh +110 -0
- data/t/t0009-broken-app.sh +56 -0
- data/t/t0009-winch_ttin.sh +59 -0
- data/t/t0010-reap-logging.sh +55 -0
- data/t/t0011-active-unix-socket.sh +79 -0
- data/t/t0012-reload-empty-config.sh +85 -0
- data/t/t0013-rewindable-input-false.sh +24 -0
- data/t/t0013.ru +12 -0
- data/t/t0014-rewindable-input-true.sh +24 -0
- data/t/t0014.ru +12 -0
- data/t/t0015-configurator-internals.sh +25 -0
- data/t/t0018-write-on-close.sh +23 -0
- data/t/t0019-max_header_len.sh +49 -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/t0100-rack-input-tests.sh +124 -0
- data/t/t0116-client_body_buffer_size.sh +80 -0
- data/t/t0116.ru +16 -0
- data/t/t0200-rack-hijack.sh +30 -0
- data/t/t0300-no-default-middleware.sh +20 -0
- data/t/t9000-preread-input.sh +48 -0
- data/t/t9001-oob_gc.sh +47 -0
- data/t/t9002-oob_gc-path.sh +75 -0
- data/t/test-lib.sh +128 -0
- data/t/write-on-close.ru +11 -0
- data/test/aggregate.rb +15 -0
- data/test/benchmark/README +50 -0
- data/test/benchmark/dd.ru +18 -0
- data/test/benchmark/stack.ru +8 -0
- data/test/exec/README +5 -0
- data/test/exec/test_exec.rb +1099 -0
- data/test/test_helper.rb +298 -0
- data/test/unit/test_configurator.rb +175 -0
- data/test/unit/test_droplet.rb +28 -0
- data/test/unit/test_http_parser.rb +886 -0
- data/test/unit/test_http_parser_ng.rb +633 -0
- data/test/unit/test_request.rb +182 -0
- data/test/unit/test_response.rb +111 -0
- data/test/unit/test_server.rb +268 -0
- data/test/unit/test_signals.rb +188 -0
- data/test/unit/test_socket_helper.rb +197 -0
- data/test/unit/test_stream_input.rb +203 -0
- data/test/unit/test_tee_input.rb +304 -0
- data/test/unit/test_upload.rb +306 -0
- data/test/unit/test_util.rb +105 -0
- data/unicorn.gemspec +50 -0
- metadata +310 -0
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
# -*- encoding: binary -*-
|
|
2
|
+
|
|
3
|
+
# Copyright (c) 2009 Eric Wong
|
|
4
|
+
# You can redistribute it and/or modify it under the same terms as Ruby 1.8 or
|
|
5
|
+
# the GPLv2+ (GPLv3+ preferred)
|
|
6
|
+
#
|
|
7
|
+
# Ensure we stay sane in the face of signals being sent to us
|
|
8
|
+
|
|
9
|
+
require './test/test_helper'
|
|
10
|
+
|
|
11
|
+
include Unicorn
|
|
12
|
+
|
|
13
|
+
class Dd
|
|
14
|
+
def initialize(bs, count)
|
|
15
|
+
@count = count
|
|
16
|
+
@buf = ' ' * bs
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def each(&block)
|
|
20
|
+
@count.times { yield @buf }
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
class SignalsTest < Test::Unit::TestCase
|
|
25
|
+
|
|
26
|
+
def setup
|
|
27
|
+
@bs = 1 * 1024 * 1024
|
|
28
|
+
@count = 100
|
|
29
|
+
@port = unused_port
|
|
30
|
+
@sock = Tempfile.new('unicorn.sock')
|
|
31
|
+
@tmp = Tempfile.new('unicorn.write')
|
|
32
|
+
@tmp.sync = true
|
|
33
|
+
File.unlink(@sock.path)
|
|
34
|
+
File.unlink(@tmp.path)
|
|
35
|
+
@server_opts = {
|
|
36
|
+
:listeners => [ "127.0.0.1:#@port", @sock.path ],
|
|
37
|
+
:after_fork => lambda { |server,worker|
|
|
38
|
+
trap(:HUP) { @tmp.syswrite('.') }
|
|
39
|
+
},
|
|
40
|
+
}
|
|
41
|
+
@server = nil
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def teardown
|
|
45
|
+
reset_sig_handlers
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def test_worker_dies_on_dead_master
|
|
49
|
+
pid = fork {
|
|
50
|
+
app = lambda { |env| [ 200, {'X-Pid' => "#$$" }, [] ] }
|
|
51
|
+
opts = @server_opts.merge(:timeout => 3)
|
|
52
|
+
redirect_test_io { HttpServer.new(app, opts).start.join }
|
|
53
|
+
}
|
|
54
|
+
wait_workers_ready("test_stderr.#{pid}.log", 1)
|
|
55
|
+
sock = TCPSocket.new('127.0.0.1', @port)
|
|
56
|
+
sock.syswrite("GET / HTTP/1.0\r\n\r\n")
|
|
57
|
+
buf = sock.readpartial(4096)
|
|
58
|
+
assert_nil sock.close
|
|
59
|
+
buf =~ /\bX-Pid: (\d+)\b/ or raise Exception
|
|
60
|
+
child = $1.to_i
|
|
61
|
+
wait_master_ready("test_stderr.#{pid}.log")
|
|
62
|
+
wait_workers_ready("test_stderr.#{pid}.log", 1)
|
|
63
|
+
Process.kill(:KILL, pid)
|
|
64
|
+
Process.waitpid(pid)
|
|
65
|
+
File.unlink("test_stderr.#{pid}.log", "test_stdout.#{pid}.log")
|
|
66
|
+
t0 = Time.now
|
|
67
|
+
assert child
|
|
68
|
+
assert t0
|
|
69
|
+
assert_raises(Errno::ESRCH) { loop { Process.kill(0, child); sleep 0.2 } }
|
|
70
|
+
assert((Time.now - t0) < 60)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def test_sleepy_kill
|
|
74
|
+
rd, wr = IO.pipe
|
|
75
|
+
pid = fork {
|
|
76
|
+
rd.close
|
|
77
|
+
app = lambda { |env| wr.syswrite('.'); sleep; [ 200, {}, [] ] }
|
|
78
|
+
redirect_test_io { HttpServer.new(app, @server_opts).start.join }
|
|
79
|
+
}
|
|
80
|
+
wr.close
|
|
81
|
+
wait_workers_ready("test_stderr.#{pid}.log", 1)
|
|
82
|
+
sock = TCPSocket.new('127.0.0.1', @port)
|
|
83
|
+
sock.syswrite("GET / HTTP/1.0\r\n\r\n")
|
|
84
|
+
buf = rd.readpartial(1)
|
|
85
|
+
wait_master_ready("test_stderr.#{pid}.log")
|
|
86
|
+
Process.kill(:INT, pid)
|
|
87
|
+
Process.waitpid(pid)
|
|
88
|
+
assert_equal '.', buf
|
|
89
|
+
buf = nil
|
|
90
|
+
assert_raises(EOFError,Errno::ECONNRESET,Errno::EPIPE,Errno::EINVAL,
|
|
91
|
+
Errno::EBADF) do
|
|
92
|
+
buf = sock.sysread(4096)
|
|
93
|
+
end
|
|
94
|
+
assert_nil buf
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def test_timeout_slow_response
|
|
98
|
+
pid = fork {
|
|
99
|
+
app = lambda { |env| sleep }
|
|
100
|
+
opts = @server_opts.merge(:timeout => 3)
|
|
101
|
+
redirect_test_io { HttpServer.new(app, opts).start.join }
|
|
102
|
+
}
|
|
103
|
+
t0 = Time.now
|
|
104
|
+
wait_workers_ready("test_stderr.#{pid}.log", 1)
|
|
105
|
+
sock = TCPSocket.new('127.0.0.1', @port)
|
|
106
|
+
sock.syswrite("GET / HTTP/1.0\r\n\r\n")
|
|
107
|
+
|
|
108
|
+
buf = nil
|
|
109
|
+
assert_raises(EOFError,Errno::ECONNRESET,Errno::EPIPE,Errno::EINVAL,
|
|
110
|
+
Errno::EBADF) do
|
|
111
|
+
buf = sock.sysread(4096)
|
|
112
|
+
end
|
|
113
|
+
diff = Time.now - t0
|
|
114
|
+
assert_nil buf
|
|
115
|
+
assert diff > 1.0, "diff was #{diff.inspect}"
|
|
116
|
+
assert diff < 60.0
|
|
117
|
+
ensure
|
|
118
|
+
Process.kill(:TERM, pid) rescue nil
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def test_response_write
|
|
122
|
+
app = lambda { |env|
|
|
123
|
+
[ 200, { 'Content-Type' => 'text/plain', 'X-Pid' => Process.pid.to_s },
|
|
124
|
+
Dd.new(@bs, @count) ]
|
|
125
|
+
}
|
|
126
|
+
redirect_test_io { @server = HttpServer.new(app, @server_opts).start }
|
|
127
|
+
wait_workers_ready("test_stderr.#{$$}.log", 1)
|
|
128
|
+
sock = TCPSocket.new('127.0.0.1', @port)
|
|
129
|
+
sock.syswrite("GET / HTTP/1.0\r\n\r\n")
|
|
130
|
+
buf = ''
|
|
131
|
+
header_len = pid = nil
|
|
132
|
+
buf = sock.sysread(16384, buf)
|
|
133
|
+
pid = buf[/\r\nX-Pid: (\d+)\r\n/, 1].to_i
|
|
134
|
+
header_len = buf[/\A(.+?\r\n\r\n)/m, 1].size
|
|
135
|
+
assert pid > 0, "pid not positive: #{pid.inspect}"
|
|
136
|
+
read = buf.size
|
|
137
|
+
size_before = @tmp.stat.size
|
|
138
|
+
assert_raises(EOFError,Errno::ECONNRESET,Errno::EPIPE,Errno::EINVAL,
|
|
139
|
+
Errno::EBADF) do
|
|
140
|
+
loop do
|
|
141
|
+
3.times { Process.kill(:HUP, pid) }
|
|
142
|
+
sock.sysread(16384, buf)
|
|
143
|
+
read += buf.size
|
|
144
|
+
3.times { Process.kill(:HUP, pid) }
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
redirect_test_io { @server.stop(true) }
|
|
149
|
+
# can't check for == since pending signals get merged
|
|
150
|
+
assert size_before < @tmp.stat.size
|
|
151
|
+
got = read - header_len
|
|
152
|
+
expect = @bs * @count
|
|
153
|
+
assert_equal(expect, got, "expect=#{expect} got=#{got}")
|
|
154
|
+
assert_nil sock.close
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def test_request_read
|
|
158
|
+
app = lambda { |env|
|
|
159
|
+
while env['rack.input'].read(4096)
|
|
160
|
+
end
|
|
161
|
+
[ 200, {'Content-Type'=>'text/plain', 'X-Pid'=>Process.pid.to_s}, [] ]
|
|
162
|
+
}
|
|
163
|
+
redirect_test_io { @server = HttpServer.new(app, @server_opts).start }
|
|
164
|
+
|
|
165
|
+
wait_workers_ready("test_stderr.#{$$}.log", 1)
|
|
166
|
+
sock = TCPSocket.new('127.0.0.1', @port)
|
|
167
|
+
sock.syswrite("GET / HTTP/1.0\r\n\r\n")
|
|
168
|
+
pid = sock.sysread(4096)[/\r\nX-Pid: (\d+)\r\n/, 1].to_i
|
|
169
|
+
assert_nil sock.close
|
|
170
|
+
|
|
171
|
+
assert pid > 0, "pid not positive: #{pid.inspect}"
|
|
172
|
+
sock = TCPSocket.new('127.0.0.1', @port)
|
|
173
|
+
sock.syswrite("PUT / HTTP/1.0\r\n")
|
|
174
|
+
sock.syswrite("Content-Length: #{@bs * @count}\r\n\r\n")
|
|
175
|
+
1000.times { Process.kill(:HUP, pid) }
|
|
176
|
+
size_before = @tmp.stat.size
|
|
177
|
+
killer = fork { loop { Process.kill(:HUP, pid); sleep(0.01) } }
|
|
178
|
+
buf = ' ' * @bs
|
|
179
|
+
@count.times { sock.syswrite(buf) }
|
|
180
|
+
Process.kill(:KILL, killer)
|
|
181
|
+
Process.waitpid2(killer)
|
|
182
|
+
redirect_test_io { @server.stop(true) }
|
|
183
|
+
# can't check for == since pending signals get merged
|
|
184
|
+
assert size_before < @tmp.stat.size
|
|
185
|
+
assert_equal pid, sock.sysread(4096)[/\r\nX-Pid: (\d+)\r\n/, 1].to_i
|
|
186
|
+
assert_nil sock.close
|
|
187
|
+
end
|
|
188
|
+
end
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
# -*- encoding: binary -*-
|
|
2
|
+
|
|
3
|
+
require './test/test_helper'
|
|
4
|
+
require 'tempfile'
|
|
5
|
+
|
|
6
|
+
class TestSocketHelper < Test::Unit::TestCase
|
|
7
|
+
include Unicorn::SocketHelper
|
|
8
|
+
attr_reader :logger
|
|
9
|
+
GET_SLASH = "GET / HTTP/1.0\r\n\r\n".freeze
|
|
10
|
+
|
|
11
|
+
def setup
|
|
12
|
+
@log_tmp = Tempfile.new 'logger'
|
|
13
|
+
@logger = Logger.new(@log_tmp.path)
|
|
14
|
+
@test_addr = ENV['UNICORN_TEST_ADDR'] || '127.0.0.1'
|
|
15
|
+
@test6_addr = ENV['UNICORN_TEST6_ADDR'] || '::1'
|
|
16
|
+
GC.disable
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def teardown
|
|
20
|
+
GC.enable
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def test_bind_listen_tcp
|
|
24
|
+
port = unused_port @test_addr
|
|
25
|
+
@tcp_listener_name = "#@test_addr:#{port}"
|
|
26
|
+
@tcp_listener = bind_listen(@tcp_listener_name)
|
|
27
|
+
assert TCPServer === @tcp_listener
|
|
28
|
+
assert_equal @tcp_listener_name, sock_name(@tcp_listener)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def test_bind_listen_options
|
|
32
|
+
port = unused_port @test_addr
|
|
33
|
+
tcp_listener_name = "#@test_addr:#{port}"
|
|
34
|
+
tmp = Tempfile.new 'unix.sock'
|
|
35
|
+
unix_listener_name = tmp.path
|
|
36
|
+
File.unlink(tmp.path)
|
|
37
|
+
[ { :backlog => 5 }, { :sndbuf => 4096 }, { :rcvbuf => 4096 },
|
|
38
|
+
{ :backlog => 16, :rcvbuf => 4096, :sndbuf => 4096 }
|
|
39
|
+
].each do |opts|
|
|
40
|
+
tcp_listener = bind_listen(tcp_listener_name, opts)
|
|
41
|
+
assert TCPServer === tcp_listener
|
|
42
|
+
tcp_listener.close
|
|
43
|
+
unix_listener = bind_listen(unix_listener_name, opts)
|
|
44
|
+
assert UNIXServer === unix_listener
|
|
45
|
+
unix_listener.close
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def test_bind_listen_unix
|
|
50
|
+
old_umask = File.umask(0777)
|
|
51
|
+
tmp = Tempfile.new 'unix.sock'
|
|
52
|
+
@unix_listener_path = tmp.path
|
|
53
|
+
File.unlink(@unix_listener_path)
|
|
54
|
+
@unix_listener = bind_listen(@unix_listener_path)
|
|
55
|
+
assert UNIXServer === @unix_listener
|
|
56
|
+
assert_equal @unix_listener_path, sock_name(@unix_listener)
|
|
57
|
+
assert File.readable?(@unix_listener_path), "not readable"
|
|
58
|
+
assert File.writable?(@unix_listener_path), "not writable"
|
|
59
|
+
assert_equal 0777, File.umask
|
|
60
|
+
ensure
|
|
61
|
+
File.umask(old_umask)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def test_bind_listen_unix_umask
|
|
65
|
+
old_umask = File.umask(0777)
|
|
66
|
+
tmp = Tempfile.new 'unix.sock'
|
|
67
|
+
@unix_listener_path = tmp.path
|
|
68
|
+
File.unlink(@unix_listener_path)
|
|
69
|
+
@unix_listener = bind_listen(@unix_listener_path, :umask => 077)
|
|
70
|
+
assert UNIXServer === @unix_listener
|
|
71
|
+
assert_equal @unix_listener_path, sock_name(@unix_listener)
|
|
72
|
+
assert_equal 0140700, File.stat(@unix_listener_path).mode
|
|
73
|
+
assert_equal 0777, File.umask
|
|
74
|
+
ensure
|
|
75
|
+
File.umask(old_umask)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def test_bind_listen_unix_idempotent
|
|
79
|
+
test_bind_listen_unix
|
|
80
|
+
a = bind_listen(@unix_listener)
|
|
81
|
+
assert_equal a.fileno, @unix_listener.fileno
|
|
82
|
+
unix_server = server_cast(@unix_listener)
|
|
83
|
+
assert UNIXServer === unix_server
|
|
84
|
+
a = bind_listen(unix_server)
|
|
85
|
+
assert_equal a.fileno, unix_server.fileno
|
|
86
|
+
assert_equal a.fileno, @unix_listener.fileno
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def test_bind_listen_tcp_idempotent
|
|
90
|
+
test_bind_listen_tcp
|
|
91
|
+
a = bind_listen(@tcp_listener)
|
|
92
|
+
assert_equal a.fileno, @tcp_listener.fileno
|
|
93
|
+
tcp_server = server_cast(@tcp_listener)
|
|
94
|
+
assert TCPServer === tcp_server
|
|
95
|
+
a = bind_listen(tcp_server)
|
|
96
|
+
assert_equal a.fileno, tcp_server.fileno
|
|
97
|
+
assert_equal a.fileno, @tcp_listener.fileno
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def test_bind_listen_unix_rebind
|
|
101
|
+
test_bind_listen_unix
|
|
102
|
+
new_listener = nil
|
|
103
|
+
assert_raises(Errno::EADDRINUSE) do
|
|
104
|
+
new_listener = bind_listen(@unix_listener_path)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
File.unlink(@unix_listener_path)
|
|
108
|
+
new_listener = bind_listen(@unix_listener_path)
|
|
109
|
+
|
|
110
|
+
assert UNIXServer === new_listener
|
|
111
|
+
assert new_listener.fileno != @unix_listener.fileno
|
|
112
|
+
assert_equal sock_name(new_listener), sock_name(@unix_listener)
|
|
113
|
+
assert_equal @unix_listener_path, sock_name(new_listener)
|
|
114
|
+
pid = fork do
|
|
115
|
+
client = server_cast(new_listener).accept
|
|
116
|
+
client.syswrite('abcde')
|
|
117
|
+
exit 0
|
|
118
|
+
end
|
|
119
|
+
s = UNIXSocket.new(@unix_listener_path)
|
|
120
|
+
IO.select([s])
|
|
121
|
+
assert_equal 'abcde', s.sysread(5)
|
|
122
|
+
pid, status = Process.waitpid2(pid)
|
|
123
|
+
assert status.success?
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def test_server_cast
|
|
127
|
+
test_bind_listen_unix
|
|
128
|
+
test_bind_listen_tcp
|
|
129
|
+
unix_listener_socket = Socket.for_fd(@unix_listener.fileno)
|
|
130
|
+
assert Socket === unix_listener_socket
|
|
131
|
+
@unix_server = server_cast(unix_listener_socket)
|
|
132
|
+
assert_equal @unix_listener.fileno, @unix_server.fileno
|
|
133
|
+
assert UNIXServer === @unix_server
|
|
134
|
+
assert_equal(@unix_server.path, @unix_listener.path,
|
|
135
|
+
"##{@unix_server.path} != #{@unix_listener.path}")
|
|
136
|
+
assert File.socket?(@unix_server.path)
|
|
137
|
+
assert_equal @unix_listener_path, sock_name(@unix_server)
|
|
138
|
+
|
|
139
|
+
tcp_listener_socket = Socket.for_fd(@tcp_listener.fileno)
|
|
140
|
+
assert Socket === tcp_listener_socket
|
|
141
|
+
@tcp_server = server_cast(tcp_listener_socket)
|
|
142
|
+
assert_equal @tcp_listener.fileno, @tcp_server.fileno
|
|
143
|
+
assert TCPServer === @tcp_server
|
|
144
|
+
assert_equal @tcp_listener_name, sock_name(@tcp_server)
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def test_sock_name
|
|
148
|
+
test_server_cast
|
|
149
|
+
sock_name(@unix_server)
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def test_tcp_defer_accept_default
|
|
153
|
+
port = unused_port @test_addr
|
|
154
|
+
name = "#@test_addr:#{port}"
|
|
155
|
+
sock = bind_listen(name)
|
|
156
|
+
cur = sock.getsockopt(Socket::SOL_TCP, TCP_DEFER_ACCEPT).unpack('i')[0]
|
|
157
|
+
assert cur >= 1
|
|
158
|
+
end if defined?(TCP_DEFER_ACCEPT)
|
|
159
|
+
|
|
160
|
+
def test_tcp_defer_accept_disable
|
|
161
|
+
port = unused_port @test_addr
|
|
162
|
+
name = "#@test_addr:#{port}"
|
|
163
|
+
sock = bind_listen(name, :tcp_defer_accept => false)
|
|
164
|
+
cur = sock.getsockopt(Socket::SOL_TCP, TCP_DEFER_ACCEPT).unpack('i')[0]
|
|
165
|
+
assert_equal 0, cur
|
|
166
|
+
end if defined?(TCP_DEFER_ACCEPT)
|
|
167
|
+
|
|
168
|
+
def test_tcp_defer_accept_nr
|
|
169
|
+
port = unused_port @test_addr
|
|
170
|
+
name = "#@test_addr:#{port}"
|
|
171
|
+
sock = bind_listen(name, :tcp_defer_accept => 60)
|
|
172
|
+
cur = sock.getsockopt(Socket::SOL_TCP, TCP_DEFER_ACCEPT).unpack('i')[0]
|
|
173
|
+
assert cur > 1
|
|
174
|
+
end if defined?(TCP_DEFER_ACCEPT)
|
|
175
|
+
|
|
176
|
+
def test_ipv6only
|
|
177
|
+
port = begin
|
|
178
|
+
unused_port "#@test6_addr"
|
|
179
|
+
rescue Errno::EINVAL
|
|
180
|
+
return
|
|
181
|
+
end
|
|
182
|
+
sock = bind_listen "[#@test6_addr]:#{port}", :ipv6only => true
|
|
183
|
+
cur = sock.getsockopt(:IPPROTO_IPV6, :IPV6_V6ONLY).unpack('i')[0]
|
|
184
|
+
assert_equal 1, cur
|
|
185
|
+
rescue Errno::EAFNOSUPPORT
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
def test_reuseport
|
|
189
|
+
port = unused_port @test_addr
|
|
190
|
+
name = "#@test_addr:#{port}"
|
|
191
|
+
sock = bind_listen(name, :reuseport => true)
|
|
192
|
+
cur = sock.getsockopt(:SOL_SOCKET, :SO_REUSEPORT).int
|
|
193
|
+
assert_operator cur, :>, 0
|
|
194
|
+
rescue Errno::ENOPROTOOPT
|
|
195
|
+
# kernel does not support SO_REUSEPORT (older Linux)
|
|
196
|
+
end if defined?(Socket::SO_REUSEPORT)
|
|
197
|
+
end
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
# -*- encoding: binary -*-
|
|
2
|
+
|
|
3
|
+
require 'test/unit'
|
|
4
|
+
require 'digest/sha1'
|
|
5
|
+
require 'unicorn'
|
|
6
|
+
|
|
7
|
+
class TestStreamInput < Test::Unit::TestCase
|
|
8
|
+
def setup
|
|
9
|
+
@rs = $/
|
|
10
|
+
@env = {}
|
|
11
|
+
@rd, @wr = Kgio::UNIXSocket.pair
|
|
12
|
+
@rd.sync = @wr.sync = true
|
|
13
|
+
@start_pid = $$
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def teardown
|
|
17
|
+
return if $$ != @start_pid
|
|
18
|
+
$/ = @rs
|
|
19
|
+
@rd.close rescue nil
|
|
20
|
+
@wr.close rescue nil
|
|
21
|
+
Process.waitall
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def test_read_negative
|
|
25
|
+
r = init_request('hello')
|
|
26
|
+
si = Unicorn::StreamInput.new(@rd, r)
|
|
27
|
+
assert_raises(ArgumentError) { si.read(-1) }
|
|
28
|
+
assert_equal 'hello', si.read
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def test_read_small
|
|
32
|
+
r = init_request('hello')
|
|
33
|
+
si = Unicorn::StreamInput.new(@rd, r)
|
|
34
|
+
assert_equal 'hello', si.read
|
|
35
|
+
assert_equal '', si.read
|
|
36
|
+
assert_nil si.read(5)
|
|
37
|
+
assert_nil si.gets
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def test_gets_oneliner
|
|
41
|
+
r = init_request('hello')
|
|
42
|
+
si = Unicorn::StreamInput.new(@rd, r)
|
|
43
|
+
assert_equal 'hello', si.gets
|
|
44
|
+
assert_nil si.gets
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def test_gets_multiline
|
|
48
|
+
r = init_request("a\nb\n\n")
|
|
49
|
+
si = Unicorn::StreamInput.new(@rd, r)
|
|
50
|
+
assert_equal "a\n", si.gets
|
|
51
|
+
assert_equal "b\n", si.gets
|
|
52
|
+
assert_equal "\n", si.gets
|
|
53
|
+
assert_nil si.gets
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def test_gets_empty_rs
|
|
57
|
+
$/ = nil
|
|
58
|
+
r = init_request("a\nb\n\n")
|
|
59
|
+
si = Unicorn::StreamInput.new(@rd, r)
|
|
60
|
+
assert_equal "a\nb\n\n", si.gets
|
|
61
|
+
assert_nil si.gets
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def test_read_with_equal_len
|
|
65
|
+
r = init_request("abcde")
|
|
66
|
+
si = Unicorn::StreamInput.new(@rd, r)
|
|
67
|
+
assert_equal "abcde", si.read(5)
|
|
68
|
+
assert_nil si.read(5)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def test_big_body_multi
|
|
72
|
+
r = init_request('.', Unicorn::Const::MAX_BODY + 1)
|
|
73
|
+
si = Unicorn::StreamInput.new(@rd, r)
|
|
74
|
+
assert_equal Unicorn::Const::MAX_BODY, @parser.content_length
|
|
75
|
+
assert ! @parser.body_eof?
|
|
76
|
+
nr = Unicorn::Const::MAX_BODY / 4
|
|
77
|
+
pid = fork {
|
|
78
|
+
@rd.close
|
|
79
|
+
nr.times { @wr.write('....') }
|
|
80
|
+
@wr.close
|
|
81
|
+
}
|
|
82
|
+
@wr.close
|
|
83
|
+
assert_equal '.', si.read(1)
|
|
84
|
+
nr.times { |x|
|
|
85
|
+
assert_equal '....', si.read(4), "nr=#{x}"
|
|
86
|
+
}
|
|
87
|
+
assert_nil si.read(1)
|
|
88
|
+
pid, status = Process.waitpid2(pid)
|
|
89
|
+
assert status.success?
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def test_gets_long
|
|
93
|
+
r = init_request("hello", 5 + (4096 * 4 * 3) + "#$/foo#$/".size)
|
|
94
|
+
si = Unicorn::StreamInput.new(@rd, r)
|
|
95
|
+
status = line = nil
|
|
96
|
+
pid = fork {
|
|
97
|
+
@rd.close
|
|
98
|
+
3.times { @wr.write("ffff" * 4096) }
|
|
99
|
+
@wr.write "#$/foo#$/"
|
|
100
|
+
@wr.close
|
|
101
|
+
}
|
|
102
|
+
@wr.close
|
|
103
|
+
line = si.gets
|
|
104
|
+
assert_equal(4096 * 4 * 3 + 5 + $/.size, line.size)
|
|
105
|
+
assert_equal("hello" << ("ffff" * 4096 * 3) << "#$/", line)
|
|
106
|
+
line = si.gets
|
|
107
|
+
assert_equal "foo#$/", line
|
|
108
|
+
assert_nil si.gets
|
|
109
|
+
pid, status = Process.waitpid2(pid)
|
|
110
|
+
assert status.success?
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def test_read_with_buffer
|
|
114
|
+
r = init_request('hello')
|
|
115
|
+
si = Unicorn::StreamInput.new(@rd, r)
|
|
116
|
+
buf = ''
|
|
117
|
+
rv = si.read(4, buf)
|
|
118
|
+
assert_equal 'hell', rv
|
|
119
|
+
assert_equal 'hell', buf
|
|
120
|
+
assert_equal rv.object_id, buf.object_id
|
|
121
|
+
assert_equal 'o', si.read
|
|
122
|
+
assert_equal nil, si.read(5, buf)
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def test_read_with_buffer_clobbers
|
|
126
|
+
r = init_request('hello')
|
|
127
|
+
si = Unicorn::StreamInput.new(@rd, r)
|
|
128
|
+
buf = 'foo'
|
|
129
|
+
assert_equal 'hello', si.read(nil, buf)
|
|
130
|
+
assert_equal 'hello', buf
|
|
131
|
+
assert_equal '', si.read(nil, buf)
|
|
132
|
+
assert_equal '', buf
|
|
133
|
+
buf = 'asdf'
|
|
134
|
+
assert_nil si.read(5, buf)
|
|
135
|
+
assert_equal '', buf
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def test_read_zero
|
|
139
|
+
r = init_request('hello')
|
|
140
|
+
si = Unicorn::StreamInput.new(@rd, r)
|
|
141
|
+
assert_equal '', si.read(0)
|
|
142
|
+
buf = 'asdf'
|
|
143
|
+
rv = si.read(0, buf)
|
|
144
|
+
assert_equal rv.object_id, buf.object_id
|
|
145
|
+
assert_equal '', buf
|
|
146
|
+
assert_equal 'hello', si.read
|
|
147
|
+
assert_nil si.read(5)
|
|
148
|
+
assert_equal '', si.read(0)
|
|
149
|
+
buf = 'hello'
|
|
150
|
+
rv = si.read(0, buf)
|
|
151
|
+
assert_equal rv.object_id, buf.object_id
|
|
152
|
+
assert_equal '', rv
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def test_gets_read_mix
|
|
156
|
+
r = init_request("hello\nasdfasdf")
|
|
157
|
+
si = Unicorn::StreamInput.new(@rd, r)
|
|
158
|
+
assert_equal "hello\n", si.gets
|
|
159
|
+
assert_equal "asdfasdf", si.read(9)
|
|
160
|
+
assert_nil si.read(9)
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def test_gets_read_mix_chunked
|
|
164
|
+
r = @parser = Unicorn::HttpParser.new
|
|
165
|
+
body = "6\r\nhello"
|
|
166
|
+
@buf = "POST / HTTP/1.1\r\n" \
|
|
167
|
+
"Host: localhost\r\n" \
|
|
168
|
+
"Transfer-Encoding: chunked\r\n" \
|
|
169
|
+
"\r\n#{body}"
|
|
170
|
+
assert_equal @env, @parser.headers(@env, @buf)
|
|
171
|
+
assert_equal body, @buf
|
|
172
|
+
si = Unicorn::StreamInput.new(@rd, r)
|
|
173
|
+
@wr.syswrite "\n\r\n"
|
|
174
|
+
assert_equal "hello\n", si.gets
|
|
175
|
+
@wr.syswrite "8\r\nasdfasdf\r\n"
|
|
176
|
+
assert_equal"asdfasdf", si.read(9) + si.read(9)
|
|
177
|
+
@wr.syswrite "0\r\n\r\n"
|
|
178
|
+
assert_nil si.read(9)
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
def test_gets_read_mix_big
|
|
182
|
+
r = init_request("hello\n#{'.' * 65536}")
|
|
183
|
+
si = Unicorn::StreamInput.new(@rd, r)
|
|
184
|
+
assert_equal "hello\n", si.gets
|
|
185
|
+
assert_equal '.' * 16384, si.read(16384)
|
|
186
|
+
assert_equal '.' * 16383, si.read(16383)
|
|
187
|
+
assert_equal '.' * 16384, si.read(16384)
|
|
188
|
+
assert_equal '.' * 16385, si.read(16385)
|
|
189
|
+
assert_nil si.gets
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
def init_request(body, size = nil)
|
|
193
|
+
@parser = Unicorn::HttpParser.new
|
|
194
|
+
body = body.to_s.freeze
|
|
195
|
+
@buf = "POST / HTTP/1.1\r\n" \
|
|
196
|
+
"Host: localhost\r\n" \
|
|
197
|
+
"Content-Length: #{size || body.size}\r\n" \
|
|
198
|
+
"\r\n#{body}"
|
|
199
|
+
assert_equal @env, @parser.headers(@env, @buf)
|
|
200
|
+
assert_equal body, @buf
|
|
201
|
+
@parser
|
|
202
|
+
end
|
|
203
|
+
end
|