unicorn 5.7.0 → 6.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.manifest +3 -2
- data/.olddoc.yml +7 -4
- data/CONTRIBUTORS +6 -2
- data/GIT-VERSION-FILE +1 -1
- data/GIT-VERSION-GEN +1 -1
- data/GNUmakefile +4 -3
- data/HACKING +1 -8
- data/ISSUES +18 -20
- data/LATEST +23 -22
- data/NEWS +132 -0
- data/README +5 -6
- data/Sandbox +1 -1
- data/ext/unicorn_http/c_util.h +5 -13
- data/ext/unicorn_http/common_field_optimization.h +0 -1
- data/ext/unicorn_http/epollexclusive.h +124 -0
- data/ext/unicorn_http/ext_help.h +0 -24
- data/ext/unicorn_http/extconf.rb +2 -11
- data/ext/unicorn_http/global_variables.h +1 -1
- data/ext/unicorn_http/httpdate.c +1 -0
- data/ext/unicorn_http/unicorn_http.c +215 -223
- data/ext/unicorn_http/unicorn_http.rl +5 -13
- data/lib/unicorn/http_request.rb +0 -1
- data/lib/unicorn/http_server.rb +26 -27
- data/lib/unicorn/oob_gc.rb +3 -3
- data/lib/unicorn/select_waiter.rb +6 -0
- data/lib/unicorn/version.rb +1 -1
- data/lib/unicorn.rb +0 -2
- data/t/README +1 -1
- data/t/test-lib.sh +2 -1
- data/test/exec/test_exec.rb +5 -5
- data/test/test_helper.rb +18 -2
- data/test/unit/test_ccc.rb +4 -3
- data/test/unit/test_server.rb +52 -8
- data/test/unit/test_signals.rb +6 -6
- data/test/unit/test_socket_helper.rb +1 -1
- data/test/unit/test_upload.rb +5 -5
- data/test/unit/test_util.rb +4 -3
- data/test/unit/test_waiter.rb +34 -0
- data/unicorn.gemspec +3 -3
- metadata +7 -5
- data/t/hijack.ru +0 -55
- data/t/t0200-rack-hijack.sh +0 -51
@@ -12,6 +12,7 @@
|
|
12
12
|
#include "common_field_optimization.h"
|
13
13
|
#include "global_variables.h"
|
14
14
|
#include "c_util.h"
|
15
|
+
#include "epollexclusive.h"
|
15
16
|
|
16
17
|
void init_unicorn_httpdate(void);
|
17
18
|
|
@@ -65,18 +66,6 @@ struct http_parser {
|
|
65
66
|
static ID id_set_backtrace, id_is_chunked_p;
|
66
67
|
static VALUE cHttpParser;
|
67
68
|
|
68
|
-
#ifdef HAVE_RB_HASH_CLEAR /* Ruby >= 2.0 */
|
69
|
-
# define my_hash_clear(h) (void)rb_hash_clear(h)
|
70
|
-
#else /* !HAVE_RB_HASH_CLEAR - Ruby <= 1.9.3 */
|
71
|
-
|
72
|
-
static ID id_clear;
|
73
|
-
|
74
|
-
static void my_hash_clear(VALUE h)
|
75
|
-
{
|
76
|
-
rb_funcall(h, id_clear, 0);
|
77
|
-
}
|
78
|
-
#endif /* HAVE_RB_HASH_CLEAR */
|
79
|
-
|
80
69
|
static void finalize_header(struct http_parser *hp);
|
81
70
|
|
82
71
|
static void parser_raise(VALUE klass, const char *msg)
|
@@ -650,7 +639,7 @@ static VALUE HttpParser_clear(VALUE self)
|
|
650
639
|
return HttpParser_init(self);
|
651
640
|
|
652
641
|
http_parser_init(hp);
|
653
|
-
|
642
|
+
rb_hash_clear(hp->env);
|
654
643
|
|
655
644
|
return self;
|
656
645
|
}
|
@@ -979,6 +968,7 @@ void Init_unicorn_http(void)
|
|
979
968
|
e414 = rb_define_class_under(mUnicorn, "RequestURITooLongError",
|
980
969
|
eHttpParserError);
|
981
970
|
|
971
|
+
id_uminus = rb_intern("-@");
|
982
972
|
init_globals();
|
983
973
|
rb_define_alloc_func(cHttpParser, HttpParser_alloc);
|
984
974
|
rb_define_method(cHttpParser, "initialize", HttpParser_init, 0);
|
@@ -1029,5 +1019,7 @@ void Init_unicorn_http(void)
|
|
1029
1019
|
id_clear = rb_intern("clear");
|
1030
1020
|
#endif
|
1031
1021
|
id_is_chunked_p = rb_intern("is_chunked?");
|
1022
|
+
|
1023
|
+
init_epollexclusive(mUnicorn);
|
1032
1024
|
}
|
1033
1025
|
#undef SET_GLOBAL
|
data/lib/unicorn/http_request.rb
CHANGED
data/lib/unicorn/http_server.rb
CHANGED
@@ -69,7 +69,6 @@ class Unicorn::HttpServer
|
|
69
69
|
# incoming requests on the socket.
|
70
70
|
def initialize(app, options = {})
|
71
71
|
@app = app
|
72
|
-
@request = Unicorn::HttpRequest.new
|
73
72
|
@reexec_pid = 0
|
74
73
|
@default_middleware = true
|
75
74
|
options = options.dup
|
@@ -610,7 +609,7 @@ class Unicorn::HttpServer
|
|
610
609
|
def e100_response_write(client, env)
|
611
610
|
# We use String#freeze to avoid allocations under Ruby 2.1+
|
612
611
|
# Not many users hit this code path, so it's better to reduce the
|
613
|
-
# constant table sizes even for
|
612
|
+
# constant table sizes even for Ruby 2.0 users who'll hit extra
|
614
613
|
# allocations here.
|
615
614
|
client.write(@request.response_start_sent ?
|
616
615
|
"100 Continue\r\n\r\nHTTP/1.1 ".freeze :
|
@@ -621,6 +620,7 @@ class Unicorn::HttpServer
|
|
621
620
|
# once a client is accepted, it is processed in its entirety here
|
622
621
|
# in 3 easy steps: read request, call app, write app response
|
623
622
|
def process_client(client)
|
623
|
+
@request = Unicorn::HttpRequest.new
|
624
624
|
env = @request.read(client)
|
625
625
|
|
626
626
|
if early_hints
|
@@ -629,6 +629,8 @@ class Unicorn::HttpServer
|
|
629
629
|
end
|
630
630
|
end
|
631
631
|
|
632
|
+
env["rack.after_reply"] = []
|
633
|
+
|
632
634
|
status, headers, body = @app.call(env)
|
633
635
|
|
634
636
|
begin
|
@@ -651,6 +653,8 @@ class Unicorn::HttpServer
|
|
651
653
|
end
|
652
654
|
rescue => e
|
653
655
|
handle_error(client, e)
|
656
|
+
ensure
|
657
|
+
env["rack.after_reply"].each(&:call) if env
|
654
658
|
end
|
655
659
|
|
656
660
|
def nuke_listeners!(readers)
|
@@ -681,7 +685,6 @@ class Unicorn::HttpServer
|
|
681
685
|
LISTENERS.each { |sock| sock.close_on_exec = true }
|
682
686
|
|
683
687
|
worker.user(*user) if user.kind_of?(Array) && ! worker.switched
|
684
|
-
self.timeout /= 2.0 # halve it for select()
|
685
688
|
@config = nil
|
686
689
|
build_app! unless preload_app
|
687
690
|
@after_fork = @listener_opts = @orig_app = nil
|
@@ -695,59 +698,55 @@ class Unicorn::HttpServer
|
|
695
698
|
logger.info "worker=#{worker_nr} reopening logs..."
|
696
699
|
Unicorn::Util.reopen_logs
|
697
700
|
logger.info "worker=#{worker_nr} done reopening logs"
|
701
|
+
false
|
698
702
|
rescue => e
|
699
703
|
logger.error(e) rescue nil
|
700
704
|
exit!(77) # EX_NOPERM in sysexits.h
|
701
705
|
end
|
702
706
|
|
707
|
+
def prep_readers(readers)
|
708
|
+
wtr = Unicorn::Waiter.prep_readers(readers)
|
709
|
+
@timeout *= 500 # to milliseconds for epoll, but halved
|
710
|
+
wtr
|
711
|
+
rescue
|
712
|
+
require_relative 'select_waiter'
|
713
|
+
@timeout /= 2.0 # halved for IO.select
|
714
|
+
Unicorn::SelectWaiter.new
|
715
|
+
end
|
716
|
+
|
703
717
|
# runs inside each forked worker, this sits around and waits
|
704
718
|
# for connections and doesn't die until the parent dies (or is
|
705
719
|
# given a INT, QUIT, or TERM signal)
|
706
720
|
def worker_loop(worker)
|
707
|
-
ppid = @master_pid
|
708
721
|
readers = init_worker_process(worker)
|
709
|
-
|
722
|
+
waiter = prep_readers(readers)
|
723
|
+
reopen = false
|
710
724
|
|
711
725
|
# this only works immediately if the master sent us the signal
|
712
726
|
# (which is the normal case)
|
713
|
-
trap(:USR1) {
|
727
|
+
trap(:USR1) { reopen = true }
|
714
728
|
|
715
729
|
ready = readers.dup
|
716
|
-
nr_listeners = readers.size
|
717
730
|
@after_worker_ready.call(self, worker)
|
718
731
|
|
719
732
|
begin
|
720
|
-
|
721
|
-
nr = 0
|
733
|
+
reopen = reopen_worker_logs(worker.nr) if reopen
|
722
734
|
worker.tick = time_now.to_i
|
723
|
-
|
724
|
-
while sock = tmp.shift
|
735
|
+
while sock = ready.shift
|
725
736
|
# Unicorn::Worker#kgio_tryaccept is not like accept(2) at all,
|
726
737
|
# but that will return false
|
727
738
|
if client = sock.kgio_tryaccept
|
728
739
|
process_client(client)
|
729
|
-
nr += 1
|
730
740
|
worker.tick = time_now.to_i
|
731
741
|
end
|
732
|
-
break if
|
733
|
-
end
|
734
|
-
|
735
|
-
# make the following bet: if we accepted clients this round,
|
736
|
-
# we're probably reasonably busy, so avoid calling select()
|
737
|
-
# and do a speculative non-blocking accept() on ready listeners
|
738
|
-
# before we sleep again in select().
|
739
|
-
if nr == nr_listeners
|
740
|
-
tmp = ready.dup
|
741
|
-
redo
|
742
|
+
break if reopen
|
742
743
|
end
|
743
744
|
|
744
|
-
|
745
|
-
|
746
|
-
# timeout used so we can detect parent death:
|
745
|
+
# timeout so we can .tick and keep parent from SIGKILL-ing us
|
747
746
|
worker.tick = time_now.to_i
|
748
|
-
|
747
|
+
waiter.get_readers(ready, readers, @timeout)
|
749
748
|
rescue => e
|
750
|
-
redo if
|
749
|
+
redo if reopen && readers[0]
|
751
750
|
Unicorn.log_error(@logger, "listen loop error", e) if readers[0]
|
752
751
|
end while readers[0]
|
753
752
|
end
|
data/lib/unicorn/oob_gc.rb
CHANGED
@@ -60,7 +60,6 @@ module Unicorn::OobGC
|
|
60
60
|
self.const_set :OOBGC_INTERVAL, interval
|
61
61
|
ObjectSpace.each_object(Unicorn::HttpServer) do |s|
|
62
62
|
s.extend(self)
|
63
|
-
self.const_set :OOBGC_ENV, s.instance_variable_get(:@request).env
|
64
63
|
end
|
65
64
|
app # pretend to be Rack middleware since it was in the past
|
66
65
|
end
|
@@ -68,9 +67,10 @@ module Unicorn::OobGC
|
|
68
67
|
#:stopdoc:
|
69
68
|
def process_client(client)
|
70
69
|
super(client) # Unicorn::HttpServer#process_client
|
71
|
-
|
70
|
+
env = instance_variable_get(:@request).env
|
71
|
+
if OOBGC_PATH =~ env['PATH_INFO'] && ((@@nr -= 1) <= 0)
|
72
72
|
@@nr = OOBGC_INTERVAL
|
73
|
-
|
73
|
+
env.clear
|
74
74
|
disabled = GC.enable
|
75
75
|
GC.start
|
76
76
|
GC.disable if disabled
|
data/lib/unicorn/version.rb
CHANGED
@@ -1 +1 @@
|
|
1
|
-
Unicorn::Const::UNICORN_VERSION = '
|
1
|
+
Unicorn::Const::UNICORN_VERSION = '6.1.0'
|
data/lib/unicorn.rb
CHANGED
@@ -114,8 +114,6 @@ module Unicorn
|
|
114
114
|
|
115
115
|
def self.pipe # :nodoc:
|
116
116
|
Kgio::Pipe.new.each do |io|
|
117
|
-
io.close_on_exec = true # remove this when we only support Ruby >= 2.0
|
118
|
-
|
119
117
|
# shrink pipes to minimize impact on /proc/sys/fs/pipe-user-pages-soft
|
120
118
|
# limits.
|
121
119
|
if defined?(F_SETPIPE_SZ)
|
data/t/README
CHANGED
@@ -10,7 +10,7 @@ comfortable writing integration tests with.
|
|
10
10
|
|
11
11
|
== Requirements
|
12
12
|
|
13
|
-
* {Ruby
|
13
|
+
* {Ruby 2.0.0+}[https://www.ruby-lang.org/en/] (duh!)
|
14
14
|
* {GNU make}[https://www.gnu.org/software/make/]
|
15
15
|
* {socat}[http://www.dest-unreach.org/socat/]
|
16
16
|
* {curl}[https://curl.haxx.se/]
|
data/t/test-lib.sh
CHANGED
@@ -94,7 +94,8 @@ check_stderr () {
|
|
94
94
|
set +u
|
95
95
|
_r_err=${1-${r_err}}
|
96
96
|
set -u
|
97
|
-
if grep -v $T $_r_err | grep -i Error
|
97
|
+
if grep -v $T $_r_err | grep -i Error | \
|
98
|
+
grep -v NameError.*Unicorn::Waiter
|
98
99
|
then
|
99
100
|
die "Errors found in $_r_err"
|
100
101
|
elif grep SIGKILL $_r_err
|
data/test/exec/test_exec.rb
CHANGED
@@ -574,7 +574,7 @@ EOF
|
|
574
574
|
assert_equal String, results[0].class
|
575
575
|
worker_pid = results[0].to_i
|
576
576
|
assert_not_equal pid, worker_pid
|
577
|
-
s =
|
577
|
+
s = unix_socket(tmp.path)
|
578
578
|
s.syswrite("GET / HTTP/1.0\r\n\r\n")
|
579
579
|
results = ''
|
580
580
|
loop { results << s.sysread(4096) } rescue nil
|
@@ -732,7 +732,7 @@ EOF
|
|
732
732
|
wait_for_file(sock_path)
|
733
733
|
assert File.socket?(sock_path)
|
734
734
|
|
735
|
-
sock =
|
735
|
+
sock = unix_socket(sock_path)
|
736
736
|
sock.syswrite("GET / HTTP/1.0\r\n\r\n")
|
737
737
|
results = sock.sysread(4096)
|
738
738
|
|
@@ -742,7 +742,7 @@ EOF
|
|
742
742
|
wait_for_file(sock_path)
|
743
743
|
assert File.socket?(sock_path)
|
744
744
|
|
745
|
-
sock =
|
745
|
+
sock = unix_socket(sock_path)
|
746
746
|
sock.syswrite("GET / HTTP/1.0\r\n\r\n")
|
747
747
|
results = sock.sysread(4096)
|
748
748
|
|
@@ -777,7 +777,7 @@ EOF
|
|
777
777
|
assert_equal pid, File.read(pid_file).to_i
|
778
778
|
assert File.socket?(sock_path), "socket created"
|
779
779
|
|
780
|
-
sock =
|
780
|
+
sock = unix_socket(sock_path)
|
781
781
|
sock.syswrite("GET / HTTP/1.0\r\n\r\n")
|
782
782
|
results = sock.sysread(4096)
|
783
783
|
|
@@ -803,7 +803,7 @@ EOF
|
|
803
803
|
wait_for_file(new_sock_path)
|
804
804
|
assert File.socket?(new_sock_path), "socket exists"
|
805
805
|
@sockets.each do |path|
|
806
|
-
sock =
|
806
|
+
sock = unix_socket(path)
|
807
807
|
sock.syswrite("GET / HTTP/1.0\r\n\r\n")
|
808
808
|
results = sock.sysread(4096)
|
809
809
|
assert_equal String, results.class
|
data/test/test_helper.rb
CHANGED
@@ -28,6 +28,7 @@ require 'tempfile'
|
|
28
28
|
require 'fileutils'
|
29
29
|
require 'logger'
|
30
30
|
require 'unicorn'
|
31
|
+
require 'io/nonblock'
|
31
32
|
|
32
33
|
if ENV['DEBUG']
|
33
34
|
require 'ruby-debug'
|
@@ -42,6 +43,7 @@ end
|
|
42
43
|
def redirect_test_io
|
43
44
|
orig_err = STDERR.dup
|
44
45
|
orig_out = STDOUT.dup
|
46
|
+
rdr_pid = $$
|
45
47
|
new_out = File.open("test_stdout.#$$.log", "a")
|
46
48
|
new_err = File.open("test_stderr.#$$.log", "a")
|
47
49
|
new_out.sync = new_err.sync = true
|
@@ -59,8 +61,10 @@ def redirect_test_io
|
|
59
61
|
STDERR.sync = STDOUT.sync = true
|
60
62
|
|
61
63
|
at_exit do
|
62
|
-
|
63
|
-
|
64
|
+
if rdr_pid == $$
|
65
|
+
File.unlink(new_out.path) rescue nil
|
66
|
+
File.unlink(new_err.path) rescue nil
|
67
|
+
end
|
64
68
|
end
|
65
69
|
|
66
70
|
begin
|
@@ -288,3 +292,15 @@ def reset_sig_handlers
|
|
288
292
|
trap(sig, "DEFAULT")
|
289
293
|
end
|
290
294
|
end
|
295
|
+
|
296
|
+
def tcp_socket(*args)
|
297
|
+
sock = TCPSocket.new(*args)
|
298
|
+
sock.nonblock = false
|
299
|
+
sock
|
300
|
+
end
|
301
|
+
|
302
|
+
def unix_socket(*args)
|
303
|
+
sock = UNIXSocket.new(*args)
|
304
|
+
sock.nonblock = false
|
305
|
+
sock
|
306
|
+
end
|
data/test/unit/test_ccc.rb
CHANGED
@@ -3,6 +3,7 @@ require 'unicorn'
|
|
3
3
|
require 'io/wait'
|
4
4
|
require 'tempfile'
|
5
5
|
require 'test/unit'
|
6
|
+
require './test/test_helper'
|
6
7
|
|
7
8
|
class TestCccTCPI < Test::Unit::TestCase
|
8
9
|
def test_ccc_tcpi
|
@@ -42,7 +43,7 @@ class TestCccTCPI < Test::Unit::TestCase
|
|
42
43
|
wr.close
|
43
44
|
|
44
45
|
# make sure the server is running, at least
|
45
|
-
client =
|
46
|
+
client = tcp_socket(host, port)
|
46
47
|
client.write("GET / HTTP/1.1\r\nHost: example.com\r\n\r\n")
|
47
48
|
assert client.wait(10), 'never got response from server'
|
48
49
|
res = client.read
|
@@ -51,13 +52,13 @@ class TestCccTCPI < Test::Unit::TestCase
|
|
51
52
|
client.close
|
52
53
|
|
53
54
|
# start a slow request...
|
54
|
-
sleeper =
|
55
|
+
sleeper = tcp_socket(host, port)
|
55
56
|
sleeper.write("GET /sleep HTTP/1.1\r\nHost: example.com\r\n\r\n")
|
56
57
|
|
57
58
|
# and a bunch of aborted ones
|
58
59
|
nr = 100
|
59
60
|
nr.times do |i|
|
60
|
-
client =
|
61
|
+
client = tcp_socket(host, port)
|
61
62
|
client.write("GET /collections/#{rand(10000)} HTTP/1.1\r\n" \
|
62
63
|
"Host: example.com\r\n\r\n")
|
63
64
|
client.close
|
data/test/unit/test_server.rb
CHANGED
@@ -34,6 +34,24 @@ class TestEarlyHintsHandler
|
|
34
34
|
end
|
35
35
|
end
|
36
36
|
|
37
|
+
class TestRackAfterReply
|
38
|
+
def initialize
|
39
|
+
@called = false
|
40
|
+
end
|
41
|
+
|
42
|
+
def call(env)
|
43
|
+
while env['rack.input'].read(4096)
|
44
|
+
end
|
45
|
+
|
46
|
+
env["rack.after_reply"] << -> { @called = true }
|
47
|
+
|
48
|
+
[200, { 'Content-Type' => 'text/plain' }, ["after_reply_called: #{@called}"]]
|
49
|
+
rescue Unicorn::ClientShutdown, Unicorn::HttpParserError => e
|
50
|
+
$stderr.syswrite("#{e.class}: #{e.message} #{e.backtrace.empty?}\n")
|
51
|
+
raise e
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
37
55
|
class WebServerTest < Test::Unit::TestCase
|
38
56
|
|
39
57
|
def setup
|
@@ -103,7 +121,7 @@ class WebServerTest < Test::Unit::TestCase
|
|
103
121
|
@server.start
|
104
122
|
end
|
105
123
|
|
106
|
-
sock =
|
124
|
+
sock = tcp_socket('127.0.0.1', @port)
|
107
125
|
sock.syswrite("GET / HTTP/1.0\r\n\r\n")
|
108
126
|
|
109
127
|
responses = sock.read(4096)
|
@@ -114,6 +132,32 @@ class WebServerTest < Test::Unit::TestCase
|
|
114
132
|
assert_match %r{^HTTP/1.[01] 200\b}, responses
|
115
133
|
end
|
116
134
|
|
135
|
+
def test_after_reply
|
136
|
+
teardown
|
137
|
+
|
138
|
+
redirect_test_io do
|
139
|
+
@server = HttpServer.new(TestRackAfterReply.new,
|
140
|
+
:listeners => [ "127.0.0.1:#@port"])
|
141
|
+
@server.start
|
142
|
+
end
|
143
|
+
|
144
|
+
sock = tcp_socket('127.0.0.1', @port)
|
145
|
+
sock.syswrite("GET / HTTP/1.0\r\n\r\n")
|
146
|
+
|
147
|
+
responses = sock.read(4096)
|
148
|
+
assert_match %r{\AHTTP/1.[01] 200\b}, responses
|
149
|
+
assert_match %r{^after_reply_called: false}, responses
|
150
|
+
|
151
|
+
sock = tcp_socket('127.0.0.1', @port)
|
152
|
+
sock.syswrite("GET / HTTP/1.0\r\n\r\n")
|
153
|
+
|
154
|
+
responses = sock.read(4096)
|
155
|
+
assert_match %r{\AHTTP/1.[01] 200\b}, responses
|
156
|
+
assert_match %r{^after_reply_called: true}, responses
|
157
|
+
|
158
|
+
sock.close
|
159
|
+
end
|
160
|
+
|
117
161
|
def test_broken_app
|
118
162
|
teardown
|
119
163
|
app = lambda { |env| raise RuntimeError, "hello" }
|
@@ -122,7 +166,7 @@ class WebServerTest < Test::Unit::TestCase
|
|
122
166
|
@server = HttpServer.new(app, :listeners => [ "127.0.0.1:#@port"] )
|
123
167
|
@server.start
|
124
168
|
end
|
125
|
-
sock =
|
169
|
+
sock = tcp_socket('127.0.0.1', @port)
|
126
170
|
sock.syswrite("GET / HTTP/1.0\r\n\r\n")
|
127
171
|
assert_match %r{\AHTTP/1.[01] 500\b}, sock.sysread(4096)
|
128
172
|
assert_nil sock.close
|
@@ -135,7 +179,7 @@ class WebServerTest < Test::Unit::TestCase
|
|
135
179
|
|
136
180
|
def test_client_shutdown_writes
|
137
181
|
bs = 15609315 * rand
|
138
|
-
sock =
|
182
|
+
sock = tcp_socket('127.0.0.1', @port)
|
139
183
|
sock.syswrite("PUT /hello HTTP/1.1\r\n")
|
140
184
|
sock.syswrite("Host: example.com\r\n")
|
141
185
|
sock.syswrite("Transfer-Encoding: chunked\r\n")
|
@@ -162,7 +206,7 @@ class WebServerTest < Test::Unit::TestCase
|
|
162
206
|
|
163
207
|
def test_client_shutdown_write_truncates
|
164
208
|
bs = 15609315 * rand
|
165
|
-
sock =
|
209
|
+
sock = tcp_socket('127.0.0.1', @port)
|
166
210
|
sock.syswrite("PUT /hello HTTP/1.1\r\n")
|
167
211
|
sock.syswrite("Host: example.com\r\n")
|
168
212
|
sock.syswrite("Transfer-Encoding: chunked\r\n")
|
@@ -188,7 +232,7 @@ class WebServerTest < Test::Unit::TestCase
|
|
188
232
|
|
189
233
|
def test_client_malformed_body
|
190
234
|
bs = 15653984
|
191
|
-
sock =
|
235
|
+
sock = tcp_socket('127.0.0.1', @port)
|
192
236
|
sock.syswrite("PUT /hello HTTP/1.1\r\n")
|
193
237
|
sock.syswrite("Host: example.com\r\n")
|
194
238
|
sock.syswrite("Transfer-Encoding: chunked\r\n")
|
@@ -210,7 +254,7 @@ class WebServerTest < Test::Unit::TestCase
|
|
210
254
|
|
211
255
|
def do_test(string, chunk, close_after=nil, shutdown_delay=0)
|
212
256
|
# Do not use instance variables here, because it needs to be thread safe
|
213
|
-
socket =
|
257
|
+
socket = tcp_socket("127.0.0.1", @port);
|
214
258
|
request = StringIO.new(string)
|
215
259
|
chunks_out = 0
|
216
260
|
|
@@ -255,14 +299,14 @@ class WebServerTest < Test::Unit::TestCase
|
|
255
299
|
end
|
256
300
|
|
257
301
|
def test_bad_client_400
|
258
|
-
sock =
|
302
|
+
sock = tcp_socket('127.0.0.1', @port)
|
259
303
|
sock.syswrite("GET / HTTP/1.0\r\nHost: foo\rbar\r\n\r\n")
|
260
304
|
assert_match %r{\AHTTP/1.[01] 400\b}, sock.sysread(4096)
|
261
305
|
assert_nil sock.close
|
262
306
|
end
|
263
307
|
|
264
308
|
def test_http_0_9
|
265
|
-
sock =
|
309
|
+
sock = tcp_socket('127.0.0.1', @port)
|
266
310
|
sock.syswrite("GET /hello\r\n")
|
267
311
|
assert_match 'hello!\n', sock.sysread(4096)
|
268
312
|
assert_nil sock.close
|
data/test/unit/test_signals.rb
CHANGED
@@ -52,7 +52,7 @@ class SignalsTest < Test::Unit::TestCase
|
|
52
52
|
redirect_test_io { HttpServer.new(app, opts).start.join }
|
53
53
|
}
|
54
54
|
wait_workers_ready("test_stderr.#{pid}.log", 1)
|
55
|
-
sock =
|
55
|
+
sock = tcp_socket('127.0.0.1', @port)
|
56
56
|
sock.syswrite("GET / HTTP/1.0\r\n\r\n")
|
57
57
|
buf = sock.readpartial(4096)
|
58
58
|
assert_nil sock.close
|
@@ -79,7 +79,7 @@ class SignalsTest < Test::Unit::TestCase
|
|
79
79
|
}
|
80
80
|
wr.close
|
81
81
|
wait_workers_ready("test_stderr.#{pid}.log", 1)
|
82
|
-
sock =
|
82
|
+
sock = tcp_socket('127.0.0.1', @port)
|
83
83
|
sock.syswrite("GET / HTTP/1.0\r\n\r\n")
|
84
84
|
buf = rd.readpartial(1)
|
85
85
|
wait_master_ready("test_stderr.#{pid}.log")
|
@@ -102,7 +102,7 @@ class SignalsTest < Test::Unit::TestCase
|
|
102
102
|
}
|
103
103
|
t0 = Time.now
|
104
104
|
wait_workers_ready("test_stderr.#{pid}.log", 1)
|
105
|
-
sock =
|
105
|
+
sock = tcp_socket('127.0.0.1', @port)
|
106
106
|
sock.syswrite("GET / HTTP/1.0\r\n\r\n")
|
107
107
|
|
108
108
|
buf = nil
|
@@ -125,7 +125,7 @@ class SignalsTest < Test::Unit::TestCase
|
|
125
125
|
}
|
126
126
|
redirect_test_io { @server = HttpServer.new(app, @server_opts).start }
|
127
127
|
wait_workers_ready("test_stderr.#{$$}.log", 1)
|
128
|
-
sock =
|
128
|
+
sock = tcp_socket('127.0.0.1', @port)
|
129
129
|
sock.syswrite("GET / HTTP/1.0\r\n\r\n")
|
130
130
|
buf = ''
|
131
131
|
header_len = pid = nil
|
@@ -163,13 +163,13 @@ class SignalsTest < Test::Unit::TestCase
|
|
163
163
|
redirect_test_io { @server = HttpServer.new(app, @server_opts).start }
|
164
164
|
|
165
165
|
wait_workers_ready("test_stderr.#{$$}.log", 1)
|
166
|
-
sock =
|
166
|
+
sock = tcp_socket('127.0.0.1', @port)
|
167
167
|
sock.syswrite("GET / HTTP/1.0\r\n\r\n")
|
168
168
|
pid = sock.sysread(4096)[/\r\nX-Pid: (\d+)\r\n/, 1].to_i
|
169
169
|
assert_nil sock.close
|
170
170
|
|
171
171
|
assert pid > 0, "pid not positive: #{pid.inspect}"
|
172
|
-
sock =
|
172
|
+
sock = tcp_socket('127.0.0.1', @port)
|
173
173
|
sock.syswrite("PUT / HTTP/1.0\r\n")
|
174
174
|
sock.syswrite("Content-Length: #{@bs * @count}\r\n\r\n")
|
175
175
|
1000.times { Process.kill(:HUP, pid) }
|
@@ -116,7 +116,7 @@ class TestSocketHelper < Test::Unit::TestCase
|
|
116
116
|
client.syswrite('abcde')
|
117
117
|
exit 0
|
118
118
|
end
|
119
|
-
s =
|
119
|
+
s = unix_socket(@unix_listener_path)
|
120
120
|
IO.select([s])
|
121
121
|
assert_equal 'abcde', s.sysread(5)
|
122
122
|
pid, status = Process.waitpid2(pid)
|
data/test/unit/test_upload.rb
CHANGED
@@ -60,7 +60,7 @@ class UploadTest < Test::Unit::TestCase
|
|
60
60
|
|
61
61
|
def test_put
|
62
62
|
start_server(@sha1_app)
|
63
|
-
sock =
|
63
|
+
sock = tcp_socket(@addr, @port)
|
64
64
|
sock.syswrite("PUT / HTTP/1.0\r\nContent-Length: #{length}\r\n\r\n")
|
65
65
|
@count.times do |i|
|
66
66
|
buf = @random.sysread(@bs)
|
@@ -77,7 +77,7 @@ class UploadTest < Test::Unit::TestCase
|
|
77
77
|
def test_put_content_md5
|
78
78
|
md5 = Digest::MD5.new
|
79
79
|
start_server(@sha1_app)
|
80
|
-
sock =
|
80
|
+
sock = tcp_socket(@addr, @port)
|
81
81
|
sock.syswrite("PUT / HTTP/1.0\r\nTransfer-Encoding: chunked\r\n" \
|
82
82
|
"Trailer: Content-MD5\r\n\r\n")
|
83
83
|
@count.times do |i|
|
@@ -103,7 +103,7 @@ class UploadTest < Test::Unit::TestCase
|
|
103
103
|
@count, @bs = 2, 128
|
104
104
|
start_server(@sha1_app)
|
105
105
|
assert_equal 256, length
|
106
|
-
sock =
|
106
|
+
sock = tcp_socket(@addr, @port)
|
107
107
|
hdr = "PUT / HTTP/1.0\r\nContent-Length: #{length}\r\n\r\n"
|
108
108
|
@count.times do
|
109
109
|
buf = @random.sysread(@bs)
|
@@ -122,7 +122,7 @@ class UploadTest < Test::Unit::TestCase
|
|
122
122
|
|
123
123
|
def test_put_keepalive_truncates_small_overwrite
|
124
124
|
start_server(@sha1_app)
|
125
|
-
sock =
|
125
|
+
sock = tcp_socket(@addr, @port)
|
126
126
|
to_upload = length + 1
|
127
127
|
sock.syswrite("PUT / HTTP/1.0\r\nContent-Length: #{to_upload}\r\n\r\n")
|
128
128
|
@count.times do
|
@@ -155,7 +155,7 @@ class UploadTest < Test::Unit::TestCase
|
|
155
155
|
tmp.write(nr.to_s)
|
156
156
|
[ 200, @hdr, [] ]
|
157
157
|
})
|
158
|
-
sock =
|
158
|
+
sock = tcp_socket(@addr, @port)
|
159
159
|
buf = ' ' * @bs
|
160
160
|
sock.syswrite("PUT / HTTP/1.0\r\nContent-Length: #{length}\r\n\r\n")
|
161
161
|
|
data/test/unit/test_util.rb
CHANGED
@@ -51,7 +51,7 @@ class TestUtil < Test::Unit::TestCase
|
|
51
51
|
def test_reopen_logs_renamed_with_encoding
|
52
52
|
tmp = Tempfile.new('')
|
53
53
|
tmp_path = tmp.path.dup.freeze
|
54
|
-
Encoding.list.each { |encoding|
|
54
|
+
Encoding.list.sample(5).each { |encoding|
|
55
55
|
File.open(tmp_path, "a:#{encoding.to_s}") { |fp|
|
56
56
|
fp.sync = true
|
57
57
|
assert_equal encoding, fp.external_encoding
|
@@ -74,8 +74,9 @@ class TestUtil < Test::Unit::TestCase
|
|
74
74
|
def test_reopen_logs_renamed_with_internal_encoding
|
75
75
|
tmp = Tempfile.new('')
|
76
76
|
tmp_path = tmp.path.dup.freeze
|
77
|
-
Encoding.list
|
78
|
-
|
77
|
+
full = Encoding.list
|
78
|
+
full.sample(2).each { |ext|
|
79
|
+
full.sample(2).each { |int|
|
79
80
|
next if ext == int
|
80
81
|
File.open(tmp_path, "a:#{ext.to_s}:#{int.to_s}") { |fp|
|
81
82
|
fp.sync = true
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require 'unicorn'
|
3
|
+
require 'unicorn/select_waiter'
|
4
|
+
class TestSelectWaiter < Test::Unit::TestCase
|
5
|
+
|
6
|
+
def test_select_timeout # n.b. this is level-triggered
|
7
|
+
sw = Unicorn::SelectWaiter.new
|
8
|
+
IO.pipe do |r,w|
|
9
|
+
sw.get_readers(ready = [], [r], 0)
|
10
|
+
assert_equal [], ready
|
11
|
+
w.syswrite '.'
|
12
|
+
sw.get_readers(ready, [r], 1000)
|
13
|
+
assert_equal [r], ready
|
14
|
+
sw.get_readers(ready, [r], 0)
|
15
|
+
assert_equal [r], ready
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def test_linux # ugh, also level-triggered, unlikely to change
|
20
|
+
IO.pipe do |r,w|
|
21
|
+
wtr = Unicorn::Waiter.prep_readers([r])
|
22
|
+
wtr.get_readers(ready = [], [r], 0)
|
23
|
+
assert_equal [], ready
|
24
|
+
w.syswrite '.'
|
25
|
+
wtr.get_readers(ready = [], [r], 1000)
|
26
|
+
assert_equal [r], ready
|
27
|
+
wtr.get_readers(ready = [], [r], 1000)
|
28
|
+
assert_equal [r], ready, 'still ready (level-triggered :<)'
|
29
|
+
assert_nil wtr.close
|
30
|
+
end
|
31
|
+
rescue SystemCallError => e
|
32
|
+
warn "#{e.message} (#{e.class})"
|
33
|
+
end if Unicorn.const_defined?(:Waiter)
|
34
|
+
end
|