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,245 @@
|
|
1
|
+
# -*- encoding: binary -*-
|
2
|
+
# frozen_string_literal: false
|
3
|
+
|
4
|
+
# Copyright (c) 2005 Zed A. Shaw
|
5
|
+
# You can redistribute it and/or modify it under the same terms as Ruby 1.8 or
|
6
|
+
# the GPLv2+ (GPLv3+ preferred)
|
7
|
+
#
|
8
|
+
# Additional work donated by contributors. See git history
|
9
|
+
# for more information.
|
10
|
+
|
11
|
+
require './test/test_helper'
|
12
|
+
|
13
|
+
include Unicorn
|
14
|
+
|
15
|
+
class TestHandler
|
16
|
+
|
17
|
+
def call(env)
|
18
|
+
while env['rack.input'].read(4096)
|
19
|
+
end
|
20
|
+
[200, { 'content-type' => 'text/plain' }, ['hello!\n']]
|
21
|
+
rescue Unicorn::ClientShutdown, Unicorn::HttpParserError => e
|
22
|
+
$stderr.syswrite("#{e.class}: #{e.message} #{e.backtrace.empty?}\n")
|
23
|
+
raise e
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
class TestRackAfterReply
|
28
|
+
def initialize
|
29
|
+
@called = false
|
30
|
+
end
|
31
|
+
|
32
|
+
def call(env)
|
33
|
+
while env['rack.input'].read(4096)
|
34
|
+
end
|
35
|
+
|
36
|
+
env["rack.after_reply"] << -> { @called = true }
|
37
|
+
|
38
|
+
[200, { 'content-type' => 'text/plain' }, ["after_reply_called: #{@called}"]]
|
39
|
+
rescue Unicorn::ClientShutdown, Unicorn::HttpParserError => e
|
40
|
+
$stderr.syswrite("#{e.class}: #{e.message} #{e.backtrace.empty?}\n")
|
41
|
+
raise e
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
class WebServerTest < Test::Unit::TestCase
|
46
|
+
|
47
|
+
def setup
|
48
|
+
@valid_request = "GET / HTTP/1.1\r\nHost: www.zedshaw.com\r\nContent-Type: text/plain\r\n\r\n"
|
49
|
+
@port = unused_port
|
50
|
+
@tester = TestHandler.new
|
51
|
+
redirect_test_io do
|
52
|
+
@server = HttpServer.new(@tester, :listeners => [ "127.0.0.1:#{@port}" ] )
|
53
|
+
@server.start
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def teardown
|
58
|
+
redirect_test_io do
|
59
|
+
wait_workers_ready("test_stderr.#$$.log", 1)
|
60
|
+
File.truncate("test_stderr.#$$.log", 0)
|
61
|
+
@server.stop(false)
|
62
|
+
end
|
63
|
+
reset_sig_handlers
|
64
|
+
end
|
65
|
+
|
66
|
+
def test_preload_app_config
|
67
|
+
teardown
|
68
|
+
tmp = Tempfile.new('test_preload_app_config')
|
69
|
+
ObjectSpace.undefine_finalizer(tmp)
|
70
|
+
app = lambda { ||
|
71
|
+
tmp.sysseek(0)
|
72
|
+
tmp.truncate(0)
|
73
|
+
tmp.syswrite($$)
|
74
|
+
lambda { |env| [ 200, { 'content-type' => 'text/plain' }, [ "#$$\n" ] ] }
|
75
|
+
}
|
76
|
+
redirect_test_io do
|
77
|
+
@server = HttpServer.new(app, :listeners => [ "127.0.0.1:#@port"] )
|
78
|
+
@server.start
|
79
|
+
end
|
80
|
+
results = hit(["http://localhost:#@port/"])
|
81
|
+
worker_pid = results[0].to_i
|
82
|
+
assert worker_pid != 0
|
83
|
+
tmp.sysseek(0)
|
84
|
+
loader_pid = tmp.sysread(4096).to_i
|
85
|
+
assert loader_pid != 0
|
86
|
+
assert_equal worker_pid, loader_pid
|
87
|
+
teardown
|
88
|
+
|
89
|
+
redirect_test_io do
|
90
|
+
@server = HttpServer.new(app, :listeners => [ "127.0.0.1:#@port"],
|
91
|
+
:preload_app => true)
|
92
|
+
@server.start
|
93
|
+
end
|
94
|
+
results = hit(["http://localhost:#@port/"])
|
95
|
+
worker_pid = results[0].to_i
|
96
|
+
assert worker_pid != 0
|
97
|
+
tmp.sysseek(0)
|
98
|
+
loader_pid = tmp.sysread(4096).to_i
|
99
|
+
assert_equal $$, loader_pid
|
100
|
+
assert worker_pid != loader_pid
|
101
|
+
ensure
|
102
|
+
tmp.close!
|
103
|
+
end
|
104
|
+
|
105
|
+
def test_after_reply
|
106
|
+
teardown
|
107
|
+
|
108
|
+
redirect_test_io do
|
109
|
+
@server = HttpServer.new(TestRackAfterReply.new,
|
110
|
+
:listeners => [ "127.0.0.1:#@port"])
|
111
|
+
@server.start
|
112
|
+
end
|
113
|
+
|
114
|
+
sock = tcp_socket('127.0.0.1', @port)
|
115
|
+
sock.syswrite("GET / HTTP/1.0\r\n\r\n")
|
116
|
+
|
117
|
+
responses = sock.read(4096)
|
118
|
+
assert_match %r{\AHTTP/1.[01] 200\b}, responses
|
119
|
+
assert_match %r{^after_reply_called: false}, responses
|
120
|
+
|
121
|
+
sock = tcp_socket('127.0.0.1', @port)
|
122
|
+
sock.syswrite("GET / HTTP/1.0\r\n\r\n")
|
123
|
+
|
124
|
+
responses = sock.read(4096)
|
125
|
+
assert_match %r{\AHTTP/1.[01] 200\b}, responses
|
126
|
+
assert_match %r{^after_reply_called: true}, responses
|
127
|
+
|
128
|
+
sock.close
|
129
|
+
end
|
130
|
+
|
131
|
+
def test_simple_server
|
132
|
+
results = hit(["http://localhost:#{@port}/test"])
|
133
|
+
assert_equal 'hello!\n', results[0], "Handler didn't really run"
|
134
|
+
end
|
135
|
+
|
136
|
+
def test_client_malformed_body
|
137
|
+
bs = 15653984
|
138
|
+
sock = tcp_socket('127.0.0.1', @port)
|
139
|
+
sock.syswrite("PUT /hello HTTP/1.1\r\n")
|
140
|
+
sock.syswrite("Host: example.com\r\n")
|
141
|
+
sock.syswrite("Transfer-Encoding: chunked\r\n")
|
142
|
+
sock.syswrite("Trailer: X-Foo\r\n")
|
143
|
+
sock.syswrite("\r\n")
|
144
|
+
sock.syswrite("%x\r\n" % [ bs ])
|
145
|
+
sock.syswrite("F" * bs)
|
146
|
+
begin
|
147
|
+
File.open("/dev/urandom", "rb") { |fp| sock.syswrite(fp.sysread(16384)) }
|
148
|
+
rescue
|
149
|
+
end
|
150
|
+
assert_nil sock.close
|
151
|
+
next_client = Net::HTTP.get(URI.parse("http://127.0.0.1:#@port/"))
|
152
|
+
assert_equal 'hello!\n', next_client
|
153
|
+
lines = File.readlines("test_stderr.#$$.log")
|
154
|
+
lines = lines.grep(/^Unicorn::HttpParserError: .* true$/)
|
155
|
+
assert_equal 1, lines.size
|
156
|
+
end
|
157
|
+
|
158
|
+
def do_test(string, chunk, close_after=nil, shutdown_delay=0)
|
159
|
+
# Do not use instance variables here, because it needs to be thread safe
|
160
|
+
socket = tcp_socket("127.0.0.1", @port);
|
161
|
+
request = StringIO.new(string)
|
162
|
+
chunks_out = 0
|
163
|
+
|
164
|
+
while data = request.read(chunk)
|
165
|
+
chunks_out += socket.write(data)
|
166
|
+
socket.flush
|
167
|
+
sleep 0.2
|
168
|
+
if close_after and chunks_out > close_after
|
169
|
+
socket.close
|
170
|
+
sleep 1
|
171
|
+
end
|
172
|
+
end
|
173
|
+
sleep(shutdown_delay)
|
174
|
+
socket.write(" ") # Some platforms only raise the exception on attempted write
|
175
|
+
socket.flush
|
176
|
+
end
|
177
|
+
|
178
|
+
def test_trickle_attack
|
179
|
+
do_test(@valid_request, 3)
|
180
|
+
end
|
181
|
+
|
182
|
+
def test_close_client
|
183
|
+
assert_raises IOError do
|
184
|
+
do_test(@valid_request, 10, 20)
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
def test_bad_client
|
189
|
+
redirect_test_io do
|
190
|
+
do_test("GET /test HTTP/BAD", 3)
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
def test_logger_set
|
195
|
+
assert_equal @server.logger, Unicorn::HttpRequest::DEFAULTS["rack.logger"]
|
196
|
+
end
|
197
|
+
|
198
|
+
def test_logger_changed
|
199
|
+
tmp = Logger.new($stdout)
|
200
|
+
@server.logger = tmp
|
201
|
+
assert_equal tmp, Unicorn::HttpRequest::DEFAULTS["rack.logger"]
|
202
|
+
end
|
203
|
+
|
204
|
+
def test_bad_client_400
|
205
|
+
sock = tcp_socket('127.0.0.1', @port)
|
206
|
+
sock.syswrite("GET / HTTP/1.0\r\nHost: foo\rbar\r\n\r\n")
|
207
|
+
assert_match %r{\AHTTP/1.[01] 400\b}, sock.sysread(4096)
|
208
|
+
assert_nil sock.close
|
209
|
+
end
|
210
|
+
|
211
|
+
def test_http_0_9
|
212
|
+
sock = tcp_socket('127.0.0.1', @port)
|
213
|
+
sock.syswrite("GET /hello\r\n")
|
214
|
+
assert_match 'hello!\n', sock.sysread(4096)
|
215
|
+
assert_nil sock.close
|
216
|
+
end
|
217
|
+
|
218
|
+
def test_header_is_too_long
|
219
|
+
redirect_test_io do
|
220
|
+
long = "GET /test HTTP/1.1\r\n" + ("X-Big: stuff\r\n" * 15000) + "\r\n"
|
221
|
+
assert_raises Errno::ECONNRESET, Errno::EPIPE, Errno::ECONNABORTED, Errno::EINVAL, IOError do
|
222
|
+
do_test(long, long.length/2, 10)
|
223
|
+
end
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
def test_file_streamed_request
|
228
|
+
body = "a" * (Unicorn::Const::MAX_BODY * 2)
|
229
|
+
long = "PUT /test HTTP/1.1\r\nContent-length: #{body.length}\r\n\r\n" + body
|
230
|
+
do_test(long, Unicorn::Const::CHUNK_SIZE * 2 - 400)
|
231
|
+
end
|
232
|
+
|
233
|
+
def test_file_streamed_request_bad_body
|
234
|
+
body = "a" * (Unicorn::Const::MAX_BODY * 2)
|
235
|
+
long = "GET /test HTTP/1.1\r\nContent-ength: #{body.length}\r\n\r\n" + body
|
236
|
+
assert_raises(EOFError,Errno::ECONNRESET,Errno::EPIPE,Errno::EINVAL,
|
237
|
+
Errno::EBADF) {
|
238
|
+
do_test(long, Unicorn::Const::CHUNK_SIZE * 2 - 400)
|
239
|
+
}
|
240
|
+
end
|
241
|
+
|
242
|
+
def test_listener_names
|
243
|
+
assert_equal [ "127.0.0.1:#@port" ], Unicorn.listener_names
|
244
|
+
end
|
245
|
+
end
|
@@ -0,0 +1,189 @@
|
|
1
|
+
# -*- encoding: binary -*-
|
2
|
+
# frozen_string_literal: false
|
3
|
+
|
4
|
+
# Copyright (c) 2009 Eric Wong
|
5
|
+
# You can redistribute it and/or modify it under the same terms as Ruby 1.8 or
|
6
|
+
# the GPLv2+ (GPLv3+ preferred)
|
7
|
+
#
|
8
|
+
# Ensure we stay sane in the face of signals being sent to us
|
9
|
+
|
10
|
+
require './test/test_helper'
|
11
|
+
|
12
|
+
include Unicorn
|
13
|
+
|
14
|
+
class Dd
|
15
|
+
def initialize(bs, count)
|
16
|
+
@count = count
|
17
|
+
@buf = ' ' * bs
|
18
|
+
end
|
19
|
+
|
20
|
+
def each(&block)
|
21
|
+
@count.times { yield @buf }
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
class SignalsTest < Test::Unit::TestCase
|
26
|
+
|
27
|
+
def setup
|
28
|
+
@bs = 1 * 1024 * 1024
|
29
|
+
@count = 100
|
30
|
+
@port = unused_port
|
31
|
+
@sock = Tempfile.new('unicorn.sock')
|
32
|
+
@tmp = Tempfile.new('unicorn.write')
|
33
|
+
@tmp.sync = true
|
34
|
+
File.unlink(@sock.path)
|
35
|
+
File.unlink(@tmp.path)
|
36
|
+
@server_opts = {
|
37
|
+
:listeners => [ "127.0.0.1:#@port", @sock.path ],
|
38
|
+
:after_fork => lambda { |server,worker|
|
39
|
+
trap(:HUP) { @tmp.syswrite('.') }
|
40
|
+
},
|
41
|
+
}
|
42
|
+
@server = nil
|
43
|
+
end
|
44
|
+
|
45
|
+
def teardown
|
46
|
+
reset_sig_handlers
|
47
|
+
end
|
48
|
+
|
49
|
+
def test_worker_dies_on_dead_master
|
50
|
+
pid = fork {
|
51
|
+
app = lambda { |env| [ 200, {'x-pid' => "#$$" }, [] ] }
|
52
|
+
opts = @server_opts.merge(:timeout => 3)
|
53
|
+
redirect_test_io { HttpServer.new(app, opts).start.join }
|
54
|
+
}
|
55
|
+
wait_workers_ready("test_stderr.#{pid}.log", 1)
|
56
|
+
sock = tcp_socket('127.0.0.1', @port)
|
57
|
+
sock.syswrite("GET / HTTP/1.0\r\n\r\n")
|
58
|
+
buf = sock.readpartial(4096)
|
59
|
+
assert_nil sock.close
|
60
|
+
buf =~ /\bx-pid: (\d+)\b/ or raise Exception
|
61
|
+
child = $1.to_i
|
62
|
+
wait_master_ready("test_stderr.#{pid}.log")
|
63
|
+
wait_workers_ready("test_stderr.#{pid}.log", 1)
|
64
|
+
Process.kill(:KILL, pid)
|
65
|
+
Process.waitpid(pid)
|
66
|
+
File.unlink("test_stderr.#{pid}.log", "test_stdout.#{pid}.log")
|
67
|
+
t0 = Time.now
|
68
|
+
assert child
|
69
|
+
assert t0
|
70
|
+
assert_raises(Errno::ESRCH) { loop { Process.kill(0, child); sleep 0.2 } }
|
71
|
+
assert((Time.now - t0) < 60)
|
72
|
+
end
|
73
|
+
|
74
|
+
def test_sleepy_kill
|
75
|
+
rd, wr = IO.pipe
|
76
|
+
pid = fork {
|
77
|
+
rd.close
|
78
|
+
app = lambda { |env| wr.syswrite('.'); sleep; [ 200, {}, [] ] }
|
79
|
+
redirect_test_io { HttpServer.new(app, @server_opts).start.join }
|
80
|
+
}
|
81
|
+
wr.close
|
82
|
+
wait_workers_ready("test_stderr.#{pid}.log", 1)
|
83
|
+
sock = tcp_socket('127.0.0.1', @port)
|
84
|
+
sock.syswrite("GET / HTTP/1.0\r\n\r\n")
|
85
|
+
buf = rd.readpartial(1)
|
86
|
+
wait_master_ready("test_stderr.#{pid}.log")
|
87
|
+
Process.kill(:INT, pid)
|
88
|
+
Process.waitpid(pid)
|
89
|
+
assert_equal '.', buf
|
90
|
+
buf = nil
|
91
|
+
assert_raises(EOFError,Errno::ECONNRESET,Errno::EPIPE,Errno::EINVAL,
|
92
|
+
Errno::EBADF) do
|
93
|
+
buf = sock.sysread(4096)
|
94
|
+
end
|
95
|
+
assert_nil buf
|
96
|
+
end
|
97
|
+
|
98
|
+
def test_timeout_slow_response
|
99
|
+
pid = fork {
|
100
|
+
app = lambda { |env| sleep }
|
101
|
+
opts = @server_opts.merge(:timeout => 3)
|
102
|
+
redirect_test_io { HttpServer.new(app, opts).start.join }
|
103
|
+
}
|
104
|
+
t0 = Time.now
|
105
|
+
wait_workers_ready("test_stderr.#{pid}.log", 1)
|
106
|
+
sock = tcp_socket('127.0.0.1', @port)
|
107
|
+
sock.syswrite("GET / HTTP/1.0\r\n\r\n")
|
108
|
+
|
109
|
+
buf = nil
|
110
|
+
assert_raises(EOFError,Errno::ECONNRESET,Errno::EPIPE,Errno::EINVAL,
|
111
|
+
Errno::EBADF) do
|
112
|
+
buf = sock.sysread(4096)
|
113
|
+
end
|
114
|
+
diff = Time.now - t0
|
115
|
+
assert_nil buf
|
116
|
+
assert diff > 1.0, "diff was #{diff.inspect}"
|
117
|
+
assert diff < 60.0
|
118
|
+
ensure
|
119
|
+
Process.kill(:TERM, pid) rescue nil
|
120
|
+
end
|
121
|
+
|
122
|
+
def test_response_write
|
123
|
+
app = lambda { |env|
|
124
|
+
[ 200, { 'content-type' => 'text/plain', 'x-pid' => Process.pid.to_s },
|
125
|
+
Dd.new(@bs, @count) ]
|
126
|
+
}
|
127
|
+
redirect_test_io { @server = HttpServer.new(app, @server_opts).start }
|
128
|
+
wait_workers_ready("test_stderr.#{$$}.log", 1)
|
129
|
+
sock = tcp_socket('127.0.0.1', @port)
|
130
|
+
sock.syswrite("GET / HTTP/1.0\r\n\r\n")
|
131
|
+
buf = ''
|
132
|
+
header_len = pid = nil
|
133
|
+
buf = sock.sysread(16384, buf)
|
134
|
+
pid = buf[/\r\nx-pid: (\d+)\r\n/, 1].to_i
|
135
|
+
header_len = buf[/\A(.+?\r\n\r\n)/m, 1].size
|
136
|
+
assert pid > 0, "pid not positive: #{pid.inspect}"
|
137
|
+
read = buf.size
|
138
|
+
size_before = @tmp.stat.size
|
139
|
+
assert_raises(EOFError,Errno::ECONNRESET,Errno::EPIPE,Errno::EINVAL,
|
140
|
+
Errno::EBADF) do
|
141
|
+
loop do
|
142
|
+
3.times { Process.kill(:HUP, pid) }
|
143
|
+
sock.sysread(16384, buf)
|
144
|
+
read += buf.size
|
145
|
+
3.times { Process.kill(:HUP, pid) }
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
redirect_test_io { @server.stop(true) }
|
150
|
+
# can't check for == since pending signals get merged
|
151
|
+
assert size_before < @tmp.stat.size
|
152
|
+
got = read - header_len
|
153
|
+
expect = @bs * @count
|
154
|
+
assert_equal(expect, got, "expect=#{expect} got=#{got}")
|
155
|
+
assert_nil sock.close
|
156
|
+
end
|
157
|
+
|
158
|
+
def test_request_read
|
159
|
+
app = lambda { |env|
|
160
|
+
while env['rack.input'].read(4096)
|
161
|
+
end
|
162
|
+
[ 200, {'content-type'=>'text/plain', 'x-pid'=>Process.pid.to_s}, [] ]
|
163
|
+
}
|
164
|
+
redirect_test_io { @server = HttpServer.new(app, @server_opts).start }
|
165
|
+
|
166
|
+
wait_workers_ready("test_stderr.#{$$}.log", 1)
|
167
|
+
sock = tcp_socket('127.0.0.1', @port)
|
168
|
+
sock.syswrite("GET / HTTP/1.0\r\n\r\n")
|
169
|
+
pid = sock.sysread(4096)[/\r\nx-pid: (\d+)\r\n/, 1].to_i
|
170
|
+
assert_nil sock.close
|
171
|
+
|
172
|
+
assert pid > 0, "pid not positive: #{pid.inspect}"
|
173
|
+
sock = tcp_socket('127.0.0.1', @port)
|
174
|
+
sock.syswrite("PUT / HTTP/1.0\r\n")
|
175
|
+
sock.syswrite("Content-Length: #{@bs * @count}\r\n\r\n")
|
176
|
+
1000.times { Process.kill(:HUP, pid) }
|
177
|
+
size_before = @tmp.stat.size
|
178
|
+
killer = fork { loop { Process.kill(:HUP, pid); sleep(0.01) } }
|
179
|
+
buf = ' ' * @bs
|
180
|
+
@count.times { sock.syswrite(buf) }
|
181
|
+
Process.kill(:KILL, killer)
|
182
|
+
Process.waitpid2(killer)
|
183
|
+
redirect_test_io { @server.stop(true) }
|
184
|
+
# can't check for == since pending signals get merged
|
185
|
+
assert size_before < @tmp.stat.size
|
186
|
+
assert_equal pid, sock.sysread(4096)[/\r\nx-pid: (\d+)\r\n/, 1].to_i
|
187
|
+
assert_nil sock.close
|
188
|
+
end
|
189
|
+
end
|
@@ -0,0 +1,160 @@
|
|
1
|
+
# -*- encoding: binary -*-
|
2
|
+
# frozen_string_literal: false
|
3
|
+
|
4
|
+
require './test/test_helper'
|
5
|
+
require 'tempfile'
|
6
|
+
|
7
|
+
class TestSocketHelper < Test::Unit::TestCase
|
8
|
+
include Unicorn::SocketHelper
|
9
|
+
attr_reader :logger
|
10
|
+
GET_SLASH = "GET / HTTP/1.0\r\n\r\n".freeze
|
11
|
+
|
12
|
+
def setup
|
13
|
+
@log_tmp = Tempfile.new 'logger'
|
14
|
+
@logger = Logger.new(@log_tmp.path)
|
15
|
+
@test_addr = ENV['UNICORN_TEST_ADDR'] || '127.0.0.1'
|
16
|
+
@test6_addr = ENV['UNICORN_TEST6_ADDR'] || '::1'
|
17
|
+
GC.disable
|
18
|
+
end
|
19
|
+
|
20
|
+
def teardown
|
21
|
+
GC.enable
|
22
|
+
end
|
23
|
+
|
24
|
+
def test_bind_listen_tcp
|
25
|
+
port = unused_port @test_addr
|
26
|
+
@tcp_listener_name = "#@test_addr:#{port}"
|
27
|
+
@tcp_listener = bind_listen(@tcp_listener_name)
|
28
|
+
assert Socket === @tcp_listener
|
29
|
+
assert @tcp_listener.local_address.ip?
|
30
|
+
assert_equal @tcp_listener_name, sock_name(@tcp_listener)
|
31
|
+
end
|
32
|
+
|
33
|
+
def test_bind_listen_options
|
34
|
+
port = unused_port @test_addr
|
35
|
+
tcp_listener_name = "#@test_addr:#{port}"
|
36
|
+
tmp = Tempfile.new 'unix.sock'
|
37
|
+
unix_listener_name = tmp.path
|
38
|
+
File.unlink(tmp.path)
|
39
|
+
[ { :backlog => 5 }, { :sndbuf => 4096 }, { :rcvbuf => 4096 },
|
40
|
+
{ :backlog => 16, :rcvbuf => 4096, :sndbuf => 4096 }
|
41
|
+
].each do |opts|
|
42
|
+
tcp_listener = bind_listen(tcp_listener_name, opts)
|
43
|
+
assert tcp_listener.local_address.ip?
|
44
|
+
tcp_listener.close
|
45
|
+
unix_listener = bind_listen(unix_listener_name, opts)
|
46
|
+
assert unix_listener.local_address.unix?
|
47
|
+
unix_listener.close
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def test_bind_listen_unix
|
52
|
+
old_umask = File.umask(0777)
|
53
|
+
tmp = Tempfile.new 'unix.sock'
|
54
|
+
@unix_listener_path = tmp.path
|
55
|
+
File.unlink(@unix_listener_path)
|
56
|
+
@unix_listener = bind_listen(@unix_listener_path)
|
57
|
+
assert Socket === @unix_listener
|
58
|
+
assert @unix_listener.local_address.unix?
|
59
|
+
assert_equal @unix_listener_path, sock_name(@unix_listener)
|
60
|
+
assert File.readable?(@unix_listener_path), "not readable"
|
61
|
+
assert File.writable?(@unix_listener_path), "not writable"
|
62
|
+
assert_equal 0777, File.umask
|
63
|
+
assert_equal @unix_listener, bind_listen(@unix_listener)
|
64
|
+
ensure
|
65
|
+
File.umask(old_umask)
|
66
|
+
end
|
67
|
+
|
68
|
+
def test_bind_listen_unix_umask
|
69
|
+
old_umask = File.umask(0777)
|
70
|
+
tmp = Tempfile.new 'unix.sock'
|
71
|
+
@unix_listener_path = tmp.path
|
72
|
+
File.unlink(@unix_listener_path)
|
73
|
+
@unix_listener = bind_listen(@unix_listener_path, :umask => 077)
|
74
|
+
assert_equal @unix_listener_path, sock_name(@unix_listener)
|
75
|
+
assert_equal 0140700, File.stat(@unix_listener_path).mode
|
76
|
+
assert_equal 0777, File.umask
|
77
|
+
ensure
|
78
|
+
File.umask(old_umask)
|
79
|
+
end
|
80
|
+
|
81
|
+
def test_bind_listen_unix_rebind
|
82
|
+
test_bind_listen_unix
|
83
|
+
new_listener = nil
|
84
|
+
assert_raises(Errno::EADDRINUSE) do
|
85
|
+
new_listener = bind_listen(@unix_listener_path)
|
86
|
+
end
|
87
|
+
|
88
|
+
File.unlink(@unix_listener_path)
|
89
|
+
new_listener = bind_listen(@unix_listener_path)
|
90
|
+
|
91
|
+
assert new_listener.fileno != @unix_listener.fileno
|
92
|
+
assert_equal sock_name(new_listener), sock_name(@unix_listener)
|
93
|
+
assert_equal @unix_listener_path, sock_name(new_listener)
|
94
|
+
pid = fork do
|
95
|
+
begin
|
96
|
+
client, _ = new_listener.accept
|
97
|
+
client.syswrite('abcde')
|
98
|
+
exit 0
|
99
|
+
rescue => e
|
100
|
+
warn "#{e.message} (#{e.class})"
|
101
|
+
exit 1
|
102
|
+
end
|
103
|
+
end
|
104
|
+
s = unix_socket(@unix_listener_path)
|
105
|
+
IO.select([s])
|
106
|
+
assert_equal 'abcde', s.sysread(5)
|
107
|
+
pid, status = Process.waitpid2(pid)
|
108
|
+
assert status.success?
|
109
|
+
end
|
110
|
+
|
111
|
+
def test_tcp_defer_accept_default
|
112
|
+
return unless defined?(TCP_DEFER_ACCEPT)
|
113
|
+
port = unused_port @test_addr
|
114
|
+
name = "#@test_addr:#{port}"
|
115
|
+
sock = bind_listen(name)
|
116
|
+
cur = sock.getsockopt(Socket::SOL_TCP, TCP_DEFER_ACCEPT).unpack('i')[0]
|
117
|
+
assert cur >= 1
|
118
|
+
end
|
119
|
+
|
120
|
+
def test_tcp_defer_accept_disable
|
121
|
+
return unless defined?(TCP_DEFER_ACCEPT)
|
122
|
+
port = unused_port @test_addr
|
123
|
+
name = "#@test_addr:#{port}"
|
124
|
+
sock = bind_listen(name, :tcp_defer_accept => false)
|
125
|
+
cur = sock.getsockopt(Socket::SOL_TCP, TCP_DEFER_ACCEPT).unpack('i')[0]
|
126
|
+
assert_equal 0, cur
|
127
|
+
end
|
128
|
+
|
129
|
+
def test_tcp_defer_accept_nr
|
130
|
+
return unless defined?(TCP_DEFER_ACCEPT)
|
131
|
+
port = unused_port @test_addr
|
132
|
+
name = "#@test_addr:#{port}"
|
133
|
+
sock = bind_listen(name, :tcp_defer_accept => 60)
|
134
|
+
cur = sock.getsockopt(Socket::SOL_TCP, TCP_DEFER_ACCEPT).unpack('i')[0]
|
135
|
+
assert cur > 1
|
136
|
+
end
|
137
|
+
|
138
|
+
def test_ipv6only
|
139
|
+
port = begin
|
140
|
+
unused_port "#@test6_addr"
|
141
|
+
rescue Errno::EINVAL
|
142
|
+
return
|
143
|
+
end
|
144
|
+
sock = bind_listen "[#@test6_addr]:#{port}", :ipv6only => true
|
145
|
+
cur = sock.getsockopt(:IPPROTO_IPV6, :IPV6_V6ONLY).unpack('i')[0]
|
146
|
+
assert_equal 1, cur
|
147
|
+
rescue Errno::EAFNOSUPPORT
|
148
|
+
end
|
149
|
+
|
150
|
+
def test_reuseport
|
151
|
+
return unless defined?(Socket::SO_REUSEPORT)
|
152
|
+
port = unused_port @test_addr
|
153
|
+
name = "#@test_addr:#{port}"
|
154
|
+
sock = bind_listen(name, :reuseport => true)
|
155
|
+
cur = sock.getsockopt(:SOL_SOCKET, :SO_REUSEPORT).int
|
156
|
+
assert_operator cur, :>, 0
|
157
|
+
rescue Errno::ENOPROTOOPT
|
158
|
+
# kernel does not support SO_REUSEPORT (older Linux)
|
159
|
+
end
|
160
|
+
end
|